diff --git a/Java-base/maven-release/Dockerfile b/Java-base/maven-release/Dockerfile
new file mode 100644
index 000000000..e208c4890
--- /dev/null
+++ b/Java-base/maven-release/Dockerfile
@@ -0,0 +1,28 @@
+FROM ubuntu:22.04
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+ && apt-get update \
+ && apt-get install -y software-properties-common \
+ && add-apt-repository ppa:deadsnakes/ppa \
+ && apt-get update \
+ && apt-get install -y \
+ build-essential \
+ git \
+ vim \
+ jq \
+ && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/list/*
+
+RUN apt-get -y install sudo \
+ openjdk-8-jdk \
+ maven
+
+RUN bash -c "echo 2 | update-alternatives --config java"
+
+COPY src /workspace
+WORKDIR /workspace
+
+RUN mvn install -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false
+
+RUN mvn test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100
+
+ENV TZ=Asia/Seoul
diff --git a/Java-base/maven-release/src/Jenkinsfile b/Java-base/maven-release/src/Jenkinsfile
new file mode 100644
index 000000000..e9f05f7d9
--- /dev/null
+++ b/Java-base/maven-release/src/Jenkinsfile
@@ -0,0 +1,20 @@
+/**
+ * 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.
+ */
+
+asfMavenTlpPlgnBuild()
diff --git a/Java-base/maven-release/src/README.TXT b/Java-base/maven-release/src/README.TXT
new file mode 100644
index 000000000..793e8f3df
--- /dev/null
+++ b/Java-base/maven-release/src/README.TXT
@@ -0,0 +1,11 @@
+Issue Tracking
+--------------
+
+https://issues.apache.org/jira/projects/MRELEASE/summary
+
+GitHub is provided only for Pull Requests.
+
+Deploying web site
+-------------------
+
+see http://maven.apache.org/developers/website/deploy-component-reference-documentation.html
diff --git a/Java-base/maven-release/src/README.md b/Java-base/maven-release/src/README.md
new file mode 100644
index 000000000..604ea89c9
--- /dev/null
+++ b/Java-base/maven-release/src/README.md
@@ -0,0 +1,99 @@
+
+Contributing to [Apache Maven Release (Plugin)](https://maven.apache.org/maven-release/)
+======================
+
+[![ASF Jira](https://img.shields.io/endpoint?url=https%3A%2F%2Fmaven.apache.org%2Fbadges%2Fasf_jira-MRELEASE.json)][jira]
+[![Apache License, Version 2.0, January 2004](https://img.shields.io/github/license/apache/maven.svg?label=License)][license]
+[![Maven Central](https://img.shields.io/maven-central/v/org.apache.maven.plugins/maven-release-plugin.svg?label=Maven%20Central)](https://search.maven.org/artifact/org.apache.maven.plugins/maven-release-plugin)
+[![Jenkins Status](https://img.shields.io/jenkins/s/https/builds.apache.org/job/maven-box/job/maven-release/job/master.svg?)][build]
+[![Jenkins tests](https://img.shields.io/jenkins/t/https/builds.apache.org/job/maven-box/job/maven-release/job/master.svg?)][test-results]
+
+
+You have found a bug or you have an idea for a cool new feature? Contributing
+code is a great way to give something back to the open source community. Before
+you dig right into the code, there are a few guidelines that we need
+contributors to follow so that we can have a chance of keeping on top of
+things.
+
+Getting Started
+---------------
+
++ Make sure you have a [JIRA account](https://issues.apache.org/jira/).
++ Make sure you have a [GitHub account](https://github.com/signup/free).
++ If you're planning to implement a new feature, it makes sense to discuss your changes
+ on the [dev list][ml-list] first.
+ This way you can make sure you're not wasting your time on something that isn't
+ considered to be in Apache Maven's scope.
++ Submit a ticket for your issue, assuming one does not already exist.
+ + Clearly describe the issue, including steps to reproduce when it is a bug.
+ + Make sure you fill in the earliest version that you know has the issue.
++ Fork the repository on GitHub.
+
+Making and Submitting Changes
+--------------
+
+We accept Pull Requests via GitHub. The [developer mailing list][ml-list] is the
+main channel of communication for contributors.
+There are some guidelines which will make applying PRs easier for us:
++ Create a topic branch from where you want to base your work (this is usually the master branch).
+ Push your changes to a topic branch in your fork of the repository.
++ Make commits of logical units.
++ Respect the original code style: by using the same [codestyle][code-style],
+ patches should only highlight the actual difference, not being disturbed by any formatting issues:
+ + Only use spaces for indentation.
+ + Create minimal diffs - disable on save actions like reformat source code or organize imports.
+ If you feel the source code should be reformatted, create a separate PR for this change.
+ + Check for unnecessary whitespace with `git diff --check` before committing.
++ Make sure your commit messages are in the proper format. Your commit message should contain the key of the JIRA issue.
+```
+[MASSEMBLY-XXX] - Subject of the JIRA Ticket
+ Optional supplemental description.
+```
++ Make sure you have added the necessary tests (JUnit/IT) for your changes.
++ Run all the tests with `mvn -Prun-its verify` to assure nothing else was accidentally broken.
++ Submit a pull request to the repository in the Apache organization.
++ Update your JIRA ticket and include a link to the pull request in the ticket.
+
+If you plan to contribute on a regular basis, please consider filing a [contributor license agreement][cla].
+
+Making Trivial Changes
+----------------------
+
+For changes of a trivial nature to comments and documentation, it is not always
+necessary to create a new ticket in JIRA. In this case, it is appropriate to
+start the first line of a commit with '(doc)' instead of a ticket number.
+
+Additional Resources
+--------------------
+
++ [Contributing patches](https://maven.apache.org/guides/development/guide-maven-development.html#Creating_and_submitting_a_patch)
++ [Apache Maven Release JIRA project page][jira]
++ [Contributor License Agreement][cla]
++ [General GitHub documentation](https://help.github.com/)
++ [GitHub pull request documentation](https://help.github.com/send-pull-requests/)
++ [Apache Maven Twitter Account](https://twitter.com/ASFMavenProject)
++ #Maven IRC channel on freenode.org
+
+[jira]: https://issues.apache.org/jira/projects/MRELEASE/
+[license]: https://www.apache.org/licenses/LICENSE-2.0
+[ml-list]: https://maven.apache.org/mailing-lists.html
+[code-style]: https://maven.apache.org/developers/conventions/code.html
+[cla]: https://www.apache.org/licenses/#clas
+[maven-wiki]: https://cwiki.apache.org/confluence/display/MAVEN/Index
+[test-results]: https://builds.apache.org/job/maven-box/job/maven-release/job/master/lastCompletedBuild/testReport/
+[build]: https://builds.apache.org/job/maven-box/job/maven-release/job/master/
diff --git a/Java-base/maven-release/src/deploySite.bat b/Java-base/maven-release/src/deploySite.bat
new file mode 100644
index 000000000..e1cc989fd
--- /dev/null
+++ b/Java-base/maven-release/src/deploySite.bat
@@ -0,0 +1,21 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+mvn -Preporting site site:stage %*
+mvn scm-publish:publish-scm %*
diff --git a/Java-base/maven-release/src/deploySite.sh b/Java-base/maven-release/src/deploySite.sh
new file mode 100644
index 000000000..f6c265d75
--- /dev/null
+++ b/Java-base/maven-release/src/deploySite.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+#
+# 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.
+#
+
+mvn -Preporting site site:stage $@
+mvn scm-publish:publish-scm $@
diff --git a/Java-base/maven-release/src/maven-release-api/pom.xml b/Java-base/maven-release/src/maven-release-api/pom.xml
new file mode 100644
index 000000000..03985e6e7
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/pom.xml
@@ -0,0 +1,68 @@
+
+
+
+
+ 4.0.0
+
+ org.apache.maven.release
+ maven-release
+ 3.0.0-SNAPSHOT
+
+
+ maven-release-api
+
+ Maven Release APIs
+ APIs to implement to extend maven-release-plugin.
+
+
+
+ org.apache.maven
+ maven-repository-metadata
+
+
+ org.apache.maven
+ maven-artifact
+
+
+ org.apache.maven
+ maven-core
+
+
+ org.apache.maven
+ maven-model
+
+
+ org.apache.maven
+ maven-settings
+
+
+ org.eclipse.aether
+ aether-util
+ 1.0.0.v20140518
+ true
+
+
+
+ junit
+ junit
+ test
+
+
+
\ No newline at end of file
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/ReleaseExecutionException.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/ReleaseExecutionException.java
new file mode 100644
index 000000000..afaf6ad8e
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/ReleaseExecutionException.java
@@ -0,0 +1,39 @@
+package org.apache.maven.shared.release;
+
+/*
+ * 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.
+ */
+
+/**
+ * Exception occuring during release execution.
+ *
+ * @author Brett Porter
+ */
+public class ReleaseExecutionException
+ extends Exception
+{
+ public ReleaseExecutionException( String message )
+ {
+ super( message );
+ }
+
+ public ReleaseExecutionException( String message, Throwable t )
+ {
+ super( message, t );
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/ReleaseFailureException.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/ReleaseFailureException.java
new file mode 100644
index 000000000..b93f4ef82
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/ReleaseFailureException.java
@@ -0,0 +1,34 @@
+package org.apache.maven.shared.release;
+
+/*
+ * 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.
+ */
+
+/**
+ * A failure during the release process.
+ *
+ * @author Brett Porter
+ */
+public class ReleaseFailureException
+ extends Exception
+{
+ public ReleaseFailureException( String message )
+ {
+ super( message );
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/ReleaseResult.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/ReleaseResult.java
new file mode 100644
index 000000000..04547ed3a
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/ReleaseResult.java
@@ -0,0 +1,135 @@
+package org.apache.maven.shared.release;
+
+/*
+ * 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.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+/**
+ * @author Edwin Punzalan
+ */
+public class ReleaseResult
+{
+ public static final int UNDEFINED = -1, SUCCESS = 0, ERROR = 1;
+
+ private StringBuilder stdOut = new StringBuilder();
+
+ private int resultCode = UNDEFINED;
+
+ private long startTime;
+
+ private long endTime;
+
+ private static final String LS = System.getProperty( "line.separator" );
+
+ public void appendInfo( String message )
+ {
+ stdOut.append( "[INFO] " ).append( message ).append( LS );
+ }
+
+ public void appendWarn( String message )
+ {
+ stdOut.append( "[WARN] " ).append( message ).append( LS );
+ }
+
+ public void appendDebug( String message )
+ {
+ stdOut.append( "[DEBUG] " ).append( message ).append( LS );
+ }
+
+ public void appendDebug( String message, Exception e )
+ {
+ appendDebug( message );
+
+ stdOut.append( getStackTrace( e ) ).append( LS );
+ }
+
+ public void appendError( String message )
+ {
+ stdOut.append( "[ERROR] " ).append( message ).append( LS );
+
+ setResultCode( ERROR );
+ }
+
+ public void appendError( Exception e )
+ {
+ appendError( getStackTrace( e ) );
+ }
+
+ public void appendError( String message, Exception e )
+ {
+ appendError( message );
+
+ stdOut.append( getStackTrace( e ) ).append( LS );
+ }
+
+ public void appendOutput( String message )
+ {
+ stdOut.append( message );
+ }
+
+ public String getOutput()
+ {
+ return stdOut.toString();
+ }
+
+ public int getResultCode()
+ {
+ return resultCode;
+ }
+
+ public void setResultCode( int resultCode )
+ {
+ this.resultCode = resultCode;
+ }
+
+ public long getStartTime()
+ {
+ return startTime;
+ }
+
+ public void setStartTime( long startTime )
+ {
+ this.startTime = startTime;
+ }
+
+ public long getEndTime()
+ {
+ return endTime;
+ }
+
+ public void setEndTime( long endTime )
+ {
+ this.endTime = endTime;
+ }
+
+ private String getStackTrace( Exception e )
+ {
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+
+ PrintStream stream = new PrintStream( byteStream );
+
+ e.printStackTrace( stream );
+
+ stream.flush();
+
+ return byteStream.toString();
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/config/ReleaseDescriptor.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/config/ReleaseDescriptor.java
new file mode 100644
index 000000000..a48272749
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/config/ReleaseDescriptor.java
@@ -0,0 +1,495 @@
+package org.apache.maven.shared.release.config;
+
+/*
+ * 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.util.List;
+
+import org.apache.maven.model.Scm;
+
+/**
+ *
+ * @author Robert Scholte
+ */
+public interface ReleaseDescriptor
+{
+ /**
+ * Get if updateDependencies is false, dependencies version won't be updated to the next development version.
+ *
+ * @return boolean
+ */
+ boolean isUpdateDependencies();
+
+ /**
+ * Get whether to use the release profile that adds sources and javadocs to the released artifact, if appropriate.
+ * If set to true, this will set the property "performRelease" to true.
+ *
+ * @return boolean
+ */
+ boolean isUseReleaseProfile();
+
+ /**
+ * Get whether to use the parent pom version for submodule versions.
+ *
+ * @return boolean
+ */
+ boolean isAutoVersionSubmodules();
+
+ /**
+ * Get whether a SNAPSHOT of the release plugin is allowed.
+ *
+ * @return boolean
+ */
+ boolean isSnapshotReleasePluginAllowed();
+
+ /**
+ * Get the commits must be done by modules or not. Set it to true in case of flat directory structure.
+ *
+ * @return boolean
+ */
+ boolean isCommitByProject();
+
+ /**
+ * Get whether to create a branch instead of do a release.
+ *
+ * @return boolean
+ */
+ boolean isBranchCreation();
+
+ /**
+ * Get whether to update branch POM versions.
+ *
+ * @return boolean
+ */
+ boolean isUpdateBranchVersions();
+
+ /**
+ * Get whether to update working copy POM versions.
+ *
+ * @return boolean
+ */
+ boolean isUpdateWorkingCopyVersions();
+
+ /**
+ * Get whether to suppress a commit of changes to the working copy before a tag or branch is created.
+ *
+ * @return boolean
+ */
+ boolean isSuppressCommitBeforeTagOrBranch();
+
+ /**
+ * Get should timestamped SNAPSHOT dependencies be allowed? Default is to fail when any SNAPSHOT dependency is
+ * found.
+ *
+ * @return boolean
+ */
+ boolean isAllowTimestampedSnapshots();
+
+ /**
+ * Get whether to update branch versions to SNAPSHOT.
+ *
+ * @return boolean
+ */
+ boolean isUpdateVersionsToSnapshot();
+
+ /**
+ * Get nOTE : currently only implemented with svn scm. Enable a workaround to prevent issue due to svn client >
+ * 1.5.0 (https://issues.apache.org/jira/browse/SCM-406).
+ *
+ * @return boolean
+ */
+ boolean isRemoteTagging();
+
+ /**
+ * Get if the scm provider should use local checkouts via file://${basedir} instead of doing a clean checkout over
+ * the network. This is very helpful for releasing large projects!
+ *
+ * @return boolean
+ */
+ boolean isLocalCheckout();
+
+ /**
+ * Get should distributed changes be pushed to the central repository? For many distributed SCMs like Git, a change
+ * like a commit is only stored in your local copy of the repository. Pushing the change allows your to more easily
+ * share it with other users.
+ *
+ * @return boolean
+ */
+ boolean isPushChanges();
+
+ /**
+ * Get default version to use for new working copy.
+ *
+ * Some SCMs may require a Work Item or a Task to allow the
+ * changes to be pushed or delivered.
+ * This field allows you to specify that Work Item
+ * or Task. It is optional, and only relevant if pushChanges is true.
+ *
+ * @return String
+ */
+ String getWorkItem();
+
+ /**
+ * Get default version to use for new working copy.
+ *
+ * @return String
+ */
+ String getDefaultDevelopmentVersion();
+
+ /**
+ * Get relative path of the project returned by the checkout command.
+ *
+ * @return String
+ */
+ String getScmRelativePathProjectDirectory();
+
+ /**
+ * Get the directory where the tag will be checked out.
+ *
+ * @return String
+ */
+ String getCheckoutDirectory();
+
+ /**
+ * Get the goals to execute in perform phase for the release.
+ *
+ * @return String
+ */
+ String getPerformGoals();
+
+ /**
+ * Get default version to use for the tagged release or the new branch.
+ *
+ * @return String
+ */
+ String getDefaultReleaseVersion();
+
+ /**
+ * Get nOTE : currently only implemented with svn scm. It contains the revision of the committed released pom to
+ * remotely tag the source code with this revision.
+ *
+ * @return String
+ */
+ String getScmReleasedPomRevision();
+
+ /**
+ * Get whether to add the model schema to the top of the rewritten POM if it wasn't there already. If
+ * false
then the root element will remain untouched.
+ *
+ * @return boolean
+ */
+ boolean isAddSchema();
+
+ /**
+ * Get whether to generate release POMs.
+ *
+ * @return boolean
+ */
+ boolean isGenerateReleasePoms();
+
+ /**
+ * Get whether the release process is interactive and the release manager should be prompted to confirm values, or
+ * whether the defaults are used regardless.
+ *
+ * @return boolean
+ */
+ boolean isInteractive();
+
+ /**
+ * Get whether to use edit mode when making SCM modifications. This setting is disregarded if the SCM does not
+ * support edit mode, or if edit mode is compulsory for the given SCM.
+ *
+ * @return boolean
+ */
+ boolean isScmUseEditMode();
+
+ /**
+ *
+ * @return list of profiles to activate
+ */
+ List getActivateProfiles();
+
+ /**
+ * Get the last completed phase.
+ *
+ * @return String
+ */
+ String getCompletedPhase();
+
+ /**
+ * Method getCheckModificationExcludes.
+ *
+ * @return List
+ */
+ List getCheckModificationExcludes();
+
+ /**
+ * Get additional arguments to pass to any executed Maven process.
+ *
+ * @return String
+ */
+ String getAdditionalArguments();
+
+ /**
+ * Get the goals to execute in preparation for the release.
+ *
+ * @return String
+ */
+ String getPreparationGoals();
+
+ /**
+ * Get the goals to execute in on completion of preparation for the release.
+ *
+ * @return String
+ */
+ String getCompletionGoals();
+
+ /**
+ * Get the file name of the POM to pass to any executed Maven process.
+ *
+ * @return String
+ */
+ String getPomFileName();
+
+ /**
+ * Get the prefix of SCM modification messages.
+ *
+ * @return String
+ */
+ String getScmCommentPrefix();
+
+ /**
+ * Get the SCM commit comment when setting pom.xml to release.
+ *
+ * @return String
+ * @since 3.0.0-M1
+ */
+ String getScmReleaseCommitComment();
+
+ /**
+ * Get the SCM commit comment when setting pom.xml back to development.
+ *
+ * @return String
+ * @since 3.0.0-M1
+ */
+ String getScmDevelopmentCommitComment();
+
+ /**
+ * Get the SCM commit comment when branching.
+ *
+ * @return String
+ * @since 3.0.0-M1
+ */
+ String getScmBranchCommitComment();
+
+ /**
+ * Get the SCM commit comment when rolling back.
+ *
+ * @return String
+ * @since 3.0.0-M1
+ */
+ String getScmRollbackCommitComment();
+
+ /**
+ * Get pass phrase for the private key.
+ *
+ * @return String
+ */
+ String getScmPrivateKeyPassPhrase();
+
+ /**
+ * Get the password for the user interacting with the scm.
+ *
+ * @return String
+ */
+ String getScmPassword();
+
+ /**
+ * Get private key for an SSH based SCM repository.
+ *
+ * @return String
+ */
+ String getScmPrivateKey();
+
+ /**
+ * Get tag or branch name: the identifier for the tag/branch. Example: maven-release-plugin-2.0.
+ *
+ * @return String
+ */
+ String getScmReleaseLabel();
+
+ /**
+ * Get where you are going to put your tagged sources Example https://svn.apache.org/repos/asf/maven/plugins/tags.
+ *
+ * @return String
+ */
+ String getScmTagBase();
+
+ /**
+ * Get where you are going to put your branched sources Example
+ * https://svn.apache.org/repos/asf/maven/plugins/branches.
+ *
+ * @return String
+ */
+ String getScmBranchBase();
+
+ /**
+ * Get the id can be used to get the credentials by the server-id from the settings.xml.
+ *
+ * @return String
+ */
+ String getScmId();
+
+ /**
+ * Get this is a MavenSCM of where you're going to get the sources to make the release with. Example:
+ * scm:svn:https://svn.apache.org/repos/asf/maven/plugins/trunk/maven-release-plugin.
+ *
+ * @return String
+ */
+ String getScmSourceUrl();
+
+ /**
+ * Get the user name to interact with the scm.
+ *
+ * @return String
+ */
+ String getScmUsername();
+
+ /**
+ * Get wait the specified number of seconds before creating a tag.
+ *
+ * @return int
+ */
+ int getWaitBeforeTagging();
+
+ /**
+ * Get the directory where the release is performed.
+ *
+ * @return String
+ */
+ String getWorkingDirectory();
+
+ /**
+ * Get specifies the format for generating a tag name. Property expansion is used with the optional prefix of
+ * project, where properties are delimited with @{ and }.
+ *
+ * @return String
+ */
+ String getScmTagNameFormat();
+
+ /**
+ * Get the role-hint for the NamingPolicy implementation used to calculate the project branch and tag names.
+ *
+ * @return String
+ */
+ String getProjectNamingPolicyId();
+
+ /**
+ * Get the role-hint for the VersionPolicy implementation used to calculate the project versions.
+ *
+ * @return String
+ */
+ String getProjectVersionPolicyId();
+
+ /**
+ * Get the role-hint for the release Strategy implementation.
+ *
+ * @return String
+ */
+ String getReleaseStrategyId();
+
+ /**
+ * @return {@code String} The original version for the resolved snapshot dependency.
+ *
+ * @param artifactKey the artifact key {@code String}
+ */
+ String getDependencyOriginalVersion( String artifactKey );
+
+ /**
+ * @return {@code String} the release version for the resolved snapshot dependency.
+ *
+ * @param artifactKey the artifact key {@code String}
+ */
+ String getDependencyReleaseVersion( String artifactKey );
+
+ /**
+ * @return {@code String} the release version for the resolved snapshot dependency.
+ *
+ * @param artifactKey the artifact key {@code String}
+ */
+ String getDependencyDevelopmentVersion( String artifactKey );
+
+
+ String getProjectOriginalVersion( String projectKey );
+
+ String getProjectDevelopmentVersion( String projectKey );
+
+ String getProjectReleaseVersion( String key );
+
+ /**
+ * @return the original {@code Scm} information.
+ *
+ * @param projectKey the project key {@code String}
+ */
+ Scm getOriginalScmInfo( String projectKey );
+
+ // Modifiable
+ void addDependencyOriginalVersion( String versionlessKey, String string );
+
+ void addDependencyReleaseVersion( String versionlessKey, String version );
+
+ void addDependencyDevelopmentVersion( String versionlessKey, String version );
+
+ void addReleaseVersion( String projectId, String nextVersion );
+
+ void addDevelopmentVersion( String projectId, String nextVersion );
+
+ void setScmReleaseLabel( String tag );
+
+ void setScmReleasedPomRevision( String scmRevision );
+
+ void setScmRelativePathProjectDirectory( String scmRelativePathProjectDirectory );
+
+ void setScmSourceUrl( String scmUrl );
+
+ /**
+ * Returns whether unresolved SNAPSHOT dependencies should automatically be resolved.
+ * If this is set, then this specifies the default answer to be used when unresolved SNAPSHOT
+ * dependencies should automatically be resolved ( 0:All 1:Project Dependencies 2:Plugins
+ * 3:Reports 4:Extensions ). Possible values are:
+ *
+ * - "all" or "0": resolve all kinds of snapshots, ie. project, plugin, report and extension dependencies
+ * - "dependencies" or "1": resolve project dependencies
+ * - "plugins" or "2": resolve plugin dependencis
+ * - "reports" or "3": resolve report dependencies
+ * - "extensions" or "4": resolve extension dependencies
+ *
+ *
+ * @return String
+ */
+ String getAutoResolveSnapshots();
+
+ /**
+ * Determines whether the {@code --pin-externals} option in {@code svn copy} command is enabled
+ * which is new in Subversion 1.9.
+ *
+ * @return boolean
+ */
+ boolean isPinExternals();
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/env/ReleaseEnvironment.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/env/ReleaseEnvironment.java
new file mode 100644
index 000000000..88ef9e20c
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/env/ReleaseEnvironment.java
@@ -0,0 +1,51 @@
+package org.apache.maven.shared.release.env;
+
+/*
+ * 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.File;
+import java.util.Locale;
+
+import org.apache.maven.settings.Settings;
+
+/**
+ *
+ */
+public interface ReleaseEnvironment
+{
+
+ String DEFAULT_MAVEN_EXECUTOR_ID = "forked-path";
+
+ String getMavenExecutorId();
+
+ File getLocalRepositoryDirectory();
+
+ Settings getSettings();
+
+ File getMavenHome();
+
+ File getJavaHome();
+
+ /**
+ *
+ * @return the locale
+ * @since 2.4
+ */
+ Locale getLocale();
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/phase/AbstractReleasePhase.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/phase/AbstractReleasePhase.java
new file mode 100644
index 000000000..672c18e07
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/phase/AbstractReleasePhase.java
@@ -0,0 +1,72 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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 org.apache.maven.shared.release.ReleaseResult;
+import org.codehaus.plexus.logging.AbstractLogEnabled;
+
+/**
+ * Base class for all phases.
+ *
+ * @author Brett Porter
+ */
+public abstract class AbstractReleasePhase
+ extends AbstractLogEnabled
+ implements ReleasePhase
+{
+ protected void logInfo( ReleaseResult result, String message )
+ {
+ result.appendInfo( message );
+ getLogger().info( message );
+ }
+
+ protected void logWarn( ReleaseResult result, String message )
+ {
+ result.appendWarn( message );
+ getLogger().warn( message );
+ }
+
+ protected void logError( ReleaseResult result, String message )
+ {
+ result.appendWarn( message );
+ getLogger().error( message );
+ }
+
+ protected void logDebug( ReleaseResult result, String message )
+ {
+ result.appendDebug( message );
+ getLogger().debug( message );
+ }
+
+ protected void logDebug( ReleaseResult result, String message, Exception e )
+ {
+ result.appendDebug( message, e );
+ getLogger().debug( message, e );
+ }
+
+ protected ReleaseResult getReleaseResultSuccess()
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/phase/ReleasePhase.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/phase/ReleasePhase.java
new file mode 100644
index 000000000..1170856f9
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/phase/ReleasePhase.java
@@ -0,0 +1,66 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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 org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseFailureException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+
+import java.util.List;
+
+/**
+ * A phase in the release cycle.
+ *
+ * @author Brett Porter
+ */
+public interface ReleasePhase
+{
+ /**
+ * Execute the phase.
+ *
+ * @param releaseDescriptor the configuration to use
+ * @param releaseEnvironment the environmental configuration, such as Maven settings, Maven home, etc.
+ * @param reactorProjects the reactor projects
+ * @throws ReleaseExecutionException an exception during the execution of the phase
+ * @throws ReleaseFailureException a failure during the execution of the phase
+ * @return the release result
+ */
+ ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException;
+
+ /**
+ * Simulate the phase, but don't make any changes to the project.
+ *
+ * @param releaseDescriptor the configuration to use
+ * @param releaseEnvironment the environmental configuration, such as Maven settings, Maven home, etc.
+ * @param reactorProjects the reactor projects
+ * @throws ReleaseExecutionException an exception during the execution of the phase
+ * @throws ReleaseFailureException a failure during the execution of the phase
+ * @return the release result
+ */
+ ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException;
+
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/phase/ResourceGenerator.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/phase/ResourceGenerator.java
new file mode 100644
index 000000000..8f85e3eef
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/phase/ResourceGenerator.java
@@ -0,0 +1,42 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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.util.List;
+
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.release.ReleaseResult;
+
+/**
+ * Additional interface for ReleasePhase if the phase generates resources, which should be cleaned up afterwards.
+ *
+ * @author Robert Scholte
+ * @since 3.0.0
+ */
+public interface ResourceGenerator
+{
+ /**
+ * Clean up after a phase if it leaves any additional files in the checkout.
+ *
+ * @param reactorProjects the reactor projects
+ * @return the release result
+ */
+ ReleaseResult clean( List reactorProjects );
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/PolicyException.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/PolicyException.java
new file mode 100644
index 000000000..a3d7c340a
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/PolicyException.java
@@ -0,0 +1,39 @@
+package org.apache.maven.shared.release.policy;
+
+/*
+ * 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.
+ */
+
+/**
+ *
+ * @since 2.5.1 (MRELEASE-431)
+ */
+public class PolicyException
+ extends Exception
+{
+
+ public PolicyException( String message )
+ {
+ super( message );
+ }
+
+ public PolicyException( String message, Exception exception )
+ {
+ super( message, exception );
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/naming/NamingPolicy.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/naming/NamingPolicy.java
new file mode 100644
index 000000000..467f2cd82
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/naming/NamingPolicy.java
@@ -0,0 +1,41 @@
+package org.apache.maven.shared.release.policy.naming;
+
+/*
+ * 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 org.apache.maven.shared.release.policy.PolicyException;
+
+/**
+ * API for branch and tag naming. Used by maven-release-plugin to suggest names for tags and branches.
+ *
+ * @since 3.0.0 (MRELEASE-979)
+ */
+public interface NamingPolicy
+{
+ /**
+ * @return the calculation of the name used for branching or tagging.
+ *
+ * @param request the {@code NamingPolicyRequest}
+ *
+ * @throws PolicyException if exception in the policy
+ */
+ NamingPolicyResult getName( NamingPolicyRequest request )
+ throws PolicyException;
+
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/naming/NamingPolicyRequest.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/naming/NamingPolicyRequest.java
new file mode 100644
index 000000000..d6181425a
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/naming/NamingPolicyRequest.java
@@ -0,0 +1,67 @@
+package org.apache.maven.shared.release.policy.naming;
+
+/*
+ * 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.
+ */
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 3.0.0
+ */
+public class NamingPolicyRequest
+{
+ private String groupId;
+
+ private String artifactId;
+
+ private String version;
+
+ public String getGroupId()
+ {
+ return groupId;
+ }
+
+ public NamingPolicyRequest setGroupId( String groupId )
+ {
+ this.groupId = groupId;
+ return this;
+ }
+
+ public String getArtifactId()
+ {
+ return artifactId;
+ }
+
+ public NamingPolicyRequest setArtifactId( String artifactId )
+ {
+ this.artifactId = artifactId;
+ return this;
+ }
+
+ public String getVersion()
+ {
+ return version;
+ }
+
+ public NamingPolicyRequest setVersion( String version )
+ {
+ this.version = version;
+ return this;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/naming/NamingPolicyResult.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/naming/NamingPolicyResult.java
new file mode 100644
index 000000000..411e8b724
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/naming/NamingPolicyResult.java
@@ -0,0 +1,44 @@
+package org.apache.maven.shared.release.policy.naming;
+
+/*
+ * 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.
+ */
+
+/**
+ *
+ * @since 3.0.0 (MRELEASE-979)
+ */
+public class NamingPolicyResult
+{
+ /**
+ * The tag or branch name to use.
+ */
+ private String name;
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public NamingPolicyResult setName( String name )
+ {
+ this.name = name;
+ return this;
+ }
+
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/version/VersionPolicy.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/version/VersionPolicy.java
new file mode 100644
index 000000000..607080247
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/version/VersionPolicy.java
@@ -0,0 +1,53 @@
+package org.apache.maven.shared.release.policy.version;
+
+/*
+ * 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 org.apache.maven.shared.release.policy.PolicyException;
+import org.apache.maven.shared.release.versions.VersionParseException;
+
+/**
+ * API for next version calculations, used by maven-release-plugin to suggest release and next develoment versions.
+ *
+ * @since 2.5.1 (MRELEASE-431)
+ */
+public interface VersionPolicy
+{
+ /**
+ * @return the calculation of the release version from development state.
+ *
+ * @param request the {@code VersionPolicyRequest}
+ *
+ * @throws PolicyException if exception in the policy
+ * @throws VersionParseException if exception parsing the version
+ */
+ VersionPolicyResult getReleaseVersion( VersionPolicyRequest request )
+ throws PolicyException, VersionParseException;
+
+ /**
+ * @return the calculation of the next development version from release state.
+ *
+ * @param request the {@code VersionPolicyRequest}
+ *
+ * @throws PolicyException if exception in the policy
+ * @throws VersionParseException if exception parsing the version
+ */
+ VersionPolicyResult getDevelopmentVersion( VersionPolicyRequest request )
+ throws PolicyException, VersionParseException;
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/version/VersionPolicyRequest.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/version/VersionPolicyRequest.java
new file mode 100644
index 000000000..36bd1a148
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/version/VersionPolicyRequest.java
@@ -0,0 +1,57 @@
+package org.apache.maven.shared.release.policy.version;
+
+/*
+ * 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 org.apache.maven.artifact.repository.metadata.Metadata;
+
+/**
+ *
+ * @since 2.5.1 (MRELEASE-431)
+ */
+public class VersionPolicyRequest
+{
+
+ private String version;
+
+ private Metadata metaData;
+
+ public String getVersion()
+ {
+ return version;
+ }
+
+ public VersionPolicyRequest setVersion( String version )
+ {
+ this.version = version;
+ return this;
+ }
+
+ public Metadata getMetaData()
+ {
+ return metaData;
+ }
+
+ public VersionPolicyRequest setMetaData( Metadata metaData )
+ {
+ this.metaData = metaData;
+ return this;
+ }
+
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/version/VersionPolicyResult.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/version/VersionPolicyResult.java
new file mode 100644
index 000000000..de7fb5836
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/policy/version/VersionPolicyResult.java
@@ -0,0 +1,41 @@
+package org.apache.maven.shared.release.policy.version;
+
+/*
+ * 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.
+ */
+
+/**
+ *
+ * @since 2.5.1 (MRELEASE-431)
+ */
+public class VersionPolicyResult
+{
+ private String version;
+
+ public String getVersion()
+ {
+ return version;
+ }
+
+ public VersionPolicyResult setVersion( String version )
+ {
+ this.version = version;
+ return this;
+ }
+
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/strategy/Strategy.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/strategy/Strategy.java
new file mode 100644
index 000000000..43300b012
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/strategy/Strategy.java
@@ -0,0 +1,58 @@
+package org.apache.maven.shared.release.strategy;
+
+/*
+ * 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.util.List;
+
+/**
+ * Interface to override default strategy.
+ *
+ * If a method returns {@code null}, the default will be used, otherwise the provided collection of phaseIds
+ *
+ * @author Robert Scholte
+ * @since 3.0.0
+ */
+public interface Strategy
+{
+ /**
+ * @return The release phases to execute the calling the prepare goal
+ */
+ List getPreparePhases();
+
+ /**
+ * @return The release phases to execute the calling the perform goal
+ */
+ List getPerformPhases();
+
+ /**
+ * @return The release phases to execute the calling the branch goal
+ */
+ List getBranchPhases();
+
+ /**
+ * @return The release phases to execute the calling the rollback goal
+ */
+ List getRollbackPhases();
+
+ /**
+ * @return The release phases to execute the calling the update-versions goal
+ */
+ List getUpdateVersionsPhases();
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/versions/AetherVersion.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/versions/AetherVersion.java
new file mode 100644
index 000000000..55a014c4b
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/versions/AetherVersion.java
@@ -0,0 +1,53 @@
+package org.apache.maven.shared.release.versions;
+
+/*
+ * 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 org.eclipse.aether.util.version.GenericVersionScheme;
+import org.eclipse.aether.version.InvalidVersionSpecificationException;
+
+class AetherVersion
+ implements org.eclipse.aether.version.Version
+{
+ private final org.eclipse.aether.version.Version version;
+
+ AetherVersion( String version )
+ throws VersionParseException
+ {
+ try
+ {
+ this.version = new GenericVersionScheme().parseVersion( version );
+ }
+ catch ( InvalidVersionSpecificationException e )
+ {
+ throw new VersionParseException( e.getMessage() );
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.version.toString();
+ }
+
+ public int compareTo( org.eclipse.aether.version.Version other )
+ {
+ return this.version.compareTo( other );
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/versions/MavenArtifactVersion.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/versions/MavenArtifactVersion.java
new file mode 100644
index 000000000..25a2acb17
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/versions/MavenArtifactVersion.java
@@ -0,0 +1,109 @@
+package org.apache.maven.shared.release.versions;
+
+/*
+ * 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 org.apache.maven.artifact.versioning.ArtifactVersion;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+
+class MavenArtifactVersion
+ implements ArtifactVersion
+{
+ private final ArtifactVersion version;
+
+ MavenArtifactVersion( String version )
+ {
+ this.version = new DefaultArtifactVersion( version );
+ }
+
+ public int compareTo( Object o )
+ {
+ if ( o instanceof MavenArtifactVersion )
+ {
+ return version.compareTo( ( (MavenArtifactVersion) o ).version );
+ }
+ else
+ {
+ return version.compareTo( version );
+ }
+ }
+
+ public int getMajorVersion()
+ {
+ return version.getMajorVersion();
+ }
+
+ public int getMinorVersion()
+ {
+ return version.getMinorVersion();
+ }
+
+ public int getIncrementalVersion()
+ {
+ return version.getIncrementalVersion();
+ }
+
+ public int getBuildNumber()
+ {
+ return version.getBuildNumber();
+ }
+
+ public String getQualifier()
+ {
+ return version.getQualifier();
+ }
+
+ public void parseVersion( String version )
+ {
+ this.version.parseVersion( version );
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.version.toString();
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return this.version.hashCode();
+ }
+
+ @Override
+ public boolean equals( Object other )
+ {
+ if ( this == other )
+ {
+ return true;
+ }
+ if ( other == null )
+ {
+ return false;
+ }
+
+ if ( other instanceof MavenArtifactVersion )
+ {
+ return version.equals( ( (MavenArtifactVersion) other ).version );
+ }
+ return false;
+ }
+
+
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/versions/Version.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/versions/Version.java
new file mode 100644
index 000000000..7d4fbcded
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/versions/Version.java
@@ -0,0 +1,337 @@
+package org.apache.maven.shared.release.versions;
+
+/*
+ * 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.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.maven.artifact.ArtifactUtils;
+import org.codehaus.plexus.util.StringUtils;
+
+/**
+ *
+ */
+public class Version
+ implements Comparable, Cloneable
+{
+ private final AetherVersion aetherVersion;
+
+ private final MavenArtifactVersion mavenArtifactVersion;
+
+ private final String strVersion;
+
+ private final List digits;
+
+ private String annotation;
+
+ private String annotationRevision;
+
+ private final String buildSpecifier;
+
+ private String annotationSeparator;
+
+ private String annotationRevSeparator;
+
+ private String buildSeparator;
+
+ private static final int DIGITS_INDEX = 1;
+
+ private static final int ANNOTATION_SEPARATOR_INDEX = 2;
+
+ private static final int ANNOTATION_INDEX = 3;
+
+ private static final int ANNOTATION_REV_SEPARATOR_INDEX = 4;
+
+ private static final int ANNOTATION_REVISION_INDEX = 5;
+
+ private static final int BUILD_SEPARATOR_INDEX = 6;
+
+ private static final int BUILD_SPECIFIER_INDEX = 7;
+
+ private static final String SNAPSHOT_IDENTIFIER = "SNAPSHOT";
+
+ private static final String DIGIT_SEPARATOR_STRING = ".";
+
+ private static final String DEFAULT_ANNOTATION_REV_SEPARATOR = "-";
+
+ private static final String DEFAULT_BUILD_SEPARATOR = "-";
+
+ public static final Pattern STANDARD_PATTERN = Pattern.compile( "^((?:\\d+\\.)*\\d+)" // digit(s) and '.' repeated -
+ // followed by digit (version
+ // digits 1.22.0, etc)
+ + "([-_])?" // optional - or _ (annotation separator)
+ + "([a-zA-Z]*)" // alpha characters (looking for annotation - alpha, beta, RC, etc.)
+ + "([-_])?" // optional - or _ (annotation revision separator)
+ + "(\\d*)" // digits (any digits after rc or beta is an annotation revision)
+ + "(?:([-_])?(.*?))?$" ); // - or _ followed everything else (build specifier)
+
+ /* *
+ * cmaki 02242009 FIX for non-digit release numbers, e.g. trunk-SNAPSHOT or just SNAPSHOT This alternate pattern
+ * supports version numbers like: trunk-SNAPSHOT branchName-SNAPSHOT SNAPSHOT
+ */
+ // for SNAPSHOT releases only (possible versions include: trunk-SNAPSHOT or SNAPSHOT)
+ public static final Pattern ALTERNATE_PATTERN = Pattern.compile( "^(SNAPSHOT|[a-zA-Z]+[_-]SNAPSHOT)" );
+
+ private Version( List digits, String annotation, String annotationRevision, String buildSpecifier,
+ String annotationSeparator, String annotationRevSeparator, String buildSeparator )
+ {
+ this.digits = digits;
+ this.annotation = annotation;
+ this.annotationRevision = annotationRevision;
+ this.buildSpecifier = buildSpecifier;
+ this.annotationSeparator = annotationSeparator;
+ this.annotationRevSeparator = annotationRevSeparator;
+ this.buildSeparator = buildSeparator;
+ this.strVersion = getVersionString( this, buildSpecifier, buildSeparator );
+
+ // for now no need to reparse, original version was valid
+ this.aetherVersion = null;
+ this.mavenArtifactVersion = null;
+ }
+
+ public Version( String version )
+ throws VersionParseException
+ {
+ this.strVersion = version;
+ this.aetherVersion = new AetherVersion( version );
+ this.mavenArtifactVersion = new MavenArtifactVersion( version );
+
+ // FIX for non-digit release numbers, e.g. trunk-SNAPSHOT or just SNAPSHOT
+ Matcher matcher = ALTERNATE_PATTERN.matcher( strVersion );
+ // TODO: hack because it didn't support "SNAPSHOT"
+ if ( matcher.matches() )
+ {
+ annotation = null;
+ digits = null;
+ buildSpecifier = version;
+ buildSeparator = null;
+ return;
+ }
+
+ Matcher m = STANDARD_PATTERN.matcher( strVersion );
+ if ( m.matches() )
+ {
+ digits = parseDigits( m.group( DIGITS_INDEX ) );
+ if ( !SNAPSHOT_IDENTIFIER.equals( m.group( ANNOTATION_INDEX ) ) )
+ {
+ annotationSeparator = m.group( ANNOTATION_SEPARATOR_INDEX );
+ annotation = nullIfEmpty( m.group( ANNOTATION_INDEX ) );
+
+ if ( StringUtils.isNotEmpty( m.group( ANNOTATION_REV_SEPARATOR_INDEX ) )
+ && StringUtils.isEmpty( m.group( ANNOTATION_REVISION_INDEX ) ) )
+ {
+ // The build separator was picked up as the annotation revision separator
+ buildSeparator = m.group( ANNOTATION_REV_SEPARATOR_INDEX );
+ buildSpecifier = nullIfEmpty( m.group( BUILD_SPECIFIER_INDEX ) );
+ }
+ else
+ {
+ annotationRevSeparator = m.group( ANNOTATION_REV_SEPARATOR_INDEX );
+ annotationRevision = nullIfEmpty( m.group( ANNOTATION_REVISION_INDEX ) );
+
+ buildSeparator = m.group( BUILD_SEPARATOR_INDEX );
+ buildSpecifier = nullIfEmpty( m.group( BUILD_SPECIFIER_INDEX ) );
+ }
+ }
+ else
+ {
+ // Annotation was "SNAPSHOT" so populate the build specifier with that data
+ buildSeparator = m.group( ANNOTATION_SEPARATOR_INDEX );
+ buildSpecifier = nullIfEmpty( m.group( ANNOTATION_INDEX ) );
+ }
+ }
+ else
+ {
+ throw new VersionParseException( "Unable to parse the version string: \"" + version + "\"" );
+ }
+ }
+
+ public boolean isSnapshot()
+ {
+ return ArtifactUtils.isSnapshot( strVersion );
+ }
+
+ public String toString()
+ {
+ return strVersion;
+ }
+
+ protected static String getVersionString( Version info, String buildSpecifier, String buildSeparator )
+ {
+ StringBuilder sb = new StringBuilder();
+
+ if ( info.digits != null )
+ {
+ sb.append( joinDigitString( info.digits ) );
+ }
+
+ if ( StringUtils.isNotEmpty( info.annotation ) )
+ {
+ sb.append( StringUtils.defaultString( info.annotationSeparator ) );
+ sb.append( info.annotation );
+ }
+
+ if ( StringUtils.isNotEmpty( info.annotationRevision ) )
+ {
+ if ( StringUtils.isEmpty( info.annotation ) )
+ {
+ sb.append( StringUtils.defaultString( info.annotationSeparator ) );
+ }
+ else
+ {
+ sb.append( StringUtils.defaultString( info.annotationRevSeparator ) );
+ }
+ sb.append( info.annotationRevision );
+ }
+
+ if ( StringUtils.isNotEmpty( buildSpecifier ) )
+ {
+ sb.append( StringUtils.defaultString( buildSeparator ) );
+ sb.append( buildSpecifier );
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Simply joins the items in the list with "." period
+ *
+ * @return a {@code String} containing the items in the list joined by "." period
+ * @param digits the list of digits {@code List}
+ */
+ protected static String joinDigitString( List digits )
+ {
+ return digits != null ? StringUtils.join( digits.iterator(), DIGIT_SEPARATOR_STRING ) : null;
+ }
+
+ /**
+ * Splits the string on "." and returns a list containing each digit.
+ *
+ * @param strDigits
+ */
+ private List parseDigits( String strDigits )
+ {
+ return Arrays.asList( StringUtils.split( strDigits, DIGIT_SEPARATOR_STRING ) );
+ }
+
+ private static String nullIfEmpty( String s )
+ {
+ return StringUtils.isEmpty( s ) ? null : s;
+ }
+
+ public List getDigits()
+ {
+ return digits;
+ }
+
+ public String getAnnotation()
+ {
+ return annotation;
+ }
+
+ public String getAnnotationRevSeparator()
+ {
+ return annotationRevSeparator;
+ }
+
+ public String getAnnotationRevision()
+ {
+ return annotationRevision;
+ }
+
+ public String getBuildSeparator()
+ {
+ return buildSeparator;
+ }
+
+ public String getBuildSpecifier()
+ {
+ return buildSpecifier;
+ }
+
+ /**
+ *
+ * @param newDigits the new list of digits
+ * @return a new instance of Version
+ */
+ public Version setDigits( List newDigits )
+ {
+ return new Version( newDigits, this.annotation, this.annotationRevision, this.buildSpecifier,
+ this.annotationSeparator, this.annotationRevSeparator, this.buildSeparator );
+ }
+
+ /**
+ *
+ * @param newAnnotationRevision the new annotation revision
+ * @return a new instance of Version
+ */
+ public Version setAnnotationRevision( String newAnnotationRevision )
+ {
+ return new Version( this.digits, this.annotation, newAnnotationRevision, this.buildSpecifier,
+ this.annotationSeparator,
+ Objects.toString( this.annotationRevSeparator, DEFAULT_ANNOTATION_REV_SEPARATOR ),
+ this.buildSeparator );
+ }
+
+ /**
+ *
+ * @param newBuildSpecifier the new build specifier
+ * @return a new instance of Version
+ */
+ public Version setBuildSpecifier( String newBuildSpecifier )
+ {
+ return new Version( this.digits, this.annotation, this.annotationRevision, newBuildSpecifier,
+ this.annotationSeparator, this.annotationRevSeparator,
+ Objects.toString( this.buildSeparator, DEFAULT_BUILD_SEPARATOR ) );
+ }
+
+ /**
+ * @throws VersionComparisonConflictException if {@link org.eclipse.aether.version.Version} and
+ * {@link org.apache.maven.artifact.versioning.ArtifactVersion ArtifactVersion} give different results
+ */
+ public int compareTo( Version other )
+ throws VersionComparisonConflictException
+ {
+ int aetherComparisonResult = this.aetherVersion.compareTo( other.aetherVersion );
+ int mavenComparisonResult = this.mavenArtifactVersion.compareTo( other.mavenArtifactVersion );
+
+ if ( aetherComparisonResult < 0 && mavenComparisonResult < 0 )
+ {
+ return -1;
+ }
+ else if ( aetherComparisonResult == 0 && mavenComparisonResult == 0 )
+ {
+ return 0;
+ }
+ else if ( aetherComparisonResult > 0 && mavenComparisonResult > 0 )
+ {
+ return 1;
+ }
+ else
+ {
+ throw new VersionComparisonConflictException( this.strVersion, other.strVersion, aetherComparisonResult,
+ mavenComparisonResult );
+ }
+ }
+
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/versions/VersionComparisonConflictException.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/versions/VersionComparisonConflictException.java
new file mode 100644
index 000000000..441692eb7
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/versions/VersionComparisonConflictException.java
@@ -0,0 +1,53 @@
+package org.apache.maven.shared.release.versions;
+
+/*
+ * 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.
+ */
+
+/**
+ *
+ */
+public class VersionComparisonConflictException
+ extends RuntimeException
+{
+
+ private final String lhsVersion;
+
+ private final String rhsVersion;
+
+ private int aetherComparisonResult;
+
+ private int mavenComparisonResult;
+
+ public VersionComparisonConflictException( String lhsVersion, String rhsVersion, int aetherComparisonResult,
+ int mavenComparisonResult )
+ {
+ this.lhsVersion = lhsVersion;
+ this.rhsVersion = rhsVersion;
+ this.aetherComparisonResult = aetherComparisonResult;
+ this.mavenComparisonResult = mavenComparisonResult;
+ }
+
+ @Override
+ public String getMessage()
+ {
+ return "Conflict when comparing " + lhsVersion + " with " + rhsVersion + "; Aether: " + aetherComparisonResult
+ + "; Maven: " + mavenComparisonResult;
+ }
+
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/versions/VersionParseException.java b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/versions/VersionParseException.java
new file mode 100644
index 000000000..d37cd9218
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/main/java/org/apache/maven/shared/release/versions/VersionParseException.java
@@ -0,0 +1,32 @@
+package org.apache.maven.shared.release.versions;
+
+/*
+ * 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.
+ */
+
+/**
+ *
+ */
+public class VersionParseException
+ extends Exception
+{
+ public VersionParseException( String message )
+ {
+ super( message );
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-api/src/site/site.xml b/Java-base/maven-release/src/maven-release-api/src/site/site.xml
new file mode 100644
index 000000000..220ecbb57
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-api/src/site/site.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Java-base/maven-release/src/maven-release-manager/pom.xml b/Java-base/maven-release/src/maven-release-manager/pom.xml
new file mode 100644
index 000000000..9f1edb004
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/pom.xml
@@ -0,0 +1,289 @@
+
+
+
+
+ 4.0.0
+
+
+ org.apache.maven.release
+ maven-release
+ 3.0.0-SNAPSHOT
+
+
+ maven-release-manager
+
+ Maven Release Manager
+
+
+
+ org.apache.maven.release
+ maven-release-api
+ 3.0.0-SNAPSHOT
+
+
+ org.codehaus.plexus
+ plexus-utils
+
+
+ org.codehaus.plexus
+ plexus-interactivity-api
+ 1.0-alpha-6
+
+
+ plexus
+ plexus-utils
+
+
+ org.codehaus.plexus
+ plexus-component-api
+
+
+
+
+ org.codehaus.plexus
+ plexus-interpolation
+ 1.17
+
+
+
+ org.sonatype.plexus
+ plexus-sec-dispatcher
+ 1.3
+
+
+ org.sonatype.plexus
+ plexus-cipher
+ 1.4
+
+
+
+ org.apache.maven
+ maven-model
+
+
+ org.apache.maven
+ maven-artifact
+
+
+ org.apache.maven
+ maven-core
+
+
+ org.apache.maven
+ maven-settings
+
+
+ org.apache.maven.shared
+ maven-invoker
+ 2.2
+
+
+ org.apache.commons
+ commons-lang3
+ 3.7
+
+
+ commons-cli
+ commons-cli
+ 1.2
+
+
+ commons-lang
+ commons-lang
+
+
+
+
+ commons-io
+ commons-io
+ 2.6
+
+
+
+ org.apache.maven.scm
+ maven-scm-providers-standard
+ pom
+ runtime
+
+
+ org.apache.maven.scm
+ maven-scm-manager-plexus
+ runtime
+
+
+ org.apache.maven.scm
+ maven-scm-api
+
+
+ org.apache.maven.scm
+ maven-scm-provider-svn-commons
+
+
+ org.apache.maven.shared
+ maven-artifact-transfer
+ 0.9.1
+
+
+
+ org.jdom
+ jdom
+
+
+
+ junit
+ junit
+ test
+
+
+ org.apache.maven
+ maven-compat
+ test
+
+
+ org.apache.maven.scm
+ maven-scm-test
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.xmlunit
+ xmlunit-core
+ test
+
+
+ org.sonatype.aether
+ aether-connector-file
+ 1.7
+ test
+
+
+ org.sonatype.aether
+ aether-connector-wagon
+ 1.7
+ test
+
+
+
+
+
+
+ maven-surefire-plugin
+
+ -Xmx256m
+
+ ${project.build.testOutputDirectory}/settings-security.xml
+ ${maven.home}
+
+
+
+
+ org.codehaus.modello
+ modello-maven-plugin
+
+
+
+ xpp3-reader
+ java
+ xpp3-writer
+
+
+
+
+ 3.0.0
+ false
+ true
+
+ src/main/mdo/release-descriptor.mdo
+
+
+
+
+ org.codehaus.plexus
+ plexus-component-metadata
+
+
+
+ generate-metadata
+
+
+
+ class
+
+
+
+
+ merge
+ process-classes
+
+ merge-metadata
+
+
+
+
+ ${project.build.outputDirectory}/META-INF/plexus/components.xml
+ src/main/components-fragment.xml
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+ org.apache.maven.shared.release.config
+
+
+
+
+
+
+
+ reporting
+
+
+
+ org.codehaus.plexus
+ plexus-maven-plugin
+ 1.3.8
+
+
+
+
+
+
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/components-fragment.xml b/Java-base/maven-release/src/maven-release-manager/src/main/components-fragment.xml
new file mode 100644
index 000000000..6bb0f3165
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/components-fragment.xml
@@ -0,0 +1,261 @@
+
+
+
+
+
+
+
+ org.apache.maven.shared.release.strategy.Strategy
+ default
+ org.apache.maven.shared.release.strategies.DefaultStrategy
+
+
+ check-poms
+ scm-check-modifications
+ check-dependency-snapshots
+ create-backup-poms
+ map-release-versions
+ input-variables
+ map-development-versions
+ rewrite-poms-for-release
+ generate-release-poms
+ run-preparation-goals
+ scm-commit-release
+ scm-tag
+ rewrite-poms-for-development
+ remove-release-poms
+ run-completion-goals
+ scm-commit-development
+ end-release
+
+
+ verify-completed-prepare-phases
+ checkout-project-from-scm
+ run-perform-goals
+
+
+ restore-backup-poms
+ scm-commit-rollback
+ remove-scm-tag
+
+
+ check-poms
+ scm-check-modifications
+ create-backup-poms
+ map-branch-versions
+ branch-input-variables
+ map-development-versions
+ rewrite-poms-for-branch
+ scm-commit-branch
+ scm-branch
+ rewrite-poms-for-development
+ scm-commit-development
+ end-release
+
+
+ check-poms-updateversions
+ create-backup-poms
+ map-development-versions
+ rewrite-pom-versions
+
+
+
+
+
+ org.apache.maven.shared.release.phase.ReleasePhase
+ map-release-versions
+ org.apache.maven.shared.release.phase.MapVersionsPhase
+
+ false
+
+
+
+ org.codehaus.plexus.components.interactivity.Prompter
+ default
+
+
+ org.apache.maven.shared.release.policy.version.VersionPolicy
+ versionPolicies
+
+
+
+
+ org.apache.maven.shared.release.phase.ReleasePhase
+ map-development-versions
+ org.apache.maven.shared.release.phase.MapVersionsPhase
+
+ true
+
+
+
+ org.codehaus.plexus.components.interactivity.Prompter
+ default
+
+
+ org.apache.maven.shared.release.policy.version.VersionPolicy
+ versionPolicies
+
+
+
+
+ org.apache.maven.shared.release.phase.ReleasePhase
+ map-branch-versions
+ org.apache.maven.shared.release.phase.MapVersionsPhase
+
+ true
+ true
+
+
+
+ org.codehaus.plexus.components.interactivity.Prompter
+ default
+
+
+ org.apache.maven.shared.release.policy.version.VersionPolicy
+ versionPolicies
+
+
+
+
+ org.apache.maven.shared.release.phase.ReleasePhase
+ scm-commit-release
+ org.apache.maven.shared.release.phase.ScmCommitPreparationPhase
+
+
+ org.apache.maven.shared.release.scm.ScmRepositoryConfigurator
+
+
+
+ getScmReleaseCommitComment
+
+
+
+ org.apache.maven.shared.release.phase.ReleasePhase
+ scm-commit-development
+ org.apache.maven.shared.release.phase.ScmCommitDevelopmentPhase
+
+
+ org.apache.maven.shared.release.scm.ScmRepositoryConfigurator
+
+
+
+ getScmDevelopmentCommitComment
+ rollback changes from release preparation of {0}
+
+
+
+ org.apache.maven.shared.release.phase.ReleasePhase
+ scm-commit-branch
+ org.apache.maven.shared.release.phase.ScmCommitPreparationPhase
+
+
+ org.apache.maven.shared.release.scm.ScmRepositoryConfigurator
+
+
+
+ getScmBranchCommitComment
+
+
+
+ org.apache.maven.shared.release.phase.ReleasePhase
+ scm-commit-rollback
+ org.apache.maven.shared.release.phase.ScmCommitPreparationPhase
+
+
+ org.apache.maven.shared.release.scm.ScmRepositoryConfigurator
+
+
+
+ getScmRollbackCommitComment
+
+
+
+ org.apache.maven.shared.release.phase.ReleasePhase
+ check-poms
+ org.apache.maven.shared.release.phase.CheckPomPhase
+
+ true
+ true
+
+
+
+ org.apache.maven.shared.release.scm.ScmRepositoryConfigurator
+
+
+
+
+ org.apache.maven.shared.release.phase.ReleasePhase
+ check-poms-updateversions
+ org.apache.maven.shared.release.phase.CheckPomPhase
+
+ false
+ false
+
+
+
+ org.apache.maven.shared.release.scm.ScmRepositoryConfigurator
+
+
+
+
+ org.apache.maven.shared.release.phase.ReleasePhase
+ input-variables
+ org.apache.maven.shared.release.phase.InputVariablesPhase
+
+ false
+ default
+
+
+
+ org.codehaus.plexus.components.interactivity.Prompter
+ default
+
+
+ org.apache.maven.shared.release.scm.ScmRepositoryConfigurator
+
+
+ org.apache.maven.shared.release.policy.naming.NamingPolicy
+ namingPolicies
+
+
+
+
+ org.apache.maven.shared.release.phase.ReleasePhase
+ branch-input-variables
+ org.apache.maven.shared.release.phase.InputVariablesPhase
+
+ true
+
+
+
+ org.codehaus.plexus.components.interactivity.Prompter
+ default
+
+
+ org.apache.maven.shared.release.scm.ScmRepositoryConfigurator
+
+
+ org.apache.maven.shared.release.policy.naming.NamingPolicy
+ namingPolicies
+
+
+
+
+
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/AbstractReleaseRequest.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/AbstractReleaseRequest.java
new file mode 100644
index 000000000..470c6c24a
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/AbstractReleaseRequest.java
@@ -0,0 +1,87 @@
+package org.apache.maven.shared.release;
+
+/*
+ * 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.util.List;
+
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.release.config.ReleaseDescriptorBuilder;
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 2.3
+ */
+public abstract class AbstractReleaseRequest
+{
+ private ReleaseDescriptorBuilder releaseDescriptorBuilder;
+
+ private List reactorProjects;
+
+ private ReleaseManagerListener releaseManagerListener;
+
+ /**
+ * @return the releaseDescriptor
+ */
+ public ReleaseDescriptorBuilder getReleaseDescriptorBuilder()
+ {
+ return releaseDescriptorBuilder;
+ }
+
+ /**
+ * @param releaseDescriptor the releaseDescriptor to set
+ */
+ public void setReleaseDescriptorBuilder( ReleaseDescriptorBuilder releaseDescriptor )
+ {
+ this.releaseDescriptorBuilder = releaseDescriptor;
+ }
+
+ /**
+ * @return the reactorProjects
+ */
+ public List getReactorProjects()
+ {
+ return reactorProjects;
+ }
+
+ /**
+ * @param reactorProjects the reactorProjects to set
+ */
+ public void setReactorProjects( List reactorProjects )
+ {
+ this.reactorProjects = reactorProjects;
+ }
+
+ /**
+ * @return the releaseManagerListener
+ */
+ public ReleaseManagerListener getReleaseManagerListener()
+ {
+ return releaseManagerListener;
+ }
+
+ /**
+ * @param releaseManagerListener the releaseManagerListener to set
+ */
+ public void setReleaseManagerListener( ReleaseManagerListener releaseManagerListener )
+ {
+ this.releaseManagerListener = releaseManagerListener;
+ }
+}
\ No newline at end of file
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/DefaultReleaseManager.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/DefaultReleaseManager.java
new file mode 100644
index 000000000..f023ab680
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/DefaultReleaseManager.java
@@ -0,0 +1,699 @@
+package org.apache.maven.shared.release;
+
+/*
+ * 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.File;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.config.ReleaseDescriptorBuilder;
+import org.apache.maven.shared.release.config.ReleaseDescriptorBuilder.BuilderReleaseDescriptor;
+import org.apache.maven.shared.release.config.ReleaseDescriptorStore;
+import org.apache.maven.shared.release.config.ReleaseDescriptorStoreException;
+import org.apache.maven.shared.release.config.ReleaseUtils;
+import org.apache.maven.shared.release.phase.ReleasePhase;
+import org.apache.maven.shared.release.phase.ResourceGenerator;
+import org.apache.maven.shared.release.strategy.Strategy;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.logging.AbstractLogEnabled;
+import org.codehaus.plexus.util.StringUtils;
+
+/**
+ * Implementation of the release manager.
+ *
+ * @author Brett Porter
+ */
+@Component( role = ReleaseManager.class )
+public class DefaultReleaseManager
+ extends AbstractLogEnabled
+ implements ReleaseManager
+{
+ @Requirement
+ private Map strategies;
+
+ /**
+ * The available phases.
+ */
+ @Requirement
+ private Map releasePhases;
+
+ /**
+ * The configuration storage.
+ */
+ @Requirement( hint = "properties" )
+ private ReleaseDescriptorStore configStore;
+
+ private static final int PHASE_SKIP = 0, PHASE_START = 1, PHASE_END = 2, GOAL_END = 12, ERROR = 99;
+
+ @Override
+ public ReleaseResult prepareWithResult( ReleasePrepareRequest prepareRequest )
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ result.setStartTime( System.currentTimeMillis() );
+
+ try
+ {
+ prepare( prepareRequest, result );
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+ }
+ catch ( ReleaseExecutionException | ReleaseFailureException e )
+ {
+ captureException( result, prepareRequest.getReleaseManagerListener(), e );
+ }
+ finally
+ {
+ result.setEndTime( System.currentTimeMillis() );
+ }
+
+ return result;
+ }
+
+ @Override
+ public void prepare( ReleasePrepareRequest prepareRequest )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ prepare( prepareRequest, new ReleaseResult() );
+ }
+
+ private void prepare( ReleasePrepareRequest prepareRequest, ReleaseResult result )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+
+ final ReleaseDescriptorBuilder builder = prepareRequest.getReleaseDescriptorBuilder();
+
+ // Create a config containing values from the session properties (ie command line properties with cli).
+ ReleaseUtils.copyPropertiesToReleaseDescriptor( prepareRequest.getUserProperties(),
+ new ReleaseDescriptorBuilder()
+ {
+ public ReleaseDescriptorBuilder addDevelopmentVersion( String key,
+ String value )
+ {
+ builder.addDevelopmentVersion( key, value );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder addReleaseVersion( String key,
+ String value )
+ {
+ builder.addReleaseVersion( key, value );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder addDependencyReleaseVersion( String dependencyKey,
+ String version )
+ {
+ builder.addDependencyReleaseVersion( dependencyKey, version );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder addDependencyDevelopmentVersion( String dependencyKey,
+ String version )
+ {
+ builder.addDependencyDevelopmentVersion( dependencyKey, version );
+ return this;
+ }
+ } );
+
+ BuilderReleaseDescriptor config;
+ if ( BooleanUtils.isNotFalse( prepareRequest.getResume() ) )
+ {
+ config = loadReleaseDescriptor( builder, prepareRequest.getReleaseManagerListener() );
+ }
+ else
+ {
+ config = ReleaseUtils.buildReleaseDescriptor( builder );
+ }
+
+ Strategy releaseStrategy = getStrategy( config.getReleaseStrategyId() );
+
+ List preparePhases = getGoalPhases( releaseStrategy, "prepare" );
+
+ goalStart( prepareRequest.getReleaseManagerListener(), "prepare", preparePhases );
+
+ // Later, it would be a good idea to introduce a proper workflow tool so that the release can be made up of a
+ // more flexible set of steps.
+
+ String completedPhase = config.getCompletedPhase();
+ int index = preparePhases.indexOf( completedPhase );
+
+ for ( int idx = 0; idx <= index; idx++ )
+ {
+ updateListener( prepareRequest.getReleaseManagerListener(), preparePhases.get( idx ), PHASE_SKIP );
+ }
+
+ if ( index == preparePhases.size() - 1 )
+ {
+ logInfo( result, "Release preparation already completed. You can now continue with release:perform, "
+ + "or start again using the -Dresume=false flag" );
+ }
+ else if ( index >= 0 )
+ {
+ logInfo( result, "Resuming release from phase '" + preparePhases.get( index + 1 ) + "'" );
+ }
+
+ // start from next phase
+ for ( int i = index + 1; i < preparePhases.size(); i++ )
+ {
+ String name = preparePhases.get( i );
+
+ ReleasePhase phase = releasePhases.get( name );
+
+ if ( phase == null )
+ {
+ throw new ReleaseExecutionException( "Unable to find phase '" + name + "' to execute" );
+ }
+
+ updateListener( prepareRequest.getReleaseManagerListener(), name, PHASE_START );
+
+ ReleaseResult phaseResult = null;
+ try
+ {
+ if ( BooleanUtils.isTrue( prepareRequest.getDryRun() ) )
+ {
+ phaseResult = phase.simulate( config,
+ prepareRequest.getReleaseEnvironment(),
+ prepareRequest.getReactorProjects() );
+ }
+ else
+ {
+ phaseResult = phase.execute( config,
+ prepareRequest.getReleaseEnvironment(),
+ prepareRequest.getReactorProjects() );
+ }
+ }
+ finally
+ {
+ if ( result != null && phaseResult != null )
+ {
+ result.appendOutput( phaseResult.getOutput() );
+ }
+ }
+
+ config.setCompletedPhase( name );
+ try
+ {
+ configStore.write( config );
+ }
+ catch ( ReleaseDescriptorStoreException e )
+ {
+ // TODO: rollback?
+ throw new ReleaseExecutionException( "Error writing release properties after completing phase", e );
+ }
+
+ updateListener( prepareRequest.getReleaseManagerListener(), name, PHASE_END );
+ }
+
+ updateListener( prepareRequest.getReleaseManagerListener(), "prepare", GOAL_END );
+ }
+
+ @Override
+ public void rollback( ReleaseRollbackRequest rollbackRequest )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseDescriptor releaseDescriptor =
+ loadReleaseDescriptor( rollbackRequest.getReleaseDescriptorBuilder(), null );
+
+ Strategy releaseStrategy = getStrategy( releaseDescriptor.getReleaseStrategyId() );
+
+ List rollbackPhases = getGoalPhases( releaseStrategy, "rollback" );
+
+ goalStart( rollbackRequest.getReleaseManagerListener(), "rollback", rollbackPhases );
+
+ for ( String name : rollbackPhases )
+ {
+ ReleasePhase phase = releasePhases.get( name );
+
+ if ( phase == null )
+ {
+ throw new ReleaseExecutionException( "Unable to find phase '" + name + "' to execute" );
+ }
+
+ updateListener( rollbackRequest.getReleaseManagerListener(), name, PHASE_START );
+ phase.execute( releaseDescriptor,
+ rollbackRequest.getReleaseEnvironment(),
+ rollbackRequest.getReactorProjects() );
+ updateListener( rollbackRequest.getReleaseManagerListener(), name, PHASE_END );
+ }
+
+ //call release:clean so that resume will not be possible anymore after a rollback
+ clean( rollbackRequest );
+ updateListener( rollbackRequest.getReleaseManagerListener(), "rollback", GOAL_END );
+ }
+
+ @Override
+ public ReleaseResult performWithResult( ReleasePerformRequest performRequest )
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ try
+ {
+ result.setStartTime( System.currentTimeMillis() );
+
+ perform( performRequest, result );
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+ }
+ catch ( ReleaseExecutionException | ReleaseFailureException e )
+ {
+ captureException( result, performRequest.getReleaseManagerListener(), e );
+ }
+ finally
+ {
+ result.setEndTime( System.currentTimeMillis() );
+ }
+
+ return result;
+ }
+
+ @Override
+ public void perform( ReleasePerformRequest performRequest )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ perform( performRequest, new ReleaseResult() );
+ }
+
+ private void perform( ReleasePerformRequest performRequest, ReleaseResult result )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ List specificProfiles =
+ ReleaseUtils.buildReleaseDescriptor( performRequest.getReleaseDescriptorBuilder() )
+ .getActivateProfiles();
+
+ ReleaseDescriptor releaseDescriptor =
+ loadReleaseDescriptor( performRequest.getReleaseDescriptorBuilder(),
+ performRequest.getReleaseManagerListener() );
+
+ if ( specificProfiles != null && !specificProfiles.isEmpty() )
+ {
+ for ( String specificProfile : specificProfiles )
+ {
+ if ( !releaseDescriptor.getActivateProfiles().contains( specificProfile ) )
+ {
+ releaseDescriptor.getActivateProfiles().add( specificProfile );
+ }
+ }
+ }
+
+ Strategy releaseStrategy = getStrategy( releaseDescriptor.getReleaseStrategyId() );
+
+ List performPhases = getGoalPhases( releaseStrategy, "perform" );
+
+ goalStart( performRequest.getReleaseManagerListener(), "perform", performPhases );
+
+ for ( String name : performPhases )
+ {
+ ReleasePhase phase = releasePhases.get( name );
+
+ if ( phase == null )
+ {
+ throw new ReleaseExecutionException( "Unable to find phase '" + name + "' to execute" );
+ }
+
+ updateListener( performRequest.getReleaseManagerListener(), name, PHASE_START );
+
+ ReleaseResult phaseResult = null;
+ try
+ {
+ if ( BooleanUtils.isTrue( performRequest.getDryRun() ) )
+ {
+ phaseResult = phase.simulate( releaseDescriptor,
+ performRequest.getReleaseEnvironment(),
+ performRequest.getReactorProjects() );
+ }
+ else
+ {
+ phaseResult = phase.execute( releaseDescriptor,
+ performRequest.getReleaseEnvironment(),
+ performRequest.getReactorProjects() );
+ }
+ }
+ finally
+ {
+ if ( result != null && phaseResult != null )
+ {
+ result.appendOutput( phaseResult.getOutput() );
+ }
+ }
+
+ updateListener( performRequest.getReleaseManagerListener(), name, PHASE_END );
+ }
+
+ if ( BooleanUtils.isNotFalse( performRequest.getClean() ) )
+ {
+ // call release:clean so that resume will not be possible anymore after a perform
+ clean( performRequest );
+ }
+
+ updateListener( performRequest.getReleaseManagerListener(), "perform", GOAL_END );
+ }
+
+ @Override
+ public void branch( ReleaseBranchRequest branchRequest )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ final ReleaseDescriptorBuilder builder = branchRequest.getReleaseDescriptorBuilder();
+
+ ReleaseUtils.copyPropertiesToReleaseDescriptor( branchRequest.getUserProperties(),
+ new ReleaseDescriptorBuilder()
+ {
+ public ReleaseDescriptorBuilder addDevelopmentVersion( String key,
+ String value )
+ {
+ builder.addDevelopmentVersion( key, value );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder addReleaseVersion( String key,
+ String value )
+ {
+ builder.addReleaseVersion( key, value );
+ return this;
+ }
+ } );
+
+ ReleaseDescriptor releaseDescriptor =
+ loadReleaseDescriptor( builder, branchRequest.getReleaseManagerListener() );
+
+ boolean dryRun = BooleanUtils.isTrue( branchRequest.getDryRun() );
+
+ Strategy releaseStrategy = getStrategy( releaseDescriptor.getReleaseStrategyId() );
+
+ List branchPhases = getGoalPhases( releaseStrategy, "branch" );
+
+ goalStart( branchRequest.getReleaseManagerListener(), "branch", branchPhases );
+
+ for ( String name : branchPhases )
+ {
+ ReleasePhase phase = releasePhases.get( name );
+
+ if ( phase == null )
+ {
+ throw new ReleaseExecutionException( "Unable to find phase '" + name + "' to execute" );
+ }
+
+ updateListener( branchRequest.getReleaseManagerListener(), name, PHASE_START );
+
+ if ( dryRun )
+ {
+ phase.simulate( releaseDescriptor,
+ branchRequest.getReleaseEnvironment(),
+ branchRequest.getReactorProjects() );
+ }
+ else // getDryRun is null or FALSE
+ {
+ phase.execute( releaseDescriptor,
+ branchRequest.getReleaseEnvironment(),
+ branchRequest.getReactorProjects() );
+ }
+ updateListener( branchRequest.getReleaseManagerListener(), name, PHASE_END );
+ }
+
+ if ( !dryRun )
+ {
+ clean( branchRequest );
+ }
+
+ updateListener( branchRequest.getReleaseManagerListener(), "branch", GOAL_END );
+ }
+
+ @Override
+ public void updateVersions( ReleaseUpdateVersionsRequest updateVersionsRequest )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ final ReleaseDescriptorBuilder builder = updateVersionsRequest.getReleaseDescriptorBuilder();
+
+ // Create a config containing values from the session properties (ie command line properties with cli).
+ ReleaseUtils.copyPropertiesToReleaseDescriptor( updateVersionsRequest.getUserProperties(),
+ new ReleaseDescriptorBuilder()
+ {
+ public ReleaseDescriptorBuilder addDevelopmentVersion( String key,
+ String value )
+ {
+ builder.addDevelopmentVersion( key, value );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder addReleaseVersion( String key,
+ String value )
+ {
+ builder.addReleaseVersion( key, value );
+ return this;
+ }
+ } );
+
+ ReleaseDescriptor releaseDescriptor =
+ loadReleaseDescriptor( builder, updateVersionsRequest.getReleaseManagerListener() );
+
+ Strategy releaseStrategy = getStrategy( releaseDescriptor.getReleaseStrategyId() );
+
+ List updateVersionsPhases = getGoalPhases( releaseStrategy, "updateVersions" );
+
+ goalStart( updateVersionsRequest.getReleaseManagerListener(), "updateVersions", updateVersionsPhases );
+
+ for ( String name : updateVersionsPhases )
+ {
+ ReleasePhase phase = releasePhases.get( name );
+
+ if ( phase == null )
+ {
+ throw new ReleaseExecutionException( "Unable to find phase '" + name + "' to execute" );
+ }
+
+ updateListener( updateVersionsRequest.getReleaseManagerListener(), name, PHASE_START );
+ phase.execute( releaseDescriptor,
+ updateVersionsRequest.getReleaseEnvironment(),
+ updateVersionsRequest.getReactorProjects() );
+ updateListener( updateVersionsRequest.getReleaseManagerListener(), name, PHASE_END );
+ }
+
+ clean( updateVersionsRequest );
+
+ updateListener( updateVersionsRequest.getReleaseManagerListener(), "updateVersions", GOAL_END );
+ }
+
+ /**
+ * Determines the path of the working directory. By default, this is the
+ * checkout directory. For some SCMs, the project root directory is not the
+ * checkout directory itself, but a SCM-specific subdirectory.
+ *
+ * @param checkoutDirectory The checkout directory as java.io.File
+ * @param relativePathProjectDirectory The relative path of the project directory within the checkout
+ * directory or ""
+ * @return The working directory
+ */
+ protected File determineWorkingDirectory( File checkoutDirectory, String relativePathProjectDirectory )
+ {
+ if ( StringUtils.isNotEmpty( relativePathProjectDirectory ) )
+ {
+ return new File( checkoutDirectory, relativePathProjectDirectory );
+ }
+ else
+ {
+ return checkoutDirectory;
+ }
+ }
+
+ private BuilderReleaseDescriptor loadReleaseDescriptor( ReleaseDescriptorBuilder builder,
+ ReleaseManagerListener listener )
+ throws ReleaseExecutionException
+ {
+ try
+ {
+ updateListener( listener, "verify-release-configuration", PHASE_START );
+ BuilderReleaseDescriptor descriptor = ReleaseUtils.buildReleaseDescriptor( configStore.read( builder ) );
+ updateListener( listener, "verify-release-configuration", PHASE_END );
+ return descriptor;
+ }
+ catch ( ReleaseDescriptorStoreException e )
+ {
+ updateListener( listener, e.getMessage(), ERROR );
+
+ throw new ReleaseExecutionException( "Error reading stored configuration: " + e.getMessage(), e );
+ }
+ }
+
+
+ protected void clean( AbstractReleaseRequest releaseRequest ) throws ReleaseFailureException
+ {
+ ReleaseCleanRequest cleanRequest = new ReleaseCleanRequest();
+ cleanRequest.setReleaseDescriptorBuilder( releaseRequest.getReleaseDescriptorBuilder() );
+ cleanRequest.setReleaseManagerListener( releaseRequest.getReleaseManagerListener() );
+ cleanRequest.setReactorProjects( releaseRequest.getReactorProjects() );
+
+ clean( cleanRequest );
+ }
+
+ @Override
+ public void clean( ReleaseCleanRequest cleanRequest ) throws ReleaseFailureException
+ {
+ updateListener( cleanRequest.getReleaseManagerListener(), "cleanup", PHASE_START );
+
+ getLogger().info( "Cleaning up after release..." );
+
+ ReleaseDescriptor releaseDescriptor =
+ ReleaseUtils.buildReleaseDescriptor( cleanRequest.getReleaseDescriptorBuilder() );
+
+ configStore.delete( releaseDescriptor );
+
+ Strategy releaseStrategy = getStrategy( releaseDescriptor.getReleaseStrategyId() );
+
+ Set phases = new LinkedHashSet<>();
+ phases.addAll( getGoalPhases( releaseStrategy, "prepare" ) );
+ phases.addAll( getGoalPhases( releaseStrategy, "branch" ) );
+
+ for ( String name : phases )
+ {
+ ReleasePhase phase = releasePhases.get( name );
+
+ if ( phase instanceof ResourceGenerator )
+ {
+ ( (ResourceGenerator) phase ).clean( cleanRequest.getReactorProjects() );
+ }
+ }
+
+ updateListener( cleanRequest.getReleaseManagerListener(), "cleanup", PHASE_END );
+ }
+
+ void setConfigStore( ReleaseDescriptorStore configStore )
+ {
+ this.configStore = configStore;
+ }
+
+ void goalStart( ReleaseManagerListener listener, String goal, List phases )
+ {
+ if ( listener != null )
+ {
+ listener.goalStart( goal, phases );
+ }
+ }
+
+ void updateListener( ReleaseManagerListener listener, String name, int state )
+ {
+ if ( listener != null )
+ {
+ switch ( state )
+ {
+ case GOAL_END:
+ listener.goalEnd();
+ break;
+ case PHASE_SKIP:
+ listener.phaseSkip( name );
+ break;
+ case PHASE_START:
+ listener.phaseStart( name );
+ break;
+ case PHASE_END:
+ listener.phaseEnd();
+ break;
+ default:
+ listener.error( name );
+ }
+ }
+ }
+
+ private Strategy getStrategy( String strategyId ) throws ReleaseFailureException
+ {
+ Strategy strategy = strategies.get( strategyId );
+ if ( strategy == null )
+ {
+ throw new ReleaseFailureException( "Unknown strategy: " + strategyId );
+ }
+ return strategy;
+ }
+
+ private List getGoalPhases( Strategy strategy, String goal )
+ {
+ List phases;
+
+ if ( "prepare".equals( goal ) )
+ {
+ phases = strategy.getPreparePhases();
+ if ( phases == null )
+ {
+ phases = strategies.get( "default" ).getPreparePhases();
+ }
+ }
+ else if ( "perform".equals( goal ) )
+ {
+ phases = strategy.getPerformPhases();
+ if ( phases == null )
+ {
+ phases = strategies.get( "default" ).getPerformPhases();
+ }
+ }
+ else if ( "rollback".equals( goal ) )
+ {
+ phases = strategy.getRollbackPhases();
+ if ( phases == null )
+ {
+ phases = strategies.get( "default" ).getRollbackPhases();
+ }
+ }
+ else if ( "branch".equals( goal ) )
+ {
+ phases = strategy.getBranchPhases();
+ if ( phases == null )
+ {
+ phases = strategies.get( "default" ).getBranchPhases();
+ }
+ }
+ else if ( "updateVersions".equals( goal ) )
+ {
+ phases = strategy.getUpdateVersionsPhases();
+ if ( phases == null )
+ {
+ phases = strategies.get( "default" ).getUpdateVersionsPhases();
+ }
+ }
+ else
+ {
+ phases = null;
+ }
+
+ return Collections.unmodifiableList( phases );
+ }
+
+ private void logInfo( ReleaseResult result, String message )
+ {
+ if ( result != null )
+ {
+ result.appendInfo( message );
+ }
+
+ getLogger().info( message );
+ }
+
+ private void captureException( ReleaseResult result, ReleaseManagerListener listener, Exception e )
+ {
+ updateListener( listener, e.getMessage(), ERROR );
+
+ result.appendError( e );
+
+ result.setResultCode( ReleaseResult.ERROR );
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/DefaultReleaseManagerListener.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/DefaultReleaseManagerListener.java
new file mode 100644
index 000000000..b8bbb2315
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/DefaultReleaseManagerListener.java
@@ -0,0 +1,109 @@
+package org.apache.maven.shared.release;
+
+/*
+ * 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.util.List;
+
+import org.apache.maven.plugin.logging.Log;
+import org.codehaus.plexus.util.StringUtils;
+
+import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
+
+/**
+ * @author Hervé Boutemy
+ */
+public class DefaultReleaseManagerListener
+ implements ReleaseManagerListener
+{
+ private final Log log;
+
+ private final boolean dryRun;
+
+ private String goal;
+
+ private List phases;
+
+ private int currentPhase;
+
+ public DefaultReleaseManagerListener( Log log )
+ {
+ this( log, false );
+ }
+
+ public DefaultReleaseManagerListener( Log log, boolean dryRun )
+ {
+ this.log = log;
+ this.dryRun = dryRun;
+ }
+
+ private void nextPhase( String name )
+ {
+ currentPhase++;
+ if ( !name.equals( phases.get( currentPhase ) ) )
+ {
+ log.warn( "inconsistent phase name: expected '" + phases.get( currentPhase ) + "' but got '" + name + "'" );
+ }
+ }
+
+ public void goalStart( String goal, List phases )
+ {
+ log.info( "starting " + buffer().strong( goal ) + " goal" + ( dryRun ? " in dry-run mode" : "" )
+ + ", composed of " + phases.size() + " phases: " + StringUtils.join( phases.iterator(), ", " ) );
+ currentPhase = -1;
+ this.phases = phases;
+ this.goal = goal;
+ }
+
+ public void phaseStart( String name )
+ {
+ if ( goal == null || ( ( currentPhase + 1 ) >= phases.size() ) )
+ {
+ // out of goal phase
+ log.info( "phase " + buffer().strong( name ) + ( dryRun ? " (dry-run)" : "" ) );
+ return;
+ }
+
+ nextPhase( name );
+ log.info( buffer().strong( "[" + goal + ( dryRun ? " dry-run" : "" ) + "] " ).toString() + ( currentPhase + 1 )
+ + "/" + phases.size() + " " + buffer().strong( name ) );
+ }
+
+ public void phaseEnd()
+ {
+ // NOOP
+ }
+
+ public void phaseSkip( String name )
+ {
+ nextPhase( name );
+ }
+
+ public void goalEnd()
+ {
+ goal = null;
+ phases = null;
+ }
+
+ public void error( String reason )
+ {
+ log.error( "error during phase " + ( currentPhase + 1 ) + "/" + phases.size() + " " + phases.get( currentPhase )
+ + ": " + reason );
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleaseBranchRequest.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleaseBranchRequest.java
new file mode 100644
index 000000000..45b69c85e
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleaseBranchRequest.java
@@ -0,0 +1,80 @@
+package org.apache.maven.shared.release;
+
+/*
+ * 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.util.Properties;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 2.3
+ */
+public class ReleaseBranchRequest extends AbstractReleaseRequest
+{
+ // using Boolean to detect if has been set explicitly
+ private Boolean dryRun;
+
+ private ReleaseEnvironment releaseEnvironment;
+
+ private Properties userProperties;
+
+ /**
+ * @return the dryRun
+ */
+ public Boolean getDryRun()
+ {
+ return dryRun;
+ }
+
+ /**
+ * @param dryRun the dryRun to set
+ */
+ public void setDryRun( Boolean dryRun )
+ {
+ this.dryRun = dryRun;
+ }
+
+ /**
+ * @return the releaseEnvironment
+ */
+ public ReleaseEnvironment getReleaseEnvironment()
+ {
+ return releaseEnvironment;
+ }
+
+ /**
+ * @param releaseEnvironment the releaseEnvironment to set
+ */
+ public void setReleaseEnvironment( ReleaseEnvironment releaseEnvironment )
+ {
+ this.releaseEnvironment = releaseEnvironment;
+ }
+
+ public Properties getUserProperties()
+ {
+ return userProperties;
+ }
+
+ public void setUserProperties( Properties userProperties )
+ {
+ this.userProperties = userProperties;
+ }
+}
\ No newline at end of file
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleaseCleanRequest.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleaseCleanRequest.java
new file mode 100644
index 000000000..a1a32d0df
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleaseCleanRequest.java
@@ -0,0 +1,30 @@
+package org.apache.maven.shared.release;
+
+/*
+ * 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.
+ */
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 2.3
+ */
+public class ReleaseCleanRequest
+ extends AbstractReleaseRequest
+{
+}
\ No newline at end of file
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleaseManager.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleaseManager.java
new file mode 100644
index 000000000..fdfb89ffe
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleaseManager.java
@@ -0,0 +1,94 @@
+package org.apache.maven.shared.release;
+
+/*
+ * 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.
+ */
+
+/**
+ * Release management classes.
+ *
+ * @author Brett Porter
+ */
+public interface ReleaseManager
+{
+ /**
+ * Prepare a release.
+ *
+ * @param prepareRequest all prepare arguments
+ * @throws ReleaseExecutionException if there is a problem performing the release
+ * @throws ReleaseFailureException if there is a problem performing the release
+ * @since 2.3
+ */
+ void prepare( ReleasePrepareRequest prepareRequest ) throws ReleaseExecutionException, ReleaseFailureException;
+
+ ReleaseResult prepareWithResult( ReleasePrepareRequest prepareRequest );
+
+ ReleaseResult performWithResult( ReleasePerformRequest performRequest );
+
+ /**
+ * Perform a release
+ *
+ * @param performRequest all perform arguments
+ * @throws ReleaseExecutionException if there is a problem performing the release
+ * @throws ReleaseFailureException if there is a problem performing the release
+ * @since 2.3
+ */
+ void perform( ReleasePerformRequest performRequest )
+ throws ReleaseExecutionException, ReleaseFailureException;
+
+ /**
+ * Clean a release.
+ *
+ * @param cleanRequest all clean arguments
+ * @throws ReleaseFailureException if exception when releasing
+ * @since 2.3
+ */
+ void clean( ReleaseCleanRequest cleanRequest ) throws ReleaseFailureException;
+
+ /**
+ * Rollback changes made by the previous release
+ *
+ * @param rollbackRequest all rollback arguments
+ * @throws ReleaseExecutionException if there is a problem during release rollback
+ * @throws ReleaseFailureException if there is a problem during release rollback
+ * @since 2.3
+ */
+ void rollback( ReleaseRollbackRequest rollbackRequest )
+ throws ReleaseExecutionException, ReleaseFailureException;
+
+ /**
+ * Branch a project
+ *
+ * @param branchRequest all branch arguments
+ * @throws ReleaseExecutionException if there is a problem during release branch
+ * @throws ReleaseFailureException if there is a problem during release branch
+ * @since 2.3
+ */
+ void branch( ReleaseBranchRequest branchRequest ) throws ReleaseExecutionException, ReleaseFailureException;
+
+ /**
+ * Update version numbers for a project
+ *
+ * @param updateVersionsRequest all update versions arguments
+ * @throws ReleaseExecutionException if there is a problem during update versions
+ * @throws ReleaseFailureException if there is a problem during update versions
+ * @since 2.3
+ */
+ void updateVersions( ReleaseUpdateVersionsRequest updateVersionsRequest )
+ throws ReleaseExecutionException, ReleaseFailureException;
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleaseManagerListener.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleaseManagerListener.java
new file mode 100644
index 000000000..755e3e7df
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleaseManagerListener.java
@@ -0,0 +1,40 @@
+package org.apache.maven.shared.release;
+
+/*
+ * 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.util.List;
+
+/**
+ * @author Edwin Punzalan
+ */
+public interface ReleaseManagerListener
+{
+ void goalStart( String goal, List phases );
+
+ void phaseStart( String name );
+
+ void phaseEnd();
+
+ void phaseSkip( String name );
+
+ void goalEnd();
+
+ void error( String reason );
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleasePerformRequest.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleasePerformRequest.java
new file mode 100644
index 000000000..5a0f51631
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleasePerformRequest.java
@@ -0,0 +1,87 @@
+package org.apache.maven.shared.release;
+
+/*
+ * 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 org.apache.maven.shared.release.env.ReleaseEnvironment;
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 2.3
+ */
+public class ReleasePerformRequest
+ extends AbstractReleaseRequest
+{
+ // using Boolean to detect if has been set explicitly
+ private Boolean dryRun;
+
+ // using Boolean to detect if has been set explicitly
+ private Boolean clean;
+
+ private ReleaseEnvironment releaseEnvironment;
+
+ /**
+ * @return the dryRun
+ */
+ public Boolean getDryRun()
+ {
+ return dryRun;
+ }
+
+ /**
+ * @param dryRun the dryRun to set
+ */
+ public void setDryRun( Boolean dryRun )
+ {
+ this.dryRun = dryRun;
+ }
+
+ /**
+ * @return the clean
+ */
+ public Boolean getClean()
+ {
+ return clean;
+ }
+
+ /**
+ * @param clean the clean to set
+ */
+ public void setClean( Boolean clean )
+ {
+ this.clean = clean;
+ }
+
+ /**
+ * @return the releaseEnvironment
+ */
+ public ReleaseEnvironment getReleaseEnvironment()
+ {
+ return releaseEnvironment;
+ }
+
+ /**
+ * @param releaseEnvironment the releaseEnvironment to set
+ */
+ public void setReleaseEnvironment( ReleaseEnvironment releaseEnvironment )
+ {
+ this.releaseEnvironment = releaseEnvironment;
+ }
+}
\ No newline at end of file
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleasePrepareRequest.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleasePrepareRequest.java
new file mode 100644
index 000000000..7ac691559
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleasePrepareRequest.java
@@ -0,0 +1,101 @@
+package org.apache.maven.shared.release;
+
+/*
+ * 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.util.Properties;
+
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 2.3
+ */
+public class ReleasePrepareRequest
+ extends AbstractReleaseRequest
+{
+ // using Boolean to detect if has been set explicitly
+ private Boolean dryRun;
+
+ // using Boolean to detect if has been set explicitly
+ private Boolean resume;
+
+ private ReleaseEnvironment releaseEnvironment;
+
+ private Properties userProperties;
+
+ /**
+ * @return the dryRun
+ */
+ public Boolean getDryRun()
+ {
+ return dryRun;
+ }
+
+ /**
+ * @param dryRun the dryRun to set
+ */
+ public void setDryRun( Boolean dryRun )
+ {
+ this.dryRun = dryRun;
+ }
+
+ /**
+ * @return the resume
+ */
+ public Boolean getResume()
+ {
+ return resume;
+ }
+
+ /**
+ * @param resume the resume to set
+ */
+ public void setResume( Boolean resume )
+ {
+ this.resume = resume;
+ }
+
+ /**
+ * @return the releaseEnvironment
+ */
+ public ReleaseEnvironment getReleaseEnvironment()
+ {
+ return releaseEnvironment;
+ }
+
+ /**
+ * @param releaseEnvironment the releaseEnvironment to set
+ */
+ public void setReleaseEnvironment( ReleaseEnvironment releaseEnvironment )
+ {
+ this.releaseEnvironment = releaseEnvironment;
+ }
+
+ public Properties getUserProperties()
+ {
+ return userProperties;
+ }
+
+ public void setUserProperties( Properties userProperties )
+ {
+ this.userProperties = userProperties;
+ }
+}
\ No newline at end of file
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleaseRollbackRequest.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleaseRollbackRequest.java
new file mode 100644
index 000000000..d91dd3a2c
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleaseRollbackRequest.java
@@ -0,0 +1,49 @@
+package org.apache.maven.shared.release;
+
+/*
+ * 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 org.apache.maven.shared.release.env.ReleaseEnvironment;
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 2.3
+ */
+public class ReleaseRollbackRequest
+ extends AbstractReleaseRequest
+{
+ private ReleaseEnvironment releaseEnvironment;
+
+ /**
+ * @return the releaseEnvironment
+ */
+ public ReleaseEnvironment getReleaseEnvironment()
+ {
+ return releaseEnvironment;
+ }
+
+ /**
+ * @param releaseEnvironment the releaseEnvironment to set
+ */
+ public void setReleaseEnvironment( ReleaseEnvironment releaseEnvironment )
+ {
+ this.releaseEnvironment = releaseEnvironment;
+ }
+}
\ No newline at end of file
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleaseUpdateVersionsRequest.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleaseUpdateVersionsRequest.java
new file mode 100644
index 000000000..81c768c35
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/ReleaseUpdateVersionsRequest.java
@@ -0,0 +1,63 @@
+package org.apache.maven.shared.release;
+
+/*
+ * 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.util.Properties;
+
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 2.3
+ */
+public class ReleaseUpdateVersionsRequest
+ extends AbstractReleaseRequest
+{
+ private ReleaseEnvironment releaseEnvironment;
+
+ private Properties userProperties;
+
+ /**
+ * @return the releaseEnvironment
+ */
+ public ReleaseEnvironment getReleaseEnvironment()
+ {
+ return releaseEnvironment;
+ }
+
+ /**
+ * @param releaseEnvironment the releaseEnvironment to set
+ */
+ public void setReleaseEnvironment( ReleaseEnvironment releaseEnvironment )
+ {
+ this.releaseEnvironment = releaseEnvironment;
+ }
+
+ public Properties getUserProperties()
+ {
+ return userProperties;
+ }
+
+ public void setUserProperties( Properties userProperties )
+ {
+ this.userProperties = userProperties;
+ }
+}
\ No newline at end of file
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/config/PropertiesReleaseDescriptorStore.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/config/PropertiesReleaseDescriptorStore.java
new file mode 100644
index 000000000..a7506b8fa
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/config/PropertiesReleaseDescriptorStore.java
@@ -0,0 +1,425 @@
+package org.apache.maven.shared.release.config;
+
+/*
+ * 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.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.maven.model.Scm;
+import org.apache.maven.shared.release.config.ReleaseDescriptorBuilder.BuilderReleaseDescriptor;
+import org.apache.maven.shared.release.scm.IdentifiedScm;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.logging.AbstractLogEnabled;
+import org.codehaus.plexus.util.StringUtils;
+import org.sonatype.plexus.components.cipher.DefaultPlexusCipher;
+import org.sonatype.plexus.components.cipher.PlexusCipherException;
+import org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher;
+import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;
+import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException;
+import org.sonatype.plexus.components.sec.dispatcher.SecUtil;
+import org.sonatype.plexus.components.sec.dispatcher.model.SettingsSecurity;
+
+/**
+ * Read and write release configuration and state from a properties file.
+ *
+ * @author Brett Porter
+ */
+@Component( role = ReleaseDescriptorStore.class, hint = "properties" )
+public class PropertiesReleaseDescriptorStore
+ extends AbstractLogEnabled
+ implements ReleaseDescriptorStore
+{
+
+ /**
+ * When this plugin requires Maven 3.0 as minimum, this component can be removed and o.a.m.s.c.SettingsDecrypter be
+ * used instead.
+ */
+ @Requirement( role = SecDispatcher.class, hint = "mng-4384" )
+ private DefaultSecDispatcher secDispatcher;
+
+ @Override
+ public ReleaseDescriptorBuilder read( ReleaseDescriptorBuilder mergeDescriptor )
+ throws ReleaseDescriptorStoreException
+ {
+ return read( mergeDescriptor, getDefaultReleasePropertiesFile( mergeDescriptor.build() ) );
+ }
+
+ public ReleaseDescriptorBuilder read( File file )
+ throws ReleaseDescriptorStoreException
+ {
+ return read( null, file );
+ }
+
+ public ReleaseDescriptorBuilder read( ReleaseDescriptorBuilder mergeDescriptor, File file )
+ throws ReleaseDescriptorStoreException
+ {
+ Properties properties = new Properties();
+
+ try ( InputStream inStream = new FileInputStream( file ) )
+ {
+ properties.load( inStream );
+ }
+ catch ( FileNotFoundException e )
+ {
+ getLogger().debug( file.getName() + " not found - using empty properties" );
+ }
+ catch ( IOException e )
+ {
+ throw new ReleaseDescriptorStoreException(
+ "Error reading properties file '" + file.getName() + "': " + e.getMessage(), e );
+ }
+
+ try
+ {
+ decryptProperties( properties );
+ }
+ catch ( IllegalStateException | SecDispatcherException | PlexusCipherException e )
+ {
+ getLogger().debug( e.getMessage() );
+ }
+
+ ReleaseDescriptorBuilder builder;
+ if ( mergeDescriptor != null )
+ {
+ builder = mergeDescriptor;
+ }
+ else
+ {
+ builder = new ReleaseDescriptorBuilder();
+ }
+
+ ReleaseUtils.copyPropertiesToReleaseDescriptor( properties, builder );
+
+ return builder;
+ }
+
+ @Override
+ public void write( ReleaseDescriptor config )
+ throws ReleaseDescriptorStoreException
+ {
+ write( (BuilderReleaseDescriptor) config, getDefaultReleasePropertiesFile( config ) );
+ }
+
+ @Override
+ public void delete( ReleaseDescriptor config )
+ {
+ File file = getDefaultReleasePropertiesFile( config );
+ if ( file.exists() )
+ {
+ file.delete();
+ }
+ }
+
+ public void write( BuilderReleaseDescriptor config, File file )
+ throws ReleaseDescriptorStoreException
+ {
+ Properties properties = new Properties();
+ properties.setProperty( "completedPhase", config.getCompletedPhase() );
+ if ( config.isCommitByProject() ) //default is false
+ {
+ properties.setProperty( "commitByProject", "true" );
+ }
+ properties.setProperty( "scm.url", config.getScmSourceUrl() );
+ if ( config.getScmId() != null )
+ {
+ properties.setProperty( "scm.id", config.getScmId() );
+ }
+ if ( config.getScmUsername() != null )
+ {
+ properties.setProperty( "scm.username", config.getScmUsername() );
+ }
+ if ( config.getScmPassword() != null )
+ {
+ String password = config.getScmPassword();
+ try
+ {
+ password = encryptAndDecorate( password );
+ }
+ catch ( IllegalStateException | SecDispatcherException | PlexusCipherException e )
+ {
+ getLogger().debug( e.getMessage() );
+ }
+ properties.setProperty( "scm.password", password );
+ }
+ if ( config.getScmPrivateKey() != null )
+ {
+ properties.setProperty( "scm.privateKey", config.getScmPrivateKey() );
+ }
+ if ( config.getScmPrivateKeyPassPhrase() != null )
+ {
+ String passPhrase = config.getScmPrivateKeyPassPhrase();
+ try
+ {
+ passPhrase = encryptAndDecorate( passPhrase );
+ }
+ catch ( IllegalStateException | SecDispatcherException | PlexusCipherException e )
+ {
+ getLogger().debug( e.getMessage() );
+ }
+ properties.setProperty( "scm.passphrase", passPhrase );
+ }
+ if ( config.getScmTagBase() != null )
+ {
+ properties.setProperty( "scm.tagBase", config.getScmTagBase() );
+ }
+ if ( config.getScmBranchBase() != null )
+ {
+ properties.setProperty( "scm.branchBase", config.getScmBranchBase() );
+ }
+ if ( config.getScmReleaseLabel() != null )
+ {
+ properties.setProperty( "scm.tag", config.getScmReleaseLabel() );
+ }
+ if ( config.getScmTagNameFormat() != null )
+ {
+ properties.setProperty( "scm.tagNameFormat", config.getScmTagNameFormat() );
+ }
+ if ( config.getScmCommentPrefix() != null )
+ {
+ properties.setProperty( "scm.commentPrefix", config.getScmCommentPrefix() );
+ }
+ if ( config.getScmDevelopmentCommitComment() != null )
+ {
+ properties.setProperty( "scm.developmentCommitComment", config.getScmDevelopmentCommitComment() );
+ }
+ if ( config.getScmReleaseCommitComment() != null )
+ {
+ properties.setProperty( "scm.releaseCommitComment", config.getScmReleaseCommitComment() );
+ }
+ if ( config.getScmBranchCommitComment() != null )
+ {
+ properties.setProperty( "scm.branchCommitComment", config.getScmBranchCommitComment() );
+ }
+ if ( config.getScmRollbackCommitComment() != null )
+ {
+ properties.setProperty( "scm.rollbackCommitComment", config.getScmRollbackCommitComment() );
+ }
+ if ( config.getAdditionalArguments() != null )
+ {
+ properties.setProperty( "exec.additionalArguments", config.getAdditionalArguments() );
+ }
+ if ( config.getPomFileName() != null )
+ {
+ properties.setProperty( "exec.pomFileName", config.getPomFileName() );
+ }
+ if ( !config.getActivateProfiles().isEmpty() )
+ {
+ properties.setProperty( "exec.activateProfiles",
+ StringUtils.join( config.getActivateProfiles().iterator(), "," ) );
+ }
+ if ( config.getPreparationGoals() != null )
+ {
+ properties.setProperty( "preparationGoals", config.getPreparationGoals() );
+ }
+ if ( config.getCompletionGoals() != null )
+ {
+ properties.setProperty( "completionGoals", config.getCompletionGoals() );
+ }
+ if ( config.getProjectVersionPolicyId() != null )
+ {
+ properties.setProperty( "projectVersionPolicyId", config.getProjectVersionPolicyId() );
+ }
+ if ( config.getProjectNamingPolicyId() != null )
+ {
+ properties.setProperty( "projectNamingPolicyId", config.getProjectNamingPolicyId() );
+ }
+ if ( config.getReleaseStrategyId() != null )
+ {
+ properties.setProperty( "releaseStrategyId", config.getReleaseStrategyId() );
+ }
+
+ properties.setProperty( "exec.snapshotReleasePluginAllowed",
+ Boolean.toString( config.isSnapshotReleasePluginAllowed() ) );
+
+ properties.setProperty( "remoteTagging", Boolean.toString( config.isRemoteTagging() ) );
+
+ properties.setProperty( "pinExternals", Boolean.toString( config.isPinExternals() ) );
+
+ properties.setProperty( "pushChanges", Boolean.toString( config.isPushChanges() ) );
+
+ if ( config.getWorkItem() != null )
+ {
+ properties.setProperty( "workItem", config.getWorkItem() );
+ }
+
+ if ( config.getAutoResolveSnapshots() != null )
+ {
+ properties.setProperty( "autoResolveSnapshots", config.getAutoResolveSnapshots() );
+ }
+
+ // others boolean properties are not written to the properties file because the value from the caller is always
+ // used
+
+
+ for ( Map.Entry entry : config.getProjectVersions().entrySet() )
+ {
+ if ( entry.getValue().getRelease() != null )
+ {
+ properties.setProperty( "project.rel." + entry.getKey(), entry.getValue().getRelease() );
+ }
+ if ( entry.getValue().getDevelopment() != null )
+ {
+ properties.setProperty( "project.dev." + entry.getKey(), entry.getValue().getDevelopment() );
+ }
+ }
+
+ for ( Map.Entry entry : config.getOriginalScmInfo().entrySet() )
+ {
+ Scm scm = entry.getValue();
+ String prefix = "project.scm." + entry.getKey();
+ if ( scm != null )
+ {
+ if ( scm.getConnection() != null )
+ {
+ properties.setProperty( prefix + ".connection", scm.getConnection() );
+ }
+ if ( scm.getDeveloperConnection() != null )
+ {
+ properties.setProperty( prefix + ".developerConnection", scm.getDeveloperConnection() );
+ }
+ if ( scm.getUrl() != null )
+ {
+ properties.setProperty( prefix + ".url", scm.getUrl() );
+ }
+ if ( scm.getTag() != null )
+ {
+ properties.setProperty( prefix + ".tag", scm.getTag() );
+ }
+ if ( scm instanceof IdentifiedScm )
+ {
+ IdentifiedScm identifiedScm = (IdentifiedScm) scm;
+ if ( identifiedScm.getId() != null )
+ {
+ properties.setProperty( prefix + ".id", identifiedScm.getId() );
+ }
+ }
+ }
+ else
+ {
+ properties.setProperty( prefix + ".empty", "true" );
+ }
+ }
+
+ if ( ( config.getResolvedSnapshotDependencies() != null )
+ && ( config.getResolvedSnapshotDependencies().size() > 0 ) )
+ {
+ processResolvedDependencies( properties, config.getResolvedSnapshotDependencies() );
+ }
+
+ try ( OutputStream outStream = new FileOutputStream( file ) )
+ {
+ properties.store( outStream, "release configuration" );
+ }
+ catch ( IOException e )
+ {
+ throw new ReleaseDescriptorStoreException(
+ "Error writing properties file '" + file.getName() + "': " + e.getMessage(), e );
+ }
+ }
+
+ private void processResolvedDependencies( Properties prop, Map resolvedDependencies )
+ {
+ for ( Map.Entry currentEntry : resolvedDependencies.entrySet() )
+ {
+ ReleaseStageVersions versionMap = currentEntry.getValue();
+
+ prop.setProperty( "dependency." + currentEntry.getKey() + ".release",
+ versionMap.getRelease() );
+ prop.setProperty( "dependency." + currentEntry.getKey() + ".development",
+ versionMap.getDevelopment() );
+ }
+ }
+
+ private static File getDefaultReleasePropertiesFile( ReleaseDescriptor mergeDescriptor )
+ {
+ return new File( mergeDescriptor.getWorkingDirectory(), "release.properties" );
+ }
+
+ private void decryptProperties( Properties properties )
+ throws IllegalStateException, SecDispatcherException, PlexusCipherException
+ {
+ String[] keys = new String[] { "scm.password", "scm.passphrase" };
+
+ for ( String key : keys )
+ {
+ String value = properties.getProperty( key );
+ if ( value != null )
+ {
+ properties.put( key, decrypt( value ) );
+ }
+ }
+ }
+
+ // From org.apache.maven.cli.MavenCli.encryption(CliRequest)
+ private String encryptAndDecorate( String passwd )
+ throws IllegalStateException, SecDispatcherException, PlexusCipherException
+ {
+ final String master = getMaster();
+
+ DefaultPlexusCipher cipher = new DefaultPlexusCipher();
+ String masterPasswd = cipher.decryptDecorated( master, DefaultSecDispatcher.SYSTEM_PROPERTY_SEC_LOCATION );
+ return cipher.encryptAndDecorate( passwd, masterPasswd );
+ }
+
+ private String decrypt( String value ) throws IllegalStateException, SecDispatcherException, PlexusCipherException
+ {
+ final String master = getMaster();
+
+ DefaultPlexusCipher cipher = new DefaultPlexusCipher();
+ String masterPasswd = cipher.decryptDecorated( master, DefaultSecDispatcher.SYSTEM_PROPERTY_SEC_LOCATION );
+ return cipher.decryptDecorated( value, masterPasswd );
+ }
+
+ private String getMaster() throws SecDispatcherException
+ {
+ String configurationFile = secDispatcher.getConfigurationFile();
+
+ if ( configurationFile.startsWith( "~" ) )
+ {
+ configurationFile = System.getProperty( "user.home" ) + configurationFile.substring( 1 );
+ }
+
+ String file = System.getProperty( DefaultSecDispatcher.SYSTEM_PROPERTY_SEC_LOCATION, configurationFile );
+
+ String master = null;
+
+ SettingsSecurity sec = SecUtil.read( file, true );
+ if ( sec != null )
+ {
+ master = sec.getMaster();
+ }
+
+ if ( master == null )
+ {
+ throw new IllegalStateException( "Master password is not set in the setting security file: " + file );
+ }
+
+ return master;
+ }
+
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/config/ReleaseDescriptorBuilder.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/config/ReleaseDescriptorBuilder.java
new file mode 100644
index 000000000..7ff677ae8
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/config/ReleaseDescriptorBuilder.java
@@ -0,0 +1,459 @@
+package org.apache.maven.shared.release.config;
+
+/*
+ * 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.util.List;
+
+import org.apache.maven.model.Scm;
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 3.0.0
+ */
+public class ReleaseDescriptorBuilder
+{
+ /**
+ * Hides inner logic of the release descriptor
+ *
+ * @author Robert Scholte
+ *
+ */
+ public static final class BuilderReleaseDescriptor extends ModelloReleaseDescriptor implements ReleaseDescriptor
+ {
+ private BuilderReleaseDescriptor()
+ {
+ }
+ }
+
+ private final BuilderReleaseDescriptor releaseDescriptor;
+
+ public ReleaseDescriptorBuilder()
+ {
+ this.releaseDescriptor = new BuilderReleaseDescriptor();
+ }
+
+ public ReleaseDescriptorBuilder addCheckModificationExclude( String string )
+ {
+ releaseDescriptor.addCheckModificationExclude( string );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setActivateProfiles( List profiles )
+ {
+ releaseDescriptor.setActivateProfiles( profiles );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setAddSchema( boolean addSchema )
+ {
+ releaseDescriptor.setAddSchema( addSchema );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setAdditionalArguments( String additionalArguments )
+ {
+ releaseDescriptor.setAdditionalArguments( additionalArguments );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setAllowTimestampedSnapshots( boolean allowTimestampedSnapshots )
+ {
+ releaseDescriptor.setAllowTimestampedSnapshots( allowTimestampedSnapshots );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setAutoVersionSubmodules( boolean autoVersionSubmodules )
+ {
+ releaseDescriptor.setAutoVersionSubmodules( autoVersionSubmodules );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setBranchCreation( boolean branchCreation )
+ {
+ releaseDescriptor.setBranchCreation( branchCreation );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setCheckModificationExcludes( List checkModificationExcludes )
+ {
+ releaseDescriptor.setCheckModificationExcludes( checkModificationExcludes );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setCheckoutDirectory( String checkoutDirectory )
+ {
+ releaseDescriptor.setCheckoutDirectory( checkoutDirectory );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setCommitByProject( boolean commitByProject )
+ {
+ releaseDescriptor.setCommitByProject( commitByProject );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setCompletedPhase( String completedPhase )
+ {
+ releaseDescriptor.setCompletedPhase( completedPhase );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setCompletionGoals( String completionGoals )
+ {
+ releaseDescriptor.setCompletionGoals( completionGoals );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setDefaultDevelopmentVersion( String defaultDevelopmentVersion )
+ {
+ releaseDescriptor.setDefaultDevelopmentVersion( defaultDevelopmentVersion );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setDefaultReleaseVersion( String defaultReleaseVersion )
+ {
+ releaseDescriptor.setDefaultReleaseVersion( defaultReleaseVersion );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setDescription( String description )
+ {
+ releaseDescriptor.setDescription( description );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setGenerateReleasePoms( boolean generateReleasePoms )
+ {
+ releaseDescriptor.setGenerateReleasePoms( generateReleasePoms );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setInteractive( boolean interactive )
+ {
+ releaseDescriptor.setInteractive( interactive );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setLocalCheckout( boolean localCheckout )
+ {
+ releaseDescriptor.setLocalCheckout( localCheckout );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setModelEncoding( String modelEncoding )
+ {
+ releaseDescriptor.setModelEncoding( modelEncoding );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setName( String name )
+ {
+ releaseDescriptor.setName( name );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setPerformGoals( String performGoals )
+ {
+ releaseDescriptor.setPerformGoals( performGoals );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setPomFileName( String pomFileName )
+ {
+ releaseDescriptor.setPomFileName( pomFileName );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setPreparationGoals( String preparationGoals )
+ {
+ releaseDescriptor.setPreparationGoals( preparationGoals );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setProjectNamingPolicyId( String projectNamingPolicyId )
+ {
+ releaseDescriptor.setProjectNamingPolicyId( projectNamingPolicyId );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setProjectVersionPolicyId( String projectVersionPolicyId )
+ {
+ releaseDescriptor.setProjectVersionPolicyId( projectVersionPolicyId );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setPushChanges( boolean pushChanges )
+ {
+ releaseDescriptor.setPushChanges( pushChanges );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setWorkItem( String workItem )
+ {
+ releaseDescriptor.setWorkItem( workItem );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setReleaseStrategyId( String releaseStrategyId )
+ {
+ releaseDescriptor.setReleaseStrategyId( releaseStrategyId );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setRemoteTagging( boolean remoteTagging )
+ {
+ releaseDescriptor.setRemoteTagging( remoteTagging );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setScmBranchBase( String scmBranchBase )
+ {
+ releaseDescriptor.setScmBranchBase( scmBranchBase );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setScmCommentPrefix( String scmCommentPrefix )
+ {
+ releaseDescriptor.setScmCommentPrefix( scmCommentPrefix );
+ return this;
+ }
+
+ /**
+ * @since 3.0.0-M1
+ */
+ public ReleaseDescriptorBuilder setScmReleaseCommitComment( String scmReleaseCommitComment )
+ {
+ releaseDescriptor.setScmReleaseCommitComment( scmReleaseCommitComment );
+ return this;
+ }
+
+ /**
+ * @since 3.0.0-M1
+ */
+ public ReleaseDescriptorBuilder setScmDevelopmentCommitComment( String scmDevelopmentCommitComment )
+ {
+ releaseDescriptor.setScmDevelopmentCommitComment( scmDevelopmentCommitComment );
+ return this;
+ }
+
+ /**
+ * @since 3.0.0-M1
+ */
+ public ReleaseDescriptorBuilder setScmBranchCommitComment( String scmBranchCommitComment )
+ {
+ releaseDescriptor.setScmBranchCommitComment( scmBranchCommitComment );
+ return this;
+ }
+
+ /**
+ * @since 3.0.0-M1
+ */
+ public ReleaseDescriptorBuilder setScmRollbackCommitComment( String scmRollbackCommitComment )
+ {
+ releaseDescriptor.setScmRollbackCommitComment( scmRollbackCommitComment );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setScmId( String scmId )
+ {
+ releaseDescriptor.setScmId( scmId );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setScmPassword( String scmPassword )
+ {
+ releaseDescriptor.setScmPassword( scmPassword );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setScmPrivateKey( String scmPrivateKey )
+ {
+ releaseDescriptor.setScmPrivateKey( scmPrivateKey );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setScmPrivateKeyPassPhrase( String scmPrivateKeyPassPhrase )
+ {
+ releaseDescriptor.setScmPrivateKeyPassPhrase( scmPrivateKeyPassPhrase );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setScmRelativePathProjectDirectory( String scmRelativePathProjectDirectory )
+ {
+ releaseDescriptor.setScmRelativePathProjectDirectory( scmRelativePathProjectDirectory );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setScmReleaseLabel( String scmReleaseLabel )
+ {
+ releaseDescriptor.setScmReleaseLabel( scmReleaseLabel );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setScmReleasedPomRevision( String scmReleasedPomRevision )
+ {
+ releaseDescriptor.setScmReleasedPomRevision( scmReleasedPomRevision );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setScmSourceUrl( String scmSourceUrl )
+ {
+ releaseDescriptor.setScmSourceUrl( scmSourceUrl );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setScmTagBase( String scmTagBase )
+ {
+ releaseDescriptor.setScmTagBase( scmTagBase );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setScmTagNameFormat( String scmTagNameFormat )
+ {
+ releaseDescriptor.setScmTagNameFormat( scmTagNameFormat );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setScmUseEditMode( boolean scmUseEditMode )
+ {
+ releaseDescriptor.setScmUseEditMode( scmUseEditMode );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setScmUsername( String scmUsername )
+ {
+ releaseDescriptor.setScmUsername( scmUsername );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setSnapshotReleasePluginAllowed( boolean snapshotReleasePluginAllowed )
+ {
+ releaseDescriptor.setSnapshotReleasePluginAllowed( snapshotReleasePluginAllowed );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setSuppressCommitBeforeTagOrBranch( boolean suppressCommitBeforeTagOrBranch )
+ {
+ releaseDescriptor.setSuppressCommitBeforeTagOrBranch( suppressCommitBeforeTagOrBranch );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setUpdateBranchVersions( boolean updateBranchVersions )
+ {
+ releaseDescriptor.setUpdateBranchVersions( updateBranchVersions );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setUpdateDependencies( boolean updateDependencies )
+ {
+ releaseDescriptor.setUpdateDependencies( updateDependencies );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setUpdateVersionsToSnapshot( boolean updateVersionsToSnapshot )
+ {
+ releaseDescriptor.setUpdateVersionsToSnapshot( updateVersionsToSnapshot );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setUpdateWorkingCopyVersions( boolean updateWorkingCopyVersions )
+ {
+ releaseDescriptor.setUpdateWorkingCopyVersions( updateWorkingCopyVersions );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setUseReleaseProfile( boolean useReleaseProfile )
+ {
+ releaseDescriptor.setUseReleaseProfile( useReleaseProfile );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setWaitBeforeTagging( int waitBeforeTagging )
+ {
+ releaseDescriptor.setWaitBeforeTagging( waitBeforeTagging );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setWorkingDirectory( String workingDirectory )
+ {
+ releaseDescriptor.setWorkingDirectory( workingDirectory );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder addReleaseVersion( String key, String value )
+ {
+ releaseDescriptor.addReleaseVersion( key, value );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder addDevelopmentVersion( String key, String value )
+ {
+ releaseDescriptor.addDevelopmentVersion( key, value );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder addOriginalScmInfo( String key, Scm value )
+ {
+ releaseDescriptor.addOriginalScmInfo( key, value );
+ return this;
+ }
+
+ public void putOriginalVersion( String projectKey, String version )
+ {
+ releaseDescriptor.addOriginalVersion( projectKey, version );
+ }
+
+ public ReleaseDescriptorBuilder addDependencyOriginalVersion( String dependencyKey, String version )
+ {
+ releaseDescriptor.addDependencyOriginalVersion( dependencyKey, version );
+ return this;
+
+ }
+
+ public ReleaseDescriptorBuilder addDependencyReleaseVersion( String dependencyKey, String version )
+ {
+ releaseDescriptor.addDependencyReleaseVersion( dependencyKey, version );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder addDependencyDevelopmentVersion( String dependencyKey, String version )
+ {
+ releaseDescriptor.addDependencyDevelopmentVersion( dependencyKey, version );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setAutoResolveSnapshots( String autoResolveSnapshots )
+ {
+ releaseDescriptor.setAutoResolveSnapshots( autoResolveSnapshots );
+ return this;
+ }
+
+ public ReleaseDescriptorBuilder setPinExternals( boolean pinExternals )
+ {
+ releaseDescriptor.setPinExternals( pinExternals );
+ return this;
+ }
+
+ BuilderReleaseDescriptor build()
+ {
+ return releaseDescriptor;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/config/ReleaseDescriptorStore.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/config/ReleaseDescriptorStore.java
new file mode 100644
index 000000000..69092c174
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/config/ReleaseDescriptorStore.java
@@ -0,0 +1,53 @@
+package org.apache.maven.shared.release.config;
+
+/*
+ * 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.
+ */
+
+/**
+ * Storage for reading and writing release configuration.
+ *
+ * @author Brett Porter
+ */
+public interface ReleaseDescriptorStore
+{
+ /**
+ * Read a configuration.
+ *
+ * @param mergeDescriptor configuration to merge with the loaded configuration. Some values are used as defaults,
+ * while others are used to override
+ * @return the configuration
+ */
+ ReleaseDescriptorBuilder read( ReleaseDescriptorBuilder mergeDescriptor )
+ throws ReleaseDescriptorStoreException;
+
+ /**
+ * Save a configuration.
+ *
+ * @param config the configuration
+ */
+ void write( ReleaseDescriptor config )
+ throws ReleaseDescriptorStoreException;
+
+ /**
+ * Remove a configuration.
+ *
+ * @param config the location of the configuration
+ */
+ void delete( ReleaseDescriptor config );
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/config/ReleaseDescriptorStoreException.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/config/ReleaseDescriptorStoreException.java
new file mode 100644
index 000000000..443da6c45
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/config/ReleaseDescriptorStoreException.java
@@ -0,0 +1,34 @@
+package org.apache.maven.shared.release.config;
+
+/*
+ * 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.
+ */
+
+/**
+ * Exception occurring within the configuration store.
+ *
+ * @author Brett Porter
+ */
+public class ReleaseDescriptorStoreException
+ extends Exception
+{
+ public ReleaseDescriptorStoreException( String message, Throwable t )
+ {
+ super( message, t );
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/config/ReleaseUtils.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/config/ReleaseUtils.java
new file mode 100644
index 000000000..ed31afe1a
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/config/ReleaseUtils.java
@@ -0,0 +1,292 @@
+package org.apache.maven.shared.release.config;
+
+/*
+ * 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.util.Arrays;
+import java.util.Iterator;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.maven.shared.release.config.ReleaseDescriptorBuilder.BuilderReleaseDescriptor;
+import org.apache.maven.shared.release.scm.IdentifiedScm;
+
+/**
+ * Class providing utility methods used during the release process
+ *
+ * @author Jeremy Whitlock
+ */
+public class ReleaseUtils
+{
+ private static final String DEVELOPMENT_KEY = "dev";
+
+ private static final String RELEASE_KEY = "rel";
+
+ private ReleaseUtils()
+ {
+ // nothing to see here
+ }
+
+ public static BuilderReleaseDescriptor buildReleaseDescriptor( ReleaseDescriptorBuilder builder )
+ {
+ return builder.build();
+ }
+
+ public static void copyPropertiesToReleaseDescriptor( Properties properties, ReleaseDescriptorBuilder builder )
+ {
+ if ( properties.containsKey( "completedPhase" ) )
+ {
+ builder.setCompletedPhase( properties.getProperty( "completedPhase" ) );
+ }
+ if ( properties.containsKey( "commitByProject" ) )
+ {
+ builder.setCommitByProject( Boolean.parseBoolean( properties.getProperty( "commitByProject" ) ) );
+ }
+ if ( properties.containsKey( "scm.id" ) )
+ {
+ builder.setScmId( properties.getProperty( "scm.id" ) );
+ }
+ if ( properties.containsKey( "scm.url" ) )
+ {
+ builder.setScmSourceUrl( properties.getProperty( "scm.url" ) );
+ }
+ if ( properties.containsKey( "scm.username" ) )
+ {
+ builder.setScmUsername( properties.getProperty( "scm.username" ) );
+ }
+ if ( properties.containsKey( "scm.password" ) )
+ {
+ builder.setScmPassword( properties.getProperty( "scm.password" ) );
+ }
+ if ( properties.containsKey( "scm.privateKey" ) )
+ {
+ builder.setScmPrivateKey( properties.getProperty( "scm.privateKey" ) );
+ }
+ if ( properties.containsKey( "scm.passphrase" ) )
+ {
+ builder.setScmPrivateKeyPassPhrase( properties.getProperty( "scm.passphrase" ) );
+ }
+ if ( properties.containsKey( "scm.tagBase" ) )
+ {
+ builder.setScmTagBase( properties.getProperty( "scm.tagBase" ) );
+ }
+ if ( properties.containsKey( "scm.tagNameFormat" ) )
+ {
+ builder.setScmTagNameFormat( properties.getProperty( "scm.tagNameFormat" ) );
+ }
+ if ( properties.containsKey( "scm.branchBase" ) )
+ {
+ builder.setScmBranchBase( properties.getProperty( "scm.branchBase" ) );
+ }
+ if ( properties.containsKey( "scm.tag" ) )
+ {
+ builder.setScmReleaseLabel( properties.getProperty( "scm.tag" ) );
+ }
+ if ( properties.containsKey( "scm.commentPrefix" ) )
+ {
+ builder.setScmCommentPrefix( properties.getProperty( "scm.commentPrefix" ) );
+ }
+ if ( properties.containsKey( "scm.developmentCommitComment" ) )
+ {
+ builder.setScmDevelopmentCommitComment( properties.getProperty( "scm.developmentCommitComment" ) );
+ }
+ if ( properties.containsKey( "scm.releaseCommitComment" ) )
+ {
+ builder.setScmReleaseCommitComment( properties.getProperty( "scm.releaseCommitComment" ) );
+ }
+ if ( properties.containsKey( "scm.branchCommitComment" ) )
+ {
+ builder.setScmBranchCommitComment( properties.getProperty( "scm.branchCommitComment" ) );
+ }
+ if ( properties.containsKey( "scm.rollbackCommitComment" ) )
+ {
+ builder.setScmRollbackCommitComment( properties.getProperty( "scm.rollbackCommitComment" ) );
+ }
+ if ( properties.containsKey( "exec.additionalArguments" ) )
+ {
+ builder.setAdditionalArguments( properties.getProperty( "exec.additionalArguments" ) );
+ }
+ if ( properties.containsKey( "exec.pomFileName" ) )
+ {
+ builder.setPomFileName( properties.getProperty( "exec.pomFileName" ) );
+ }
+ if ( properties.containsKey( "exec.activateProfiles" ) )
+ {
+ builder.setActivateProfiles(
+ Arrays.asList( properties.getProperty( "exec.activateProfiles" ).split( "," ) ) );
+ }
+ if ( properties.containsKey( "preparationGoals" ) )
+ {
+ builder.setPreparationGoals( properties.getProperty( "preparationGoals" ) );
+ }
+ if ( properties.containsKey( "completionGoals" ) )
+ {
+ builder.setCompletionGoals( properties.getProperty( "completionGoals" ) );
+ }
+ if ( properties.containsKey( "projectVersionPolicyId" ) )
+ {
+ builder.setProjectVersionPolicyId( properties.getProperty( "projectVersionPolicyId" ) );
+ }
+ if ( properties.containsKey( "projectNamingPolicyId" ) )
+ {
+ builder.setProjectNamingPolicyId( properties.getProperty( "projectNamingPolicyId" ) );
+ }
+ if ( properties.containsKey( "releaseStrategyId" ) )
+ {
+ builder.setReleaseStrategyId( properties.getProperty( "releaseStrategyId" ) );
+ }
+ if ( properties.containsKey( "exec.snapshotReleasePluginAllowed" ) )
+ {
+ String snapshotReleasePluginAllowedStr = properties.getProperty( "exec.snapshotReleasePluginAllowed" );
+ builder.setSnapshotReleasePluginAllowed( Boolean.valueOf( snapshotReleasePluginAllowedStr ) );
+ }
+ if ( properties.containsKey( "remoteTagging" ) )
+ {
+ String remoteTaggingStr = properties.getProperty( "remoteTagging" );
+ builder.setRemoteTagging( Boolean.valueOf( remoteTaggingStr ) );
+ }
+ if ( properties.containsKey( "pinExternals" ) )
+ {
+ String pinExternals = properties.getProperty( "pinExternals" );
+ builder.setPinExternals( Boolean.valueOf( pinExternals ) );
+ }
+ if ( properties.containsKey( "pushChanges" ) )
+ {
+ String pushChanges = properties.getProperty( "pushChanges" );
+ builder.setPushChanges( Boolean.valueOf( pushChanges ) );
+ }
+ if ( properties.containsKey( "workItem" ) )
+ {
+ builder.setWorkItem( properties.getProperty( "workItem" ) );
+ }
+ if ( properties.containsKey( "autoResolveSnapshots" ) )
+ {
+ String resolve = properties.getProperty( "autoResolveSnapshots" );
+ builder.setAutoResolveSnapshots( resolve );
+ }
+
+ loadResolvedDependencies( properties, builder );
+
+ // boolean properties are not written to the properties file because the value from the caller is always used
+
+ for ( Iterator> i = properties.keySet().iterator(); i.hasNext(); )
+ {
+ String property = (String) i.next();
+ if ( property.startsWith( "project.rel." ) )
+ {
+ builder.addReleaseVersion( property.substring( "project.rel.".length() ),
+ properties.getProperty( property ) );
+ }
+ else if ( property.startsWith( "project.dev." ) )
+ {
+ builder.addDevelopmentVersion( property.substring( "project.dev.".length() ),
+ properties.getProperty( property ) );
+ }
+ else if ( property.startsWith( "dependency.rel." ) )
+ {
+ builder.addDependencyReleaseVersion( property.substring( "dependency.rel.".length() ),
+ properties.getProperty( property ) );
+ }
+ else if ( property.startsWith( "dependency.dev." ) )
+ {
+ builder.addDependencyDevelopmentVersion( property.substring( "dependency.dev.".length() ),
+ properties.getProperty( property ) );
+ }
+ else if ( property.startsWith( "project.scm." ) )
+ {
+ int index = property.lastIndexOf( '.' );
+ if ( index > "project.scm.".length() )
+ {
+ String key = property.substring( "project.scm.".length(), index );
+
+ if ( builder.build().getOriginalScmInfo( key ) == null )
+ {
+ if ( properties.getProperty( "project.scm." + key + ".empty" ) != null )
+ {
+ builder.addOriginalScmInfo( key, null );
+ }
+ else
+ {
+ IdentifiedScm scm = new IdentifiedScm();
+ scm.setConnection( properties.getProperty( "project.scm." + key + ".connection" ) );
+ scm.setDeveloperConnection(
+ properties.getProperty( "project.scm." + key + ".developerConnection" ) );
+ scm.setUrl( properties.getProperty( "project.scm." + key + ".url" ) );
+ scm.setTag( properties.getProperty( "project.scm." + key + ".tag" ) );
+ scm.setId( properties.getProperty( "project.scm." + key + ".id" ) );
+
+ builder.addOriginalScmInfo( key, scm );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static void loadResolvedDependencies( Properties prop, ReleaseDescriptorBuilder builder )
+ {
+ Set entries = prop.entrySet();
+ Iterator> iterator = entries.iterator();
+ String propertyName;
+ Entry currentEntry;
+
+ while ( iterator.hasNext() )
+ {
+ currentEntry = iterator.next();
+ propertyName = currentEntry.getKey();
+
+ if ( propertyName.startsWith( "dependency." ) )
+ {
+ String artifactVersionlessKey;
+ int startIndex = "dependency.".length();
+ int endIndex;
+ String versionType;
+
+ if ( propertyName.indexOf( ".development" ) != -1 )
+ {
+ endIndex = propertyName.lastIndexOf( ".development" );
+ versionType = DEVELOPMENT_KEY;
+ }
+ else if ( propertyName.indexOf( ".release" ) != -1 )
+ {
+ endIndex = propertyName.lastIndexOf( ".release" );
+ versionType = RELEASE_KEY;
+ }
+ else
+ {
+ // MRELEASE-834, probably a maven-dependency-plugin property
+ continue;
+ }
+
+ artifactVersionlessKey = propertyName.substring( startIndex, endIndex );
+
+ if ( RELEASE_KEY.equals( versionType ) )
+ {
+ builder.addDependencyReleaseVersion( artifactVersionlessKey, currentEntry.getValue() );
+ }
+ else if ( DEVELOPMENT_KEY.equals( versionType ) )
+ {
+ builder.addDependencyDevelopmentVersion( artifactVersionlessKey, currentEntry.getValue() );
+ }
+ }
+ }
+ }
+
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/env/DefaultReleaseEnvironment.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/env/DefaultReleaseEnvironment.java
new file mode 100644
index 000000000..3305756f3
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/env/DefaultReleaseEnvironment.java
@@ -0,0 +1,124 @@
+package org.apache.maven.shared.release.env;
+
+/*
+ * 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.File;
+import java.util.Locale;
+
+import org.apache.maven.settings.Settings;
+
+/**
+ *
+ */
+public class DefaultReleaseEnvironment
+ implements ReleaseEnvironment
+{
+
+ private File mavenHome;
+
+ private File javaHome;
+
+ private File localRepositoryDirectory;
+
+ private Settings settings;
+
+ private String mavenExecutorId = DEFAULT_MAVEN_EXECUTOR_ID;
+
+ private Locale locale = Locale.ENGLISH;
+
+ @Override
+ public File getMavenHome()
+ {
+ return mavenHome;
+ }
+
+ @Override
+ public Settings getSettings()
+ {
+ return settings;
+ }
+
+ public DefaultReleaseEnvironment setMavenHome( File mavenHome )
+ {
+ this.mavenHome = mavenHome;
+ return this;
+ }
+
+ public DefaultReleaseEnvironment setSettings( Settings settings )
+ {
+ this.settings = settings;
+ return this;
+ }
+
+ @Override
+ public String getMavenExecutorId()
+ {
+ return mavenExecutorId;
+ }
+
+ public DefaultReleaseEnvironment setMavenExecutorId( String mavenExecutorId )
+ {
+ this.mavenExecutorId = mavenExecutorId;
+ return this;
+ }
+
+ @Override
+ public File getJavaHome()
+ {
+ return javaHome;
+ }
+
+ public DefaultReleaseEnvironment setJavaHome( File javaHome )
+ {
+ this.javaHome = javaHome;
+ return this;
+ }
+
+ @Override
+ public File getLocalRepositoryDirectory()
+ {
+ File localRepo = localRepositoryDirectory;
+
+ if ( localRepo == null && settings != null && settings.getLocalRepository() != null )
+ {
+ localRepo = new File( settings.getLocalRepository() ).getAbsoluteFile();
+ }
+
+ return localRepo;
+ }
+
+ public DefaultReleaseEnvironment setLocalRepositoryDirectory( File localRepositoryDirectory )
+ {
+ this.localRepositoryDirectory = localRepositoryDirectory;
+ return this;
+ }
+
+ @Override
+ public Locale getLocale()
+ {
+ return locale;
+ }
+
+ public DefaultReleaseEnvironment setLocale( Locale locale )
+ {
+ this.locale = locale;
+ return this;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/AbstractMavenExecutor.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/AbstractMavenExecutor.java
new file mode 100644
index 000000000..e70fa0e1e
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/AbstractMavenExecutor.java
@@ -0,0 +1,202 @@
+package org.apache.maven.shared.release.exec;
+
+/*
+ * 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.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.maven.settings.Proxy;
+import org.apache.maven.settings.Server;
+import org.apache.maven.settings.Settings;
+import org.apache.maven.settings.SettingsUtils;
+import org.apache.maven.settings.io.xpp3.SettingsXpp3Writer;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.logging.LogEnabled;
+import org.codehaus.plexus.logging.Logger;
+import org.codehaus.plexus.util.StringUtils;
+import org.sonatype.plexus.components.cipher.DefaultPlexusCipher;
+import org.sonatype.plexus.components.cipher.PlexusCipher;
+import org.sonatype.plexus.components.cipher.PlexusCipherException;
+import org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher;
+import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;
+import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException;
+import org.sonatype.plexus.components.sec.dispatcher.SecUtil;
+import org.sonatype.plexus.components.sec.dispatcher.model.SettingsSecurity;
+
+/**
+ *
+ */
+public abstract class AbstractMavenExecutor
+ implements MavenExecutor, LogEnabled
+{
+
+ private Logger logger;
+
+ /**
+ * When this plugin requires Maven 3.0 as minimum, this component can be removed and o.a.m.s.c.SettingsDecrypter be
+ * used instead.
+ */
+ @Requirement( role = SecDispatcher.class, hint = "mng-4384" )
+ private DefaultSecDispatcher secDispatcher;
+
+ /**
+ *
+ */
+ @Requirement
+ private PlexusCipher cipher;
+
+ protected AbstractMavenExecutor()
+ {
+ }
+
+ @Override
+ public void executeGoals( File workingDirectory, String goals, ReleaseEnvironment releaseEnvironment,
+ boolean interactive, String additionalArguments, String pomFileName,
+ ReleaseResult result )
+ throws MavenExecutorException
+ {
+ List goalsList = new ArrayList<>();
+ if ( goals != null )
+ {
+ // accept both space and comma, so the old way still work
+ // also accept line separators, so that goal lists can be spread
+ // across multiple lines in the POM.
+ for ( String token : StringUtils.split( goals, ", \n\r\t" ) )
+ {
+ goalsList.add( token );
+ }
+ }
+ executeGoals( workingDirectory, goalsList, releaseEnvironment, interactive, additionalArguments, pomFileName,
+ result );
+ }
+
+ protected abstract void executeGoals( File workingDirectory, List goals,
+ ReleaseEnvironment releaseEnvironment, boolean interactive,
+ String additionalArguments, String pomFileName, ReleaseResult result )
+ throws MavenExecutorException;
+
+ protected final Logger getLogger()
+ {
+ return logger;
+ }
+
+ @Override
+ public void enableLogging( Logger logger )
+ {
+ this.logger = logger;
+ }
+
+
+ protected Settings encryptSettings( Settings settings )
+ {
+ Settings encryptedSettings = SettingsUtils.copySettings( settings );
+
+ for ( Server server : encryptedSettings.getServers() )
+ {
+ String password = server.getPassword();
+ if ( password != null && !isEncryptedString( password ) )
+ {
+ try
+ {
+ server.setPassword( encryptAndDecorate( password ) );
+ }
+ catch ( IllegalStateException | SecDispatcherException | PlexusCipherException e )
+ {
+ // ignore
+ }
+ }
+
+ String passphrase = server.getPassphrase();
+ if ( passphrase != null && !isEncryptedString( passphrase ) )
+ {
+ try
+ {
+ server.setPassphrase( encryptAndDecorate( passphrase ) );
+ }
+ catch ( IllegalStateException | SecDispatcherException | PlexusCipherException e )
+ {
+ // ignore
+ }
+ }
+ }
+
+ for ( Proxy proxy : encryptedSettings.getProxies() )
+ {
+ String password = proxy.getPassword();
+ if ( password != null && !isEncryptedString( password ) )
+ {
+ try
+ {
+ proxy.setPassword( encryptAndDecorate( password ) );
+ }
+ catch ( IllegalStateException | SecDispatcherException | PlexusCipherException e )
+ {
+ // ignore
+ }
+ }
+ }
+
+ return encryptedSettings;
+ }
+
+ // From org.apache.maven.cli.MavenCli.encryption(CliRequest)
+ private String encryptAndDecorate( String passwd )
+ throws IllegalStateException, SecDispatcherException, PlexusCipherException
+ {
+ String configurationFile = secDispatcher.getConfigurationFile();
+
+ if ( configurationFile.startsWith( "~" ) )
+ {
+ configurationFile = System.getProperty( "user.home" ) + configurationFile.substring( 1 );
+ }
+
+ String file = System.getProperty( DefaultSecDispatcher.SYSTEM_PROPERTY_SEC_LOCATION, configurationFile );
+
+ String master = null;
+
+ SettingsSecurity sec = SecUtil.read( file, true );
+ if ( sec != null )
+ {
+ master = sec.getMaster();
+ }
+
+ if ( master == null )
+ {
+ throw new IllegalStateException( "Master password is not set in the setting security file: " + file );
+ }
+
+ DefaultPlexusCipher cipher = new DefaultPlexusCipher();
+ String masterPasswd = cipher.decryptDecorated( master, DefaultSecDispatcher.SYSTEM_PROPERTY_SEC_LOCATION );
+ return cipher.encryptAndDecorate( passwd, masterPasswd );
+ }
+
+ private boolean isEncryptedString( String str )
+ {
+ return cipher.isEncryptedString( str );
+ }
+
+ protected SettingsXpp3Writer getSettingsWriter()
+ {
+ return new SettingsXpp3Writer();
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/CommandLineFactory.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/CommandLineFactory.java
new file mode 100644
index 000000000..3699f68a4
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/CommandLineFactory.java
@@ -0,0 +1,40 @@
+package org.apache.maven.shared.release.exec;
+
+/*
+ * 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 org.codehaus.plexus.util.cli.Commandline;
+
+/**
+ * Create a command line for execution. Componentised to allow mocking.
+ *
+ * @author Brett Porter
+ */
+public interface CommandLineFactory
+{
+ /**
+ * Create a command line object with default environment for the given executable.
+ *
+ * @param executable the executable
+ * @return the command line
+ * @throws MavenExecutorException if there was a problem creating the command line
+ */
+ Commandline createCommandLine( String executable )
+ throws MavenExecutorException;
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/DefaultCommandLineFactory.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/DefaultCommandLineFactory.java
new file mode 100644
index 000000000..e89b42bdc
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/DefaultCommandLineFactory.java
@@ -0,0 +1,52 @@
+package org.apache.maven.shared.release.exec;
+
+/*
+ * 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 org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.util.cli.Commandline;
+
+/**
+ * Create a command line instance.
+ *
+ * @author Brett Porter
+ */
+@Component( role = CommandLineFactory.class )
+public class DefaultCommandLineFactory
+ implements CommandLineFactory
+{
+ @Override
+ public Commandline createCommandLine( String executable )
+ throws MavenExecutorException
+ {
+ Commandline commandline = new Commandline();
+ commandline.setExecutable( executable );
+
+ try
+ {
+ commandline.addSystemEnvironment();
+ }
+ catch ( Exception e )
+ {
+ throw new MavenExecutorException( e.getMessage(), e );
+ }
+
+ return commandline;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/ForkedMavenExecutor.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/ForkedMavenExecutor.java
new file mode 100644
index 000000000..af0051fb2
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/ForkedMavenExecutor.java
@@ -0,0 +1,256 @@
+package org.apache.maven.shared.release.exec;
+
+/*
+ * 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.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+import org.apache.maven.settings.io.xpp3.SettingsXpp3Writer;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.util.StringUtils;
+import org.codehaus.plexus.util.cli.CommandLineException;
+import org.codehaus.plexus.util.cli.Commandline;
+
+/**
+ * Fork Maven to executed a series of goals.
+ *
+ * @author Brett Porter
+ */
+@Component( role = MavenExecutor.class, hint = "forked-path" )
+public class ForkedMavenExecutor
+ extends AbstractMavenExecutor
+{
+ /**
+ * Command line factory.
+ */
+ @Requirement
+ private CommandLineFactory commandLineFactory;
+
+ /*
+ * @noinspection UseOfSystemOutOrSystemErr
+ */
+ @Override
+ public void executeGoals( File workingDirectory, List goals, ReleaseEnvironment releaseEnvironment,
+ boolean interactive, String additionalArguments, String pomFileName,
+ ReleaseResult relResult )
+ throws MavenExecutorException
+ {
+ String mavenPath = null;
+ // if null we use the current one
+ if ( releaseEnvironment.getMavenHome() != null )
+ {
+ mavenPath = releaseEnvironment.getMavenHome().getAbsolutePath();
+ }
+ else
+ {
+ mavenPath = System.getProperty( "maven.home" );
+ }
+
+ File settingsFile = null;
+ if ( releaseEnvironment.getSettings() != null )
+ {
+ // Have to serialize to a file as if Maven is embedded, there may not actually be a settings.xml on disk
+ try
+ {
+ settingsFile = File.createTempFile( "release-settings", ".xml" );
+ SettingsXpp3Writer writer = getSettingsWriter();
+
+ try ( FileWriter fileWriter = new FileWriter( settingsFile ) )
+ {
+ writer.write( fileWriter, encryptSettings( releaseEnvironment.getSettings() ) );
+ }
+ }
+ catch ( IOException e )
+ {
+ throw new MavenExecutorException( "Could not create temporary file for release settings.xml", e );
+ }
+ }
+ try
+ {
+
+ Commandline cl =
+ commandLineFactory.createCommandLine( mavenPath + File.separator + "bin" + File.separator + "mvn" );
+
+ cl.setWorkingDirectory( workingDirectory.getAbsolutePath() );
+
+ cl.addEnvironment( "MAVEN_TERMINATE_CMD", "on" );
+
+ cl.addEnvironment( "M2_HOME", mavenPath );
+
+ if ( settingsFile != null )
+ {
+ cl.createArg().setValue( "-s" );
+ cl.createArg().setFile( settingsFile );
+ }
+
+ if ( pomFileName != null )
+ {
+ cl.createArg().setValue( "-f" );
+ cl.createArg().setValue( pomFileName );
+ }
+
+ for ( String goal : goals )
+ {
+ cl.createArg().setValue( goal );
+ }
+
+ if ( !interactive )
+ {
+ cl.createArg().setValue( "--batch-mode" );
+ }
+
+ if ( !StringUtils.isEmpty( additionalArguments ) )
+ {
+ cl.createArg().setLine( additionalArguments );
+ }
+
+ TeeOutputStream stdOut = new TeeOutputStream( System.out );
+
+ TeeOutputStream stdErr = new TeeOutputStream( System.err );
+
+ try
+ {
+ relResult.appendInfo( "Executing: " + cl.toString() );
+ getLogger().info( "Executing: " + cl.toString() );
+
+ int result = executeCommandLine( cl, System.in, stdOut, stdErr );
+
+ if ( result != 0 )
+ {
+ throw new MavenExecutorException( "Maven execution failed, exit code: \'" + result + "\'", result );
+ }
+ }
+ catch ( CommandLineException e )
+ {
+ throw new MavenExecutorException( "Can't run goal " + goals, e );
+ }
+ finally
+ {
+ relResult.appendOutput( stdOut.toString() );
+ }
+ }
+ finally
+ {
+ if ( settingsFile != null && settingsFile.exists() && !settingsFile.delete() )
+ {
+ settingsFile.deleteOnExit();
+ }
+ }
+ }
+
+ public void setCommandLineFactory( CommandLineFactory commandLineFactory )
+ {
+ this.commandLineFactory = commandLineFactory;
+ }
+
+
+ public static int executeCommandLine( Commandline cl, InputStream systemIn, OutputStream systemOut,
+ OutputStream systemErr )
+ throws CommandLineException
+ {
+ if ( cl == null )
+ {
+ throw new IllegalArgumentException( "cl cannot be null." );
+ }
+
+ Process p = cl.execute();
+
+ //processes.put( new Long( cl.getPid() ), p );
+
+ RawStreamPumper inputFeeder = null;
+
+ if ( systemIn != null )
+ {
+ inputFeeder = new RawStreamPumper( systemIn, p.getOutputStream(), true );
+ }
+
+ RawStreamPumper outputPumper = new RawStreamPumper( p.getInputStream(), systemOut );
+ RawStreamPumper errorPumper = new RawStreamPumper( p.getErrorStream(), systemErr );
+
+ if ( inputFeeder != null )
+ {
+ inputFeeder.start();
+ }
+
+ outputPumper.start();
+
+ errorPumper.start();
+
+ try
+ {
+ int returnValue = p.waitFor();
+
+ if ( inputFeeder != null )
+ {
+ inputFeeder.setDone();
+ }
+ outputPumper.setDone();
+ errorPumper.setDone();
+
+ //processes.remove( new Long( cl.getPid() ) );
+
+ return returnValue;
+ }
+ catch ( InterruptedException ex )
+ {
+ //killProcess( cl.getPid() );
+ throw new CommandLineException( "Error while executing external command, process killed.", ex );
+ }
+ finally
+ {
+ try
+ {
+ errorPumper.closeInput();
+ }
+ catch ( IOException e )
+ {
+ //ignore
+ }
+ try
+ {
+ outputPumper.closeInput();
+ }
+ catch ( IOException e )
+ {
+ //ignore
+ }
+ if ( inputFeeder != null )
+ {
+ try
+ {
+ inputFeeder.closeOutput();
+ }
+ catch ( IOException e )
+ {
+ //ignore
+ }
+ }
+ }
+ }
+
+
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/InvokerMavenExecutor.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/InvokerMavenExecutor.java
new file mode 100644
index 000000000..eb9e04487
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/InvokerMavenExecutor.java
@@ -0,0 +1,554 @@
+package org.apache.maven.shared.release.exec;
+
+/*
+ * 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.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.PosixParser;
+import org.apache.maven.settings.io.xpp3.SettingsXpp3Writer;
+import org.apache.maven.shared.invoker.DefaultInvocationRequest;
+import org.apache.maven.shared.invoker.DefaultInvoker;
+import org.apache.maven.shared.invoker.InvocationOutputHandler;
+import org.apache.maven.shared.invoker.InvocationRequest;
+import org.apache.maven.shared.invoker.InvocationResult;
+import org.apache.maven.shared.invoker.Invoker;
+import org.apache.maven.shared.invoker.InvokerLogger;
+import org.apache.maven.shared.invoker.MavenInvocationException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.logging.Logger;
+import org.codehaus.plexus.util.cli.CommandLineUtils;
+
+/**
+ * Fork Maven using the maven-invoker shared library.
+ */
+@Component( role = MavenExecutor.class, hint = "invoker" )
+@SuppressWarnings( "static-access" )
+public class InvokerMavenExecutor
+ extends AbstractMavenExecutor
+{
+
+ private static final Options OPTIONS = new Options();
+
+ private static final char SET_SYSTEM_PROPERTY = 'D';
+
+ private static final char OFFLINE = 'o';
+
+ private static final char REACTOR = 'r';
+
+ private static final char QUIET = 'q';
+
+ private static final char DEBUG = 'X';
+
+ private static final char ERRORS = 'e';
+
+ private static final char NON_RECURSIVE = 'N';
+
+ private static final char UPDATE_SNAPSHOTS = 'U';
+
+ private static final char ACTIVATE_PROFILES = 'P';
+
+ private static final char CHECKSUM_FAILURE_POLICY = 'C';
+
+ private static final char CHECKSUM_WARNING_POLICY = 'c';
+
+ private static final char ALTERNATE_USER_SETTINGS = 's';
+
+ private static final String ALTERNATE_GLOBAL_SETTINGS = "gs";
+
+ private static final String FAIL_FAST = "ff";
+
+ private static final String FAIL_AT_END = "fae";
+
+ private static final String FAIL_NEVER = "fn";
+
+ private static final String ALTERNATE_POM_FILE = "f";
+
+ private static final String THREADS = "T";
+
+ private static final String BATCH_MODE = "B";
+
+ public static final char ALTERNATE_USER_TOOLCHAINS = 't';
+
+ static
+ {
+ OPTIONS.addOption(
+ OptionBuilder.withLongOpt( "define" ).hasArg().withDescription( "Define a system property" ).create(
+ SET_SYSTEM_PROPERTY ) );
+
+ OPTIONS.addOption( OptionBuilder.withLongOpt( "offline" ).withDescription( "Work offline" ).create( OFFLINE ) );
+
+ OPTIONS.addOption(
+ OptionBuilder.withLongOpt( "quiet" ).withDescription( "Quiet output - only show errors" ).create( QUIET ) );
+
+ OPTIONS.addOption(
+ OptionBuilder.withLongOpt( "debug" ).withDescription( "Produce execution debug output" ).create( DEBUG ) );
+
+ OPTIONS.addOption(
+ OptionBuilder.withLongOpt( "errors" ).withDescription( "Produce execution error messages" ).create(
+ ERRORS ) );
+
+ OPTIONS.addOption( OptionBuilder.withLongOpt( "reactor" ).withDescription(
+ "Execute goals for project found in the reactor" ).create( REACTOR ) );
+
+ OPTIONS.addOption(
+ OptionBuilder.withLongOpt( "non-recursive" ).withDescription( "Do not recurse into sub-projects" ).create(
+ NON_RECURSIVE ) );
+
+ OPTIONS.addOption( OptionBuilder.withLongOpt( "update-snapshots" ).withDescription(
+ "Forces a check for updated releases and snapshots on remote repositories" ).create( UPDATE_SNAPSHOTS ) );
+
+ OPTIONS.addOption( OptionBuilder.withLongOpt( "activate-profiles" ).withDescription(
+ "Comma-delimited list of profiles to activate" ).hasArg().create( ACTIVATE_PROFILES ) );
+
+ OPTIONS.addOption( OptionBuilder.withLongOpt( "strict-checksums" ).withDescription(
+ "Fail the build if checksums don't match" ).create( CHECKSUM_FAILURE_POLICY ) );
+
+ OPTIONS.addOption(
+ OptionBuilder.withLongOpt( "lax-checksums" ).withDescription( "Warn if checksums don't match" ).create(
+ CHECKSUM_WARNING_POLICY ) );
+
+ OPTIONS.addOption( OptionBuilder.withLongOpt( "settings" ).withDescription(
+ "Alternate path for the user settings file" ).hasArg().create( ALTERNATE_USER_SETTINGS ) );
+
+ OPTIONS.addOption( OptionBuilder.withLongOpt( "global-settings" ).withDescription(
+ " Alternate path for the global settings file" ).hasArg().create( ALTERNATE_GLOBAL_SETTINGS ) );
+
+ OPTIONS.addOption( OptionBuilder.withLongOpt( "fail-fast" ).withDescription(
+ "Stop at first failure in reactorized builds" ).create( FAIL_FAST ) );
+
+ OPTIONS.addOption( OptionBuilder.withLongOpt( "fail-at-end" ).withDescription(
+ "Only fail the build afterwards; allow all non-impacted builds to continue" ).create( FAIL_AT_END ) );
+
+ OPTIONS.addOption( OptionBuilder.withLongOpt( "fail-never" ).withDescription(
+ "NEVER fail the build, regardless of project result" ).create( FAIL_NEVER ) );
+
+ OPTIONS.addOption( OptionBuilder.withLongOpt( "file" ).withDescription(
+ "Force the use of an alternate POM file." ).hasArg().create( ALTERNATE_POM_FILE ) );
+
+ OPTIONS.addOption( OptionBuilder.withLongOpt( "threads" ).withDescription(
+ "Thread count, for instance 2.0C where C is core multiplied" ).hasArg().create( THREADS ) );
+
+ OPTIONS.addOption( OptionBuilder.withLongOpt( "batch-mode" ).withDescription(
+ "Run in non-interactive (batch) mode" ).create( BATCH_MODE ) );
+
+ OPTIONS.addOption( OptionBuilder.withLongOpt( "toolchains" ).withDescription(
+ "Alternate path for the user toolchains file" ).hasArg().create( ALTERNATE_USER_TOOLCHAINS ) );
+ }
+
+ // TODO: Configuring an invocation request from a command line could as well be part of the Invoker API
+ protected void setupRequest( InvocationRequest req,
+ InvokerLogger bridge,
+ String additionalArguments )
+ throws MavenExecutorException
+ {
+ try
+ {
+ String[] args = CommandLineUtils.translateCommandline( additionalArguments );
+ CommandLine cli = new PosixParser().parse( OPTIONS, args );
+
+ if ( cli.hasOption( SET_SYSTEM_PROPERTY ) )
+ {
+ String[] properties = cli.getOptionValues( SET_SYSTEM_PROPERTY );
+ Properties props = new Properties();
+ for ( int i = 0; i < properties.length; i++ )
+ {
+ String property = properties[i];
+ String name, value;
+ int sep = property.indexOf( "=" );
+ if ( sep <= 0 )
+ {
+ name = property.trim();
+ value = "true";
+ }
+ else
+ {
+ name = property.substring( 0, sep ).trim();
+ value = property.substring( sep + 1 ).trim();
+ }
+ props.setProperty( name, value );
+ }
+
+ req.setProperties( props );
+ }
+
+ if ( cli.hasOption( OFFLINE ) )
+ {
+ req.setOffline( true );
+ }
+
+ if ( cli.hasOption( QUIET ) )
+ {
+ // TODO: setQuiet() currently not supported by InvocationRequest
+ req.setDebug( false );
+ }
+ else if ( cli.hasOption( DEBUG ) )
+ {
+ req.setDebug( true );
+ }
+ else if ( cli.hasOption( ERRORS ) )
+ {
+ req.setShowErrors( true );
+ }
+
+ if ( cli.hasOption( REACTOR ) )
+ {
+ req.setRecursive( true );
+ }
+ else if ( cli.hasOption( NON_RECURSIVE ) )
+ {
+ req.setRecursive( false );
+ }
+
+ if ( cli.hasOption( UPDATE_SNAPSHOTS ) )
+ {
+ req.setUpdateSnapshots( true );
+ }
+
+ if ( cli.hasOption( ACTIVATE_PROFILES ) )
+ {
+ String[] profiles = cli.getOptionValues( ACTIVATE_PROFILES );
+
+ if ( profiles != null )
+ {
+ req.setProfiles( Arrays.asList( profiles ) );
+ }
+ }
+
+ if ( cli.hasOption( CHECKSUM_FAILURE_POLICY ) )
+ {
+ req.setGlobalChecksumPolicy( InvocationRequest.CHECKSUM_POLICY_FAIL );
+ }
+ else if ( cli.hasOption( CHECKSUM_WARNING_POLICY ) )
+ {
+ req.setGlobalChecksumPolicy( InvocationRequest.CHECKSUM_POLICY_WARN );
+ }
+
+ if ( cli.hasOption( ALTERNATE_USER_SETTINGS ) )
+ {
+ req.setUserSettingsFile( new File( cli.getOptionValue( ALTERNATE_USER_SETTINGS ) ) );
+ }
+
+ if ( cli.hasOption( ALTERNATE_GLOBAL_SETTINGS ) )
+ {
+ req.setGlobalSettingsFile( new File( cli.getOptionValue( ALTERNATE_GLOBAL_SETTINGS ) ) );
+ }
+
+ if ( cli.hasOption( FAIL_AT_END ) )
+ {
+ req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_AT_END );
+ }
+ else if ( cli.hasOption( FAIL_FAST ) )
+ {
+ req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_FAST );
+ }
+ if ( cli.hasOption( FAIL_NEVER ) )
+ {
+ req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_NEVER );
+ }
+ if ( cli.hasOption( ALTERNATE_POM_FILE ) )
+ {
+ if ( req.getPomFileName() != null )
+ {
+ getLogger().info( "pomFileName is already set, ignoring the -f argument" );
+ }
+ else
+ {
+ req.setPomFileName( cli.getOptionValue( ALTERNATE_POM_FILE ) );
+ }
+ }
+
+ if ( cli.hasOption( THREADS ) )
+ {
+ req.setThreads( cli.getOptionValue( THREADS ) );
+ }
+
+ if ( cli.hasOption( BATCH_MODE ) )
+ {
+ req.setInteractive( false );
+ }
+
+ if ( cli.hasOption( ALTERNATE_USER_TOOLCHAINS ) )
+ {
+ req.setToolchainsFile( new File( cli.getOptionValue( ALTERNATE_USER_TOOLCHAINS ) ) );
+ }
+
+ }
+ catch ( Exception e )
+ {
+ throw new MavenExecutorException( "Failed to re-parse additional arguments for Maven invocation.", e );
+ }
+ }
+
+ @Override
+ public void executeGoals( File workingDirectory, List goals, ReleaseEnvironment releaseEnvironment,
+ boolean interactive, String additionalArguments, String pomFileName,
+ ReleaseResult result )
+ throws MavenExecutorException
+ {
+ InvocationOutputHandler handler = getOutputHandler();
+ InvokerLogger bridge = getInvokerLogger();
+
+ File mavenPath = null;
+ // if null we use the current one
+ if ( releaseEnvironment.getMavenHome() != null )
+ {
+ mavenPath = releaseEnvironment.getMavenHome();
+ }
+ else
+ {
+ String mavenHome = System.getProperty( "maven.home" );
+ if ( mavenHome == null )
+ {
+ mavenHome = System.getenv( "MAVEN_HOME" );
+ }
+ if ( mavenHome == null )
+ {
+ mavenHome = System.getenv( "M2_HOME" );
+ }
+ mavenPath = mavenHome == null ? null : new File( mavenHome );
+ }
+ Invoker invoker =
+ new DefaultInvoker().setMavenHome( mavenPath ).setLogger( bridge )
+ .setOutputHandler( handler ).setErrorHandler( handler );
+
+ InvocationRequest req =
+ new DefaultInvocationRequest().setDebug( getLogger().isDebugEnabled() )
+ .setBaseDirectory( workingDirectory ).setInteractive( interactive );
+
+ if ( pomFileName != null )
+ {
+ req.setPomFileName( pomFileName );
+ }
+
+ File settingsFile = null;
+ if ( releaseEnvironment.getSettings() != null )
+ {
+ // Have to serialize to a file as if Maven is embedded, there may not actually be a settings.xml on disk
+ try
+ {
+ settingsFile = File.createTempFile( "release-settings", ".xml" );
+ SettingsXpp3Writer writer = getSettingsWriter();
+
+ try ( FileWriter fileWriter = new FileWriter( settingsFile ) )
+ {
+ writer.write( fileWriter, encryptSettings( releaseEnvironment.getSettings() ) );
+ }
+ req.setUserSettingsFile( settingsFile );
+ }
+ catch ( IOException e )
+ {
+ throw new MavenExecutorException( "Could not create temporary file for release settings.xml", e );
+ }
+ }
+ try
+ {
+ File localRepoDir = releaseEnvironment.getLocalRepositoryDirectory();
+ if ( localRepoDir != null )
+ {
+ req.setLocalRepositoryDirectory( localRepoDir );
+ }
+
+ setupRequest( req, bridge, additionalArguments );
+
+ req.setGoals( goals );
+
+ try
+ {
+ InvocationResult invocationResult = invoker.execute( req );
+
+ if ( invocationResult.getExecutionException() != null )
+ {
+ throw new MavenExecutorException( "Error executing Maven.",
+ invocationResult.getExecutionException() );
+ }
+ if ( invocationResult.getExitCode() != 0 )
+ {
+ throw new MavenExecutorException(
+ "Maven execution failed, exit code: \'" + invocationResult.getExitCode() + "\'",
+ invocationResult.getExitCode() );
+ }
+ }
+ catch ( MavenInvocationException e )
+ {
+ throw new MavenExecutorException( "Failed to invoke Maven build.", e );
+ }
+ }
+ finally
+ {
+ if ( settingsFile != null && settingsFile.exists() && !settingsFile.delete() )
+ {
+ settingsFile.deleteOnExit();
+ }
+ }
+ }
+
+ protected InvokerLogger getInvokerLogger()
+ {
+ return new LoggerBridge( getLogger() );
+ }
+
+ protected InvocationOutputHandler getOutputHandler()
+ {
+ return new Handler( getLogger() );
+ }
+
+ private static final class Handler
+ implements InvocationOutputHandler
+ {
+ private Logger logger;
+
+ Handler( Logger logger )
+ {
+ this.logger = logger;
+ }
+
+ public void consumeLine( String line )
+ {
+ logger.info( line );
+ }
+ }
+
+ private static final class LoggerBridge
+ implements InvokerLogger
+ {
+
+ private Logger logger;
+
+ LoggerBridge( Logger logger )
+ {
+ this.logger = logger;
+ }
+
+ @Override
+ public void debug( String message, Throwable error )
+ {
+ logger.debug( message, error );
+ }
+
+ @Override
+ public void debug( String message )
+ {
+ logger.debug( message );
+ }
+
+ @Override
+ public void error( String message, Throwable error )
+ {
+ logger.error( message, error );
+ }
+
+ @Override
+ public void error( String message )
+ {
+ logger.error( message );
+ }
+
+ @Override
+ public void fatalError( String message, Throwable error )
+ {
+ logger.fatalError( message, error );
+ }
+
+ @Override
+ public void fatalError( String message )
+ {
+ logger.fatalError( message );
+ }
+
+ @Override
+ public int getThreshold()
+ {
+ return logger.getThreshold();
+ }
+
+ @Override
+ public void info( String message, Throwable error )
+ {
+ logger.info( message, error );
+ }
+
+ @Override
+ public void info( String message )
+ {
+ logger.info( message );
+ }
+
+ @Override
+ public boolean isDebugEnabled()
+ {
+ return logger.isDebugEnabled();
+ }
+
+ @Override
+ public boolean isErrorEnabled()
+ {
+ return logger.isErrorEnabled();
+ }
+
+ @Override
+ public boolean isFatalErrorEnabled()
+ {
+ return logger.isFatalErrorEnabled();
+ }
+
+ @Override
+ public boolean isInfoEnabled()
+ {
+ return logger.isInfoEnabled();
+ }
+
+ @Override
+ public boolean isWarnEnabled()
+ {
+ return logger.isWarnEnabled();
+ }
+
+ @Override
+ public void setThreshold( int level )
+ {
+ // NOTE:
+ // logger.setThreadhold( level )
+ // is not supported in plexus-container-default:1.0-alpha-9 as used in Maven 2.x
+ }
+
+ @Override
+ public void warn( String message, Throwable error )
+ {
+ logger.warn( message, error );
+ }
+
+ @Override
+ public void warn( String message )
+ {
+ logger.warn( message );
+ }
+ }
+
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/MavenExecutor.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/MavenExecutor.java
new file mode 100644
index 000000000..e1fe9f54d
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/MavenExecutor.java
@@ -0,0 +1,49 @@
+package org.apache.maven.shared.release.exec;
+
+/*
+ * 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 org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+
+import java.io.File;
+
+/**
+ * Execute Maven. May be implemented as a forked instance, or embedded.
+ *
+ * @author Brett Porter
+ */
+public interface MavenExecutor
+{
+ /**
+ * Execute goals using Maven.
+ *
+ * @param workingDirectory the directory to execute in
+ * @param goals the goals to run (space delimited)
+ * @param releaseEnvironment the environmental settings, maven-home, etc used for this release
+ * @param interactive whether to execute in interactive mode, or the default batch mode
+ * @param additionalArguments additional arguments to pass to the Maven command
+ * @param pomFileName the file name of the POM to execute on
+ * @param result holds all results of the execution
+ * @throws MavenExecutorException if an error occurred executing Maven
+ */
+ void executeGoals( File workingDirectory, String goals, ReleaseEnvironment releaseEnvironment,
+ boolean interactive, String additionalArguments, String pomFileName, ReleaseResult result )
+ throws MavenExecutorException;
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/MavenExecutorException.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/MavenExecutorException.java
new file mode 100644
index 000000000..74300c730
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/MavenExecutorException.java
@@ -0,0 +1,48 @@
+package org.apache.maven.shared.release.exec;
+
+/*
+ * 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.
+ */
+
+/**
+ * Exception executing Maven.
+ *
+ * @author Brett Porter
+ */
+public class MavenExecutorException
+ extends Exception
+{
+ private int exitCode;
+
+ public MavenExecutorException( String message, int exitCode )
+ {
+ super( message );
+
+ this.exitCode = exitCode;
+ }
+
+ public MavenExecutorException( String message, Throwable cause )
+ {
+ super( message, cause );
+ }
+
+ public int getExitCode()
+ {
+ return exitCode;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/RawStreamPumper.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/RawStreamPumper.java
new file mode 100644
index 000000000..f75b69e51
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/RawStreamPumper.java
@@ -0,0 +1,128 @@
+package org.apache.maven.shared.release.exec;
+
+/*
+ * 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.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ *
+ */
+public class RawStreamPumper
+ extends Thread
+{
+ private InputStream in;
+
+ private OutputStream out;
+
+ boolean done;
+
+ boolean poll;
+
+ byte buffer[] = new byte[256];
+
+ public RawStreamPumper( InputStream in , OutputStream out, boolean poll )
+ {
+ this.in = in;
+ this.out = out;
+ this.poll = poll;
+ }
+
+ public RawStreamPumper( InputStream in , OutputStream out )
+ {
+ this.in = in;
+ this.out = out;
+ this.poll = false;
+ }
+
+ public void setDone()
+ {
+ done = true;
+ }
+
+ public void closeInput()
+ throws IOException
+ {
+ in.close();
+ }
+
+ public void closeOutput()
+ throws IOException
+ {
+ out.close();
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ if ( poll )
+ {
+ while ( !done )
+ {
+ if ( in.available() > 0 )
+ {
+ int i = in.read( buffer );
+ if ( i != -1 )
+ {
+ out.write( buffer, 0, i );
+ out.flush();
+ }
+ else
+ {
+ done = true;
+ }
+ }
+ else
+ {
+ Thread.sleep( 1 );
+ }
+ }
+ }
+ else
+ {
+ int i = in.read( buffer );
+ while ( i != -1 && !done )
+ {
+ if ( i != -1 )
+ {
+ out.write( buffer, 0, i );
+ out.flush();
+ }
+ else
+ {
+ done = true;
+ }
+ i = in.read( buffer );
+ }
+ }
+ }
+ catch ( Throwable e )
+ {
+ // Catched everything
+ }
+ finally
+ {
+ done = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/TeeConsumer.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/TeeConsumer.java
new file mode 100644
index 000000000..1ca009244
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/TeeConsumer.java
@@ -0,0 +1,76 @@
+package org.apache.maven.shared.release.exec;
+
+/*
+ * 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 org.codehaus.plexus.util.cli.StreamConsumer;
+
+import java.io.PrintStream;
+
+/**
+ * Consumer that both funnels to System.out/err, and stores in an internal buffer.
+ *
+ * @author Brett Porter
+ */
+public class TeeConsumer
+ implements StreamConsumer
+{
+ private PrintStream stream;
+
+ /**
+ * @noinspection StringBufferField
+ */
+ private StringBuffer content = new StringBuffer();
+
+ private static final String LS = System.getProperty( "line.separator" );
+
+ private String indent;
+
+ public TeeConsumer( PrintStream stream )
+ {
+ this( stream, " " );
+ }
+
+ public TeeConsumer( PrintStream stream, String indent )
+ {
+ this.stream = stream;
+
+ this.indent = indent;
+ }
+
+ @Override
+ public void consumeLine( String line )
+ {
+ stream.println( indent + line );
+
+ content.append( line );
+ content.append( LS );
+ }
+
+ public String getContent()
+ {
+ return content.toString();
+ }
+
+ @Override
+ public String toString()
+ {
+ return getContent();
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/TeeOutputStream.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/TeeOutputStream.java
new file mode 100644
index 000000000..e06fa1337
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/exec/TeeOutputStream.java
@@ -0,0 +1,94 @@
+package org.apache.maven.shared.release.exec;
+
+/*
+ * 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.ByteArrayOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ *
+ */
+public class TeeOutputStream
+ extends FilterOutputStream
+{
+ private ByteArrayOutputStream bout = new ByteArrayOutputStream( 1024 * 8 );
+ private byte indent[];
+ private int last = '\n';
+
+ public TeeOutputStream( OutputStream out )
+ {
+ this( out, " " );
+ }
+
+ public TeeOutputStream( OutputStream out, String i )
+ {
+ super( out );
+ indent = i.getBytes();
+ }
+
+ @Override
+ public void write( byte[] b, int off, int len )
+ throws IOException
+ {
+ for ( int x = 0; x < len; x++ )
+ {
+ int c = b[off + x];
+ if ( last == '\n' || ( last == '\r' && c != '\n' ) )
+ {
+ out.write( b, off, x );
+ bout.write( b, off, x );
+ out.write( indent );
+ off += x;
+ len -= x;
+ x = 0;
+ }
+ last = c;
+ }
+ out.write( b, off, len );
+ bout.write( b, off, len );
+ }
+
+ @Override
+ public void write( int b )
+ throws IOException
+ {
+ if ( last == '\n' || ( last == '\r' && b != '\n' ) )
+ {
+ out.write( indent );
+ }
+ out.write( b );
+ bout.write( b );
+ last = b;
+ }
+
+ @Override
+ public String toString()
+ {
+ return bout.toString();
+ }
+
+ public String getContent()
+ {
+ return bout.toString();
+ }
+
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/AbstractBackupPomsPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/AbstractBackupPomsPhase.java
new file mode 100644
index 000000000..b2a7e8edf
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/AbstractBackupPomsPhase.java
@@ -0,0 +1,58 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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 org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.release.util.ReleaseUtil;
+
+import java.io.File;
+
+/**
+ * @author Edwin Punzalan
+ */
+public abstract class AbstractBackupPomsPhase
+ extends AbstractReleasePhase
+{
+ private final String backupSuffix = ".releaseBackup";
+
+ protected File getPomBackup( MavenProject project )
+ {
+ File pomFile = ReleaseUtil.getStandardPom( project );
+
+ if ( pomFile != null )
+ {
+ return new File( pomFile.getAbsolutePath() + backupSuffix );
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ protected void deletePomBackup( MavenProject project )
+ {
+ File pomBackup = getPomBackup( project );
+
+ if ( pomBackup != null && pomBackup.exists() )
+ {
+ pomBackup.delete();
+ }
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/AbstractReleasePomsPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/AbstractReleasePomsPhase.java
new file mode 100644
index 000000000..0555c08c9
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/AbstractReleasePomsPhase.java
@@ -0,0 +1,80 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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 org.apache.maven.scm.manager.NoSuchScmProviderException;
+import org.apache.maven.scm.provider.ScmProvider;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.maven.scm.repository.ScmRepositoryException;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseFailureException;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
+import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
+import org.codehaus.plexus.component.annotations.Requirement;
+
+/**
+ * Abstract release POM phase.
+ *
+ * @author Mark Hobson
+ */
+public abstract class AbstractReleasePomsPhase extends AbstractReleasePhase
+{
+ /**
+ * Tool that gets a configured SCM repository from release configuration.
+ */
+ @Requirement
+ private ScmRepositoryConfigurator scmRepositoryConfigurator;
+
+ protected ScmRepository getScmRepository( ReleaseDescriptor releaseDescriptor,
+ ReleaseEnvironment releaseEnvironment )
+ throws ReleaseFailureException, ReleaseExecutionException
+ {
+ try
+ {
+ return scmRepositoryConfigurator.getConfiguredRepository( releaseDescriptor,
+ releaseEnvironment.getSettings() );
+ }
+ catch ( ScmRepositoryException exception )
+ {
+ throw new ReleaseScmRepositoryException( exception.getMessage(), exception.getValidationMessages() );
+ }
+ catch ( NoSuchScmProviderException exception )
+ {
+ throw new ReleaseExecutionException( "Unable to configure SCM repository: " + exception.getMessage(),
+ exception );
+ }
+ }
+
+ protected ScmProvider getScmProvider( ScmRepository scmRepository )
+ throws ReleaseExecutionException
+ {
+ try
+ {
+ return scmRepositoryConfigurator.getRepositoryProvider( scmRepository );
+ }
+ catch ( NoSuchScmProviderException exception )
+ {
+ throw new ReleaseExecutionException( "Unable to configure SCM repository: " + exception.getMessage(),
+ exception );
+ }
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/AbstractRewritePomsPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/AbstractRewritePomsPhase.java
new file mode 100644
index 000000000..4da819909
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/AbstractRewritePomsPhase.java
@@ -0,0 +1,682 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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.File;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TimeZone;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.ArtifactUtils;
+import org.apache.maven.model.Build;
+import org.apache.maven.model.BuildBase;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.ModelBase;
+import org.apache.maven.model.Plugin;
+import org.apache.maven.model.Profile;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.scm.ScmException;
+import org.apache.maven.scm.ScmFileSet;
+import org.apache.maven.scm.command.edit.EditScmResult;
+import org.apache.maven.scm.manager.NoSuchScmProviderException;
+import org.apache.maven.scm.provider.ScmProvider;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.maven.scm.repository.ScmRepositoryException;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseFailureException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.apache.maven.shared.release.scm.ReleaseScmCommandException;
+import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
+import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
+import org.apache.maven.shared.release.scm.ScmTranslator;
+import org.apache.maven.shared.release.transform.ModelETLRequest;
+import org.apache.maven.shared.release.transform.MavenCoordinate;
+import org.apache.maven.shared.release.transform.ModelETL;
+import org.apache.maven.shared.release.transform.ModelETLFactory;
+import org.apache.maven.shared.release.transform.jdom.JDomModelETLFactory;
+import org.apache.maven.shared.release.util.ReleaseUtil;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.util.StringUtils;
+
+/**
+ * Base class for rewriting phases.
+ *
+ * @author Brett Porter
+ */
+public abstract class AbstractRewritePomsPhase
+ extends AbstractReleasePhase implements ResourceGenerator
+{
+ /**
+ * Tool that gets a configured SCM repository from release configuration.
+ */
+ @Requirement
+ private ScmRepositoryConfigurator scmRepositoryConfigurator;
+
+ @Requirement( role = ModelETLFactory.class )
+ private Map modelETLFactories;
+
+ /**
+ * Use jdom-sax as default
+ */
+ private String modelETL = JDomModelETLFactory.ROLE_HINT;
+
+ /**
+ * SCM URL translators mapped by provider name.
+ */
+ @Requirement( role = ScmTranslator.class )
+ private Map scmTranslators;
+
+ protected final Map getScmTranslators()
+ {
+ return scmTranslators;
+ }
+
+ private String ls = ReleaseUtil.LS;
+
+ public void setLs( String ls )
+ {
+ this.ls = ls;
+ }
+
+ public void setModelETL( String modelETL )
+ {
+ this.modelETL = modelETL;
+ }
+
+ private long startTime = -1 * 1000;
+
+ public void setStartTime( long startTime )
+ {
+ this.startTime = startTime;
+ }
+
+ protected abstract String getPomSuffix();
+
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ transform( releaseDescriptor, releaseEnvironment, reactorProjects, false, result );
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ transform( releaseDescriptor, releaseEnvironment, reactorProjects, true, result );
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ @Override
+ public ReleaseResult clean( List reactorProjects )
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ if ( reactorProjects != null )
+ {
+ for ( MavenProject project : reactorProjects )
+ {
+ File pomFile = ReleaseUtil.getStandardPom( project );
+ // MRELEASE-273 : if no pom
+ if ( pomFile != null )
+ {
+ File file = new File( pomFile.getParentFile(), pomFile.getName() + "." + getPomSuffix() );
+ if ( file.exists() )
+ {
+ file.delete();
+ }
+ }
+ }
+ }
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ private void transform( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects, boolean simulate, ReleaseResult result )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ result.setStartTime( ( startTime >= 0 ) ? startTime : System.currentTimeMillis() );
+
+ for ( MavenProject project : reactorProjects )
+ {
+ logInfo( result, "Transforming '" + project.getName() + "'..." );
+
+ transformProject( project, releaseDescriptor, releaseEnvironment, simulate, result );
+ }
+ }
+
+ private void transformProject( MavenProject project, ReleaseDescriptor releaseDescriptor,
+ ReleaseEnvironment releaseEnvironment, boolean simulate,
+ ReleaseResult result )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ File pomFile = ReleaseUtil.getStandardPom( project );
+
+ ModelETLRequest request = new ModelETLRequest();
+ request.setLineSeparator( ls );
+ request.setProject( project );
+ request.setReleaseDescriptor( releaseDescriptor );
+
+ ModelETL etl = modelETLFactories.get( modelETL ).newInstance( request );
+
+ etl.extract( pomFile );
+
+ ScmRepository scmRepository = null;
+ ScmProvider provider = null;
+
+ if ( isUpdateScm() )
+ {
+ try
+ {
+ scmRepository = scmRepositoryConfigurator.getConfiguredRepository( releaseDescriptor,
+ releaseEnvironment.getSettings() );
+
+ provider = scmRepositoryConfigurator.getRepositoryProvider( scmRepository );
+ }
+ catch ( ScmRepositoryException e )
+ {
+ throw new ReleaseScmRepositoryException( e.getMessage(), e.getValidationMessages() );
+ }
+ catch ( NoSuchScmProviderException e )
+ {
+ throw new ReleaseExecutionException( "Unable to configure SCM repository: " + e.getMessage(), e );
+ }
+ }
+
+ transformDocument( project, etl.getModel(), releaseDescriptor, scmRepository, result,
+ simulate );
+
+ File outputFile;
+ if ( simulate )
+ {
+ outputFile = new File( pomFile.getParentFile(), pomFile.getName() + "." + getPomSuffix() );
+ }
+ else
+ {
+ outputFile = pomFile;
+ prepareScm( pomFile, releaseDescriptor, scmRepository, provider );
+ }
+ etl.load( outputFile );
+
+ }
+
+ private void transformDocument( MavenProject project, Model modelTarget, ReleaseDescriptor releaseDescriptor,
+ ScmRepository scmRepository, ReleaseResult result,
+ boolean simulate )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ Model model = project.getModel();
+
+ Properties properties = modelTarget.getProperties();
+
+ String parentVersion = rewriteParent( project, modelTarget, releaseDescriptor, simulate );
+
+ String projectId = ArtifactUtils.versionlessKey( project.getGroupId(), project.getArtifactId() );
+
+ rewriteVersion( modelTarget, releaseDescriptor, projectId, project, parentVersion );
+
+ Build buildTarget = modelTarget.getBuild();
+ if ( buildTarget != null )
+ {
+ // profile.build.extensions doesn't exist, so only rewrite project.build.extensions
+ rewriteArtifactVersions( toMavenCoordinates( buildTarget.getExtensions() ),
+ model, properties, result, releaseDescriptor, simulate );
+
+ rewriteArtifactVersions( toMavenCoordinates( buildTarget.getPlugins() ),
+ model, properties, result, releaseDescriptor, simulate );
+
+ for ( Plugin plugin : buildTarget.getPlugins() )
+ {
+ rewriteArtifactVersions( toMavenCoordinates( plugin.getDependencies() ),
+ model, properties,
+ result, releaseDescriptor, simulate );
+ }
+
+ if ( buildTarget.getPluginManagement() != null )
+ {
+ rewriteArtifactVersions( toMavenCoordinates( buildTarget.getPluginManagement().getPlugins() ), model,
+ properties, result, releaseDescriptor, simulate );
+
+ for ( Plugin plugin : buildTarget.getPluginManagement().getPlugins() )
+ {
+ rewriteArtifactVersions( toMavenCoordinates( plugin.getDependencies() ), model, properties, result,
+ releaseDescriptor, simulate );
+ }
+ }
+ }
+
+ for ( Profile profile : modelTarget.getProfiles() )
+ {
+ BuildBase profileBuild = profile.getBuild();
+ if ( profileBuild != null )
+ {
+ rewriteArtifactVersions( toMavenCoordinates( profileBuild.getPlugins() ), model, properties, result,
+ releaseDescriptor, simulate );
+
+ for ( Plugin plugin : profileBuild.getPlugins() )
+ {
+ rewriteArtifactVersions( toMavenCoordinates( plugin.getDependencies() ), model, properties, result,
+ releaseDescriptor, simulate );
+ }
+
+ if ( profileBuild.getPluginManagement() != null )
+ {
+ rewriteArtifactVersions( toMavenCoordinates( profileBuild.getPluginManagement().getPlugins() ),
+ model, properties, result, releaseDescriptor, simulate );
+
+ for ( Plugin plugin : profileBuild.getPluginManagement().getPlugins() )
+ {
+ rewriteArtifactVersions( toMavenCoordinates( plugin.getDependencies() ), model, properties,
+ result, releaseDescriptor, simulate );
+ }
+ }
+ }
+ }
+
+ List modelBases = new ArrayList<>();
+ modelBases.add( modelTarget );
+ modelBases.addAll( modelTarget.getProfiles() );
+
+ for ( ModelBase modelBase : modelBases )
+ {
+ rewriteArtifactVersions( toMavenCoordinates( modelBase.getDependencies() ), model, properties, result,
+ releaseDescriptor, simulate );
+
+ if ( modelBase.getDependencyManagement() != null )
+ {
+ rewriteArtifactVersions( toMavenCoordinates( modelBase.getDependencyManagement().getDependencies() ),
+ model, properties, result, releaseDescriptor, simulate );
+ }
+
+ if ( modelBase.getReporting() != null )
+ {
+ rewriteArtifactVersions( toMavenCoordinates( modelBase.getReporting().getPlugins() ), model, properties,
+ result, releaseDescriptor, simulate );
+ }
+ }
+
+ transformScm( project, modelTarget, releaseDescriptor, projectId, scmRepository, result );
+
+ if ( properties != null )
+ {
+ rewriteBuildOutputTimestampProperty( properties, result );
+ }
+ }
+
+ private void rewriteBuildOutputTimestampProperty( Properties properties, ReleaseResult result )
+ {
+ String buildOutputTimestamp = properties.getProperty( "project.build.outputTimestamp" );
+ if ( buildOutputTimestamp == null )
+ {
+ // no Reproducible Builds output timestamp defined
+ return;
+ }
+ if ( buildOutputTimestamp.length() <= 1 )
+ {
+ // value length == 1 means disable Reproducible Builds
+ return;
+ }
+
+ if ( StringUtils.isNumeric( buildOutputTimestamp ) )
+ {
+ // int representing seconds since the epoch, like SOURCE_DATE_EPOCH
+ buildOutputTimestamp = String.valueOf( result.getStartTime() / 1000 );
+ }
+ else
+ {
+ // ISO-8601
+ DateFormat df = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss'Z'" );
+ df.setTimeZone( TimeZone.getTimeZone( "UTC" ) );
+ buildOutputTimestamp = df.format( new Date( result.getStartTime() ) );
+ }
+ properties.setProperty( "project.build.outputTimestamp", buildOutputTimestamp );
+ }
+
+ private void rewriteVersion( Model modelTarget, ReleaseDescriptor releaseDescriptor, String projectId,
+ MavenProject project, String parentVersion )
+ throws ReleaseFailureException
+ {
+ String version = getNextVersion( releaseDescriptor, projectId );
+ if ( version == null )
+ {
+ throw new ReleaseFailureException( "Version for '" + project.getName() + "' was not mapped" );
+ }
+
+ modelTarget.setVersion( version );
+ }
+
+ private String rewriteParent( MavenProject project, Model targetModel,
+ ReleaseDescriptor releaseDescriptor, boolean simulate )
+ throws ReleaseFailureException
+ {
+ String parentVersion = null;
+ if ( project.hasParent() )
+ {
+ MavenProject parent = project.getParent();
+ String key = ArtifactUtils.versionlessKey( parent.getGroupId(), parent.getArtifactId() );
+ parentVersion = getNextVersion( releaseDescriptor, key );
+ if ( parentVersion == null )
+ {
+ //MRELEASE-317
+ parentVersion = getResolvedSnapshotVersion( key, releaseDescriptor );
+ }
+ if ( parentVersion == null )
+ {
+ String original = getOriginalVersion( releaseDescriptor, key, simulate );
+ if ( parent.getVersion().equals( original ) )
+ {
+ throw new ReleaseFailureException( "Version for parent '" + parent.getName() + "' was not mapped" );
+ }
+ }
+ else
+ {
+ targetModel.getParent().setVersion( parentVersion );
+ }
+ }
+ return parentVersion;
+ }
+
+ private void rewriteArtifactVersions( Collection elements, Model projectModel,
+ Properties properties, ReleaseResult result,
+ ReleaseDescriptor releaseDescriptor, boolean simulate )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ if ( elements == null )
+ {
+ return;
+ }
+ String projectId = ArtifactUtils.versionlessKey( projectModel.getGroupId(), projectModel.getArtifactId() );
+ for ( MavenCoordinate coordinate : elements )
+ {
+ String rawVersion = coordinate.getVersion();
+ if ( rawVersion == null )
+ {
+ // managed dependency or unversioned plugin
+ continue;
+ }
+
+ String rawGroupId = coordinate.getGroupId();
+ if ( rawGroupId == null )
+ {
+ if ( "plugin".equals( coordinate.getName() ) )
+ {
+ rawGroupId = "org.apache.maven.plugins";
+ }
+ else
+ {
+ // incomplete dependency
+ continue;
+ }
+ }
+ String groupId = ReleaseUtil.interpolate( rawGroupId, projectModel );
+
+ String rawArtifactId = coordinate.getArtifactId();
+ if ( rawArtifactId == null )
+ {
+ // incomplete element
+ continue;
+ }
+ String artifactId = ReleaseUtil.interpolate( rawArtifactId, projectModel );
+
+ String key = ArtifactUtils.versionlessKey( groupId, artifactId );
+ String resolvedSnapshotVersion = getResolvedSnapshotVersion( key, releaseDescriptor );
+ String mappedVersion = getNextVersion( releaseDescriptor, key );
+ String originalVersion = getOriginalVersion( releaseDescriptor, key, simulate );
+ if ( originalVersion == null )
+ {
+ originalVersion = getOriginalResolvedSnapshotVersion( key, releaseDescriptor );
+ }
+
+ // MRELEASE-220
+ if ( mappedVersion != null && mappedVersion.endsWith( Artifact.SNAPSHOT_VERSION )
+ && !rawVersion.endsWith( Artifact.SNAPSHOT_VERSION ) && !releaseDescriptor.isUpdateDependencies() )
+ {
+ continue;
+ }
+
+ if ( mappedVersion != null )
+ {
+ if ( rawVersion.equals( originalVersion ) )
+ {
+ logInfo( result, " Updating " + artifactId + " to " + mappedVersion );
+ coordinate.setVersion( mappedVersion );
+ }
+ else if ( rawVersion.matches( "\\$\\{.+\\}" ) )
+ {
+ String expression = rawVersion.substring( 2, rawVersion.length() - 1 );
+
+ if ( expression.startsWith( "project." ) || expression.startsWith( "pom." )
+ || "version".equals( expression ) )
+ {
+ if ( !mappedVersion.equals( getNextVersion( releaseDescriptor, projectId ) ) )
+ {
+ logInfo( result, " Updating " + artifactId + " to " + mappedVersion );
+ coordinate.setVersion( mappedVersion );
+ }
+ else
+ {
+ logInfo( result, " Ignoring artifact version update for expression " + rawVersion );
+ }
+ }
+ else if ( properties != null )
+ {
+ // version is an expression, check for properties to update instead
+
+ String propertyValue = properties.getProperty( expression );
+
+ if ( propertyValue != null )
+ {
+ if ( propertyValue.equals( originalVersion ) )
+ {
+ logInfo( result, " Updating " + rawVersion + " to " + mappedVersion );
+ // change the property only if the property is the same as what's in the reactor
+ properties.setProperty( expression, mappedVersion );
+ }
+ else if ( mappedVersion.equals( propertyValue ) )
+ {
+ // this property may have been updated during processing a sibling.
+ logInfo( result, " Ignoring artifact version update for expression " + rawVersion
+ + " because it is already updated" );
+ }
+ else if ( !mappedVersion.equals( rawVersion ) )
+ {
+ if ( mappedVersion.matches( "\\$\\{project.+\\}" )
+ || mappedVersion.matches( "\\$\\{pom.+\\}" )
+ || "${version}".equals( mappedVersion ) )
+ {
+ logInfo( result, " Ignoring artifact version update for expression "
+ + mappedVersion );
+ // ignore... we cannot update this expression
+ }
+ else
+ {
+ // the value of the expression conflicts with what the user wanted to release
+ throw new ReleaseFailureException( "The artifact (" + key + ") requires a "
+ + "different version (" + mappedVersion + ") than what is found ("
+ + propertyValue + ") for the expression (" + expression + ") in the "
+ + "project (" + projectId + ")." );
+ }
+ }
+ }
+ else
+ {
+ // the expression used to define the version of this artifact may be inherited
+ // TODO needs a better error message, what pom? what dependency?
+ throw new ReleaseFailureException( "The version could not be updated: " + rawVersion );
+ }
+ }
+ }
+ else
+ {
+ // different/previous version not related to current release
+ }
+ }
+ else if ( resolvedSnapshotVersion != null )
+ {
+ logInfo( result, " Updating " + artifactId + " to " + resolvedSnapshotVersion );
+
+ coordinate.setVersion( resolvedSnapshotVersion );
+ }
+ else
+ {
+ // artifact not related to current release
+ }
+ }
+ }
+
+ private void prepareScm( File pomFile, ReleaseDescriptor releaseDescriptor, ScmRepository repository,
+ ScmProvider provider )
+ throws ReleaseExecutionException, ReleaseScmCommandException
+ {
+ try
+ {
+ if ( isUpdateScm() && ( releaseDescriptor.isScmUseEditMode() || provider.requiresEditMode() ) )
+ {
+ EditScmResult result = provider.edit( repository, new ScmFileSet(
+ new File( releaseDescriptor.getWorkingDirectory() ), pomFile ) );
+
+ if ( !result.isSuccess() )
+ {
+ throw new ReleaseScmCommandException( "Unable to enable editing on the POM", result );
+ }
+ }
+ }
+ catch ( ScmException e )
+ {
+ throw new ReleaseExecutionException( "An error occurred enabling edit mode: " + e.getMessage(), e );
+ }
+ }
+
+
+ protected abstract String getResolvedSnapshotVersion( String artifactVersionlessKey,
+ ReleaseDescriptor releaseDscriptor );
+
+ protected abstract String getOriginalVersion( ReleaseDescriptor releaseDescriptor, String projectKey,
+ boolean simulate );
+
+ protected abstract String getNextVersion( ReleaseDescriptor releaseDescriptor, String key );
+
+ protected abstract void transformScm( MavenProject project, Model modelTarget, ReleaseDescriptor releaseDescriptor,
+ String projectId, ScmRepository scmRepository,
+ ReleaseResult result )
+ throws ReleaseExecutionException;
+
+ /**
+ *
+ * @return {@code true} if the SCM-section should be updated, otherwise {@code false}
+ * @since 2.4
+ */
+ protected boolean isUpdateScm()
+ {
+ return true;
+ }
+
+ protected String getOriginalResolvedSnapshotVersion( String artifactVersionlessKey,
+ ReleaseDescriptor releaseDescriptor )
+ {
+ return releaseDescriptor.getDependencyOriginalVersion( artifactVersionlessKey );
+ }
+
+ /**
+ * Determines the relative path from trunk to tag, and adds this relative path
+ * to the url.
+ *
+ * @param trunkPath - The trunk url
+ * @param tagPath - The tag base
+ * @param urlPath - scm.url or scm.connection
+ * @return The url path for the tag.
+ */
+ protected static String translateUrlPath( String trunkPath, String tagPath, String urlPath )
+ {
+ trunkPath = trunkPath.trim();
+ tagPath = tagPath.trim();
+ //Strip the slash at the end if one is present
+ if ( trunkPath.endsWith( "/" ) )
+ {
+ trunkPath = trunkPath.substring( 0, trunkPath.length() - 1 );
+ }
+ if ( tagPath.endsWith( "/" ) )
+ {
+ tagPath = tagPath.substring( 0, tagPath.length() - 1 );
+ }
+ char[] tagPathChars = trunkPath.toCharArray();
+ char[] trunkPathChars = tagPath.toCharArray();
+ // Find the common path between trunk and tags
+ int i = 0;
+ while ( ( i < tagPathChars.length ) && ( i < trunkPathChars.length ) && tagPathChars[i] == trunkPathChars[i] )
+ {
+ ++i;
+ }
+ // If there is nothing common between trunk and tags, or the relative
+ // path does not exist in the url, then just return the tag.
+ if ( i == 0 || urlPath.indexOf( trunkPath.substring( i ) ) < 0 )
+ {
+ return tagPath;
+ }
+ else
+ {
+ return StringUtils.replace( urlPath, trunkPath.substring( i ), tagPath.substring( i ) );
+ }
+ }
+
+ private Collection toMavenCoordinates( List> objects )
+ {
+ Collection coordinates = new ArrayList<>( objects.size() );
+ for ( Object object : objects )
+ {
+ if ( object instanceof MavenCoordinate )
+ {
+ coordinates.add( (MavenCoordinate) object );
+ }
+ else
+ {
+ throw new UnsupportedOperationException();
+ }
+ }
+ return coordinates;
+ }
+
+
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/AbstractRunGoalsPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/AbstractRunGoalsPhase.java
new file mode 100644
index 000000000..23d4cce71
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/AbstractRunGoalsPhase.java
@@ -0,0 +1,139 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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.File;
+import java.util.Map;
+
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.apache.maven.shared.release.exec.MavenExecutor;
+import org.apache.maven.shared.release.exec.MavenExecutorException;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.util.StringUtils;
+
+/**
+ * Run the integration tests for the project to verify that it builds before committing.
+ *
+ * @author Brett Porter
+ */
+public abstract class AbstractRunGoalsPhase
+ extends AbstractReleasePhase
+{
+ /**
+ * Component to assist in executing Maven.
+ */
+ @Requirement( role = MavenExecutor.class )
+ private Map mavenExecutors;
+
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ File workingDirectory, String additionalArguments )
+ throws ReleaseExecutionException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ try
+ {
+ String goals = getGoals( releaseDescriptor );
+ if ( !StringUtils.isEmpty( goals ) )
+ {
+ logInfo( result, "Executing goals '" + goals + "'..." );
+
+ MavenExecutor mavenExecutor = mavenExecutors.get( releaseEnvironment.getMavenExecutorId() );
+
+ if ( mavenExecutor == null )
+ {
+ throw new ReleaseExecutionException(
+ "Cannot find Maven executor with id: " + releaseEnvironment.getMavenExecutorId() );
+ }
+
+ File executionRoot;
+ String pomFileName;
+ if ( releaseDescriptor.getPomFileName() != null )
+ {
+ File rootPom = new File( workingDirectory, releaseDescriptor.getPomFileName() );
+ executionRoot = rootPom.getParentFile();
+ pomFileName = rootPom.getName();
+ }
+ else
+ {
+ executionRoot = workingDirectory;
+ pomFileName = null;
+ }
+
+ mavenExecutor.executeGoals( executionRoot, goals, releaseEnvironment,
+ releaseDescriptor.isInteractive(), additionalArguments,
+ pomFileName, result );
+ }
+ }
+ catch ( MavenExecutorException e )
+ {
+ throw new ReleaseExecutionException( e.getMessage(), e );
+ }
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ protected abstract String getGoals( ReleaseDescriptor releaseDescriptor );
+
+ protected String getAdditionalArguments( ReleaseDescriptor releaseDescriptor )
+ {
+ StringBuilder builder = new StringBuilder();
+
+ if ( releaseDescriptor.getAdditionalArguments() != null )
+ {
+ builder.append( releaseDescriptor.getAdditionalArguments() );
+ }
+
+ if ( !releaseDescriptor.getActivateProfiles().isEmpty() )
+ {
+ builder.append( " -P " )
+ .append( StringUtils.join( releaseDescriptor.getActivateProfiles().iterator(), "," ) );
+ }
+
+ return builder.length() > 0 ? builder.toString().trim() : null;
+ }
+
+ /**
+ * Determines the path of the working directory. By default, this is the
+ * checkout directory. For some SCMs, the project root directory is not the
+ * checkout directory itself, but a SCM-specific subdirectory.
+ *
+ * @param checkoutDirectory The checkout directory as java.io.File
+ * @param relativePathProjectDirectory The relative path of the project directory within the checkout
+ * directory or ""
+ * @return The working directory
+ */
+ protected File determineWorkingDirectory( File checkoutDirectory, String relativePathProjectDirectory )
+ {
+ File workingDirectory = checkoutDirectory;
+
+ if ( StringUtils.isNotEmpty( relativePathProjectDirectory ) )
+ {
+ workingDirectory = new File( checkoutDirectory, relativePathProjectDirectory );
+ }
+
+ return workingDirectory;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/AbstractScmCommitPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/AbstractScmCommitPhase.java
new file mode 100644
index 000000000..3ae72acab
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/AbstractScmCommitPhase.java
@@ -0,0 +1,259 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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 org.apache.maven.project.MavenProject;
+import org.apache.maven.scm.ScmException;
+import org.apache.maven.scm.ScmFileSet;
+import org.apache.maven.scm.ScmVersion;
+import org.apache.maven.scm.command.checkin.CheckInScmResult;
+import org.apache.maven.scm.manager.NoSuchScmProviderException;
+import org.apache.maven.scm.provider.ScmProvider;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.maven.scm.repository.ScmRepositoryException;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseFailureException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.apache.maven.shared.release.scm.ReleaseScmCommandException;
+import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
+import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
+import org.apache.maven.shared.release.util.ReleaseUtil;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Holds the basic concept of committing changes to the current working copy.
+ *
+ * @author Brett Porter
+ * @author Lars Corneliussen
+ */
+public abstract class AbstractScmCommitPhase
+ extends AbstractReleasePhase
+{
+ protected boolean beforeBranchOrTag = false;
+
+ protected boolean afterBranchOrTag = false;
+
+ /**
+ * Tool that gets a configured SCM repository from release configuration.
+ */
+ protected ScmRepositoryConfigurator scmRepositoryConfigurator;
+
+ /**
+ * The getter in the descriptor for the comment.
+ */
+ protected String descriptorCommentGetter;
+
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult relResult = new ReleaseResult();
+
+ validateConfiguration( releaseDescriptor );
+
+ runLogic( releaseDescriptor, releaseEnvironment, reactorProjects, relResult, false );
+
+ relResult.setResultCode( ReleaseResult.SUCCESS );
+
+ return relResult;
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ validateConfiguration( releaseDescriptor );
+
+ runLogic( releaseDescriptor, releaseEnvironment, reactorProjects, result, true );
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+ return result;
+ }
+
+ protected abstract void runLogic( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects, ReleaseResult result, boolean simulating )
+ throws ReleaseScmCommandException, ReleaseExecutionException, ReleaseScmRepositoryException;
+
+ protected void performCheckins( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects, String message )
+ throws ReleaseScmRepositoryException, ReleaseExecutionException, ReleaseScmCommandException
+ {
+
+ getLogger().info( "Checking in modified POMs..." );
+
+ ScmRepository repository;
+ ScmProvider provider;
+ try
+ {
+ repository = scmRepositoryConfigurator.getConfiguredRepository( releaseDescriptor,
+ releaseEnvironment.getSettings() );
+
+ repository.getProviderRepository().setPushChanges( releaseDescriptor.isPushChanges() );
+
+ repository.getProviderRepository().setWorkItem( releaseDescriptor.getWorkItem() );
+
+ provider = scmRepositoryConfigurator.getRepositoryProvider( repository );
+ }
+ catch ( ScmRepositoryException e )
+ {
+ throw new ReleaseScmRepositoryException( e.getMessage(), e.getValidationMessages() );
+ }
+ catch ( NoSuchScmProviderException e )
+ {
+ throw new ReleaseExecutionException( "Unable to configure SCM repository: " + e.getMessage(), e );
+ }
+
+ if ( releaseDescriptor.isCommitByProject() )
+ {
+ for ( MavenProject project : reactorProjects )
+ {
+ List pomFiles = createPomFiles( releaseDescriptor, project );
+ ScmFileSet fileSet = new ScmFileSet( project.getFile().getParentFile(), pomFiles );
+
+ checkin( provider, repository, fileSet, releaseDescriptor, message );
+ }
+ }
+ else
+ {
+ List pomFiles = createPomFiles( releaseDescriptor, reactorProjects );
+ ScmFileSet fileSet = new ScmFileSet( new File( releaseDescriptor.getWorkingDirectory() ), pomFiles );
+
+ checkin( provider, repository, fileSet, releaseDescriptor, message );
+ }
+ }
+
+ private void checkin( ScmProvider provider, ScmRepository repository, ScmFileSet fileSet,
+ ReleaseDescriptor releaseDescriptor, String message )
+ throws ReleaseExecutionException, ReleaseScmCommandException
+ {
+ CheckInScmResult result;
+ try
+ {
+ result = provider.checkIn( repository, fileSet, (ScmVersion) null, message );
+ }
+ catch ( ScmException e )
+ {
+ throw new ReleaseExecutionException( "An error is occurred in the checkin process: " + e.getMessage(), e );
+ }
+
+ if ( !result.isSuccess() )
+ {
+ throw new ReleaseScmCommandException( "Unable to commit files", result );
+ }
+ if ( releaseDescriptor.isRemoteTagging() )
+ {
+ releaseDescriptor.setScmReleasedPomRevision( result.getScmRevision() );
+ }
+ }
+
+ protected void simulateCheckins( ReleaseDescriptor releaseDescriptor, List reactorProjects,
+ ReleaseResult result, String message )
+ {
+ Collection pomFiles = createPomFiles( releaseDescriptor, reactorProjects );
+ logInfo( result, "Full run would be commit " + pomFiles.size() + " files with message: '" + message + "'" );
+ }
+
+ protected void validateConfiguration( ReleaseDescriptor releaseDescriptor )
+ throws ReleaseFailureException
+ {
+ if ( releaseDescriptor.getScmReleaseLabel() == null )
+ {
+ throw new ReleaseFailureException( "A release label is required for committing" );
+ }
+ }
+
+ protected String createMessage( List reactorProjects,
+ ReleaseDescriptor releaseDescriptor )
+ throws ReleaseExecutionException
+ {
+ String comment;
+ boolean branch = false;
+ if ( "getScmReleaseCommitComment".equals( descriptorCommentGetter ) )
+ {
+ comment = releaseDescriptor.getScmReleaseCommitComment();
+ }
+ else if ( "getScmDevelopmentCommitComment".equals( descriptorCommentGetter ) )
+ {
+ comment = releaseDescriptor.getScmDevelopmentCommitComment();
+ }
+ else if ( "getScmBranchCommitComment".equals( descriptorCommentGetter ) )
+ {
+ comment = releaseDescriptor.getScmBranchCommitComment();
+ branch = true;
+ }
+ else if ( "getScmRollbackCommitComment".equals( descriptorCommentGetter ) )
+ {
+ comment = releaseDescriptor.getScmRollbackCommitComment();
+ }
+ else
+ {
+ throw new ReleaseExecutionException( "Invalid configuration in components-fragment.xml" );
+ }
+
+ MavenProject project = ReleaseUtil.getRootProject( reactorProjects );
+ comment = comment.replace( "@{prefix}", releaseDescriptor.getScmCommentPrefix().trim() );
+ comment = comment.replace( "@{groupId}", project.getGroupId() );
+ comment = comment.replace( "@{artifactId}", project.getArtifactId() );
+ if ( branch )
+ {
+ comment = comment.replace( "@{branchName}", releaseDescriptor.getScmReleaseLabel() );
+ }
+ else
+ {
+ comment = comment.replace( "@{releaseLabel}", releaseDescriptor.getScmReleaseLabel() );
+ }
+ return comment;
+ }
+
+ protected static List createPomFiles( ReleaseDescriptor releaseDescriptor, MavenProject project )
+ {
+ List pomFiles = new ArrayList<>();
+
+ pomFiles.add( ReleaseUtil.getStandardPom( project ) );
+
+ if ( releaseDescriptor.isGenerateReleasePoms() && !releaseDescriptor.isSuppressCommitBeforeTagOrBranch() )
+ {
+ pomFiles.add( ReleaseUtil.getReleasePom( project ) );
+ }
+
+ return pomFiles;
+ }
+
+ protected static List createPomFiles( ReleaseDescriptor releaseDescriptor,
+ List reactorProjects )
+ {
+ List pomFiles = new ArrayList<>();
+ for ( MavenProject project : reactorProjects )
+ {
+ pomFiles.addAll( createPomFiles( releaseDescriptor, project ) );
+ }
+ return pomFiles;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/CheckCompletedPreparePhasesPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/CheckCompletedPreparePhasesPhase.java
new file mode 100644
index 000000000..5f78c76c4
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/CheckCompletedPreparePhasesPhase.java
@@ -0,0 +1,86 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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.util.List;
+
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseFailureException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.codehaus.plexus.component.annotations.Component;
+
+/**
+ * @author Emmanuel Venisse
+ * @version $Id$
+ */
+@Component( role = ReleasePhase.class, hint = "verify-completed-prepare-phases" )
+public class CheckCompletedPreparePhasesPhase
+ extends AbstractReleasePhase
+{
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor,
+ ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ // if we stopped mid-way through preparation - don't perform
+ if ( releaseDescriptor.getCompletedPhase() != null
+ && !"end-release".equals( releaseDescriptor.getCompletedPhase() ) )
+ {
+ String message = "Cannot perform release - the preparation step was stopped mid-way. Please re-run "
+ + "release:prepare to continue, or perform the release from an SCM tag.";
+
+ result.setResultCode( ReleaseResult.ERROR );
+
+ logError( result, message );
+
+ throw new ReleaseFailureException( message );
+ }
+
+ if ( releaseDescriptor.getScmSourceUrl() == null )
+ {
+ String message = "No SCM URL was provided to perform the release from";
+
+ result.setResultCode( ReleaseResult.ERROR );
+
+ logError( result, message );
+
+ throw new ReleaseFailureException( message );
+ }
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor,
+ ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ return execute( releaseDescriptor, releaseEnvironment, reactorProjects );
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/CheckDependencySnapshotsPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/CheckDependencySnapshotsPhase.java
new file mode 100644
index 000000000..0dc4c817b
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/CheckDependencySnapshotsPhase.java
@@ -0,0 +1,535 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.ArtifactUtils;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseFailureException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.apache.maven.shared.release.versions.DefaultVersionInfo;
+import org.apache.maven.shared.release.versions.VersionInfo;
+import org.apache.maven.shared.release.versions.VersionParseException;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.components.interactivity.Prompter;
+import org.codehaus.plexus.components.interactivity.PrompterException;
+
+/**
+ * Check the dependencies of all projects being released to see if there are any unreleased snapshots.
+ *
+ * @author Brett Porter
+ */
+ // TODO plugins with no version will be resolved to RELEASE which is not a snapshot, but remains unresolved to this point. This is a potential hole in the check, and should be revisited after the release pom writing is done and resolving versions to verify whether it is.
+ // TODO plugins injected by the lifecycle are not tested here. They will be injected with a RELEASE version so are covered under the above point.
+@Component( role = ReleasePhase.class, hint = "check-dependency-snapshots" )
+public class CheckDependencySnapshotsPhase
+ extends AbstractReleasePhase
+{
+ public static final String RESOLVE_SNAPSHOT_MESSAGE = "There are still some remaining snapshot dependencies.\n";
+
+ public static final String RESOLVE_SNAPSHOT_PROMPT = "Do you want to resolve them now?";
+
+ public static final String RESOLVE_SNAPSHOT_TYPE_MESSAGE = "Dependency type to resolve,";
+
+ public static final String RESOLVE_SNAPSHOT_TYPE_PROMPT =
+ "specify the selection number ( 0:All 1:Project Dependencies 2:Plugins 3:Reports 4:Extensions ):";
+
+ /**
+ * Component used to prompt for input.
+ */
+ @Requirement
+ private Prompter prompter;
+
+ // Be aware of the difference between usedSnapshots and specifiedSnapshots:
+ // UsedSnapshots end up on the classpath.
+ // SpecifiedSnapshots are defined anywhere in the pom.
+ // We'll probably need to introduce specifiedSnapshots as well.
+ // @TODO MRELEASE-378: verify custom dependencies in plugins. Be aware of deprecated/removed Components in M3, such as PluginCollector
+ // @TODO MRELEASE-763: verify all dependencies in inactive profiles
+
+ // Don't prompt for every project in reactor, remember state of questions
+ private String resolveSnapshot;
+
+ private String resolveSnapshotType;
+
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ if ( !releaseDescriptor.isAllowTimestampedSnapshots() )
+ {
+ logInfo( result, "Checking dependencies and plugins for snapshots ..." );
+
+ for ( MavenProject project : reactorProjects )
+ {
+ checkProject( project, releaseDescriptor );
+ }
+ }
+ else
+ {
+ logInfo( result, "Ignoring SNAPSHOT depenedencies and plugins ..." );
+ }
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ private void checkProject( MavenProject project, ReleaseDescriptor releaseDescriptor )
+ throws ReleaseFailureException, ReleaseExecutionException
+ {
+ Map artifactMap = ArtifactUtils.artifactMapByVersionlessId( project.getArtifacts() );
+
+ Set usedSnapshotDependencies = new HashSet<>();
+
+ if ( project.getParentArtifact() != null )
+ {
+ if ( checkArtifact( project.getParentArtifact(), artifactMap, releaseDescriptor ) )
+ {
+ usedSnapshotDependencies.add( project.getParentArtifact() );
+ }
+ }
+
+ Set dependencyArtifacts = project.getDependencyArtifacts();
+ usedSnapshotDependencies.addAll( checkDependencies( releaseDescriptor, artifactMap, dependencyArtifacts ) );
+
+ //@todo check dependencyManagement
+
+ Set pluginArtifacts = project.getPluginArtifacts();
+ Set usedSnapshotPlugins = checkPlugins( releaseDescriptor, artifactMap, pluginArtifacts );
+
+ //@todo check pluginManagement
+
+ Set reportArtifacts = project.getReportArtifacts();
+ Set usedSnapshotReports = checkReports( releaseDescriptor, artifactMap, reportArtifacts );
+
+ Set extensionArtifacts = project.getExtensionArtifacts();
+ Set usedSnapshotExtensions = checkExtensions( releaseDescriptor, artifactMap, extensionArtifacts );
+
+ //@todo check profiles
+
+ if ( !usedSnapshotDependencies.isEmpty() || !usedSnapshotReports.isEmpty()
+ || !usedSnapshotExtensions.isEmpty() || !usedSnapshotPlugins.isEmpty() )
+ {
+ if ( releaseDescriptor.isInteractive() || null != releaseDescriptor.getAutoResolveSnapshots() )
+ {
+ resolveSnapshots( usedSnapshotDependencies, usedSnapshotReports, usedSnapshotExtensions,
+ usedSnapshotPlugins, releaseDescriptor );
+ }
+
+ if ( !usedSnapshotDependencies.isEmpty() || !usedSnapshotReports.isEmpty()
+ || !usedSnapshotExtensions.isEmpty() || !usedSnapshotPlugins.isEmpty() )
+ {
+ StringBuilder message = new StringBuilder();
+
+ printSnapshotDependencies( usedSnapshotDependencies, message );
+ printSnapshotDependencies( usedSnapshotReports, message );
+ printSnapshotDependencies( usedSnapshotExtensions, message );
+ printSnapshotDependencies( usedSnapshotPlugins, message );
+ message.append( "in project '" + project.getName() + "' (" + project.getId() + ")" );
+
+ throw new ReleaseFailureException(
+ "Can't release project due to non released dependencies :\n" + message );
+ }
+ }
+ }
+
+ private Set checkPlugins( ReleaseDescriptor releaseDescriptor,
+ Map artifactMap, Set pluginArtifacts )
+ throws ReleaseExecutionException
+ {
+ Set usedSnapshotPlugins = new HashSet<>();
+ for ( Artifact artifact : pluginArtifacts )
+ {
+ if ( checkArtifact( artifact, artifactMap, releaseDescriptor ) )
+ {
+ boolean addToFailures;
+
+ if ( "org.apache.maven.plugins".equals( artifact.getGroupId() ) && "maven-release-plugin".equals(
+ artifact.getArtifactId() ) )
+ {
+ // It's a snapshot of the release plugin. Maybe just testing - ask
+ // By default, we fail as for any other plugin
+ if ( releaseDescriptor.isSnapshotReleasePluginAllowed() )
+ {
+ addToFailures = false;
+ }
+ else if ( releaseDescriptor.isInteractive() )
+ {
+ try
+ {
+ String result;
+ if ( !releaseDescriptor.isSnapshotReleasePluginAllowed() )
+ {
+ prompter.showMessage( "This project relies on a SNAPSHOT of the release plugin. "
+ + "This may be necessary during testing.\n" );
+ result = prompter.prompt( "Do you want to continue with the release?",
+ Arrays.asList( "yes", "no" ), "no" );
+ }
+ else
+ {
+ result = "yes";
+ }
+
+ if ( result.toLowerCase( Locale.ENGLISH ).startsWith( "y" ) )
+ {
+ addToFailures = false;
+ }
+ else
+ {
+ addToFailures = true;
+ }
+ }
+ catch ( PrompterException e )
+ {
+ throw new ReleaseExecutionException( e.getMessage(), e );
+ }
+ }
+ else
+ {
+ addToFailures = true;
+ }
+ }
+ else
+ {
+ addToFailures = true;
+ }
+
+ if ( addToFailures )
+ {
+ usedSnapshotPlugins.add( artifact );
+ }
+ }
+ }
+ return usedSnapshotPlugins;
+ }
+
+ private Set checkDependencies( ReleaseDescriptor releaseDescriptor,
+ Map artifactMap,
+ Set dependencyArtifacts )
+ {
+ Set usedSnapshotDependencies = new HashSet<>();
+ for ( Artifact artifact : dependencyArtifacts )
+ {
+ if ( checkArtifact( artifact, artifactMap, releaseDescriptor ) )
+ {
+ usedSnapshotDependencies.add( getArtifactFromMap( artifact, artifactMap ) );
+ }
+ }
+ return usedSnapshotDependencies;
+ }
+
+ private Set checkReports( ReleaseDescriptor releaseDescriptor,
+ Map artifactMap, Set reportArtifacts )
+ {
+ Set usedSnapshotReports = new HashSet<>();
+ for ( Artifact artifact : reportArtifacts )
+ {
+ if ( checkArtifact( artifact, artifactMap, releaseDescriptor ) )
+ {
+ //snapshotDependencies.add( artifact );
+ usedSnapshotReports.add( artifact );
+ }
+ }
+ return usedSnapshotReports;
+ }
+
+ private Set checkExtensions( ReleaseDescriptor releaseDescriptor,
+ Map artifactMap, Set extensionArtifacts )
+ {
+ Set usedSnapshotExtensions = new HashSet<>();
+ for ( Artifact artifact : extensionArtifacts )
+ {
+ if ( checkArtifact( artifact, artifactMap, releaseDescriptor ) )
+ {
+ usedSnapshotExtensions.add( artifact );
+ }
+ }
+ return usedSnapshotExtensions;
+ }
+
+ private static boolean checkArtifact( Artifact artifact,
+ Map artifactMapByVersionlessId,
+ ReleaseDescriptor releaseDescriptor )
+ {
+ Artifact checkArtifact = getArtifactFromMap( artifact, artifactMapByVersionlessId );
+
+ return checkArtifact( checkArtifact, releaseDescriptor );
+ }
+
+ private static Artifact getArtifactFromMap( Artifact artifact, Map artifactMapByVersionlessId )
+ {
+ String versionlessId = ArtifactUtils.versionlessKey( artifact );
+ Artifact checkArtifact = artifactMapByVersionlessId.get( versionlessId );
+
+ if ( checkArtifact == null )
+ {
+ checkArtifact = artifact;
+ }
+ return checkArtifact;
+ }
+
+ private static boolean checkArtifact( Artifact artifact, ReleaseDescriptor releaseDescriptor )
+ {
+ String versionlessKey = ArtifactUtils.versionlessKey( artifact.getGroupId(), artifact.getArtifactId() );
+ String releaseDescriptorResolvedVersion = releaseDescriptor.getDependencyReleaseVersion( versionlessKey );
+
+ boolean releaseDescriptorResolvedVersionIsSnapshot = releaseDescriptorResolvedVersion == null
+ || releaseDescriptorResolvedVersion.contains( Artifact.SNAPSHOT_VERSION );
+
+ // We are only looking at dependencies external to the project - ignore anything found in the reactor as
+ // it's version will be updated
+ boolean bannedVersion = artifact.isSnapshot()
+ && !artifact.getBaseVersion().equals( releaseDescriptor.getProjectOriginalVersion( versionlessKey ) )
+ && releaseDescriptorResolvedVersionIsSnapshot;
+
+ // If we have a snapshot but allowTimestampedSnapshots is true, accept the artifact if the version
+ // indicates that it is a timestamped snapshot.
+ if ( bannedVersion && releaseDescriptor.isAllowTimestampedSnapshots() )
+ {
+ bannedVersion = artifact.getVersion().indexOf( Artifact.SNAPSHOT_VERSION ) >= 0;
+ }
+
+ return bannedVersion;
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ // It makes no modifications, so simulate is the same as execute
+ return execute( releaseDescriptor, releaseEnvironment, reactorProjects );
+ }
+
+ public void setPrompter( Prompter prompter )
+ {
+ this.prompter = prompter;
+ }
+
+ private StringBuilder printSnapshotDependencies( Set snapshotsSet, StringBuilder message )
+ {
+ List snapshotsList = new ArrayList<>( snapshotsSet );
+
+ Collections.sort( snapshotsList );
+
+ for ( Artifact artifact : snapshotsList )
+ {
+ message.append( " " );
+
+ message.append( artifact );
+
+ message.append( "\n" );
+ }
+
+ return message;
+ }
+
+ private void resolveSnapshots( Set projectDependencies, Set reportDependencies,
+ Set extensionDependencies, Set pluginDependencies,
+ ReleaseDescriptor releaseDescriptor )
+ throws ReleaseExecutionException
+ {
+ try
+ {
+ String autoResolveSnapshots = releaseDescriptor.getAutoResolveSnapshots();
+ if ( resolveSnapshot == null )
+ {
+ prompter.showMessage( RESOLVE_SNAPSHOT_MESSAGE );
+ if ( autoResolveSnapshots != null )
+ {
+ resolveSnapshot = "yes";
+ prompter.showMessage( RESOLVE_SNAPSHOT_PROMPT + " " + resolveSnapshot );
+ }
+ else
+ {
+ resolveSnapshot = prompter.prompt( RESOLVE_SNAPSHOT_PROMPT, Arrays.asList( "yes", "no" ), "no" );
+ }
+ }
+
+ if ( resolveSnapshot.toLowerCase( Locale.ENGLISH ).startsWith( "y" ) )
+ {
+ if ( resolveSnapshotType == null )
+ {
+ prompter.showMessage( RESOLVE_SNAPSHOT_TYPE_MESSAGE );
+ int defaultAnswer = -1;
+ if ( autoResolveSnapshots != null )
+ {
+ if ( "all".equalsIgnoreCase( autoResolveSnapshots ) )
+ {
+ defaultAnswer = 0;
+ }
+ else if ( "dependencies".equalsIgnoreCase( autoResolveSnapshots ) )
+ {
+ defaultAnswer = 1;
+ }
+ else if ( "plugins".equalsIgnoreCase( autoResolveSnapshots ) )
+ {
+ defaultAnswer = 2;
+ }
+ else if ( "reports".equalsIgnoreCase( autoResolveSnapshots ) )
+ {
+ defaultAnswer = 3;
+ }
+ else if ( "extensions".equalsIgnoreCase( autoResolveSnapshots ) )
+ {
+ defaultAnswer = 4;
+ }
+ else
+ {
+ try
+ {
+ defaultAnswer = Integer.parseInt( autoResolveSnapshots );
+ }
+ catch ( NumberFormatException e )
+ {
+ throw new ReleaseExecutionException( e.getMessage(), e );
+ }
+ }
+ }
+ if ( defaultAnswer >= 0 && defaultAnswer <= 4 )
+ {
+ prompter.showMessage( RESOLVE_SNAPSHOT_TYPE_PROMPT + " " + autoResolveSnapshots );
+ resolveSnapshotType = Integer.toString( defaultAnswer );
+ }
+ else
+ {
+ resolveSnapshotType =
+ prompter.prompt( RESOLVE_SNAPSHOT_TYPE_PROMPT, Arrays.asList( "0", "1", "2", "3" ), "1" );
+ }
+ }
+
+ switch ( Integer.parseInt( resolveSnapshotType.toLowerCase( Locale.ENGLISH ) ) )
+ {
+ // all
+ case 0:
+ processSnapshot( projectDependencies, releaseDescriptor, autoResolveSnapshots );
+ processSnapshot( pluginDependencies, releaseDescriptor, autoResolveSnapshots );
+ processSnapshot( reportDependencies, releaseDescriptor, autoResolveSnapshots );
+ processSnapshot( extensionDependencies, releaseDescriptor, autoResolveSnapshots );
+ break;
+
+ // project dependencies
+ case 1:
+ processSnapshot( projectDependencies, releaseDescriptor, autoResolveSnapshots );
+ break;
+
+ // plugins
+ case 2:
+ processSnapshot( pluginDependencies, releaseDescriptor, autoResolveSnapshots );
+ break;
+
+ // reports
+ case 3:
+ processSnapshot( reportDependencies, releaseDescriptor, autoResolveSnapshots );
+ break;
+
+ // extensions
+ case 4:
+ processSnapshot( extensionDependencies, releaseDescriptor, autoResolveSnapshots );
+ break;
+
+ default:
+ }
+ }
+ }
+ catch ( PrompterException | VersionParseException e )
+ {
+ throw new ReleaseExecutionException( e.getMessage(), e );
+ }
+ }
+
+ private void processSnapshot( Set snapshotSet, ReleaseDescriptor releaseDescriptor,
+ String autoResolveSnapshots )
+ throws PrompterException, VersionParseException
+ {
+ Iterator iterator = snapshotSet.iterator();
+
+ while ( iterator.hasNext() )
+ {
+ Artifact currentArtifact = iterator.next();
+ String versionlessKey = ArtifactUtils.versionlessKey( currentArtifact );
+
+ VersionInfo versionInfo = new DefaultVersionInfo( currentArtifact.getBaseVersion() );
+ releaseDescriptor.addDependencyOriginalVersion( versionlessKey, versionInfo.toString() );
+
+ prompter.showMessage(
+ "Dependency '" + versionlessKey + "' is a snapshot (" + currentArtifact.getVersion() + ")\n" );
+ String message = "Which release version should it be set to?";
+ String result;
+ if ( null != autoResolveSnapshots )
+ {
+ result = versionInfo.getReleaseVersionString();
+ prompter.showMessage( message + " " + result );
+ }
+ else
+ {
+ result = prompter.prompt( message, versionInfo.getReleaseVersionString() );
+ }
+
+ releaseDescriptor.addDependencyReleaseVersion( versionlessKey, result );
+
+ iterator.remove();
+
+ // by default, keep the same version for the dependency after release, unless it was previously newer
+ // the user may opt to type in something different
+ VersionInfo nextVersionInfo = new DefaultVersionInfo( result );
+
+ String nextVersion;
+ if ( nextVersionInfo.compareTo( versionInfo ) > 0 )
+ {
+ nextVersion = nextVersionInfo.toString();
+ }
+ else
+ {
+ nextVersion = versionInfo.toString();
+ }
+
+ message = "What version should the dependency be reset to for development?";
+ if ( null != autoResolveSnapshots )
+ {
+ result = nextVersion;
+ prompter.showMessage( message + " " + result );
+ }
+ else
+ {
+ result = prompter.prompt( message, nextVersion );
+ }
+
+ releaseDescriptor.addDependencyDevelopmentVersion( versionlessKey, result );
+ }
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/CheckPomPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/CheckPomPhase.java
new file mode 100644
index 000000000..128721fd8
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/CheckPomPhase.java
@@ -0,0 +1,120 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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 org.apache.maven.artifact.ArtifactUtils;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.scm.manager.NoSuchScmProviderException;
+import org.apache.maven.scm.repository.ScmRepositoryException;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseFailureException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
+import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
+import org.codehaus.plexus.util.StringUtils;
+
+import java.util.List;
+
+/**
+ * Phase that checks the validity of the POM before release.
+ *
+ * @author Brett Porter
+ */
+public class CheckPomPhase
+ extends AbstractReleasePhase
+{
+
+ /**
+ * @since 2.4
+ */
+ private boolean scmRequired = true;
+
+ /**
+ * @since 2.5.2
+ */
+ private boolean snapshotsRequired = true;
+
+ private ScmRepositoryConfigurator scmRepositoryConfigurator;
+
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ // Currently, we don't deal with multiple SCM locations in a multiproject
+ if ( scmRequired )
+ {
+ if ( StringUtils.isEmpty( releaseDescriptor.getScmSourceUrl() ) )
+ {
+ throw new ReleaseFailureException(
+ "Missing required setting: scm connection or developerConnection must be specified." );
+ }
+
+ try
+ {
+ scmRepositoryConfigurator.getConfiguredRepository( releaseDescriptor,
+ releaseEnvironment.getSettings() );
+ }
+ catch ( ScmRepositoryException e )
+ {
+ throw new ReleaseScmRepositoryException( e.getMessage(), e.getValidationMessages() );
+ }
+ catch ( NoSuchScmProviderException e )
+ {
+ throw new ReleaseFailureException(
+ "The provider given in the SCM URL could not be found: " + e.getMessage() );
+ }
+ }
+
+ boolean containsSnapshotProjects = false;
+
+ for ( MavenProject project : reactorProjects )
+ {
+ if ( ArtifactUtils.isSnapshot( project.getVersion() ) )
+ {
+ containsSnapshotProjects = true;
+
+ break;
+ }
+ }
+
+ if ( snapshotsRequired && !containsSnapshotProjects && !releaseDescriptor.isBranchCreation() )
+ {
+ throw new ReleaseFailureException( "You don't have a SNAPSHOT project in the reactor projects list." );
+ }
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ // It makes no modifications, so simulate is the same as execute
+ return execute( releaseDescriptor, releaseEnvironment, reactorProjects );
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/CheckoutProjectFromScm.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/CheckoutProjectFromScm.java
new file mode 100644
index 000000000..17bc3f467
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/CheckoutProjectFromScm.java
@@ -0,0 +1,275 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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.File;
+import java.io.IOException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.scm.CommandParameter;
+import org.apache.maven.scm.CommandParameters;
+import org.apache.maven.scm.ScmException;
+import org.apache.maven.scm.ScmFileSet;
+import org.apache.maven.scm.ScmTag;
+import org.apache.maven.scm.command.checkout.CheckOutScmResult;
+import org.apache.maven.scm.manager.NoSuchScmProviderException;
+import org.apache.maven.scm.provider.ScmProvider;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.maven.scm.repository.ScmRepositoryException;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseFailureException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.apache.maven.shared.release.scm.ReleaseScmCommandException;
+import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
+import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
+import org.apache.maven.shared.release.util.ReleaseUtil;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.util.FileUtils;
+import org.codehaus.plexus.util.StringUtils;
+
+/**
+ * @author Emmanuel Venisse
+ * @version $Id$
+ */
+@Component( role = ReleasePhase.class, hint = "checkout-project-from-scm" )
+public class CheckoutProjectFromScm
+ extends AbstractReleasePhase
+{
+ /**
+ * Tool that gets a configured SCM repository from release configuration.
+ */
+ @Requirement
+ private ScmRepositoryConfigurator scmRepositoryConfigurator;
+
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult releaseResult = null;
+
+ if ( releaseDescriptor.isLocalCheckout() )
+ {
+ // in the release phase we have to change the checkout URL
+ // to do a local checkout instead of going over the network.
+
+ // the first step is a bit tricky, we need to know which provider! like e.g. "scm:jgit:http://"
+ // the offset of 4 is because 'scm:' has 4 characters...
+ String providerPart = releaseDescriptor.getScmSourceUrl()
+ .substring( 0, releaseDescriptor.getScmSourceUrl().indexOf( ':', 4 ) );
+
+ String scmPath = releaseDescriptor.getWorkingDirectory();
+
+ // now we iteratively try to checkout.
+ // if the local checkout fails, then we might be in a subdirectory
+ // and need to walk a few directories up.
+ do
+ {
+ try
+ {
+ if ( scmPath.startsWith( "/" ) )
+ {
+ // cut off the first '/'
+ scmPath = scmPath.substring( 1 );
+ }
+
+ String scmUrl = providerPart + ":file:///" + scmPath;
+ releaseDescriptor.setScmSourceUrl( scmUrl );
+ getLogger().info( "Performing a LOCAL checkout from " + releaseDescriptor.getScmSourceUrl() );
+
+ releaseResult = performCheckout( releaseDescriptor, releaseEnvironment, reactorProjects );
+ }
+ catch ( ScmException scmEx )
+ {
+ // the checkout from _this_ directory failed
+ releaseResult = null;
+ }
+
+ if ( releaseResult == null || releaseResult.getResultCode() == ReleaseResult.ERROR )
+ {
+ // this means that there is no SCM repo in this directory
+ // thus we try to step one directory up
+ releaseResult = null;
+
+ // remove last sub-directory path
+ int lastSlashPos = scmPath.lastIndexOf( File.separator );
+ if ( lastSlashPos > 0 )
+ {
+ scmPath = scmPath.substring( 0, lastSlashPos );
+ }
+ else
+ {
+ throw new ReleaseExecutionException( "could not perform a local checkout" );
+ }
+ }
+ }
+ while ( releaseResult == null );
+ }
+ else
+ {
+ // when there is no localCheckout, then we just do a standard SCM checkout.
+ try
+ {
+ releaseResult = performCheckout( releaseDescriptor, releaseEnvironment, reactorProjects );
+ }
+ catch ( ScmException e )
+ {
+ releaseResult = new ReleaseResult();
+ releaseResult.setResultCode( ReleaseResult.ERROR );
+ logError( releaseResult, e.getMessage() );
+
+ throw new ReleaseExecutionException( "An error is occurred in the checkout process: "
+ + e.getMessage(), e );
+ }
+
+ }
+
+ return releaseResult;
+ }
+
+
+ private ReleaseResult performCheckout( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException, ScmException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ logInfo( result, "Checking out the project to perform the release ..." );
+
+ ScmRepository repository;
+ ScmProvider provider;
+
+ try
+ {
+ repository = scmRepositoryConfigurator.getConfiguredRepository( releaseDescriptor,
+ releaseEnvironment.getSettings() );
+
+ provider = scmRepositoryConfigurator.getRepositoryProvider( repository );
+ }
+ catch ( ScmRepositoryException e )
+ {
+ result.setResultCode( ReleaseResult.ERROR );
+ logError( result, e.getMessage() );
+
+ throw new ReleaseScmRepositoryException( e.getMessage(), e.getValidationMessages() );
+ }
+ catch ( NoSuchScmProviderException e )
+ {
+ result.setResultCode( ReleaseResult.ERROR );
+ logError( result, e.getMessage() );
+
+ throw new ReleaseExecutionException( "Unable to configure SCM repository: " + e.getMessage(), e );
+ }
+
+ MavenProject rootProject = ReleaseUtil.getRootProject( reactorProjects );
+
+ // TODO: sanity check that it is not . or .. or lower
+ File checkoutDirectory =
+ FileUtils.resolveFile( rootProject.getBasedir(), releaseDescriptor.getCheckoutDirectory() );
+
+ if ( checkoutDirectory.exists() )
+ {
+ try
+ {
+ FileUtils.deleteDirectory( checkoutDirectory );
+ }
+ catch ( IOException e )
+ {
+ result.setResultCode( ReleaseResult.ERROR );
+ logError( result, e.getMessage() );
+
+ throw new ReleaseExecutionException( "Unable to remove old checkout directory: " + e.getMessage(), e );
+ }
+ }
+
+ checkoutDirectory.mkdirs();
+
+ CommandParameters commandParameters = new CommandParameters();
+ commandParameters.setString( CommandParameter.SHALLOW, Boolean.TRUE.toString() );
+
+ CheckOutScmResult scmResult = provider.checkOut( repository, new ScmFileSet( checkoutDirectory ),
+ new ScmTag( releaseDescriptor.getScmReleaseLabel() ), commandParameters );
+
+ if ( releaseDescriptor.isLocalCheckout() && !scmResult.isSuccess() )
+ {
+ // this is not beautiful but needed to indicate that the execute() method
+ // should continue in the parent directory
+ return null;
+ }
+
+ String scmRelativePathProjectDirectory = scmResult.getRelativePathProjectDirectory();
+ if ( StringUtils.isEmpty( scmRelativePathProjectDirectory ) )
+ {
+ Path workingDirectory = Paths.get( releaseDescriptor.getWorkingDirectory() );
+
+ Path rootProjectBasedir;
+ try
+ {
+ rootProjectBasedir = rootProject.getBasedir().toPath().toRealPath( LinkOption.NOFOLLOW_LINKS );
+ }
+ catch ( IOException e )
+ {
+ throw new ReleaseExecutionException( e.getMessage(), e );
+ }
+
+ scmRelativePathProjectDirectory = workingDirectory.relativize( rootProjectBasedir ).toString();
+ }
+ releaseDescriptor.setScmRelativePathProjectDirectory( scmRelativePathProjectDirectory );
+
+ if ( !scmResult.isSuccess() )
+ {
+ result.setResultCode( ReleaseResult.ERROR );
+ logError( result, scmResult.getProviderMessage() );
+
+ throw new ReleaseScmCommandException( "Unable to checkout from SCM", scmResult );
+ }
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ if ( releaseDescriptor.isLocalCheckout() )
+ {
+ logInfo( result, "This would be a LOCAL check out to perform the release ..." );
+ }
+ else
+ {
+ logInfo( result, "The project would be checked out to perform the release ..." );
+ }
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+ return result;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/CreateBackupPomsPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/CreateBackupPomsPhase.java
new file mode 100644
index 000000000..bf07fd629
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/CreateBackupPomsPhase.java
@@ -0,0 +1,100 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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.IOException;
+import java.util.List;
+
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseFailureException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.apache.maven.shared.release.util.ReleaseUtil;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.util.FileUtils;
+
+/**
+ * @author Edwin Punzalan
+ */
+@Component( role = ReleasePhase.class, hint = "create-backup-poms" )
+public class CreateBackupPomsPhase
+ extends AbstractBackupPomsPhase implements ResourceGenerator
+{
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ // remove previous backups, if any
+ clean( reactorProjects );
+
+ for ( MavenProject project : reactorProjects )
+ {
+ createPomBackup( project );
+ }
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ @Override
+ public ReleaseResult clean( List reactorProjects )
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ for ( MavenProject project : reactorProjects )
+ {
+ deletePomBackup( project );
+ }
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ return execute( releaseDescriptor, releaseEnvironment, reactorProjects );
+ }
+
+ private void createPomBackup( MavenProject project )
+ throws ReleaseExecutionException
+ {
+ // delete any existing backup first
+ deletePomBackup( project );
+
+ try
+ {
+ FileUtils.copyFile( ReleaseUtil.getStandardPom( project ), getPomBackup( project ) );
+ }
+ catch ( IOException e )
+ {
+ throw new ReleaseExecutionException( "Error creating backup POM: " + e.getMessage(), e );
+ }
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/EndReleasePhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/EndReleasePhase.java
new file mode 100644
index 000000000..123c0a35f
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/EndReleasePhase.java
@@ -0,0 +1,68 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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 org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseFailureException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.codehaus.plexus.component.annotations.Component;
+
+import java.util.List;
+
+/**
+ * Finalise release preparation so it can be flagged complete..
+ *
+ * @author Brett Porter
+ */
+@Component( role = ReleasePhase.class, hint = "end-release" )
+public class EndReleasePhase
+ extends AbstractReleasePhase
+{
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ logInfo( result, "Release preparation complete." );
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ logInfo( result, "Release preparation simulation complete." );
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/GenerateReleasePomsPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/GenerateReleasePomsPhase.java
new file mode 100644
index 000000000..7b6004f89
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/GenerateReleasePomsPhase.java
@@ -0,0 +1,700 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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.File;
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.ArtifactUtils;
+import org.apache.maven.model.Build;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Extension;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.Plugin;
+import org.apache.maven.model.Profile;
+import org.apache.maven.model.ReportPlugin;
+import org.apache.maven.model.Reporting;
+import org.apache.maven.model.Resource;
+import org.apache.maven.model.Scm;
+import org.apache.maven.model.building.DefaultModelBuildingRequest;
+import org.apache.maven.model.building.ModelBuildingRequest;
+import org.apache.maven.model.interpolation.ModelInterpolator;
+import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
+import org.apache.maven.model.superpom.SuperPomProvider;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.scm.ScmException;
+import org.apache.maven.scm.ScmFileSet;
+import org.apache.maven.scm.command.add.AddScmResult;
+import org.apache.maven.scm.provider.ScmProvider;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseFailureException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.apache.maven.shared.release.scm.ReleaseScmCommandException;
+import org.apache.maven.shared.release.scm.ScmTranslator;
+import org.apache.maven.shared.release.util.ReleaseUtil;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.util.WriterFactory;
+
+/**
+ * Generate release POMs.
+ *
+ * @author Brett Porter
+ * @author Mark Hobson
+ */
+@Component( role = ReleasePhase.class, hint = "generate-release-poms" )
+public class GenerateReleasePomsPhase
+ extends AbstractReleasePomsPhase implements ResourceGenerator
+{
+ private static final String FINALNAME_EXPRESSION = "${project.artifactId}-${project.version}";
+
+ @Requirement
+ private SuperPomProvider superPomProvider;
+
+ @Requirement
+ private ModelInterpolator modelInterpolator;
+
+ /**
+ * SCM URL translators mapped by provider name.
+ */
+ @Requirement( role = ScmTranslator.class )
+ private Map scmTranslators;
+
+ /*
+ * @see org.apache.maven.shared.release.phase.ReleasePhase#execute(org.apache.maven.shared.release.config.ReleaseDescriptor,
+ * org.apache.maven.settings.Settings, java.util.List)
+ */
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ return execute( releaseDescriptor, releaseEnvironment, reactorProjects, false );
+ }
+
+ private ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects, boolean simulate )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ if ( releaseDescriptor.isGenerateReleasePoms() )
+ {
+ logInfo( result, "Generating release POMs..." );
+
+ generateReleasePoms( releaseDescriptor, releaseEnvironment, reactorProjects, simulate, result );
+ }
+ else
+ {
+ logInfo( result, "Not generating release POMs" );
+ }
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ private void generateReleasePoms( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects, boolean simulate, ReleaseResult result )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ List releasePoms = new ArrayList<>();
+
+ for ( MavenProject project : reactorProjects )
+ {
+ logInfo( result, "Generating release POM for '" + project.getName() + "'..." );
+
+ releasePoms.add( generateReleasePom( project, releaseDescriptor, releaseEnvironment, reactorProjects,
+ simulate, result ) );
+ }
+
+ addReleasePomsToScm( releaseDescriptor, releaseEnvironment, reactorProjects, simulate, result, releasePoms );
+ }
+
+ private File generateReleasePom( MavenProject project, ReleaseDescriptor releaseDescriptor,
+ ReleaseEnvironment releaseEnvironment, List reactorProjects,
+ boolean simulate, ReleaseResult result )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ // create release pom
+
+ Model releasePom = createReleaseModel( project, releaseDescriptor, releaseEnvironment, reactorProjects,
+ result );
+
+ // write release pom to file
+
+ MavenXpp3Writer pomWriter = new MavenXpp3Writer();
+
+ File releasePomFile = ReleaseUtil.getReleasePom( project );
+
+ // MRELEASE-273 : A release pom can be null
+ if ( releasePomFile == null )
+ {
+ throw new ReleaseExecutionException( "Cannot generate release POM : pom file is null" );
+ }
+
+
+
+ try ( Writer fileWriter = WriterFactory.newXmlWriter( releasePomFile ) )
+ {
+ pomWriter.write( fileWriter, releasePom );
+ }
+ catch ( IOException exception )
+ {
+ throw new ReleaseExecutionException( "Cannot generate release POM", exception );
+ }
+
+ return releasePomFile;
+ }
+
+ private void addReleasePomsToScm( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects, boolean simulate, ReleaseResult result,
+ List releasePoms )
+ throws ReleaseFailureException, ReleaseExecutionException
+ {
+ if ( simulate )
+ {
+ logInfo( result, "Full run would be adding " + releasePoms );
+ }
+ else
+ {
+ ScmRepository scmRepository = getScmRepository( releaseDescriptor, releaseEnvironment );
+ ScmProvider scmProvider = getScmProvider( scmRepository );
+
+ MavenProject rootProject = ReleaseUtil.getRootProject( reactorProjects );
+ ScmFileSet scmFileSet = new ScmFileSet( rootProject.getFile().getParentFile(), releasePoms );
+
+ try
+ {
+ AddScmResult scmResult = scmProvider.add( scmRepository, scmFileSet );
+
+ if ( !scmResult.isSuccess() )
+ {
+ throw new ReleaseScmCommandException( "Cannot add release POM to SCM", scmResult );
+ }
+ }
+ catch ( ScmException exception )
+ {
+ throw new ReleaseExecutionException( "Cannot add release POM to SCM: " + exception.getMessage(),
+ exception );
+ }
+ }
+ }
+
+ private Model createReleaseModel( MavenProject project, ReleaseDescriptor releaseDescriptor,
+ ReleaseEnvironment releaseEnvironment, List reactorProjects,
+ ReleaseResult result )
+ throws ReleaseFailureException, ReleaseExecutionException
+ {
+ MavenProject releaseProject = project.clone();
+ Model releaseModel = releaseProject.getModel();
+
+ // the release POM should reflect bits of these which were injected at build time...
+ // we don't need these polluting the POM.
+ releaseModel.setParent( null );
+ releaseModel.setProfiles( Collections.emptyList() );
+ releaseModel.setDependencyManagement( null );
+ releaseProject.getBuild().setPluginManagement( null );
+
+ // update project version
+ String projectVersion = releaseModel.getVersion();
+ String releaseVersion =
+ getNextVersion( releaseDescriptor, project.getGroupId(), project.getArtifactId(), projectVersion );
+ releaseModel.setVersion( releaseVersion );
+
+ String originalFinalName = releaseModel.getBuild().getFinalName();
+ // update final name if implicit
+ if ( !FINALNAME_EXPRESSION.equals( originalFinalName ) )
+ {
+ originalFinalName = findOriginalFinalName( project );
+
+ if ( originalFinalName == null )
+ {
+ // as defined in super-pom
+ originalFinalName = FINALNAME_EXPRESSION;
+ }
+ }
+
+ // make finalName always explicit
+ String finalName = ReleaseUtil.interpolate( originalFinalName, releaseModel );
+
+ // still required?
+ if ( finalName.indexOf( Artifact.SNAPSHOT_VERSION ) != -1 )
+ {
+ throw new ReleaseFailureException( "Cannot reliably adjust the finalName of project: "
+ + releaseProject.getId() );
+ }
+ releaseModel.getBuild().setFinalName( finalName );
+
+
+ // update scm
+ Scm scm = releaseModel.getScm();
+
+ if ( scm != null )
+ {
+ ScmRepository scmRepository = getScmRepository( releaseDescriptor, releaseEnvironment );
+ ScmTranslator scmTranslator = getScmTranslator( scmRepository );
+
+ if ( scmTranslator != null )
+ {
+ releaseModel.setScm( createReleaseScm( releaseModel.getScm(), scmTranslator, releaseDescriptor ) );
+ }
+ else
+ {
+ String message = "No SCM translator found - skipping rewrite";
+
+ result.appendDebug( message );
+
+ getLogger().debug( message );
+ }
+ }
+
+ // rewrite dependencies
+ releaseModel.setDependencies( createReleaseDependencies( releaseDescriptor, releaseProject ) );
+
+ // rewrite plugins
+ releaseModel.getBuild().setPlugins( createReleasePlugins( releaseDescriptor, releaseProject ) );
+
+ // rewrite reports
+ releaseModel.getReporting().setPlugins( createReleaseReportPlugins( releaseDescriptor,
+ releaseProject ) );
+
+ // rewrite extensions
+ releaseModel.getBuild().setExtensions( createReleaseExtensions( releaseDescriptor,
+ releaseProject ) );
+
+ unalignFromBaseDirectory( releaseModel, project.getBasedir() );
+
+ return releaseModel;
+ }
+
+
+ private void unalignFromBaseDirectory( Model releaseModel, File basedir )
+ {
+ Model rawSuperModel = superPomProvider.getSuperModel( releaseModel.getModelVersion() );
+
+ ModelBuildingRequest buildingRequest = new DefaultModelBuildingRequest();
+ buildingRequest.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_STRICT );
+
+ // inject proper values used by project.build.finalName
+ Properties properties = new Properties();
+ properties.put( "project.version", releaseModel.getVersion() );
+ properties.put( "project.artifactId", releaseModel.getArtifactId() );
+ buildingRequest.setUserProperties( properties );
+
+ Model interpolatedSuperModel =
+ modelInterpolator.interpolateModel( rawSuperModel.clone(), basedir, buildingRequest, null );
+
+ Build currentBuild = releaseModel.getBuild();
+ Build interpolatedSuperBuild = interpolatedSuperModel.getBuild();
+ Build rawSuperBuild = rawSuperModel.getBuild();
+
+ currentBuild.setSourceDirectory( resolvePath( basedir.toPath(), currentBuild.getSourceDirectory(),
+ interpolatedSuperBuild.getSourceDirectory(),
+ rawSuperBuild.getSourceDirectory() ) );
+ currentBuild.setScriptSourceDirectory( resolvePath( basedir.toPath(), currentBuild.getScriptSourceDirectory(),
+ interpolatedSuperBuild.getScriptSourceDirectory(),
+ rawSuperBuild.getScriptSourceDirectory() ) );
+ currentBuild.setTestSourceDirectory( resolvePath( basedir.toPath(), currentBuild.getTestSourceDirectory(),
+ interpolatedSuperBuild.getTestSourceDirectory(),
+ rawSuperBuild.getTestSourceDirectory() ) );
+ currentBuild.setOutputDirectory( resolvePath( basedir.toPath(), currentBuild.getOutputDirectory(),
+ interpolatedSuperBuild.getOutputDirectory(),
+ rawSuperBuild.getOutputDirectory() ) );
+ currentBuild.setTestOutputDirectory( resolvePath( basedir.toPath(), currentBuild.getTestOutputDirectory(),
+ interpolatedSuperBuild.getTestOutputDirectory(),
+ rawSuperBuild.getTestOutputDirectory() ) );
+ currentBuild.setDirectory( resolvePath( basedir.toPath(), currentBuild.getDirectory(),
+ interpolatedSuperBuild.getDirectory(),
+ rawSuperBuild.getDirectory() ) );
+
+ for ( Resource currentResource : currentBuild.getResources() )
+ {
+ Map superResourceDirectories =
+ new LinkedHashMap<>( interpolatedSuperBuild.getResources().size() );
+ for ( int i = 0; i < interpolatedSuperBuild.getResources().size(); i++ )
+ {
+ superResourceDirectories.put( interpolatedSuperBuild.getResources().get( i ).getDirectory(),
+ rawSuperBuild.getResources().get( i ).getDirectory() );
+ }
+ currentResource.setDirectory( resolvePath( basedir.toPath(), currentResource.getDirectory(),
+ superResourceDirectories ) );
+ }
+
+ for ( Resource currentResource : currentBuild.getTestResources() )
+ {
+ Map superResourceDirectories =
+ new LinkedHashMap<>( interpolatedSuperBuild.getTestResources().size() );
+ for ( int i = 0; i < interpolatedSuperBuild.getTestResources().size(); i++ )
+ {
+ superResourceDirectories.put( interpolatedSuperBuild.getTestResources().get( i ).getDirectory(),
+ rawSuperBuild.getTestResources().get( i ).getDirectory() );
+ }
+ currentResource.setDirectory( resolvePath( basedir.toPath(), currentResource.getDirectory(),
+ superResourceDirectories ) );
+ }
+
+
+
+ releaseModel.getReporting().setOutputDirectory( resolvePath( basedir.toPath(),
+ releaseModel.getReporting().getOutputDirectory(),
+ interpolatedSuperModel.getReporting().getOutputDirectory(),
+ rawSuperModel.getReporting().getOutputDirectory() ) );
+ }
+
+ private String resolvePath( Path basedir, String current, String superInterpolated, String superRaw )
+ {
+ return basedir.resolve( current ).equals( basedir.resolve( superInterpolated ) ) ? superRaw : current;
+ }
+
+ private String resolvePath( Path basedir,
+ String current,
+ Map superValues )
+ {
+ for ( Map.Entry superValue : superValues.entrySet() )
+ {
+ if ( basedir.resolve( current ).equals( basedir.resolve( superValue.getKey() ) ) )
+ {
+ return superValue.getValue();
+ }
+ }
+ return current;
+ }
+
+ private String findOriginalFinalName( MavenProject project )
+ {
+ if ( project.getOriginalModel().getBuild() != null
+ && project.getOriginalModel().getBuild().getFinalName() != null )
+ {
+ return project.getOriginalModel().getBuild().getFinalName();
+ }
+ else if ( project.hasParent() )
+ {
+ return findOriginalFinalName( project.getParent() );
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ return execute( releaseDescriptor, releaseEnvironment, reactorProjects, true );
+ }
+
+ private String getNextVersion( ReleaseDescriptor releaseDescriptor, String groupId, String artifactId,
+ String version )
+ throws ReleaseFailureException
+ {
+ // TODO: share with RewritePomsForReleasePhase.rewriteVersion
+
+ String id = ArtifactUtils.versionlessKey( groupId, artifactId );
+
+ String nextVersion = releaseDescriptor.getProjectReleaseVersion( id );
+
+ if ( nextVersion == null )
+ {
+ throw new ReleaseFailureException( "Version for '" + id + "' was not mapped" );
+ }
+
+ return nextVersion;
+ }
+
+ private ScmTranslator getScmTranslator( ScmRepository scmRepository )
+ {
+ return scmTranslators.get( scmRepository.getProvider() );
+ }
+
+ private Scm createReleaseScm( Scm scm, ScmTranslator scmTranslator, ReleaseDescriptor releaseDescriptor )
+ {
+ // TODO: share with RewritePomsForReleasePhase.translateScm
+
+ String tag = releaseDescriptor.getScmReleaseLabel();
+ String tagBase = releaseDescriptor.getScmTagBase();
+
+ Scm releaseScm = new Scm();
+
+ if ( scm.getConnection() != null )
+ {
+ String value = scmTranslator.translateTagUrl( scm.getConnection(), tag, tagBase );
+ releaseScm.setConnection( value );
+ }
+
+ if ( scm.getDeveloperConnection() != null )
+ {
+ String value = scmTranslator.translateTagUrl( scm.getDeveloperConnection(), tag, tagBase );
+ releaseScm.setDeveloperConnection( value );
+ }
+
+ if ( scm.getUrl() != null )
+ {
+ String value = scmTranslator.translateTagUrl( scm.getUrl(), tag, tagBase );
+ releaseScm.setUrl( value );
+ }
+
+ if ( scm.getTag() != null )
+ {
+ String value = scmTranslator.resolveTag( scm.getTag() );
+ releaseScm.setTag( value );
+ }
+
+ return releaseScm;
+ }
+
+ private List createReleaseDependencies( ReleaseDescriptor releaseDescriptor,
+ MavenProject project )
+ throws ReleaseFailureException
+ {
+ Set artifacts = project.getArtifacts();
+
+ List releaseDependencies = null;
+
+ if ( artifacts != null )
+ {
+ // make dependency order deterministic for tests (related to MNG-1412)
+ List orderedArtifacts = new ArrayList<>();
+ orderedArtifacts.addAll( artifacts );
+ Collections.sort( orderedArtifacts );
+
+ releaseDependencies = new ArrayList<>();
+
+ for ( Artifact artifact : orderedArtifacts )
+ {
+ Dependency releaseDependency = new Dependency();
+
+ releaseDependency.setGroupId( artifact.getGroupId() );
+ releaseDependency.setArtifactId( artifact.getArtifactId() );
+
+ String version = getReleaseVersion( releaseDescriptor, artifact );
+
+ releaseDependency.setVersion( version );
+ releaseDependency.setType( artifact.getType() );
+ releaseDependency.setScope( artifact.getScope() );
+ releaseDependency.setClassifier( artifact.getClassifier() );
+
+ releaseDependencies.add( releaseDependency );
+ }
+ }
+
+ return releaseDependencies;
+ }
+
+ private String getReleaseVersion( ReleaseDescriptor releaseDescriptor,
+ Artifact artifact )
+ throws ReleaseFailureException
+ {
+ String key = ArtifactUtils.versionlessKey( artifact );
+
+ String originalVersion = releaseDescriptor.getProjectOriginalVersion( key );
+ String mappedVersion = releaseDescriptor.getProjectReleaseVersion( key );
+
+ String version = artifact.getVersion();
+
+ if ( version.equals( originalVersion ) )
+ {
+ if ( mappedVersion != null )
+ {
+ version = mappedVersion;
+ }
+ else
+ {
+ throw new ReleaseFailureException( "Version '" + version + "' for '" + key + "' was not mapped" );
+ }
+ }
+ else
+ {
+ if ( !ArtifactUtils.isSnapshot( version ) )
+ {
+ version = artifact.getBaseVersion();
+ }
+ }
+
+ return version;
+ }
+
+ private List createReleasePlugins( ReleaseDescriptor releaseDescriptor,
+ MavenProject project )
+ throws ReleaseFailureException
+ {
+ List releasePlugins = null;
+
+ // Use original - don't want the lifecycle introduced ones
+ Build build = project.getOriginalModel().getBuild();
+
+ if ( build != null )
+ {
+ List plugins = build.getPlugins();
+
+ if ( plugins != null )
+ {
+ Map artifactsById = project.getPluginArtifactMap();
+
+ releasePlugins = new ArrayList<>();
+
+ for ( Plugin plugin : plugins )
+ {
+ String id = ArtifactUtils.versionlessKey( plugin.getGroupId(), plugin.getArtifactId() );
+ Artifact artifact = artifactsById.get( id );
+ String version = getReleaseVersion( releaseDescriptor, artifact );
+
+ Plugin releasePlugin = new Plugin();
+ releasePlugin.setGroupId( plugin.getGroupId() );
+ releasePlugin.setArtifactId( plugin.getArtifactId() );
+ releasePlugin.setVersion( version );
+ if ( plugin.getExtensions() != null )
+ {
+ releasePlugin.setExtensions( plugin.isExtensions() );
+ }
+ releasePlugin.setExecutions( plugin.getExecutions() );
+ releasePlugin.setDependencies( plugin.getDependencies() );
+ releasePlugin.setGoals( plugin.getGoals() );
+ releasePlugin.setInherited( plugin.getInherited() );
+ releasePlugin.setConfiguration( plugin.getConfiguration() );
+
+ releasePlugins.add( releasePlugin );
+ }
+ }
+ }
+
+ return releasePlugins;
+ }
+
+ private List createReleaseReportPlugins( ReleaseDescriptor releaseDescriptor,
+ MavenProject project )
+ throws ReleaseFailureException
+ {
+ List releaseReportPlugins = null;
+
+ Reporting reporting = project.getModel().getReporting();
+
+ if ( reporting != null )
+ {
+ List reportPlugins = reporting.getPlugins();
+
+ if ( reportPlugins != null )
+ {
+ Map artifactsById = project.getReportArtifactMap();
+
+ releaseReportPlugins = new ArrayList<>();
+
+ for ( ReportPlugin reportPlugin : reportPlugins )
+ {
+ String id = ArtifactUtils.versionlessKey( reportPlugin.getGroupId(), reportPlugin.getArtifactId() );
+ Artifact artifact = artifactsById.get( id );
+ String version = getReleaseVersion( releaseDescriptor, artifact );
+
+ ReportPlugin releaseReportPlugin = new ReportPlugin();
+ releaseReportPlugin.setGroupId( reportPlugin.getGroupId() );
+ releaseReportPlugin.setArtifactId( reportPlugin.getArtifactId() );
+ releaseReportPlugin.setVersion( version );
+ releaseReportPlugin.setInherited( reportPlugin.getInherited() );
+ releaseReportPlugin.setConfiguration( reportPlugin.getConfiguration() );
+ releaseReportPlugin.setReportSets( reportPlugin.getReportSets() );
+
+ releaseReportPlugins.add( releaseReportPlugin );
+ }
+ }
+ }
+
+ return releaseReportPlugins;
+ }
+
+ private List createReleaseExtensions( ReleaseDescriptor releaseDescriptor,
+ MavenProject project )
+ throws ReleaseFailureException
+ {
+ List releaseExtensions = null;
+
+ // Use original - don't want the lifecycle introduced ones
+ Build build = project.getOriginalModel().getBuild();
+
+ if ( build != null )
+ {
+ List extensions = build.getExtensions();
+
+ if ( extensions != null )
+ {
+ releaseExtensions = new ArrayList<>();
+
+ for ( Extension extension : extensions )
+ {
+ String id = ArtifactUtils.versionlessKey( extension.getGroupId(), extension.getArtifactId() );
+ Artifact artifact = project.getExtensionArtifactMap().get( id );
+ String version = getReleaseVersion( releaseDescriptor, artifact );
+
+ Extension releaseExtension = new Extension();
+ releaseExtension.setGroupId( extension.getGroupId() );
+ releaseExtension.setArtifactId( extension.getArtifactId() );
+ releaseExtension.setVersion( version );
+
+ releaseExtensions.add( releaseExtension );
+ }
+ }
+ }
+
+ return releaseExtensions;
+ }
+
+ /*
+ * @see org.apache.maven.shared.release.phase.AbstractReleasePhase#clean(java.util.List)
+ */
+ @Override
+ public ReleaseResult clean( List reactorProjects )
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ for ( MavenProject project : reactorProjects )
+ {
+ File releasePom = ReleaseUtil.getReleasePom( project );
+
+ // MRELEASE-273 : A release pom can be null
+ if ( releasePom != null && releasePom.exists() )
+ {
+ logInfo( result, "Deleting release POM for '" + project.getName() + "'..." );
+
+ if ( !releasePom.delete() )
+ {
+ logWarn( result, "Cannot delete release POM: " + releasePom );
+ }
+ }
+ }
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/InputVariablesPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/InputVariablesPhase.java
new file mode 100644
index 000000000..c155e3c46
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/InputVariablesPhase.java
@@ -0,0 +1,286 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.maven.artifact.ArtifactUtils;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.scm.manager.NoSuchScmProviderException;
+import org.apache.maven.scm.provider.ScmProvider;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.maven.scm.repository.ScmRepositoryException;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.apache.maven.shared.release.policy.PolicyException;
+import org.apache.maven.shared.release.policy.naming.NamingPolicy;
+import org.apache.maven.shared.release.policy.naming.NamingPolicyRequest;
+import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
+import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
+import org.apache.maven.shared.release.util.ReleaseUtil;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.components.interactivity.Prompter;
+import org.codehaus.plexus.components.interactivity.PrompterException;
+import org.codehaus.plexus.interpolation.InterpolationException;
+import org.codehaus.plexus.interpolation.Interpolator;
+import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
+import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource;
+import org.codehaus.plexus.interpolation.RecursionInterceptor;
+import org.codehaus.plexus.interpolation.StringSearchInterpolator;
+import org.codehaus.plexus.util.StringUtils;
+
+/**
+ * Input any variables that were not yet configured.
+ *
+ * @author Brett Porter
+ */
+public class InputVariablesPhase
+ extends AbstractReleasePhase
+{
+ /**
+ * Component used to prompt for input.
+ */
+ @Requirement
+ private Prompter prompter;
+
+ /**
+ * Whether this is a branch or a tag operation.
+ */
+ private boolean branchOperation;
+
+ /**
+ * Tool that gets a configured SCM repository from release configuration.
+ */
+ @Requirement
+ private ScmRepositoryConfigurator scmRepositoryConfigurator;
+
+ /**
+ * Component used for custom or default naming policy
+ */
+ @Requirement
+ private Map namingPolicies;
+
+ /**
+ * The default naming policy to apply, if any
+ */
+ private String defaultNamingPolicy;
+
+ void setPrompter( Prompter prompter )
+ {
+ this.prompter = prompter;
+ }
+
+ boolean isBranchOperation()
+ {
+ return branchOperation;
+ }
+
+ protected ScmProvider getScmProvider( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment )
+ throws ReleaseScmRepositoryException, ReleaseExecutionException
+ {
+ try
+ {
+ ScmRepository repository =
+ scmRepositoryConfigurator.getConfiguredRepository( releaseDescriptor,
+ releaseEnvironment.getSettings() );
+
+ return scmRepositoryConfigurator.getRepositoryProvider( repository );
+ }
+ catch ( ScmRepositoryException e )
+ {
+ throw new ReleaseScmRepositoryException(
+ e.getMessage() + " for URL: " + releaseDescriptor.getScmSourceUrl(), e.getValidationMessages() );
+ }
+ catch ( NoSuchScmProviderException e )
+ {
+ throw new ReleaseExecutionException( "Unable to configure SCM repository: " + e.getMessage(), e );
+ }
+ }
+
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ // get the root project
+ MavenProject project = ReleaseUtil.getRootProject( reactorProjects );
+
+ String tag = releaseDescriptor.getScmReleaseLabel();
+
+ if ( tag == null )
+ {
+ // Must get default version from mapped versions, as the project will be the incorrect snapshot
+ String key = ArtifactUtils.versionlessKey( project.getGroupId(), project.getArtifactId() );
+ String releaseVersion = releaseDescriptor.getProjectReleaseVersion( key );
+ if ( releaseVersion == null )
+ {
+ throw new ReleaseExecutionException( "Project tag cannot be selected if version is not yet mapped" );
+ }
+
+ String suggestedName;
+ String scmTagNameFormat = releaseDescriptor.getScmTagNameFormat();
+ if ( releaseDescriptor.getProjectNamingPolicyId() != null )
+ {
+ try
+ {
+ suggestedName =
+ resolveSuggestedName( releaseDescriptor.getProjectNamingPolicyId(), releaseVersion, project );
+ }
+ catch ( PolicyException e )
+ {
+ throw new ReleaseExecutionException( e.getMessage(), e );
+ }
+ }
+ else if ( scmTagNameFormat != null )
+ {
+ Interpolator interpolator = new StringSearchInterpolator( "@{", "}" );
+ List possiblePrefixes = java.util.Arrays.asList( "project", "pom" );
+ Properties values = new Properties();
+ values.setProperty( "artifactId", project.getArtifactId() );
+ values.setProperty( "groupId", project.getGroupId() );
+ values.setProperty( "version", releaseVersion );
+ interpolator.addValueSource( new PrefixedPropertiesValueSource( possiblePrefixes, values, true ) );
+ RecursionInterceptor recursionInterceptor = new PrefixAwareRecursionInterceptor( possiblePrefixes );
+ try
+ {
+ suggestedName = interpolator.interpolate( scmTagNameFormat, recursionInterceptor );
+ }
+ catch ( InterpolationException e )
+ {
+ throw new ReleaseExecutionException(
+ "Could not interpolate specified tag name format: " + scmTagNameFormat, e );
+ }
+ }
+ else
+ {
+ try
+ {
+ suggestedName = resolveSuggestedName( defaultNamingPolicy, releaseVersion, project );
+ }
+ catch ( PolicyException e )
+ {
+ throw new ReleaseExecutionException( e.getMessage(), e );
+ }
+ }
+
+ ScmProvider provider = null;
+ try
+ {
+ provider = getScmProvider( releaseDescriptor, releaseEnvironment );
+ }
+ catch ( ReleaseScmRepositoryException e )
+ {
+ throw new ReleaseExecutionException(
+ "No scm provider can be found for url: " + releaseDescriptor.getScmSourceUrl(), e );
+ }
+
+ suggestedName = provider.sanitizeTagName( suggestedName );
+
+ if ( releaseDescriptor.isInteractive() )
+ {
+ try
+ {
+ if ( branchOperation )
+ {
+ tag = prompter.prompt( "What is the branch name for \"" + project.getName() + "\"? ("
+ + project.getGroupId() + ":" + project.getArtifactId() + ")" );
+ if ( StringUtils.isEmpty( tag ) )
+ {
+ throw new ReleaseExecutionException( "No branch name was given." );
+ }
+ }
+ else
+ {
+ tag = prompter.prompt( "What is the SCM release tag or label for \"" + project.getName()
+ + "\"? (" + project.getGroupId() + ":" + project.getArtifactId() + ")", suggestedName );
+ }
+ }
+ catch ( PrompterException e )
+ {
+ throw new ReleaseExecutionException( "Error reading version from input handler: " + e.getMessage(),
+ e );
+ }
+ }
+ else if ( suggestedName == null )
+ {
+ if ( isBranchOperation() )
+ {
+ throw new ReleaseExecutionException( "No branch name was given." );
+ }
+ else
+ {
+ throw new ReleaseExecutionException( "No tag name was given." );
+ }
+ }
+ else
+ {
+ tag = suggestedName;
+ }
+ releaseDescriptor.setScmReleaseLabel( tag );
+ }
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ // It makes no modifications, so simulate is the same as execute
+ execute( releaseDescriptor, releaseEnvironment, reactorProjects );
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ private String resolveSuggestedName( String policyId, String version, MavenProject project )
+ throws PolicyException
+ {
+ if ( policyId == null )
+ {
+ return null;
+ }
+
+ NamingPolicy policy = namingPolicies.get( policyId );
+ if ( policy == null )
+ {
+ throw new PolicyException( "Policy '" + policyId + "' is unknown, available: "
+ + namingPolicies.keySet() );
+ }
+
+ NamingPolicyRequest request = new NamingPolicyRequest()
+ .setGroupId( project.getGroupId() )
+ .setArtifactId( project.getArtifactId() )
+ .setVersion( version );
+ return policy.getName( request ).getName();
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/MapVersionsPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/MapVersionsPhase.java
new file mode 100644
index 000000000..68d2e7ab1
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/MapVersionsPhase.java
@@ -0,0 +1,396 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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.text.MessageFormat;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import org.apache.maven.artifact.ArtifactUtils;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.apache.maven.shared.release.policy.PolicyException;
+import org.apache.maven.shared.release.policy.version.VersionPolicy;
+import org.apache.maven.shared.release.policy.version.VersionPolicyRequest;
+import org.apache.maven.shared.release.util.ReleaseUtil;
+import org.apache.maven.shared.release.versions.VersionParseException;
+import org.codehaus.plexus.components.interactivity.Prompter;
+import org.codehaus.plexus.components.interactivity.PrompterException;
+import org.codehaus.plexus.util.StringUtils;
+
+/**
+ * Map projects to their new versions after release / into the next development cycle.
+ *
+ * The map-phases per goal are:
+ *
+ * - release:prepare
- map-release-versions + map-development-versions; RD.isBranchCreation() = false
+ * - release:branch
- map-branch-versions + map-development-versions; RD.isBranchCreation() = true
+ * - release:update-versions
- map-development-versions; RD.isBranchCreation() = false
+ *
+ *
+ *
+ * MapVersionsPhase
+ *
+ * MapVersionsPhase field | map-release-versions | map-branch-versions |
+ * map-development-versions |
+ *
+ *
+ * convertToSnapshot | false | true | true |
+ *
+ *
+ * convertToBranch | false | true | false |
+ *
+ *
+ *
+ * @author Brett Porter
+ * @author Robert Scholte
+ */
+public class MapVersionsPhase
+ extends AbstractReleasePhase
+{
+ private ResourceBundle resourceBundle;
+
+ /**
+ * Whether to convert to a snapshot or a release.
+ */
+ private boolean convertToSnapshot;
+
+ /**
+ * Whether to convert to a snapshot or a release.
+ */
+ private boolean convertToBranch;
+
+ /**
+ * Component used to prompt for input.
+ */
+ private Prompter prompter;
+
+
+ /**
+ * Component used for custom or default version policy
+ */
+ private Map versionPolicies;
+
+ void setPrompter( Prompter prompter )
+ {
+ this.prompter = prompter;
+ }
+
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ resourceBundle = getResourceBundle( releaseEnvironment.getLocale() );
+
+ MavenProject rootProject = ReleaseUtil.getRootProject( reactorProjects );
+
+ if ( releaseDescriptor.isAutoVersionSubmodules() && ArtifactUtils.isSnapshot( rootProject.getVersion() ) )
+ {
+ // get the root project
+ MavenProject project = rootProject;
+
+ String projectId = ArtifactUtils.versionlessKey( project.getGroupId(), project.getArtifactId() );
+
+ String nextVersion = resolveNextVersion( project, projectId, releaseDescriptor, result );
+
+ if ( !convertToSnapshot )
+ {
+ releaseDescriptor.addReleaseVersion( projectId, nextVersion );
+ }
+ else if ( releaseDescriptor.isBranchCreation() && convertToBranch )
+ {
+ releaseDescriptor.addReleaseVersion( projectId, nextVersion );
+ }
+ else
+ {
+ releaseDescriptor.addDevelopmentVersion( projectId, nextVersion );
+ }
+
+ for ( MavenProject subProject : reactorProjects )
+ {
+ String subProjectId =
+ ArtifactUtils.versionlessKey( subProject.getGroupId(), subProject.getArtifactId() );
+
+ if ( convertToSnapshot )
+ {
+ String v;
+ if ( ArtifactUtils.isSnapshot( subProject.getVersion() ) )
+ {
+ v = nextVersion;
+ }
+ else
+ {
+ v = subProject.getVersion();
+ }
+
+ if ( releaseDescriptor.isBranchCreation() && convertToBranch )
+ {
+ releaseDescriptor.addReleaseVersion( subProjectId, v );
+ }
+ else
+ {
+ releaseDescriptor.addDevelopmentVersion( subProjectId, v );
+ }
+ }
+ else
+ {
+ releaseDescriptor.addReleaseVersion( subProjectId, nextVersion );
+ }
+ }
+ }
+ else
+ {
+ for ( MavenProject project : reactorProjects )
+ {
+ String projectId = ArtifactUtils.versionlessKey( project.getGroupId(), project.getArtifactId() );
+
+ String nextVersion = resolveNextVersion( project, projectId, releaseDescriptor, result );
+
+ if ( !convertToSnapshot )
+ {
+ releaseDescriptor.addReleaseVersion( projectId, nextVersion );
+ }
+ else if ( releaseDescriptor.isBranchCreation() && convertToBranch )
+ {
+ releaseDescriptor.addReleaseVersion( projectId, nextVersion );
+ }
+ else
+ {
+ releaseDescriptor.addDevelopmentVersion( projectId, nextVersion );
+ }
+ }
+ }
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ private String resolveNextVersion( MavenProject project,
+ String projectId,
+ ReleaseDescriptor releaseDescriptor,
+ ReleaseResult result )
+ throws ReleaseExecutionException
+ {
+ String defaultVersion;
+ if ( convertToBranch )
+ {
+ // no branch modification
+ if ( !( releaseDescriptor.isUpdateBranchVersions()
+ && ( ArtifactUtils.isSnapshot( project.getVersion() )
+ || releaseDescriptor.isUpdateVersionsToSnapshot() ) ) )
+ {
+ return project.getVersion();
+ }
+
+ defaultVersion = getReleaseVersion( projectId, releaseDescriptor );
+ }
+ else if ( !convertToSnapshot ) // map-release-version
+ {
+ defaultVersion = getReleaseVersion( projectId, releaseDescriptor );
+ }
+ else if ( releaseDescriptor.isBranchCreation() )
+ {
+ // no working copy modification
+ if ( !( ArtifactUtils.isSnapshot( project.getVersion() )
+ && releaseDescriptor.isUpdateWorkingCopyVersions() ) )
+ {
+ return project.getVersion();
+ }
+
+ defaultVersion = getDevelopmentVersion( projectId, releaseDescriptor );
+ }
+ else
+ {
+ // no working copy modification
+ if ( !( releaseDescriptor.isUpdateWorkingCopyVersions() ) )
+ {
+ return project.getVersion();
+ }
+
+ defaultVersion = getDevelopmentVersion( projectId, releaseDescriptor );
+ }
+ //@todo validate default version, maybe with DefaultArtifactVersion
+
+ String suggestedVersion = null;
+ String nextVersion = defaultVersion;
+ String messageKey = null;
+ try
+ {
+ while ( nextVersion == null || ArtifactUtils.isSnapshot( nextVersion ) != convertToSnapshot )
+ {
+ if ( suggestedVersion == null )
+ {
+ String baseVersion = null;
+ if ( convertToSnapshot )
+ {
+ baseVersion = getReleaseVersion( projectId, releaseDescriptor );
+ }
+ // unspecified and unmapped version, so use project version
+ if ( baseVersion == null )
+ {
+ baseVersion = project.getVersion();
+ }
+
+ try
+ {
+ try
+ {
+ suggestedVersion =
+ resolveSuggestedVersion( baseVersion, releaseDescriptor.getProjectVersionPolicyId() );
+ }
+ catch ( VersionParseException e )
+ {
+ if ( releaseDescriptor.isInteractive() )
+ {
+ suggestedVersion =
+ resolveSuggestedVersion( "1.0", releaseDescriptor.getProjectVersionPolicyId() );
+ }
+ else
+ {
+ throw new ReleaseExecutionException( "Error parsing version, cannot determine next "
+ + "version: " + e.getMessage(), e );
+ }
+ }
+ }
+ catch ( PolicyException | VersionParseException e )
+ {
+ throw new ReleaseExecutionException( e.getMessage(), e );
+ }
+ }
+
+ if ( releaseDescriptor.isInteractive() )
+ {
+ if ( messageKey == null )
+ {
+ messageKey = getMapversionPromptKey( releaseDescriptor );
+ }
+ String message =
+ MessageFormat.format( resourceBundle.getString( messageKey ), project.getName(), projectId );
+ nextVersion = prompter.prompt( message, suggestedVersion );
+
+ //@todo validate next version, maybe with DefaultArtifactVersion
+ }
+ else if ( defaultVersion == null )
+ {
+ nextVersion = suggestedVersion;
+ }
+ else if ( convertToSnapshot )
+ {
+ throw new ReleaseExecutionException( defaultVersion + " is invalid, expected a snapshot" );
+ }
+ else
+ {
+ throw new ReleaseExecutionException( defaultVersion + " is invalid, expected a non-snapshot" );
+ }
+ }
+ }
+ catch ( PrompterException e )
+ {
+ throw new ReleaseExecutionException( "Error reading version from input handler: " + e.getMessage(), e );
+ }
+ return nextVersion;
+ }
+
+ private String resolveSuggestedVersion( String baseVersion, String policyId )
+ throws PolicyException, VersionParseException
+ {
+ VersionPolicy policy = versionPolicies.get( policyId );
+ if ( policy == null )
+ {
+ throw new PolicyException( "Policy '" + policyId + "' is unknown, available: " + versionPolicies.keySet() );
+ }
+
+ VersionPolicyRequest request = new VersionPolicyRequest().setVersion( baseVersion );
+ return convertToSnapshot ? policy.getDevelopmentVersion( request ).getVersion()
+ : policy.getReleaseVersion( request ).getVersion();
+ }
+
+ private String getDevelopmentVersion( String projectId, ReleaseDescriptor releaseDescriptor )
+ {
+ String defaultVersion = releaseDescriptor.getDefaultDevelopmentVersion();
+ if ( StringUtils.isEmpty( defaultVersion ) )
+ {
+ defaultVersion = releaseDescriptor.getProjectDevelopmentVersion( projectId );
+ }
+ return defaultVersion;
+ }
+
+ private String getReleaseVersion( String projectId, ReleaseDescriptor releaseDescriptor )
+ {
+ String nextVersion = releaseDescriptor.getDefaultReleaseVersion();
+ if ( StringUtils.isEmpty( nextVersion ) )
+ {
+ nextVersion = releaseDescriptor.getProjectReleaseVersion( projectId );
+ }
+ return nextVersion;
+ }
+
+
+ private String getMapversionPromptKey( ReleaseDescriptor releaseDescriptor )
+ {
+ String messageKey;
+ if ( convertToBranch )
+ {
+ messageKey = "mapversion.branch.prompt";
+ }
+ else if ( !convertToSnapshot )
+ {
+ messageKey = "mapversion.release.prompt";
+ }
+ else if ( releaseDescriptor.isBranchCreation() )
+ {
+ messageKey = "mapversion.workingcopy.prompt";
+ }
+ else
+ {
+ messageKey = "mapversion.development.prompt";
+ }
+ return messageKey;
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ // It makes no modifications, so simulate is the same as execute
+ execute( releaseDescriptor, releaseEnvironment, reactorProjects );
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ private ResourceBundle getResourceBundle( Locale locale )
+ {
+ return ResourceBundle.getBundle( "release-messages", locale, MapVersionsPhase.class.getClassLoader() );
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RemoveReleasePomsPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RemoveReleasePomsPhase.java
new file mode 100644
index 000000000..e3f0cabaa
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RemoveReleasePomsPhase.java
@@ -0,0 +1,157 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.scm.ScmException;
+import org.apache.maven.scm.ScmFileSet;
+import org.apache.maven.scm.command.remove.RemoveScmResult;
+import org.apache.maven.scm.provider.ScmProvider;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseFailureException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.apache.maven.shared.release.scm.ReleaseScmCommandException;
+import org.apache.maven.shared.release.util.ReleaseUtil;
+import org.codehaus.plexus.component.annotations.Component;
+
+/**
+ * Remove release POMs.
+ *
+ * @author Brett Porter
+ * @author Mark Hobson
+ */
+@Component( role = ReleasePhase.class, hint = "remove-release-poms" )
+public class RemoveReleasePomsPhase
+ extends AbstractReleasePomsPhase
+{
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseFailureException, ReleaseExecutionException
+ {
+ return execute( releaseDescriptor, releaseEnvironment, reactorProjects, false );
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseFailureException, ReleaseExecutionException
+ {
+ return execute( releaseDescriptor, releaseEnvironment, reactorProjects, true );
+ }
+
+ private ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects, boolean simulate )
+ throws ReleaseFailureException, ReleaseExecutionException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ if ( releaseDescriptor.isGenerateReleasePoms() )
+ {
+ removeReleasePoms( releaseDescriptor, releaseEnvironment, simulate, result, reactorProjects );
+ }
+ else
+ {
+ logInfo( result, "Not removing release POMs" );
+ }
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ private void removeReleasePoms( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ boolean simulate, ReleaseResult result, List projects )
+ throws ReleaseFailureException, ReleaseExecutionException
+ {
+ List releasePoms = new ArrayList<>();
+
+ for ( MavenProject project : projects )
+ {
+ logInfo( result, "Removing release POM for '" + project.getName() + "'..." );
+
+ releasePoms.add( ReleaseUtil.getReleasePom( project ) );
+ }
+
+ if ( releaseDescriptor.isSuppressCommitBeforeTagOrBranch() )
+ {
+ removeReleasePomsFromFilesystem( simulate, result, releasePoms );
+ }
+ else
+ {
+ removeReleasePomsFromScm( releaseDescriptor, releaseEnvironment, simulate, result, releasePoms );
+ }
+ }
+
+ private void removeReleasePomsFromFilesystem( boolean simulate, ReleaseResult result, List releasePoms )
+ {
+ if ( simulate )
+ {
+ logInfo( result, "Full run would be removing " + releasePoms );
+ }
+ else
+ {
+ for ( File releasePom : releasePoms )
+ {
+ releasePom.delete();
+ }
+ }
+ }
+
+ private void removeReleasePomsFromScm( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ boolean simulate, ReleaseResult result, List releasePoms )
+ throws ReleaseFailureException, ReleaseExecutionException
+ {
+ if ( simulate )
+ {
+ logInfo( result, "Full run would be removing " + releasePoms );
+ }
+ else
+ {
+ ScmRepository scmRepository = getScmRepository( releaseDescriptor, releaseEnvironment );
+ ScmProvider scmProvider = getScmProvider( scmRepository );
+
+ ScmFileSet scmFileSet = new ScmFileSet( new File( releaseDescriptor.getWorkingDirectory() ), releasePoms );
+
+ try
+ {
+ RemoveScmResult scmResult =
+ scmProvider.remove( scmRepository, scmFileSet, "Removing for next development iteration." );
+
+ if ( !scmResult.isSuccess() )
+ {
+ throw new ReleaseScmCommandException( "Cannot remove release POMs from SCM", scmResult );
+ }
+ }
+ catch ( ScmException exception )
+ {
+ throw new ReleaseExecutionException( "Cannot remove release POMs from SCM: " + exception.getMessage(),
+ exception );
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RemoveScmTagPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RemoveScmTagPhase.java
new file mode 100644
index 000000000..25e031496
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RemoveScmTagPhase.java
@@ -0,0 +1,160 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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.File;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseFailureException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.codehaus.plexus.component.annotations.Component;
+
+import java.util.List;
+import org.apache.maven.scm.CommandParameter;
+import org.apache.maven.scm.CommandParameters;
+import org.apache.maven.scm.ScmException;
+import org.apache.maven.scm.ScmFileSet;
+import org.apache.maven.scm.command.untag.UntagScmResult;
+import org.apache.maven.scm.manager.NoSuchScmProviderException;
+import org.apache.maven.scm.provider.ScmProvider;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.maven.scm.repository.ScmRepositoryException;
+import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
+import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
+import org.apache.maven.shared.release.util.ReleaseUtil;
+import org.codehaus.plexus.component.annotations.Requirement;
+
+/**
+ * Remove tag from SCM repository during rollback
+ */
+@Component( role = ReleasePhase.class, hint = "remove-scm-tag" )
+public class RemoveScmTagPhase
+ extends AbstractReleasePhase
+{
+ /**
+ * Tool that gets a configured SCM repository from release configuration.
+ */
+ @Requirement
+ private ScmRepositoryConfigurator scmRepositoryConfigurator;
+
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult releaseResult = new ReleaseResult();
+
+ validateConfiguration( releaseDescriptor );
+
+ logInfo( releaseResult, "Removing tag with the label " + releaseDescriptor.getScmReleaseLabel() + " ..." );
+
+ ReleaseDescriptor basedirAlignedReleaseDescriptor =
+ ReleaseUtil.createBasedirAlignedReleaseDescriptor( releaseDescriptor, reactorProjects );
+
+ ScmRepository repository;
+ ScmProvider provider;
+ try
+ {
+ repository =
+ scmRepositoryConfigurator.getConfiguredRepository( basedirAlignedReleaseDescriptor.getScmSourceUrl(),
+ releaseDescriptor,
+ releaseEnvironment.getSettings() );
+
+ repository.getProviderRepository().setPushChanges( releaseDescriptor.isPushChanges() );
+
+ repository.getProviderRepository().setWorkItem( releaseDescriptor.getWorkItem() );
+
+ provider = scmRepositoryConfigurator.getRepositoryProvider( repository );
+ }
+ catch ( ScmRepositoryException e )
+ {
+ throw new ReleaseScmRepositoryException( e.getMessage(), e.getValidationMessages() );
+ }
+ catch ( NoSuchScmProviderException e )
+ {
+ throw new ReleaseExecutionException( "Unable to configure SCM repository: " + e.getMessage(), e );
+ }
+
+ UntagScmResult untagScmResult;
+ try
+ {
+ ScmFileSet fileSet = new ScmFileSet( new File( basedirAlignedReleaseDescriptor.getWorkingDirectory() ) );
+ String tagName = releaseDescriptor.getScmReleaseLabel();
+ String message = releaseDescriptor.getScmCommentPrefix() + "remove tag " + tagName;
+ CommandParameters commandParameters = new CommandParameters();
+ commandParameters.setString( CommandParameter.TAG_NAME, tagName );
+ commandParameters.setString( CommandParameter.MESSAGE, message );
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug(
+ "RemoveScmTagPhase :: scmUntagParameters tagName " + tagName );
+ getLogger().debug(
+ "RemoveScmTagPhase :: scmUntagParameters message " + message );
+ getLogger().debug(
+ "RemoveScmTagPhase :: fileSet " + fileSet );
+ }
+ untagScmResult = provider.untag( repository, fileSet, commandParameters );
+ }
+ catch ( ScmException e )
+ {
+ throw new ReleaseExecutionException( "An error has occurred in the remove tag process: "
+ + e.getMessage(), e );
+ }
+
+ if ( !untagScmResult.isSuccess() )
+ {
+ getLogger().warn( String.format( "Unable to remove tag%nProvider message: %s%nCommand output: %s",
+ untagScmResult.getProviderMessage(), untagScmResult.getCommandOutput() ) );
+ }
+
+ releaseResult.setResultCode( ReleaseResult.SUCCESS );
+
+ return releaseResult;
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult releaseResult = new ReleaseResult();
+
+ validateConfiguration( releaseDescriptor );
+
+ logInfo( releaseResult, "Full run would remove tag with label: '" + releaseDescriptor.getScmReleaseLabel()
+ + "'" );
+
+ releaseResult.setResultCode( ReleaseResult.SUCCESS );
+
+ return releaseResult;
+ }
+
+ private void validateConfiguration( ReleaseDescriptor releaseDescriptor )
+ throws ReleaseFailureException
+ {
+ if ( releaseDescriptor.getScmReleaseLabel() == null )
+ {
+ throw new ReleaseFailureException( "A release label is required for removal" );
+ }
+ }
+
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RestoreBackupPomsPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RestoreBackupPomsPhase.java
new file mode 100644
index 000000000..f746eedda
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RestoreBackupPomsPhase.java
@@ -0,0 +1,143 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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 org.apache.maven.project.MavenProject;
+import org.apache.maven.scm.ScmException;
+import org.apache.maven.scm.ScmFileSet;
+import org.apache.maven.scm.command.edit.EditScmResult;
+import org.apache.maven.scm.manager.NoSuchScmProviderException;
+import org.apache.maven.scm.provider.ScmProvider;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.maven.scm.repository.ScmRepositoryException;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseFailureException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.apache.maven.shared.release.scm.ReleaseScmCommandException;
+import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
+import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
+import org.apache.maven.shared.release.util.ReleaseUtil;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.util.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author Edwin Punzalan
+ */
+@Component( role = ReleasePhase.class, hint = "restore-backup-poms" )
+public class RestoreBackupPomsPhase
+ extends AbstractBackupPomsPhase
+{
+ /**
+ * Tool that gets a configured SCM repository from release configuration.
+ */
+ @Requirement
+ private ScmRepositoryConfigurator scmRepositoryConfigurator;
+
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ for ( MavenProject project : reactorProjects )
+ {
+ restorePomBackup( releaseDescriptor, releaseEnvironment, project );
+ }
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ return execute( releaseDescriptor, releaseEnvironment, reactorProjects );
+ }
+
+ protected void restorePomBackup( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ MavenProject project )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ File pomBackup = getPomBackup( project );
+
+ if ( !pomBackup.exists() )
+ {
+ throw new ReleaseExecutionException(
+ "Cannot restore from a missing backup POM: " + pomBackup.getAbsolutePath() );
+ }
+
+ try
+ {
+ ScmRepository scmRepository;
+ ScmProvider provider;
+ try
+ {
+ scmRepository =
+ scmRepositoryConfigurator.getConfiguredRepository( releaseDescriptor,
+ releaseEnvironment.getSettings() );
+
+ provider = scmRepositoryConfigurator.getRepositoryProvider( scmRepository );
+ }
+ catch ( ScmRepositoryException e )
+ {
+ throw new ReleaseScmRepositoryException( e.getMessage(), e.getValidationMessages() );
+ }
+ catch ( NoSuchScmProviderException e )
+ {
+ throw new ReleaseExecutionException( "Unable to configure SCM repository: " + e.getMessage(), e );
+ }
+
+ if ( releaseDescriptor.isScmUseEditMode() || provider.requiresEditMode() )
+ {
+ EditScmResult result = provider.edit( scmRepository, new ScmFileSet(
+ new File( releaseDescriptor.getWorkingDirectory() ), project.getFile() ) );
+
+ if ( !result.isSuccess() )
+ {
+ throw new ReleaseScmCommandException( "Unable to enable editing on the POM", result );
+ }
+ }
+ }
+ catch ( ScmException e )
+ {
+ throw new ReleaseExecutionException( "An error occurred enabling edit mode: " + e.getMessage(), e );
+ }
+
+ try
+ {
+ FileUtils.copyFile( getPomBackup( project ), ReleaseUtil.getStandardPom( project ) );
+ }
+ catch ( IOException e )
+ {
+ throw new ReleaseExecutionException( "Error restoring from backup POM: " + e.getMessage(), e );
+ }
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RewritePomVersionsPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RewritePomVersionsPhase.java
new file mode 100644
index 000000000..95d06db68
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RewritePomVersionsPhase.java
@@ -0,0 +1,78 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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 org.apache.maven.model.Model;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.codehaus.plexus.component.annotations.Component;
+
+/**
+ * Rewrite POMs for future development
+ *
+ * @author Brett Porter
+ */
+@Component( role = ReleasePhase.class, hint = "rewrite-pom-versions" )
+public class RewritePomVersionsPhase
+ extends AbstractRewritePomsPhase
+{
+ @Override
+ protected final String getPomSuffix()
+ {
+ return "next";
+ }
+
+ @Override
+ protected void transformScm( MavenProject project, Model modelTarget, ReleaseDescriptor releaseDescriptor,
+ String projectId, ScmRepository scmRepository, ReleaseResult result )
+ throws ReleaseExecutionException
+ {
+ // We are only updating versions no mods to scm needed
+ }
+
+ @Override
+ protected boolean isUpdateScm()
+ {
+ return false;
+ }
+
+ @Override
+ protected String getOriginalVersion( ReleaseDescriptor releaseDescriptor, String projectKey, boolean simulate )
+ {
+ return releaseDescriptor.getProjectOriginalVersion( projectKey );
+ }
+
+ @Override
+ protected String getNextVersion( ReleaseDescriptor releaseDescriptor, String key )
+ {
+ return releaseDescriptor.getProjectDevelopmentVersion( key );
+ }
+
+ @Override
+ protected String getResolvedSnapshotVersion( String artifactVersionlessKey,
+ ReleaseDescriptor resolvedSnapshotsMap )
+ {
+ // Only update the pom version, not the dependency versions
+ return null;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RewritePomsForBranchPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RewritePomsForBranchPhase.java
new file mode 100644
index 000000000..82e61b407
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RewritePomsForBranchPhase.java
@@ -0,0 +1,257 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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.IOException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.apache.maven.artifact.ArtifactUtils;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.Scm;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.scm.ScmTranslator;
+import org.apache.maven.shared.release.util.ReleaseUtil;
+import org.codehaus.plexus.component.annotations.Component;
+
+/**
+ * Rewrite POMs for branch.
+ *
+ * @author Emmanuel Venisse
+ * @version $Id$
+ */
+@Component( role = ReleasePhase.class, hint = "rewrite-poms-for-branch" )
+public class RewritePomsForBranchPhase
+ extends AbstractRewritePomsPhase
+{
+ @Override
+ protected final String getPomSuffix()
+ {
+ return "branch";
+ }
+
+ @Override
+ protected void transformScm( MavenProject project, Model modelTarget, ReleaseDescriptor releaseDescriptor,
+ String projectId, ScmRepository scmRepository, ReleaseResult result )
+ throws ReleaseExecutionException
+ {
+ // If SCM is null in original model, it is inherited, no mods needed
+ if ( project.getScm() != null )
+ {
+ Scm scmRoot = modelTarget.getScm();
+
+ if ( scmRoot != null )
+ {
+ try
+ {
+ translateScm( project, releaseDescriptor, scmRoot, scmRepository, result );
+ }
+ catch ( IOException e )
+ {
+ throw new ReleaseExecutionException( e.getMessage(), e );
+ }
+ }
+ else
+ {
+ MavenProject parent = project.getParent();
+ if ( parent != null )
+ {
+ // If the SCM element is not present, only add it if the parent was not mapped (ie, it's external to
+ // the release process and so has not been modified, so the values will not be correct on the tag),
+ String parentId = ArtifactUtils.versionlessKey( parent.getGroupId(), parent.getArtifactId() );
+ if ( releaseDescriptor.getOriginalScmInfo( parentId ) == null )
+ {
+ // we need to add it, since it has changed from the inherited value
+ scmRoot = new Scm();
+ // reset default value (HEAD)
+ scmRoot.setTag( null );
+
+ try
+ {
+ if ( translateScm( project, releaseDescriptor, scmRoot, scmRepository, result ) )
+ {
+ modelTarget.setScm( scmRoot );
+ }
+ }
+ catch ( IOException e )
+ {
+ throw new ReleaseExecutionException( e.getMessage(), e );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private boolean translateScm( MavenProject project, ReleaseDescriptor releaseDescriptor, Scm scmTarget,
+ ScmRepository scmRepository, ReleaseResult relResult )
+ throws IOException
+ {
+ ScmTranslator translator = getScmTranslators().get( scmRepository.getProvider() );
+ boolean result = false;
+ if ( translator != null )
+ {
+ Scm scm = project.getOriginalModel().getScm();
+ if ( scm == null )
+ {
+ scm = project.getScm();
+ }
+
+ String branchName = releaseDescriptor.getScmReleaseLabel();
+ String branchBase = releaseDescriptor.getScmBranchBase();
+
+ // TODO: svn utils should take care of prepending this
+ if ( branchBase != null )
+ {
+ branchBase = "scm:svn:" + branchBase;
+ }
+
+ Path projectBasedir = project.getBasedir().toPath().toRealPath( LinkOption.NOFOLLOW_LINKS );
+ Path workingDirectory = Paths.get( releaseDescriptor.getWorkingDirectory() );
+
+ int count = ReleaseUtil.getBaseWorkingDirectoryParentCount( workingDirectory, projectBasedir );
+
+ if ( scm.getConnection() != null )
+ {
+ String rootUrl = ReleaseUtil.realignScmUrl( count, scm.getConnection() );
+
+ String subDirectoryBranch = scm.getConnection().substring( rootUrl.length() );
+ if ( !subDirectoryBranch.startsWith( "/" ) )
+ {
+ subDirectoryBranch = "/" + subDirectoryBranch;
+ }
+
+ String scmConnectionBranch = branchBase;
+ if ( scmConnectionBranch != null )
+ {
+ String trunkUrl = scm.getDeveloperConnection();
+ if ( trunkUrl == null )
+ {
+ trunkUrl = scm.getConnection();
+ }
+ scmConnectionBranch = translateUrlPath( trunkUrl, branchBase, scm.getConnection() );
+ }
+
+ String value =
+ translator.translateBranchUrl( scm.getConnection(), branchName + subDirectoryBranch,
+ scmConnectionBranch );
+ if ( !value.equals( scm.getConnection() ) )
+ {
+ scmTarget.setConnection( value );
+ result = true;
+ }
+ }
+
+ if ( scm.getDeveloperConnection() != null )
+ {
+ String rootUrl = ReleaseUtil.realignScmUrl( count, scm.getDeveloperConnection() );
+
+ String subDirectoryBranch = scm.getDeveloperConnection().substring( rootUrl.length() );
+ if ( !subDirectoryBranch.startsWith( "/" ) )
+ {
+ subDirectoryBranch = "/" + subDirectoryBranch;
+ }
+
+ String value =
+ translator.translateBranchUrl( scm.getDeveloperConnection(), branchName + subDirectoryBranch,
+ branchBase );
+ if ( !value.equals( scm.getDeveloperConnection() ) )
+ {
+ scmTarget.setDeveloperConnection( value );
+ result = true;
+ }
+ }
+
+ if ( scm.getUrl() != null )
+ {
+ String rootUrl = ReleaseUtil.realignScmUrl( count, scm.getUrl() );
+
+ String subDirectoryBranch = scm.getUrl().substring( rootUrl.length() );
+ if ( !subDirectoryBranch.startsWith( "/" ) )
+ {
+ subDirectoryBranch = "/" + subDirectoryBranch;
+ }
+
+ String tagScmUrl = branchBase;
+ if ( tagScmUrl != null )
+ {
+ String trunkUrl = scm.getDeveloperConnection();
+ if ( trunkUrl == null )
+ {
+ trunkUrl = scm.getConnection();
+ }
+ tagScmUrl = translateUrlPath( trunkUrl, branchBase, scm.getUrl() );
+ }
+
+ // use original branch base without protocol
+ String value = translator.translateBranchUrl( scm.getUrl(), branchName + subDirectoryBranch,
+ tagScmUrl );
+ if ( !value.equals( scm.getUrl() ) )
+ {
+ scmTarget.setUrl( value );
+ result = true;
+ }
+ }
+
+ if ( branchName != null )
+ {
+ String value = translator.resolveTag( branchName );
+ if ( value != null && !value.equals( scm.getTag() ) )
+ {
+ scmTarget.setTag( value );
+ result = true;
+ }
+ }
+ }
+ else
+ {
+ String message = "No SCM translator found - skipping rewrite";
+
+ relResult.appendDebug( message );
+
+ getLogger().debug( message );
+ }
+ return result;
+ }
+
+ @Override
+ protected String getOriginalVersion( ReleaseDescriptor releaseDescriptor, String projectKey, boolean simulate )
+ {
+ return releaseDescriptor.getProjectOriginalVersion( projectKey );
+ }
+
+ @Override
+ protected String getNextVersion( ReleaseDescriptor releaseDescriptor, String key )
+ {
+ return releaseDescriptor.getProjectReleaseVersion( key );
+ }
+
+ @Override
+ protected String getResolvedSnapshotVersion( String artifactVersionlessKey,
+ ReleaseDescriptor releaseDescriptor )
+ {
+ return releaseDescriptor.getDependencyReleaseVersion( artifactVersionlessKey );
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RewritePomsForDevelopmentPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RewritePomsForDevelopmentPhase.java
new file mode 100644
index 000000000..834ca0ebd
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RewritePomsForDevelopmentPhase.java
@@ -0,0 +1,106 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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 org.apache.maven.model.Model;
+import org.apache.maven.model.Scm;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.scm.ScmTranslator;
+import org.codehaus.plexus.component.annotations.Component;
+
+/**
+ * Rewrite POMs for future development
+ *
+ * @author Brett Porter
+ */
+@Component( role = ReleasePhase.class , hint = "rewrite-poms-for-development" )
+public class RewritePomsForDevelopmentPhase
+ extends AbstractRewritePomsPhase
+{
+ @Override
+ protected final String getPomSuffix()
+ {
+ return "next";
+ }
+
+ @Override
+ protected void transformScm( MavenProject project, Model modelTarget, ReleaseDescriptor releaseDescriptor,
+ String projectId, ScmRepository scmRepository, ReleaseResult result )
+ throws ReleaseExecutionException
+ {
+ // If SCM is null in original model, it is inherited, no mods needed
+ if ( project.getScm() != null )
+ {
+ Scm scmRoot = modelTarget.getScm();
+ if ( scmRoot != null )
+ {
+ ScmTranslator translator = getScmTranslators().get( scmRepository.getProvider() );
+ if ( translator != null )
+ {
+ Scm scm = releaseDescriptor.getOriginalScmInfo( projectId );
+
+ if ( scm != null )
+ {
+ scmRoot.setConnection( scm.getConnection() );
+ scmRoot.setDeveloperConnection( scm.getDeveloperConnection() );
+ scmRoot.setUrl( scm.getUrl() );
+ scmRoot.setTag( translator.resolveTag( scm.getTag() ) );
+ }
+ else
+ {
+ // cleanly remove the SCM element
+ modelTarget.setScm( null );
+ }
+ }
+ else
+ {
+ String message = "No SCM translator found - skipping rewrite";
+ result.appendDebug( message );
+ getLogger().debug( message );
+ }
+ }
+ }
+ }
+
+ @Override
+ protected String getOriginalVersion( ReleaseDescriptor releaseDescriptor, String projectKey, boolean simulate )
+ {
+ return simulate
+ ? releaseDescriptor.getProjectOriginalVersion( projectKey )
+ : releaseDescriptor.getProjectReleaseVersion( projectKey );
+ }
+
+ @Override
+ protected String getNextVersion( ReleaseDescriptor releaseDescriptor, String key )
+ {
+ return releaseDescriptor.getProjectDevelopmentVersion( key );
+ }
+
+ @Override
+ protected String getResolvedSnapshotVersion( String artifactVersionlessKey,
+ ReleaseDescriptor releaseDescriptor )
+ {
+ return releaseDescriptor.getDependencyDevelopmentVersion( artifactVersionlessKey );
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RewritePomsForReleasePhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RewritePomsForReleasePhase.java
new file mode 100644
index 000000000..cc36600a7
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RewritePomsForReleasePhase.java
@@ -0,0 +1,252 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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.IOException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.apache.maven.artifact.ArtifactUtils;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.Scm;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.scm.ScmTranslator;
+import org.apache.maven.shared.release.util.ReleaseUtil;
+import org.codehaus.plexus.component.annotations.Component;
+
+/**
+ * Rewrite POMs for release.
+ *
+ * @author Brett Porter
+ */
+@Component( role = ReleasePhase.class, hint = "rewrite-poms-for-release" )
+public class RewritePomsForReleasePhase
+ extends AbstractRewritePomsPhase
+{
+ @Override
+ protected final String getPomSuffix()
+ {
+ return "tag";
+ }
+
+ @Override
+ protected void transformScm( MavenProject project, Model modelTarget, ReleaseDescriptor releaseDescriptor,
+ String projectId, ScmRepository scmRepository, ReleaseResult result )
+ throws ReleaseExecutionException
+ {
+ // If SCM is null in original model, it is inherited, no mods needed
+ if ( project.getScm() != null )
+ {
+ Scm scmRoot = modelTarget.getScm();
+ if ( scmRoot != null )
+ {
+ try
+ {
+ translateScm( project, releaseDescriptor, scmRoot, scmRepository, result );
+ }
+ catch ( IOException e )
+ {
+ throw new ReleaseExecutionException( e.getMessage(), e );
+ }
+ }
+ else
+ {
+ MavenProject parent = project.getParent();
+ if ( parent != null )
+ {
+ // If the SCM element is not present, only add it if the parent was not mapped (ie, it's external to
+ // the release process and so has not been modified, so the values will not be correct on the tag),
+ String parentId = ArtifactUtils.versionlessKey( parent.getGroupId(), parent.getArtifactId() );
+ if ( releaseDescriptor.getOriginalScmInfo( parentId ) == null )
+ {
+ // we need to add it, since it has changed from the inherited value
+ Scm scmTarget = new Scm();
+ // reset default value (HEAD)
+ scmTarget.setTag( null );
+
+ try
+ {
+ if ( translateScm( project, releaseDescriptor, scmTarget, scmRepository, result ) )
+ {
+ modelTarget.setScm( scmTarget );
+ }
+ }
+ catch ( IOException e )
+ {
+ throw new ReleaseExecutionException( e.getMessage(), e );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private boolean translateScm( MavenProject project, ReleaseDescriptor releaseDescriptor, Scm scmTarget,
+ ScmRepository scmRepository, ReleaseResult relResult )
+ throws IOException
+ {
+ ScmTranslator translator = getScmTranslators().get( scmRepository.getProvider() );
+ boolean result = false;
+ if ( translator != null )
+ {
+ Scm scm = project.getOriginalModel().getScm();
+ if ( scm == null )
+ {
+ scm = project.getScm();
+ }
+
+ String tag = releaseDescriptor.getScmReleaseLabel();
+ String tagBase = releaseDescriptor.getScmTagBase();
+
+ // TODO: svn utils should take care of prepending this
+ if ( tagBase != null )
+ {
+ tagBase = "scm:svn:" + tagBase;
+ }
+
+ Path projectBasedir = project.getBasedir().toPath().toRealPath( LinkOption.NOFOLLOW_LINKS );
+ Path workingDirectory = Paths.get( releaseDescriptor.getWorkingDirectory() );
+
+ int count = ReleaseUtil.getBaseWorkingDirectoryParentCount( workingDirectory, projectBasedir );
+
+ if ( scm.getConnection() != null )
+ {
+ String rootUrl = ReleaseUtil.realignScmUrl( count, scm.getConnection() );
+
+ String subDirectoryTag = scm.getConnection().substring( rootUrl.length() );
+ if ( !subDirectoryTag.startsWith( "/" ) )
+ {
+ subDirectoryTag = "/" + subDirectoryTag;
+ }
+
+ String scmConnectionTag = tagBase;
+ if ( scmConnectionTag != null )
+ {
+ String trunkUrl = scm.getDeveloperConnection();
+ if ( trunkUrl == null )
+ {
+ trunkUrl = scm.getConnection();
+ }
+ scmConnectionTag = translateUrlPath( trunkUrl, tagBase, scm.getConnection() );
+ }
+ String value =
+ translator.translateTagUrl( scm.getConnection(), tag + subDirectoryTag, scmConnectionTag );
+
+ if ( !value.equals( scm.getConnection() ) )
+ {
+ scmTarget.setConnection( value );
+ result = true;
+ }
+ }
+
+ if ( scm.getDeveloperConnection() != null )
+ {
+ String rootUrl = ReleaseUtil.realignScmUrl( count, scm.getDeveloperConnection() );
+
+ String subDirectoryTag = scm.getDeveloperConnection().substring( rootUrl.length() );
+ if ( !subDirectoryTag.startsWith( "/" ) )
+ {
+ subDirectoryTag = "/" + subDirectoryTag;
+ }
+
+ String value =
+ translator.translateTagUrl( scm.getDeveloperConnection(), tag + subDirectoryTag, tagBase );
+
+ if ( !value.equals( scm.getDeveloperConnection() ) )
+ {
+ scmTarget.setDeveloperConnection( value );
+ result = true;
+ }
+ }
+
+ if ( scm.getUrl() != null )
+ {
+ String rootUrl = ReleaseUtil.realignScmUrl( count, scm.getUrl() );
+
+ String subDirectoryTag = scm.getUrl().substring( rootUrl.length() );
+ if ( !subDirectoryTag.startsWith( "/" ) )
+ {
+ subDirectoryTag = "/" + subDirectoryTag;
+ }
+
+ String tagScmUrl = tagBase;
+ if ( tagScmUrl != null )
+ {
+ String trunkUrl = scm.getDeveloperConnection();
+ if ( trunkUrl == null )
+ {
+ trunkUrl = scm.getConnection();
+ }
+ tagScmUrl = translateUrlPath( trunkUrl, tagBase, scm.getUrl() );
+ }
+ // use original tag base without protocol
+ String value = translator.translateTagUrl( scm.getUrl(), tag + subDirectoryTag, tagScmUrl );
+ if ( !value.equals( scm.getUrl() ) )
+ {
+ scmTarget.setUrl( value );
+ result = true;
+ }
+ }
+
+ if ( tag != null )
+ {
+ String value = translator.resolveTag( tag );
+ if ( value != null && !value.equals( scm.getTag() ) )
+ {
+ scmTarget.setTag( value );
+ result = true;
+ }
+ }
+ }
+ else
+ {
+ String message = "No SCM translator found - skipping rewrite";
+
+ relResult.appendDebug( message );
+
+ getLogger().debug( message );
+ }
+ return result;
+ }
+
+ @Override
+ protected String getOriginalVersion( ReleaseDescriptor releaseDescriptor, String projectKey, boolean simulate )
+ {
+ return releaseDescriptor.getProjectOriginalVersion( projectKey );
+ }
+
+ @Override
+ protected String getNextVersion( ReleaseDescriptor releaseDescriptor, String key )
+ {
+ return releaseDescriptor.getProjectReleaseVersion( key );
+ }
+
+ @Override
+ protected String getResolvedSnapshotVersion( String artifactVersionlessKey,
+ ReleaseDescriptor releaseDescriptor )
+ {
+ return releaseDescriptor.getDependencyReleaseVersion( artifactVersionlessKey );
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RunCompleteGoalsPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RunCompleteGoalsPhase.java
new file mode 100644
index 000000000..aab47500d
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RunCompleteGoalsPhase.java
@@ -0,0 +1,71 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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.File;
+import java.util.List;
+
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.codehaus.plexus.component.annotations.Component;
+
+/**
+ * Run the completion goals for the project to before committing the continuing development stream.
+ *
+ * @author Brett Porter
+ * @author Stephen Connolly
+ */
+@Component( role = ReleasePhase.class, hint = "run-completion-goals" )
+public class RunCompleteGoalsPhase
+ extends AbstractRunGoalsPhase
+{
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException
+ {
+ return execute( releaseDescriptor, releaseEnvironment, new File( releaseDescriptor.getWorkingDirectory() ),
+ getAdditionalArguments( releaseDescriptor ) );
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ logInfo( result, "Executing completion goals - since this is simulation mode it is running against the "
+ + "original project, not the rewritten ones" );
+
+ execute( releaseDescriptor, releaseEnvironment, reactorProjects );
+
+ return result;
+ }
+
+ @Override
+ protected String getGoals( ReleaseDescriptor releaseDescriptor )
+ {
+ return releaseDescriptor.getCompletionGoals();
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RunPerformGoalsPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RunPerformGoalsPhase.java
new file mode 100644
index 000000000..7c63a5fdc
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RunPerformGoalsPhase.java
@@ -0,0 +1,145 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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 org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.apache.maven.shared.release.util.PomFinder;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.util.StringUtils;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Run the integration tests for the project to verify that it builds before committing.
+ *
+ * @author Brett Porter
+ */
+@Component( role = ReleasePhase.class, hint = "run-perform-goals" )
+public class RunPerformGoalsPhase
+ extends AbstractRunGoalsPhase
+{
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException
+ {
+ return runLogic( releaseDescriptor, releaseEnvironment, reactorProjects, false );
+ }
+
+ private ReleaseResult runLogic( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects, boolean simulate )
+ throws ReleaseExecutionException
+ {
+ String additionalArguments = getAdditionalArguments( releaseDescriptor );
+
+ if ( releaseDescriptor.isUseReleaseProfile() )
+ {
+ if ( !StringUtils.isEmpty( additionalArguments ) )
+ {
+ additionalArguments = additionalArguments + " -DperformRelease=true";
+ }
+ else
+ {
+ additionalArguments = "-DperformRelease=true";
+ }
+ }
+
+ String pomFileName = releaseDescriptor.getPomFileName();
+ if ( pomFileName == null )
+ {
+ pomFileName = "pom.xml";
+ }
+
+ // ensure we don't use the release pom for the perform goals
+ // ^^ paranoia? A MavenExecutor has already access to this. Probably worth refactoring.
+ if ( !StringUtils.isEmpty( additionalArguments ) )
+ {
+ additionalArguments = additionalArguments + " -f " + pomFileName;
+ }
+ else
+ {
+ additionalArguments = "-f " + pomFileName;
+ }
+
+ if ( simulate )
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ logDebug( result, "Additional arguments: " + additionalArguments );
+
+ logInfo( result, "Executing perform goals - since this is simulation mode these goals are skipped." );
+
+ return result;
+ }
+
+ String workDir = releaseDescriptor.getWorkingDirectory();
+ if ( workDir == null )
+ {
+ workDir = System.getProperty( "user.dir" );
+ }
+
+
+ File pomFile = new File( workDir, pomFileName );
+ PomFinder pomFinder = new PomFinder( getLogger() );
+ boolean foundPom = false;
+
+ if ( StringUtils.isEmpty( releaseDescriptor.getScmRelativePathProjectDirectory() ) )
+ {
+ foundPom = pomFinder.parsePom( pomFile );
+ }
+
+ File workDirectory = new File( releaseDescriptor.getCheckoutDirectory() );
+
+ if ( foundPom )
+ {
+ File matchingPom = pomFinder.findMatchingPom( workDirectory );
+ if ( matchingPom != null )
+ {
+ getLogger().info( "Invoking perform goals in directory " + matchingPom.getParent() );
+ // The directory of the POM in a flat project layout is not
+ // the same directory as the SCM checkout directory!
+ // The same is true for a sparse checkout in e.g. GIT
+ // the project to build could be in target/checkout/some/dir/
+ workDirectory = matchingPom.getParentFile();
+ }
+ }
+
+ return execute( releaseDescriptor, releaseEnvironment, workDirectory, additionalArguments );
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException
+ {
+ return runLogic( releaseDescriptor, releaseEnvironment, reactorProjects, true );
+ }
+
+ @Override
+ protected String getGoals( ReleaseDescriptor releaseDescriptor )
+ {
+ return releaseDescriptor.getPerformGoals();
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RunPrepareGoalsPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RunPrepareGoalsPhase.java
new file mode 100644
index 000000000..23db790b5
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/RunPrepareGoalsPhase.java
@@ -0,0 +1,70 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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 org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.codehaus.plexus.component.annotations.Component;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Run the integration tests for the project to verify that it builds before committing.
+ *
+ * @author Brett Porter
+ */
+@Component( role = ReleasePhase.class, hint = "run-preparation-goals" )
+public class RunPrepareGoalsPhase
+ extends AbstractRunGoalsPhase
+{
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException
+ {
+ return execute( releaseDescriptor, releaseEnvironment, new File( releaseDescriptor.getWorkingDirectory() ),
+ getAdditionalArguments( releaseDescriptor ) );
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ logInfo( result, "Executing preparation goals - since this is simulation mode it is running against the "
+ + "original project, not the rewritten ones" );
+
+ execute( releaseDescriptor, releaseEnvironment, reactorProjects );
+
+ return result;
+ }
+
+ @Override
+ protected String getGoals( ReleaseDescriptor releaseDescriptor )
+ {
+ return releaseDescriptor.getPreparationGoals();
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/ScmBranchPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/ScmBranchPhase.java
new file mode 100644
index 000000000..576f51112
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/ScmBranchPhase.java
@@ -0,0 +1,165 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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 org.apache.maven.project.MavenProject;
+import org.apache.maven.scm.ScmBranchParameters;
+import org.apache.maven.scm.ScmException;
+import org.apache.maven.scm.ScmFileSet;
+import org.apache.maven.scm.command.branch.BranchScmResult;
+import org.apache.maven.scm.manager.NoSuchScmProviderException;
+import org.apache.maven.scm.provider.ScmProvider;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.maven.scm.repository.ScmRepositoryException;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseFailureException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.apache.maven.shared.release.scm.ReleaseScmCommandException;
+import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
+import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
+import org.apache.maven.shared.release.util.ReleaseUtil;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Branch the SCM repository.
+ *
+ * @author Emmanuel Venisse
+ */
+@Component( role = ReleasePhase.class, hint = "scm-branch" )
+public class ScmBranchPhase
+ extends AbstractReleasePhase
+{
+ /**
+ * Tool that gets a configured SCM repository from release configuration.
+ */
+ @Requirement
+ private ScmRepositoryConfigurator scmRepositoryConfigurator;
+
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult relResult = new ReleaseResult();
+
+ validateConfiguration( releaseDescriptor );
+
+ ReleaseDescriptor basedirAlignedReleaseDescriptor =
+ ReleaseUtil.createBasedirAlignedReleaseDescriptor( releaseDescriptor, reactorProjects );
+
+ logInfo( relResult, "Branching release with the label " + basedirAlignedReleaseDescriptor.getScmReleaseLabel()
+ + "..." );
+
+ ScmRepository repository;
+ ScmProvider provider;
+ try
+ {
+ repository =
+ scmRepositoryConfigurator.getConfiguredRepository( basedirAlignedReleaseDescriptor.getScmSourceUrl(),
+ releaseDescriptor,
+ releaseEnvironment.getSettings() );
+
+ repository.getProviderRepository().setPushChanges( releaseDescriptor.isPushChanges() );
+
+ repository.getProviderRepository().setWorkItem( releaseDescriptor.getWorkItem() );
+
+ provider = scmRepositoryConfigurator.getRepositoryProvider( repository );
+
+ }
+ catch ( ScmRepositoryException e )
+ {
+ throw new ReleaseScmRepositoryException( e.getMessage(), e.getValidationMessages() );
+ }
+ catch ( NoSuchScmProviderException e )
+ {
+ throw new ReleaseExecutionException( "Unable to configure SCM repository: " + e.getMessage(), e );
+ }
+
+ BranchScmResult result;
+ try
+ {
+ ScmFileSet fileSet = new ScmFileSet( new File( basedirAlignedReleaseDescriptor.getWorkingDirectory() ) );
+ String branchName = releaseDescriptor.getScmReleaseLabel();
+
+ ScmBranchParameters scmBranchParameters = new ScmBranchParameters();
+ scmBranchParameters.setMessage( releaseDescriptor.getScmCommentPrefix() + "copy for branch " + branchName );
+ scmBranchParameters.setRemoteBranching( releaseDescriptor.isRemoteTagging() );
+ scmBranchParameters.setScmRevision( releaseDescriptor.getScmReleasedPomRevision() );
+ scmBranchParameters.setPinExternals( releaseDescriptor.isPinExternals() );
+
+ result = provider.branch( repository, fileSet, branchName, scmBranchParameters );
+ }
+ catch ( ScmException e )
+ {
+ throw new ReleaseExecutionException( "An error is occurred in the branch process: " + e.getMessage(), e );
+ }
+
+ if ( !result.isSuccess() )
+ {
+ throw new ReleaseScmCommandException( "Unable to branch SCM", result );
+ }
+
+ relResult.setResultCode( ReleaseResult.SUCCESS );
+
+ return relResult;
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ validateConfiguration( releaseDescriptor );
+ ReleaseDescriptor basedirAlignedReleaseDescriptor =
+ ReleaseUtil.createBasedirAlignedReleaseDescriptor( releaseDescriptor, reactorProjects );
+
+ logInfo( result, "Full run would branch " + basedirAlignedReleaseDescriptor.getWorkingDirectory() );
+ if ( releaseDescriptor.getScmBranchBase() != null )
+ {
+ logInfo( result, " to SCM URL " + releaseDescriptor.getScmBranchBase() );
+ }
+ logInfo( result, " with label '" + releaseDescriptor.getScmReleaseLabel() + "'" );
+ if ( releaseDescriptor.isPinExternals() )
+ {
+ logInfo( result, " and pinned externals" );
+ }
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ private static void validateConfiguration( ReleaseDescriptor releaseDescriptor )
+ throws ReleaseFailureException
+ {
+ if ( releaseDescriptor.getScmReleaseLabel() == null )
+ {
+ throw new ReleaseFailureException( "A release label is required for committing" );
+ }
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/ScmCheckModificationsPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/ScmCheckModificationsPhase.java
new file mode 100644
index 000000000..6562089c9
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/ScmCheckModificationsPhase.java
@@ -0,0 +1,206 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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 org.apache.maven.project.MavenProject;
+import org.apache.maven.scm.ScmException;
+import org.apache.maven.scm.ScmFile;
+import org.apache.maven.scm.ScmFileSet;
+import org.apache.maven.scm.command.status.StatusScmResult;
+import org.apache.maven.scm.manager.NoSuchScmProviderException;
+import org.apache.maven.scm.provider.ScmProvider;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.maven.scm.repository.ScmRepositoryException;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseFailureException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.apache.maven.shared.release.scm.ReleaseScmCommandException;
+import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
+import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
+import org.apache.maven.shared.release.scm.ScmTranslator;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.util.SelectorUtils;
+import org.codehaus.plexus.util.StringUtils;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * See if there are any local modifications to the files before proceeding with SCM operations and the release.
+ *
+ * @author Brett Porter
+ */
+@Component( role = ReleasePhase.class, hint = "scm-check-modifications" )
+public class ScmCheckModificationsPhase
+ extends AbstractReleasePhase
+{
+ /**
+ * Tool that gets a configured SCM repository from release configuration.
+ */
+ @Requirement
+ private ScmRepositoryConfigurator scmRepositoryConfigurator;
+
+ /**
+ * SCM URL translators mapped by provider name.
+ */
+ @Requirement( role = ScmTranslator.class )
+ private Map scmTranslators;
+
+ /**
+ * The filepatterns to exclude from the status check.
+ *
+ * @todo proper construction of filenames, especially release properties
+ */
+ private Set exclusionPatterns = new HashSet<>( Arrays.asList(
+ "**" + File.separator + "pom.xml.backup", "**" + File.separator + "pom.xml.tag",
+ "**" + File.separator + "pom.xml.next", "**" + File.separator + "pom.xml.branch",
+ "**" + File.separator + "release.properties", "**" + File.separator + "pom.xml.releaseBackup" ) );
+
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult relResult = new ReleaseResult();
+
+ List additionalExcludes = releaseDescriptor.getCheckModificationExcludes();
+
+ if ( additionalExcludes != null )
+ {
+ // SelectorUtils expects OS-specific paths and patterns
+ for ( String additionalExclude : additionalExcludes )
+ {
+ exclusionPatterns.add( additionalExclude.replace( "\\", File.separator )
+ .replace( "/", File.separator ) );
+ }
+ }
+
+ logInfo( relResult, "Verifying that there are no local modifications..." );
+ logInfo( relResult, " ignoring changes on: " + StringUtils.join( exclusionPatterns.toArray(), ", " ) );
+
+ ScmRepository repository;
+ ScmProvider provider;
+ try
+ {
+ repository =
+ scmRepositoryConfigurator.getConfiguredRepository( releaseDescriptor,
+ releaseEnvironment.getSettings() );
+
+ provider = scmRepositoryConfigurator.getRepositoryProvider( repository );
+ }
+ catch ( ScmRepositoryException e )
+ {
+ throw new ReleaseScmRepositoryException( e.getMessage() + " for URL: "
+ + releaseDescriptor.getScmSourceUrl(), e.getValidationMessages() );
+ }
+ catch ( NoSuchScmProviderException e )
+ {
+ throw new ReleaseExecutionException( "Unable to configure SCM repository: " + e.getMessage(), e );
+ }
+
+ StatusScmResult result;
+ try
+ {
+ result =
+ provider.status( repository, new ScmFileSet( new File( releaseDescriptor.getWorkingDirectory() ) ) );
+ }
+ catch ( ScmException e )
+ {
+ throw new ReleaseExecutionException( "An error occurred during the status check process: " + e.getMessage(),
+ e );
+ }
+
+ if ( !result.isSuccess() )
+ {
+ throw new ReleaseScmCommandException( "Unable to check for local modifications", result );
+ }
+
+ List changedFiles = result.getChangedFiles();
+
+ if ( !changedFiles.isEmpty() )
+ {
+ ScmTranslator scmTranslator = scmTranslators.get( repository.getProvider() );
+
+ // TODO: would be nice for SCM status command to do this for me.
+ for ( Iterator i = changedFiles.iterator(); i.hasNext(); )
+ {
+ ScmFile f = i.next();
+
+ String path;
+ if ( scmTranslator != null )
+ {
+ path = scmTranslator.toRelativePath( f.getPath() );
+ }
+ else
+ {
+ path = f.getPath();
+ }
+
+ // SelectorUtils expects File.separator, don't standardize!
+ String fileName = path.replace( "\\", File.separator ).replace( "/", File.separator );
+
+ for ( String exclusionPattern : exclusionPatterns )
+ {
+ if ( SelectorUtils.matchPath( exclusionPattern, fileName ) )
+ {
+ logDebug( relResult, "Ignoring changed file: " + fileName );
+ i.remove();
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !changedFiles.isEmpty() )
+ {
+ StringBuilder message = new StringBuilder();
+
+ for ( ScmFile file : changedFiles )
+ {
+ message.append( file.toString() );
+ message.append( "\n" );
+ }
+
+ throw new ReleaseFailureException( "Cannot prepare the release because you have local modifications : \n"
+ + message );
+ }
+
+ relResult.setResultCode( ReleaseResult.SUCCESS );
+
+ return relResult;
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ // It makes no modifications, so simulate is the same as execute
+ return execute( releaseDescriptor, releaseEnvironment, reactorProjects );
+ }
+}
\ No newline at end of file
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/ScmCommitDevelopmentPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/ScmCommitDevelopmentPhase.java
new file mode 100644
index 000000000..c81889e7e
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/ScmCommitDevelopmentPhase.java
@@ -0,0 +1,103 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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.File;
+import java.text.MessageFormat;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.apache.maven.shared.release.scm.ReleaseScmCommandException;
+import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
+
+/**
+ * Commit the changes that were done to prepare the branch or tag to the SCM.
+ *
+ * @author Brett Porter
+ */
+public class ScmCommitDevelopmentPhase
+ extends AbstractScmCommitPhase
+{
+
+ /**
+ * The format for the
+ */
+ private String rollbackMessageFormat;
+
+ @Override
+ protected void runLogic( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects, ReleaseResult result, boolean simulating )
+ throws ReleaseScmCommandException, ReleaseExecutionException, ReleaseScmRepositoryException
+ {
+ // no rollback required
+ if (
+ // was there no commit that has to be rolled back by a new one
+ releaseDescriptor.isSuppressCommitBeforeTagOrBranch()
+ // and working copy should not be touched
+ && !releaseDescriptor.isUpdateWorkingCopyVersions() )
+ {
+ if ( simulating )
+ {
+ logInfo( result, "Full run would not commit changes, because updateWorkingCopyVersions is false." );
+ }
+ else
+ {
+ logInfo( result, "Modified POMs are not committed because updateWorkingCopyVersions is set to false." );
+ }
+ }
+ // rollback or commit development versions required
+ else
+ {
+ String message;
+ if ( !releaseDescriptor.isUpdateWorkingCopyVersions() )
+ {
+ // the commit is a rollback
+ message = createRollbackMessage( releaseDescriptor );
+ }
+ else
+ {
+ // a normal commit
+ message = createMessage( reactorProjects, releaseDescriptor );
+ }
+ if ( simulating )
+ {
+ Collection pomFiles = createPomFiles( releaseDescriptor, reactorProjects );
+ logInfo( result,
+ "Full run would commit " + pomFiles.size() + " files with message: '" + message + "'" );
+ }
+ else
+ {
+ performCheckins( releaseDescriptor, releaseEnvironment, reactorProjects, message );
+ }
+ }
+ }
+
+ private String createRollbackMessage( ReleaseDescriptor releaseDescriptor )
+ {
+ return MessageFormat.format( releaseDescriptor.getScmCommentPrefix() + rollbackMessageFormat,
+ new Object[]{releaseDescriptor.getScmReleaseLabel()} );
+ }
+
+}
\ No newline at end of file
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/ScmCommitPreparationPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/ScmCommitPreparationPhase.java
new file mode 100644
index 000000000..b1f47e8b6
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/ScmCommitPreparationPhase.java
@@ -0,0 +1,86 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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 org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseFailureException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.apache.maven.shared.release.scm.ReleaseScmCommandException;
+import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
+
+import java.util.List;
+
+/**
+ * Commit the changes that were done to prepare the branch or tag to the SCM.
+ *
+ * @author Brett Porter
+ */
+public class ScmCommitPreparationPhase
+ extends AbstractScmCommitPhase
+{
+
+ protected void runLogic( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects, ReleaseResult result, boolean simulating )
+ throws ReleaseScmCommandException, ReleaseExecutionException, ReleaseScmRepositoryException
+ {
+ // no prepare-commit required
+ if ( releaseDescriptor.isSuppressCommitBeforeTagOrBranch() )
+ {
+ if ( simulating )
+ {
+ logInfo( result, "Full run would not commit changes, "
+ + "because suppressCommitBeforeTagOrBranch is set to true." );
+ }
+ else
+ {
+ logInfo( result,
+ "Modified POMs are not committed because suppressCommitBeforeTagOrBranch is set to true." );
+ }
+ }
+ // commit development versions required
+ else
+ {
+ String message = createMessage( reactorProjects, releaseDescriptor );
+ if ( simulating )
+ {
+ simulateCheckins( releaseDescriptor, reactorProjects, result, message );
+ }
+ else
+ {
+ performCheckins( releaseDescriptor, releaseEnvironment, reactorProjects, message );
+ }
+ }
+ }
+
+ protected void validateConfiguration( ReleaseDescriptor releaseDescriptor )
+ throws ReleaseFailureException
+ {
+ super.validateConfiguration( releaseDescriptor );
+
+ if ( releaseDescriptor.isSuppressCommitBeforeTagOrBranch() && releaseDescriptor.isRemoteTagging() )
+ {
+ throw new ReleaseFailureException(
+ "Cannot perform a remote tag or branch without committing the working copy first." );
+ }
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/ScmTagPhase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/ScmTagPhase.java
new file mode 100644
index 000000000..b85185280
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/ScmTagPhase.java
@@ -0,0 +1,193 @@
+package org.apache.maven.shared.release.phase;
+
+/*
+ * 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.File;
+import java.util.List;
+
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.scm.ScmException;
+import org.apache.maven.scm.ScmFileSet;
+import org.apache.maven.scm.ScmTagParameters;
+import org.apache.maven.scm.command.tag.TagScmResult;
+import org.apache.maven.scm.manager.NoSuchScmProviderException;
+import org.apache.maven.scm.provider.ScmProvider;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.maven.scm.repository.ScmRepositoryException;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.ReleaseFailureException;
+import org.apache.maven.shared.release.ReleaseResult;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.env.ReleaseEnvironment;
+import org.apache.maven.shared.release.scm.ReleaseScmCommandException;
+import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
+import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
+import org.apache.maven.shared.release.util.ReleaseUtil;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+
+/**
+ * Tag the SCM repository after committing the release.
+ *
+ * @author Brett Porter
+ */
+@Component( role = ReleasePhase.class, hint = "scm-tag" )
+public class ScmTagPhase
+ extends AbstractReleasePhase
+{
+ /**
+ * Tool that gets a configured SCM repository from release configuration.
+ */
+ @Requirement
+ private ScmRepositoryConfigurator scmRepositoryConfigurator;
+
+ @Override
+ public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult relResult = new ReleaseResult();
+
+ validateConfiguration( releaseDescriptor );
+
+ if ( releaseDescriptor.getWaitBeforeTagging() > 0 )
+ {
+ logInfo( relResult, "Waiting for " + releaseDescriptor.getWaitBeforeTagging()
+ + " seconds before tagging the release." );
+ try
+ {
+ Thread.sleep( 1000L * releaseDescriptor.getWaitBeforeTagging() );
+ }
+ catch ( InterruptedException e )
+ {
+ // Ignore
+ }
+ }
+
+ logInfo( relResult, "Tagging release with the label " + releaseDescriptor.getScmReleaseLabel() + "..." );
+
+ ReleaseDescriptor basedirAlignedReleaseDescriptor =
+ ReleaseUtil.createBasedirAlignedReleaseDescriptor( releaseDescriptor, reactorProjects );
+
+ ScmRepository repository;
+ ScmProvider provider;
+ try
+ {
+ repository =
+ scmRepositoryConfigurator.getConfiguredRepository( basedirAlignedReleaseDescriptor.getScmSourceUrl(),
+ releaseDescriptor,
+ releaseEnvironment.getSettings() );
+
+ repository.getProviderRepository().setPushChanges( releaseDescriptor.isPushChanges() );
+
+ repository.getProviderRepository().setWorkItem( releaseDescriptor.getWorkItem() );
+
+ provider = scmRepositoryConfigurator.getRepositoryProvider( repository );
+ }
+ catch ( ScmRepositoryException e )
+ {
+ throw new ReleaseScmRepositoryException( e.getMessage(), e.getValidationMessages() );
+ }
+ catch ( NoSuchScmProviderException e )
+ {
+ throw new ReleaseExecutionException( "Unable to configure SCM repository: " + e.getMessage(), e );
+ }
+
+ TagScmResult result;
+ try
+ {
+ // TODO: want includes/excludes?
+ ScmFileSet fileSet = new ScmFileSet( new File( basedirAlignedReleaseDescriptor.getWorkingDirectory() ) );
+ String tagName = releaseDescriptor.getScmReleaseLabel();
+ ScmTagParameters scmTagParameters =
+ new ScmTagParameters( releaseDescriptor.getScmCommentPrefix() + "copy for tag " + tagName );
+ scmTagParameters.setRemoteTagging( releaseDescriptor.isRemoteTagging() );
+ scmTagParameters.setScmRevision( releaseDescriptor.getScmReleasedPomRevision() );
+ scmTagParameters.setPinExternals( releaseDescriptor.isPinExternals() );
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug(
+ "ScmTagPhase :: scmTagParameters remotingTag " + releaseDescriptor.isRemoteTagging() );
+ getLogger().debug(
+ "ScmTagPhase :: scmTagParameters scmRevision " + releaseDescriptor.getScmReleasedPomRevision() );
+ getLogger().debug(
+ "ScmTagPhase :: scmTagParameters pinExternals " + releaseDescriptor.isPinExternals() );
+ getLogger().debug( "ScmTagPhase :: fileSet " + fileSet );
+ }
+ result = provider.tag( repository, fileSet, tagName, scmTagParameters );
+ }
+ catch ( ScmException e )
+ {
+ throw new ReleaseExecutionException( "An error is occurred in the tag process: " + e.getMessage(), e );
+ }
+
+ if ( !result.isSuccess() )
+ {
+ throw new ReleaseScmCommandException( "Unable to tag SCM", result );
+ }
+
+ relResult.setResultCode( ReleaseResult.SUCCESS );
+
+ return relResult;
+ }
+
+ @Override
+ public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
+ List reactorProjects )
+ throws ReleaseExecutionException, ReleaseFailureException
+ {
+ ReleaseResult result = new ReleaseResult();
+
+ validateConfiguration( releaseDescriptor );
+
+ ReleaseDescriptor basedirAlignedReleaseDescriptor =
+ ReleaseUtil.createBasedirAlignedReleaseDescriptor( releaseDescriptor, reactorProjects );
+
+ if ( releaseDescriptor.isRemoteTagging() )
+ {
+ logInfo( result,
+ "Full run would tag working copy '"
+ + basedirAlignedReleaseDescriptor.getWorkingDirectory() + "'" );
+ }
+ else
+ {
+ logInfo( result, "Full run would tag remotely '"
+ + basedirAlignedReleaseDescriptor.getScmSourceUrl() + "'" );
+ }
+ logInfo( result, " with label '" + releaseDescriptor.getScmReleaseLabel() + "'" );
+ if ( releaseDescriptor.isPinExternals() )
+ {
+ logInfo( result, " and pinned externals" );
+ }
+
+ result.setResultCode( ReleaseResult.SUCCESS );
+
+ return result;
+ }
+
+ private static void validateConfiguration( ReleaseDescriptor releaseDescriptor )
+ throws ReleaseFailureException
+ {
+ if ( releaseDescriptor.getScmReleaseLabel() == null )
+ {
+ throw new ReleaseFailureException( "A release label is required for committing" );
+ }
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/policies/DefaultNamingPolicy.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/policies/DefaultNamingPolicy.java
new file mode 100644
index 000000000..a646dfcc1
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/policies/DefaultNamingPolicy.java
@@ -0,0 +1,42 @@
+package org.apache.maven.shared.release.policies;
+
+/*
+ * 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 org.apache.maven.shared.release.policy.PolicyException;
+import org.apache.maven.shared.release.policy.naming.NamingPolicy;
+import org.apache.maven.shared.release.policy.naming.NamingPolicyRequest;
+import org.apache.maven.shared.release.policy.naming.NamingPolicyResult;
+import org.codehaus.plexus.component.annotations.Component;
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 3.0.0
+ */
+@Component( role = NamingPolicy.class, hint = "default" )
+public class DefaultNamingPolicy implements NamingPolicy
+{
+ @Override
+ public NamingPolicyResult getName( NamingPolicyRequest request )
+ throws PolicyException
+ {
+ return new NamingPolicyResult().setName( request.getArtifactId() + "-" + request.getVersion() );
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/policies/DefaultVersionPolicy.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/policies/DefaultVersionPolicy.java
new file mode 100644
index 000000000..3f323719a
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/policies/DefaultVersionPolicy.java
@@ -0,0 +1,56 @@
+package org.apache.maven.shared.release.policies;
+
+/*
+ * 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 org.apache.maven.shared.release.policy.PolicyException;
+import org.apache.maven.shared.release.policy.version.VersionPolicy;
+import org.apache.maven.shared.release.policy.version.VersionPolicyRequest;
+import org.apache.maven.shared.release.policy.version.VersionPolicyResult;
+import org.apache.maven.shared.release.versions.DefaultVersionInfo;
+import org.apache.maven.shared.release.versions.VersionParseException;
+import org.codehaus.plexus.component.annotations.Component;
+
+/**
+ *
+ * @author Robert Scholte
+ */
+@Component( role = VersionPolicy.class, hint = "default" )
+public class DefaultVersionPolicy
+ implements VersionPolicy
+{
+
+ @Override
+ public VersionPolicyResult getReleaseVersion( VersionPolicyRequest request )
+ throws PolicyException, VersionParseException
+ {
+ String releaseVersion = new DefaultVersionInfo( request.getVersion() ).getReleaseVersionString();
+ return new VersionPolicyResult().setVersion( releaseVersion );
+ }
+
+ @Override
+ public VersionPolicyResult getDevelopmentVersion( VersionPolicyRequest request )
+ throws PolicyException, VersionParseException
+ {
+ String developmentVersion =
+ new DefaultVersionInfo( request.getVersion() ).getNextVersion().getSnapshotVersionString();
+ return new VersionPolicyResult().setVersion( developmentVersion );
+ }
+
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/ClearCaseScmTranslator.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/ClearCaseScmTranslator.java
new file mode 100644
index 000000000..2baa91520
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/ClearCaseScmTranslator.java
@@ -0,0 +1,75 @@
+package org.apache.maven.shared.release.scm;
+
+/*
+ * 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 org.codehaus.plexus.component.annotations.Component;
+
+/**
+ * ClearCase tag translator.
+ *
+ * @author Arne Degenring
+ */
+@Component( role = ScmTranslator.class, hint = "clearcase" )
+public class ClearCaseScmTranslator
+ implements ScmTranslator
+{
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String translateBranchUrl( String url, String branchName, String branchBase )
+ {
+ return url;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String translateTagUrl( String url, String tag, String tagBase )
+ {
+ return url;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String resolveTag( String tag )
+ {
+ if ( !"HEAD".equals( tag ) )
+ {
+ return tag;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toRelativePath( String path )
+ {
+ return path;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/CvsScmTranslator.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/CvsScmTranslator.java
new file mode 100644
index 000000000..60370a932
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/CvsScmTranslator.java
@@ -0,0 +1,75 @@
+package org.apache.maven.shared.release.scm;
+
+/*
+ * 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 org.codehaus.plexus.component.annotations.Component;
+
+/**
+ * CVS tag translator.
+ *
+ * @author Brett Porter
+ */
+@Component( role = ScmTranslator.class, hint = "cvs" )
+public class CvsScmTranslator
+ implements ScmTranslator
+{
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String translateBranchUrl( String url, String branchName, String branchBase )
+ {
+ return url;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String translateTagUrl( String url, String tag, String tagBase )
+ {
+ return url;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String resolveTag( String tag )
+ {
+ if ( !"HEAD".equals( tag ) )
+ {
+ return tag;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toRelativePath( String path )
+ {
+ return path;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/DefaultScmRepositoryConfigurator.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/DefaultScmRepositoryConfigurator.java
new file mode 100644
index 000000000..3bfa4dd7c
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/DefaultScmRepositoryConfigurator.java
@@ -0,0 +1,214 @@
+package org.apache.maven.shared.release.scm;
+
+/*
+ * 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 org.apache.maven.scm.manager.NoSuchScmProviderException;
+import org.apache.maven.scm.manager.ScmManager;
+import org.apache.maven.scm.provider.ScmProvider;
+import org.apache.maven.scm.provider.ScmProviderRepository;
+import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost;
+import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.maven.scm.repository.ScmRepositoryException;
+import org.apache.maven.settings.Server;
+import org.apache.maven.settings.Settings;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.logging.AbstractLogEnabled;
+import org.codehaus.plexus.util.StringUtils;
+import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;
+import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException;
+
+/**
+ * Tool that gets a configured SCM repository from release configuration.
+ *
+ * @author Brett Porter
+ */
+@Component( role = ScmRepositoryConfigurator.class, instantiationStrategy = "singleton" )
+public class DefaultScmRepositoryConfigurator
+ extends AbstractLogEnabled
+ implements ScmRepositoryConfigurator
+{
+ /**
+ * The SCM manager.
+ */
+ @Requirement
+ private ScmManager scmManager;
+
+ /**
+ * When this plugin requires Maven 3.0 as minimum, this component can be removed and o.a.m.s.c.SettingsDecrypter be
+ * used instead.
+ */
+ @Requirement( hint = "mng-4384" )
+ private SecDispatcher secDispatcher;
+
+ @Override
+ public ScmRepository getConfiguredRepository( ReleaseDescriptor releaseDescriptor, Settings settings )
+ throws ScmRepositoryException, NoSuchScmProviderException
+ {
+ String url = releaseDescriptor.getScmSourceUrl();
+ return getConfiguredRepository( url, releaseDescriptor, settings );
+ }
+
+ @Override
+ public ScmRepository getConfiguredRepository( String url, ReleaseDescriptor releaseDescriptor, Settings settings )
+ throws ScmRepositoryException, NoSuchScmProviderException
+ {
+ String username = releaseDescriptor.getScmUsername();
+ String password = releaseDescriptor.getScmPassword();
+ String privateKey = releaseDescriptor.getScmPrivateKey();
+ String passphrase = releaseDescriptor.getScmPrivateKeyPassPhrase();
+
+ ScmRepository repository = scmManager.makeScmRepository( url );
+
+ ScmProviderRepository scmRepo = repository.getProviderRepository();
+
+ //MRELEASE-76
+ scmRepo.setPersistCheckout( false );
+
+ if ( settings != null )
+ {
+ Server server = null;
+
+ if ( releaseDescriptor.getScmId() != null )
+ {
+ server = settings.getServer( releaseDescriptor.getScmId() );
+ }
+
+ if ( server == null && repository.getProviderRepository() instanceof ScmProviderRepositoryWithHost )
+ {
+ ScmProviderRepositoryWithHost repositoryWithHost =
+ (ScmProviderRepositoryWithHost) repository.getProviderRepository();
+ String host = repositoryWithHost.getHost();
+
+ int port = repositoryWithHost.getPort();
+
+ if ( port > 0 )
+ {
+ host += ":" + port;
+ }
+
+ // TODO: this is a bit dodgy - id is not host, but since we don't have a field we make an assumption
+ server = settings.getServer( host );
+ }
+
+ if ( server != null )
+ {
+ if ( username == null )
+ {
+ username = server.getUsername();
+ }
+
+ if ( password == null )
+ {
+ password = decrypt( server.getPassword(), server.getId() );
+ }
+
+ if ( privateKey == null )
+ {
+ privateKey = server.getPrivateKey();
+ }
+
+ if ( passphrase == null )
+ {
+ passphrase = decrypt( server.getPassphrase(), server.getId() );
+ }
+ }
+ }
+
+ if ( !StringUtils.isEmpty( username ) )
+ {
+ scmRepo.setUser( username );
+ }
+ if ( !StringUtils.isEmpty( password ) )
+ {
+ scmRepo.setPassword( password );
+ }
+
+ if ( scmRepo instanceof ScmProviderRepositoryWithHost )
+ {
+ ScmProviderRepositoryWithHost repositoryWithHost = (ScmProviderRepositoryWithHost) scmRepo;
+ if ( !StringUtils.isEmpty( privateKey ) )
+ {
+ repositoryWithHost.setPrivateKey( privateKey );
+ }
+
+ if ( !StringUtils.isEmpty( passphrase ) )
+ {
+ repositoryWithHost.setPassphrase( passphrase );
+ }
+ }
+
+ if ( "svn".equals( repository.getProvider() ) )
+ {
+ SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) repository.getProviderRepository();
+
+ String tagBase = releaseDescriptor.getScmTagBase();
+ if ( !StringUtils.isEmpty( tagBase ) )
+ {
+ svnRepo.setTagBase( tagBase );
+ }
+
+ String branchBase = releaseDescriptor.getScmBranchBase();
+ if ( !StringUtils.isEmpty( branchBase ) )
+ {
+ svnRepo.setBranchBase( branchBase );
+ }
+ }
+
+ return repository;
+ }
+
+ private String decrypt( String str, String server )
+ {
+ try
+ {
+ return secDispatcher.decrypt( str );
+ }
+ catch ( SecDispatcherException e )
+ {
+ String msg =
+ "Failed to decrypt password/passphrase for server " + server + ", using auth token as is: "
+ + e.getMessage();
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().warn( msg, e );
+ }
+ else
+ {
+ getLogger().warn( msg );
+ }
+ return str;
+ }
+ }
+
+ @Override
+ public ScmProvider getRepositoryProvider( ScmRepository repository )
+ throws NoSuchScmProviderException
+ {
+ return scmManager.getProviderByRepository( repository );
+ }
+
+ public void setScmManager( ScmManager scmManager )
+ {
+ this.scmManager = scmManager;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/GitScmTranslator.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/GitScmTranslator.java
new file mode 100644
index 000000000..ccc67ce2e
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/GitScmTranslator.java
@@ -0,0 +1,54 @@
+package org.apache.maven.shared.release.scm;
+
+/*
+ * 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 org.codehaus.plexus.component.annotations.Component;
+
+/**
+ */
+@Component( role = ScmTranslator.class, hint = "git" )
+public class GitScmTranslator
+ implements ScmTranslator
+{
+
+ @Override
+ public String translateBranchUrl( String url, String branchName, String branchBase )
+ {
+ return url;
+ }
+
+ @Override
+ public String translateTagUrl( String url, String tag, String tagBase )
+ {
+ return url;
+ }
+
+ @Override
+ public String resolveTag( String tag )
+ {
+ return tag;
+ }
+
+ @Override
+ public String toRelativePath( String path )
+ {
+ return path;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/HgScmTranslator.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/HgScmTranslator.java
new file mode 100644
index 000000000..953d5346f
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/HgScmTranslator.java
@@ -0,0 +1,54 @@
+package org.apache.maven.shared.release.scm;
+
+/*
+ * 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 org.codehaus.plexus.component.annotations.Component;
+
+/**
+ */
+@Component( role = ScmTranslator.class, hint = "hg" )
+public class HgScmTranslator
+ implements ScmTranslator
+{
+
+ @Override
+ public String translateBranchUrl( String url, String branchName, String branchBase )
+ {
+ return url;
+ }
+
+ @Override
+ public String translateTagUrl( String url, String tag, String tagBase )
+ {
+ return url;
+ }
+
+ @Override
+ public String resolveTag( String tag )
+ {
+ return tag;
+ }
+
+ @Override
+ public String toRelativePath( String path )
+ {
+ return path;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/IdentifiedScm.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/IdentifiedScm.java
new file mode 100644
index 000000000..51b3ad1bb
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/IdentifiedScm.java
@@ -0,0 +1,53 @@
+package org.apache.maven.shared.release.scm;
+
+/*
+ * 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 org.apache.maven.model.Scm;
+
+/**
+ * Unlike repositories the scm of the Project Model doesn't have an id.
+ * This makes it quite hard to bind it to the credentials of a server as specified in the settings.xml
+ *
+ * @author Robert Scholte
+ *
+ * @since 2.3
+ */
+public class IdentifiedScm
+ extends Scm
+{
+
+ private String id;
+
+ /**
+ * @return the id
+ */
+ public String getId()
+ {
+ return id;
+ }
+
+ /**
+ * @param id the id to set
+ */
+ public void setId( String id )
+ {
+ this.id = id;
+ }
+}
\ No newline at end of file
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/JazzScmTranslator.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/JazzScmTranslator.java
new file mode 100644
index 000000000..d5fd8cded
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/JazzScmTranslator.java
@@ -0,0 +1,104 @@
+package org.apache.maven.shared.release.scm;
+
+/*
+ * 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.File;
+
+import org.codehaus.plexus.component.annotations.Component;
+
+/**
+ * Jazz tag translator.
+ *
+ * @author Chris Graham
+ */
+@Component( role = ScmTranslator.class, hint = "jazz" )
+public class JazzScmTranslator
+ implements ScmTranslator
+{
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String translateBranchUrl( String url, String branchName, String branchBase )
+ {
+ // Jazz URL's (currently) take the form:
+ // "scm:jazz:[username[;password]@]http[s]://server_name[:port]/jazzPath:repositoryWorkspace"
+ // Eg:
+ // scm:jazz:Deb;Deb@https://rtc:9444/jazz:BogusRepositoryWorkspace
+ int i = url.lastIndexOf( ':' );
+ url = url.substring( 0, i + 1 );
+ if ( branchName != null && branchName.endsWith( "/" ) )
+ {
+ // Remove the trailing "/", if present.
+ branchName = branchName.substring( 0, branchName.length() - 1 );
+ }
+ url = url + branchName;
+ return url;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String translateTagUrl( String url, String tag, String tagBase )
+ {
+ // Jazz URL's (currently) take the form:
+ // "scm:jazz:[username[;password]@]http[s]://server_name[:port]/jazzPath:repositoryWorkspace"
+ // Eg:
+ // scm:jazz:Deb;Deb@https://rtc:9444/jazz:BogusRepositoryWorkspace
+ int i = url.lastIndexOf( ':' );
+ url = url.substring( 0, i + 1 );
+ if ( tag != null && tag.endsWith( "/" ) )
+ {
+ // Remove the trailing "/", if present.
+ tag = tag.substring( 0, tag.length() - 1 );
+ }
+ url = url + tag;
+ return url;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String resolveTag( String tag )
+ {
+ // project.scm.tag is not required, so return null.
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toRelativePath( String path )
+ {
+ String relativePath;
+ if ( path.startsWith( "\\" ) || path.startsWith( "/" ) )
+ {
+ relativePath = path.substring( 1 );
+ }
+ else
+ {
+ relativePath = path;
+ }
+ return relativePath.replace( "\\", File.separator ).replace( "/", File.separator );
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/ReleaseScmCommandException.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/ReleaseScmCommandException.java
new file mode 100644
index 000000000..96562f202
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/ReleaseScmCommandException.java
@@ -0,0 +1,38 @@
+package org.apache.maven.shared.release.scm;
+
+/*
+ * 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 org.apache.maven.scm.ScmResult;
+import org.apache.maven.shared.release.ReleaseFailureException;
+
+/**
+ * Exception occurring during an SCM operation.
+ *
+ * @author Brett Porter
+ */
+public class ReleaseScmCommandException
+ extends ReleaseFailureException
+{
+ public ReleaseScmCommandException( String message, ScmResult result )
+ {
+ super( message + "\nProvider message:\n" + result.getProviderMessage() + "\nCommand output:\n"
+ + result.getCommandOutput() );
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/ReleaseScmRepositoryException.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/ReleaseScmRepositoryException.java
new file mode 100644
index 000000000..6edfb228b
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/ReleaseScmRepositoryException.java
@@ -0,0 +1,54 @@
+package org.apache.maven.shared.release.scm;
+
+/*
+ * 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 org.apache.maven.shared.release.ReleaseFailureException;
+
+import java.util.List;
+
+/**
+ * Exception occurring during an SCM repository operation.
+ *
+ * @author Brett Porter
+ */
+public class ReleaseScmRepositoryException
+ extends ReleaseFailureException
+{
+ public ReleaseScmRepositoryException( String message, List validationMessages )
+ {
+ super( message + listValidationMessages( validationMessages ) );
+ }
+
+ private static String listValidationMessages( List messages )
+ {
+ StringBuilder buffer = new StringBuilder();
+
+ if ( messages != null )
+ {
+ for ( String message : messages )
+ {
+ buffer.append( "\n - " );
+ buffer.append( message );
+ }
+ }
+
+ return buffer.toString();
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/ScmRepositoryConfigurator.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/ScmRepositoryConfigurator.java
new file mode 100644
index 000000000..6281d631e
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/ScmRepositoryConfigurator.java
@@ -0,0 +1,70 @@
+package org.apache.maven.shared.release.scm;
+
+/*
+ * 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 org.apache.maven.scm.manager.NoSuchScmProviderException;
+import org.apache.maven.scm.provider.ScmProvider;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.maven.scm.repository.ScmRepositoryException;
+import org.apache.maven.settings.Settings;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+
+/**
+ * Configure an SCM repository using release configuration.
+ *
+ * @author Brett Porter
+ */
+public interface ScmRepositoryConfigurator
+{
+ /**
+ * Construct a configured SCM repository from a release configuration.
+ *
+ * @param releaseDescriptor the configuration to insert into the repository
+ * @param settings the settings.xml configuraiton
+ * @return the repository created
+ * @throws ScmRepositoryException if it is not possible to create a suitable SCM repository
+ * @throws NoSuchScmProviderException if the requested SCM provider is not available
+ */
+ ScmRepository getConfiguredRepository( ReleaseDescriptor releaseDescriptor, Settings settings )
+ throws ScmRepositoryException, NoSuchScmProviderException;
+
+ /**
+ * Get the SCM provider used for the given SCM repository.
+ *
+ * @param repository the SCM repository
+ * @return the SCM provider
+ * @throws NoSuchScmProviderException if the requested SCM provider is not available
+ */
+ ScmProvider getRepositoryProvider( ScmRepository repository )
+ throws NoSuchScmProviderException;
+
+ /**
+ * Construct a configured SCM repository from a release configuration with an overridden base SCM URL.
+ *
+ * @param url the SCM URL to use instead of the one from the release descriptor
+ * @param releaseDescriptor the configuration to insert into the repository
+ * @param settings the settings.xml configuraiton
+ * @return the repository created
+ * @throws ScmRepositoryException if it is not possible to create a suitable SCM repository
+ * @throws NoSuchScmProviderException if the requested SCM provider is not available
+ */
+ ScmRepository getConfiguredRepository( String url, ReleaseDescriptor releaseDescriptor, Settings settings )
+ throws ScmRepositoryException, NoSuchScmProviderException;
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/ScmTranslator.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/ScmTranslator.java
new file mode 100644
index 000000000..693f6469a
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/ScmTranslator.java
@@ -0,0 +1,66 @@
+package org.apache.maven.shared.release.scm;
+
+/*
+ * 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.
+ */
+
+/**
+ * Translate the SCM information after tagging/reverting to trunk.
+ *
+ * @author Brett Porter
+ */
+// TODO move this API into SCM?
+public interface ScmTranslator
+{
+ /**
+ * Take an URL and find the correct replacement URL for a given branch.
+ *
+ * @param url the source URL
+ * @param branchName the branch name
+ * @param branchBase the branch base for providers that support it
+ * @return the replacement URL
+ */
+ String translateBranchUrl( String url, String branchName, String branchBase );
+
+ /**
+ * Take an URL and find the correct replacement URL for a given tag.
+ *
+ * @param url the source URL
+ * @param tag the tag
+ * @param tagBase the tag base for providers that support it
+ * @return the replacement URL
+ */
+ String translateTagUrl( String url, String tag, String tagBase );
+
+ /**
+ * Determine what tag should be added to the POM given the original tag and the new one.
+ *
+ * @param tag the new tag
+ * @return the tag to use, or null
if the provider does not use tags
+ */
+ String resolveTag( String tag );
+
+ /**
+ * Translates an ScmFile path to a path relative to the working directory.
+ *
+ * @param path the ScmFile path
+ * @return the relative path with OS specific File separator
+ * @since 2.3.1
+ */
+ String toRelativePath( String path );
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/SubversionScmTranslator.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/SubversionScmTranslator.java
new file mode 100644
index 000000000..85b6bb698
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/scm/SubversionScmTranslator.java
@@ -0,0 +1,72 @@
+package org.apache.maven.shared.release.scm;
+
+/*
+ * 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 org.apache.maven.scm.ScmBranch;
+import org.apache.maven.scm.ScmTag;
+import org.apache.maven.scm.provider.svn.SvnTagBranchUtils;
+import org.codehaus.plexus.component.annotations.Component;
+
+/**
+ * Subversion tag translator.
+ *
+ * @author Brett Porter
+ */
+@Component( role = ScmTranslator.class, hint = "svn" )
+public class SubversionScmTranslator
+ implements ScmTranslator
+{
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String translateBranchUrl( String url, String branchName, String branchBase )
+ {
+ return SvnTagBranchUtils.resolveUrl( url, branchBase, SvnTagBranchUtils.SVN_BRANCHES,
+ new ScmBranch( branchName ) );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String translateTagUrl( String url, String tag, String tagBase )
+ {
+ return SvnTagBranchUtils.resolveUrl( url, tagBase, SvnTagBranchUtils.SVN_TAGS, new ScmTag( tag ) );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String resolveTag( String tag )
+ {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toRelativePath( String path )
+ {
+ return path;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/strategies/DefaultStrategy.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/strategies/DefaultStrategy.java
new file mode 100644
index 000000000..ad5c1ee6e
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/strategies/DefaultStrategy.java
@@ -0,0 +1,112 @@
+package org.apache.maven.shared.release.strategies;
+
+/*
+ * 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.util.List;
+
+import org.apache.maven.shared.release.strategy.Strategy;
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 3.0.0
+ */
+public class DefaultStrategy implements Strategy
+{
+ /**
+ * The phases of release to run, and in what order.
+ */
+ private List preparePhases;
+
+ /**
+ * The phases of release to run to perform.
+ */
+ private List performPhases;
+
+ /**
+ * The phases of release to run to rollback changes
+ */
+ private List rollbackPhases;
+
+ /**
+ * The phases to create a branch.
+ */
+ private List branchPhases;
+
+ /**
+ * The phases to create update versions.
+ */
+ private List updateVersionsPhases;
+
+ @Override
+ public List getPreparePhases()
+ {
+ return preparePhases;
+ }
+
+ public void setPreparePhases( List preparePhases )
+ {
+ this.preparePhases = preparePhases;
+ }
+
+ @Override
+ public List getPerformPhases()
+ {
+ return performPhases;
+ }
+
+ public void setPerformPhases( List performPhases )
+ {
+ this.performPhases = performPhases;
+ }
+
+ @Override
+ public List getRollbackPhases()
+ {
+ return rollbackPhases;
+ }
+
+ public void setRollbackPhases( List rollbackPhases )
+ {
+ this.rollbackPhases = rollbackPhases;
+ }
+
+ @Override
+ public List getBranchPhases()
+ {
+ return branchPhases;
+ }
+
+ public void setBranchPhases( List branchPhases )
+ {
+ this.branchPhases = branchPhases;
+ }
+
+ @Override
+ public List getUpdateVersionsPhases()
+ {
+ return updateVersionsPhases;
+ }
+
+ public void setUpdateVersionsPhases( List updateVersionsPhases )
+ {
+ this.updateVersionsPhases = updateVersionsPhases;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/MavenCoordinate.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/MavenCoordinate.java
new file mode 100644
index 000000000..69b08e172
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/MavenCoordinate.java
@@ -0,0 +1,39 @@
+package org.apache.maven.shared.release.transform;
+
+/*
+ * 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.
+ */
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 3.0
+ */
+public interface MavenCoordinate
+{
+ String getGroupId();
+
+ String getArtifactId();
+
+ String getVersion();
+
+ void setVersion( String version );
+
+ // @todo helper method during refactoring, will be removed
+ String getName();
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/ModelETL.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/ModelETL.java
new file mode 100644
index 000000000..e0c255058
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/ModelETL.java
@@ -0,0 +1,43 @@
+package org.apache.maven.shared.release.transform;
+
+/*
+ * 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.File;
+
+import org.apache.maven.model.Model;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 3.0
+ */
+public interface ModelETL
+{
+ void extract( File pomFile ) throws ReleaseExecutionException;
+
+ void transform();
+
+ void load( File pomFile ) throws ReleaseExecutionException;
+
+ // will be removed once transform() is implemented
+ @Deprecated
+ Model getModel();
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/ModelETLFactory.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/ModelETLFactory.java
new file mode 100644
index 000000000..4748edc93
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/ModelETLFactory.java
@@ -0,0 +1,30 @@
+package org.apache.maven.shared.release.transform;
+
+/*
+ * 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.
+ */
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 3.0
+ */
+public interface ModelETLFactory
+{
+ ModelETL newInstance( ModelETLRequest request );
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/ModelETLRequest.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/ModelETLRequest.java
new file mode 100644
index 000000000..ae57d1f55
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/ModelETLRequest.java
@@ -0,0 +1,67 @@
+package org.apache.maven.shared.release.transform;
+
+/*
+ * 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 org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 3.0
+ */
+public class ModelETLRequest
+{
+ private String lineSeparator;
+
+ private MavenProject project;
+
+ private ReleaseDescriptor releaseDescriptor;
+
+ public String getLineSeparator()
+ {
+ return lineSeparator;
+ }
+
+ public void setLineSeparator( String lineSeparator )
+ {
+ this.lineSeparator = lineSeparator;
+ }
+
+ public MavenProject getProject()
+ {
+ return project;
+ }
+
+ public void setProject( MavenProject project )
+ {
+ this.project = project;
+ }
+
+ public ReleaseDescriptor getReleaseDescriptor()
+ {
+ return releaseDescriptor;
+ }
+
+ public void setReleaseDescriptor( ReleaseDescriptor releaseDescriptor )
+ {
+ this.releaseDescriptor = releaseDescriptor;
+ }
+}
\ No newline at end of file
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomBuild.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomBuild.java
new file mode 100644
index 000000000..433c8f49c
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomBuild.java
@@ -0,0 +1,329 @@
+package org.apache.maven.shared.release.transform.jdom;
+
+/*
+ * 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.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.maven.model.Build;
+import org.apache.maven.model.Extension;
+import org.apache.maven.model.Plugin;
+import org.apache.maven.model.PluginManagement;
+import org.apache.maven.model.Resource;
+import org.jdom.Element;
+/**
+ * JDom implementation of poms BUILD element
+ *
+ * @author Robert Scholte
+ * @since 3.0
+ */
+public class JDomBuild
+ extends Build
+{
+ private final Element build;
+
+ public JDomBuild( Element build )
+ {
+ this.build = build;
+ }
+
+ @Override
+ public void addExtension( Extension extension )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List getExtensions()
+ {
+ Element extensionsElm = build.getChild( "extensions", build.getNamespace() );
+ if ( extensionsElm == null )
+ {
+ return Collections.emptyList();
+ }
+ else
+ {
+ List extensionElms = extensionsElm.getChildren( "extension", build.getNamespace() );
+
+ List extensions = new ArrayList<>( extensionElms.size() );
+ for ( Element extensionElm : extensionElms )
+ {
+ extensions.add( new JDomExtension( extensionElm ) );
+ }
+ return extensions;
+ }
+ }
+
+ @Override
+ public String getOutputDirectory()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getScriptSourceDirectory()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getSourceDirectory()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getTestOutputDirectory()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getTestSourceDirectory()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeExtension( Extension extension )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setExtensions( List extensions )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setOutputDirectory( String outputDirectory )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setScriptSourceDirectory( String scriptSourceDirectory )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setSourceDirectory( String sourceDirectory )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setTestOutputDirectory( String testOutputDirectory )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setTestSourceDirectory( String testSourceDirectory )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addFilter( String string )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addResource( Resource resource )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addTestResource( Resource resource )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getDefaultGoal()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getDirectory()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List getFilters()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getFinalName()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List getResources()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List getTestResources()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeFilter( String string )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeResource( Resource resource )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeTestResource( Resource resource )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setDefaultGoal( String defaultGoal )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setDirectory( String directory )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setFilters( List filters )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setFinalName( String finalName )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setResources( List resources )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setTestResources( List testResources )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public PluginManagement getPluginManagement()
+ {
+ Element pluginManagementElm = build.getChild( "pluginManagement", build.getNamespace() );
+ if ( pluginManagementElm == null )
+ {
+ return null;
+ }
+ else
+ {
+ return new JDomPluginManagement( pluginManagementElm );
+ }
+ }
+
+ @Override
+ public void setPluginManagement( PluginManagement pluginManagement )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addPlugin( Plugin plugin )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List getPlugins()
+ {
+ Element pluginsElm = build.getChild( "plugins", build.getNamespace() );
+ if ( pluginsElm == null )
+ {
+ return Collections.emptyList();
+ }
+ else
+ {
+ List pluginElms =
+ pluginsElm.getChildren( "plugin", build.getNamespace() );
+
+ List plugins = new ArrayList<>( pluginElms.size() );
+
+ for ( Element pluginElm : pluginElms )
+ {
+ plugins.add( new JDomPlugin( pluginElm ) );
+ }
+
+ return plugins;
+ }
+ }
+
+ @Override
+ public void removePlugin( Plugin plugin )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setPlugins( List plugins )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void flushPluginMap()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Map getPluginsAsMap()
+ {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomDependency.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomDependency.java
new file mode 100644
index 000000000..96b99d1fd
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomDependency.java
@@ -0,0 +1,169 @@
+package org.apache.maven.shared.release.transform.jdom;
+
+/*
+ * 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.util.List;
+
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Exclusion;
+import org.apache.maven.shared.release.transform.MavenCoordinate;
+import org.jdom.Element;
+
+/**
+ * JDom implementation of poms DEPENDENCY element
+ *
+ * @author Robert Scholte
+ * @since 3.0
+ */
+public class JDomDependency extends Dependency implements MavenCoordinate
+{
+ private final MavenCoordinate coordinate;
+
+ public JDomDependency( Element dependency )
+ {
+ this.coordinate = new JDomMavenCoordinate( dependency );
+ }
+
+ @Override
+ public void addExclusion( Exclusion exclusion )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getArtifactId()
+ {
+ return coordinate.getArtifactId();
+ }
+
+ @Override
+ public String getClassifier()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List getExclusions()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getGroupId()
+ {
+ return coordinate.getGroupId();
+ }
+
+ @Override
+ public String getScope()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getSystemPath()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getType()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getVersion()
+ {
+ return coordinate.getVersion();
+ }
+
+ @Override
+ public boolean isOptional()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeExclusion( Exclusion exclusion )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setArtifactId( String artifactId )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setClassifier( String classifier )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setExclusions( List exclusions )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setGroupId( String groupId )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setOptional( boolean optional )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setScope( String scope )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setSystemPath( String systemPath )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setType( String type )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setVersion( String version )
+ {
+ coordinate.setVersion( version );
+ }
+
+ @Override
+ public String getName()
+ {
+ return "dependency";
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomDependencyManagement.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomDependencyManagement.java
new file mode 100644
index 000000000..aade7c8ef
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomDependencyManagement.java
@@ -0,0 +1,87 @@
+package org.apache.maven.shared.release.transform.jdom;
+
+/*
+ * 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.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.DependencyManagement;
+import org.jdom.Element;
+
+/**
+ * JDom implementation of poms DEPENDENCYMANAGEMENT element
+ *
+ * @author Robert Scholte
+ * @since 3.0
+ */
+public class JDomDependencyManagement extends DependencyManagement
+{
+ private final Element dependencyManagement;
+
+ public JDomDependencyManagement( Element dependencyManagement )
+ {
+ this.dependencyManagement = dependencyManagement;
+ }
+
+ @Override
+ public void addDependency( Dependency dependency )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List getDependencies()
+ {
+ Element dependenciesElm = dependencyManagement.getChild( "dependencies", dependencyManagement.getNamespace() );
+ if ( dependenciesElm == null )
+ {
+ return Collections.emptyList();
+ }
+ else
+ {
+ List dependencyElms =
+ dependenciesElm.getChildren( "dependency", dependencyManagement.getNamespace() );
+
+ List dependencies = new ArrayList<>( dependencyElms.size() );
+
+ for ( Element dependencyElm : dependencyElms )
+ {
+ dependencies.add( new JDomDependency( dependencyElm ) );
+ }
+
+ return dependencies;
+ }
+ }
+
+ @Override
+ public void removeDependency( Dependency dependency )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setDependencies( List dependencies )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomExtension.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomExtension.java
new file mode 100644
index 000000000..a5dca63f3
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomExtension.java
@@ -0,0 +1,82 @@
+package org.apache.maven.shared.release.transform.jdom;
+
+/*
+ * 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 org.apache.maven.model.Extension;
+import org.apache.maven.shared.release.transform.MavenCoordinate;
+import org.jdom.Element;
+
+/**
+ * JDom implementation of poms EXTENSION element
+ *
+ * @author Robert Scholte
+ * @since 3.0
+ */
+public class JDomExtension extends Extension implements MavenCoordinate
+{
+ private final MavenCoordinate coordinate;
+
+ public JDomExtension( Element extension )
+ {
+ this.coordinate = new JDomMavenCoordinate( extension );
+ }
+
+ @Override
+ public String getArtifactId()
+ {
+ return coordinate.getArtifactId();
+ }
+
+ @Override
+ public String getGroupId()
+ {
+ return coordinate.getGroupId();
+ }
+
+ @Override
+ public String getVersion()
+ {
+ return coordinate.getVersion();
+ }
+
+ @Override
+ public void setArtifactId( String artifactId )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setGroupId( String groupId )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setVersion( String version )
+ {
+ coordinate.setVersion( version );
+ }
+
+ @Override
+ public String getName()
+ {
+ return "extension";
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomMavenCoordinate.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomMavenCoordinate.java
new file mode 100644
index 000000000..83b927a4c
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomMavenCoordinate.java
@@ -0,0 +1,82 @@
+package org.apache.maven.shared.release.transform.jdom;
+
+/*
+ * 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 org.apache.maven.shared.release.transform.MavenCoordinate;
+import org.jdom.Element;
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 3.0
+ */
+public class JDomMavenCoordinate implements MavenCoordinate
+{
+ private final Element element;
+
+ public JDomMavenCoordinate( Element elm )
+ {
+ this.element = elm;
+ }
+
+ @Override
+ public String getGroupId()
+ {
+ return element.getChildTextTrim( "groupId", element.getNamespace() );
+ }
+
+ @Override
+ public String getArtifactId()
+ {
+ return element.getChildTextTrim( "artifactId", element.getNamespace() );
+ }
+
+ @Override
+ public String getVersion()
+ {
+ Element version = getVersionElement();
+ if ( version == null )
+ {
+ return null;
+ }
+ else
+ {
+ return version.getTextTrim();
+ }
+
+ }
+
+ private Element getVersionElement()
+ {
+ return element.getChild( "version", element.getNamespace() );
+ }
+
+ @Override
+ public void setVersion( String version )
+ {
+ JDomUtils.rewriteValue( getVersionElement(), version );
+ }
+
+ @Override
+ public String getName()
+ {
+ return element.getName();
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomModel.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomModel.java
new file mode 100644
index 000000000..88271ba26
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomModel.java
@@ -0,0 +1,227 @@
+package org.apache.maven.shared.release.transform.jdom;
+
+/*
+ * 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.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.maven.model.Build;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.DependencyManagement;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.Parent;
+import org.apache.maven.model.Profile;
+import org.apache.maven.model.Reporting;
+import org.apache.maven.model.Scm;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.Text;
+
+/**
+ * JDom implementation of poms PROJECT element
+ *
+ * @author Robert Scholte
+ * @since 3.0
+ */
+public class JDomModel extends Model
+{
+ private final Element project;
+
+ private final JDomModelBase modelBase;
+
+ public JDomModel( Document document )
+ {
+ this( document.getRootElement() );
+ }
+
+ public JDomModel( Element project )
+ {
+ this.project = project;
+ this.modelBase = new JDomModelBase( project );
+ }
+
+ @Override
+ public Build getBuild()
+ {
+ return modelBase.getBuild();
+ }
+
+ @Override
+ public List getDependencies()
+ {
+ return modelBase.getDependencies();
+ }
+
+ @Override
+ public DependencyManagement getDependencyManagement()
+ {
+ return modelBase.getDependencyManagement();
+ }
+
+ @Override
+ public Parent getParent()
+ {
+ Element elm = getParentElement();
+ if ( elm == null )
+ {
+ return null;
+ }
+ else
+ {
+ // this way scm setters change DOM tree immediately
+ return new JDomParent( elm );
+ }
+ }
+
+ private Element getParentElement()
+ {
+ return project.getChild( "parent", project.getNamespace() );
+ }
+
+ @Override
+ public List getProfiles()
+ {
+ Element profilesElm = project.getChild( "profiles", project.getNamespace() );
+ if ( profilesElm == null )
+ {
+ return Collections.emptyList();
+ }
+ else
+ {
+ List profileElms = profilesElm.getChildren( "profile", project.getNamespace() );
+
+ List profiles = new ArrayList<>( profileElms.size() );
+
+ for ( Element profileElm : profileElms )
+ {
+ profiles.add( new JDomProfile( profileElm ) );
+ }
+
+ return profiles;
+ }
+ }
+
+
+ @Override
+ public Properties getProperties()
+ {
+ Element properties = project.getChild( "properties", project.getNamespace() );
+
+ if ( properties == null )
+ {
+ return null;
+ }
+ else
+ {
+ return new JDomProperties( properties );
+ }
+ }
+
+ @Override
+ public Reporting getReporting()
+ {
+ Element reporting = project.getChild( "reporting", project.getNamespace() );
+
+ if ( reporting == null )
+ {
+ return null;
+ }
+ else
+ {
+ return new JDomReporting( reporting );
+ }
+ }
+
+ @Override
+ public void setScm( Scm scm )
+ {
+ if ( scm == null )
+ {
+ JDomUtils.rewriteElement( "scm", null, project, project.getNamespace() );
+ }
+ else
+ {
+ Element scmRoot = new Element( "scm" );
+ scmRoot.addContent( "\n " );
+
+ // Write current values to JDom tree
+ Scm jdomScm = new JDomScm( scmRoot );
+ jdomScm.setConnection( scm.getConnection() );
+ jdomScm.setDeveloperConnection( scm.getDeveloperConnection() );
+ jdomScm.setTag( scm.getTag() );
+ jdomScm.setUrl( scm.getUrl() );
+
+ project.addContent( "\n " ).addContent( scmRoot ).addContent( "\n" );
+ }
+ }
+
+ @Override
+ public Scm getScm()
+ {
+ Element elm = project.getChild( "scm", project.getNamespace() );
+ if ( elm == null )
+ {
+ return null;
+ }
+ else
+ {
+ // this way scm setters change DOM tree immediately
+ return new JDomScm( elm );
+ }
+ }
+
+ @Override
+ public void setVersion( String version )
+ {
+ Element versionElement = project.getChild( "version", project.getNamespace() );
+
+ String parentVersion;
+ Element parent = getParentElement();
+ if ( parent != null )
+ {
+ parentVersion = parent.getChildTextTrim( "version", project.getNamespace() );
+ }
+ else
+ {
+ parentVersion = null;
+ }
+
+ if ( versionElement == null )
+ {
+ if ( !version.equals( parentVersion ) )
+ {
+ // we will add this after artifactId, since it was missing but different from the inherited version
+ Element artifactIdElement = project.getChild( "artifactId", project.getNamespace() );
+ int index = project.indexOf( artifactIdElement );
+
+ versionElement = new Element( "version", project.getNamespace() );
+ versionElement.setText( version );
+ project.addContent( index + 1, new Text( "\n " ) );
+ project.addContent( index + 2, versionElement );
+ }
+ }
+ else
+ {
+ JDomUtils.rewriteValue( versionElement, version );
+ }
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomModelBase.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomModelBase.java
new file mode 100644
index 000000000..cdc6442ce
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomModelBase.java
@@ -0,0 +1,94 @@
+package org.apache.maven.shared.release.transform.jdom;
+
+/*
+ * 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.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.maven.model.Build;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.DependencyManagement;
+import org.jdom.Element;
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 3.0
+ */
+public class JDomModelBase
+{
+ private final Element modelBase;
+
+ public JDomModelBase( Element modelBase )
+ {
+ this.modelBase = modelBase;
+ }
+
+ public Build getBuild()
+ {
+ Element elm = modelBase.getChild( "build", modelBase.getNamespace() );
+ if ( elm == null )
+ {
+ return null;
+ }
+ else
+ {
+ // this way build setters change DOM tree immediately
+ return new JDomBuild( elm );
+ }
+ }
+
+ public List getDependencies()
+ {
+ Element dependenciesElm = modelBase.getChild( "dependencies", modelBase.getNamespace() );
+ if ( dependenciesElm == null )
+ {
+ return Collections.emptyList();
+ }
+ else
+ {
+ List dependencyElms = dependenciesElm.getChildren( "dependency", modelBase.getNamespace() );
+
+ List dependencies = new ArrayList<>( dependencyElms.size() );
+
+ for ( Element dependencyElm : dependencyElms )
+ {
+ dependencies.add( new JDomDependency( dependencyElm ) );
+ }
+
+ return dependencies;
+ }
+ }
+
+ public DependencyManagement getDependencyManagement()
+ {
+ Element elm = modelBase.getChild( "dependencyManagement", modelBase.getNamespace() );
+ if ( elm == null )
+ {
+ return null;
+ }
+ else
+ {
+ // this way build setters change DOM tree immediately
+ return new JDomDependencyManagement( elm );
+ }
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomModelETL.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomModelETL.java
new file mode 100644
index 000000000..e0b845788
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomModelETL.java
@@ -0,0 +1,234 @@
+package org.apache.maven.shared.release.transform.jdom;
+
+/*
+ * 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.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Iterator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.maven.model.Model;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.release.ReleaseExecutionException;
+import org.apache.maven.shared.release.config.ReleaseDescriptor;
+import org.apache.maven.shared.release.transform.ModelETL;
+import org.apache.maven.shared.release.util.ReleaseUtil;
+import org.codehaus.plexus.util.WriterFactory;
+import org.jdom.CDATA;
+import org.jdom.Comment;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.JDOMException;
+import org.jdom.Namespace;
+import org.jdom.filter.ContentFilter;
+import org.jdom.filter.ElementFilter;
+import org.jdom.input.SAXBuilder;
+import org.jdom.output.Format;
+import org.jdom.output.XMLOutputter;
+
+/**
+ * JDom implementation for extracting, transform, loading the Model (pom.xml)
+ *
+ * @author Robert Scholte
+ * @since 3.0
+ */
+public class JDomModelETL implements ModelETL
+{
+ private ReleaseDescriptor releaseDescriptor;
+
+ private MavenProject project;
+
+ private Document document;
+
+ private String intro = null;
+ private String outtro = null;
+
+ private String ls = ReleaseUtil.LS;
+
+ public void setLs( String ls )
+ {
+ this.ls = ls;
+ }
+
+ public void setReleaseDescriptor( ReleaseDescriptor releaseDescriptor )
+ {
+ this.releaseDescriptor = releaseDescriptor;
+ }
+
+ public void setProject( MavenProject project )
+ {
+ this.project = project;
+ }
+
+ @Override
+ public void extract( File pomFile ) throws ReleaseExecutionException
+ {
+ try
+ {
+ String content = ReleaseUtil.readXmlFile( pomFile, ls );
+ // we need to eliminate any extra whitespace inside elements, as JDOM will nuke it
+ content = content.replaceAll( "<([^!][^>]*?)\\s{2,}([^>]*?)>", "<$1 $2>" );
+ content = content.replaceAll( "(\\s{2,})/>", "$1 />" );
+
+ SAXBuilder builder = new SAXBuilder();
+ document = builder.build( new StringReader( content ) );
+
+ // Normalize line endings to platform's style (XML processors like JDOM normalize line endings to "\n" as
+ // per section 2.11 of the XML spec)
+ normaliseLineEndings( document );
+
+ // rewrite DOM as a string to find differences, since text outside the root element is not tracked
+ StringWriter w = new StringWriter();
+ Format format = Format.getRawFormat();
+ format.setLineSeparator( ls );
+ XMLOutputter out = new XMLOutputter( format );
+ out.output( document.getRootElement(), w );
+
+ int index = content.indexOf( w.toString() );
+ if ( index >= 0 )
+ {
+ intro = content.substring( 0, index );
+ outtro = content.substring( index + w.toString().length() );
+ }
+ else
+ {
+ /*
+ * NOTE: Due to whitespace, attribute reordering or entity expansion the above indexOf test can easily
+ * fail. So let's try harder. Maybe some day, when JDOM offers a StaxBuilder and this builder employes
+ * XMLInputFactory2.P_REPORT_PROLOG_WHITESPACE, this whole mess can be avoided.
+ */
+ // CHECKSTYLE_OFF: LocalFinalVariableName
+ final String SPACE = "\\s++";
+ final String XML = "<\\?(?:(?:[^\"'>]++)|(?:\"[^\"]*+\")|(?:'[^\']*+'))*+>";
+ final String INTSUB = "\\[(?:(?:[^\"'\\]]++)|(?:\"[^\"]*+\")|(?:'[^\']*+'))*+\\]";
+ final String DOCTYPE =
+ "]++)|(?:\"[^\"]*+\")|(?:'[^\']*+')|(?:" + INTSUB + "))*+>";
+ final String PI = XML;
+ final String COMMENT = "";
+
+ final String INTRO =
+ "(?:(?:" + SPACE + ")|(?:" + XML + ")|(?:" + DOCTYPE + ")|(?:" + COMMENT + ")|(?:" + PI + "))*";
+ final String OUTRO = "(?:(?:" + SPACE + ")|(?:" + COMMENT + ")|(?:" + PI + "))*";
+ final String POM = "(?s)(" + INTRO + ")(.*?)(" + OUTRO + ")";
+ // CHECKSTYLE_ON: LocalFinalVariableName
+
+ Matcher matcher = Pattern.compile( POM ).matcher( content );
+ if ( matcher.matches() )
+ {
+ intro = matcher.group( 1 );
+ outtro = matcher.group( matcher.groupCount() );
+ }
+ }
+ }
+ catch ( JDOMException | IOException e )
+ {
+ throw new ReleaseExecutionException( "Error reading POM: " + e.getMessage(), e );
+ }
+ }
+
+ @Override
+ public void transform()
+ {
+
+ }
+
+ @Override
+ public void load( File targetFile ) throws ReleaseExecutionException
+ {
+ writePom( targetFile, document, releaseDescriptor, project.getModelVersion(), intro, outtro );
+ }
+
+ @Override
+ public Model getModel()
+ {
+ return new JDomModel( document );
+ }
+
+ private void normaliseLineEndings( Document document )
+ {
+ for ( Iterator> i = document.getDescendants( new ContentFilter( ContentFilter.COMMENT ) ); i.hasNext(); )
+ {
+ Comment c = (Comment) i.next();
+ c.setText( ReleaseUtil.normalizeLineEndings( c.getText(), ls ) );
+ }
+ for ( Iterator> i = document.getDescendants( new ContentFilter( ContentFilter.CDATA ) ); i.hasNext(); )
+ {
+ CDATA c = (CDATA) i.next();
+ c.setText( ReleaseUtil.normalizeLineEndings( c.getText(), ls ) );
+ }
+ }
+
+ private void writePom( File pomFile, Document document, ReleaseDescriptor releaseDescriptor, String modelVersion,
+ String intro, String outtro )
+ throws ReleaseExecutionException
+ {
+ Element rootElement = document.getRootElement();
+
+ if ( releaseDescriptor.isAddSchema() )
+ {
+ Namespace pomNamespace = Namespace.getNamespace( "", "http://maven.apache.org/POM/" + modelVersion );
+ rootElement.setNamespace( pomNamespace );
+ Namespace xsiNamespace = Namespace.getNamespace( "xsi", "http://www.w3.org/2001/XMLSchema-instance" );
+ rootElement.addNamespaceDeclaration( xsiNamespace );
+
+ if ( rootElement.getAttribute( "schemaLocation", xsiNamespace ) == null )
+ {
+ rootElement.setAttribute( "schemaLocation", "http://maven.apache.org/POM/" + modelVersion
+ + " https://maven.apache.org/xsd/maven-" + modelVersion + ".xsd", xsiNamespace );
+ }
+
+ // the empty namespace is considered equal to the POM namespace, so match them up to avoid extra xmlns=""
+ ElementFilter elementFilter = new ElementFilter( Namespace.getNamespace( "" ) );
+ for ( Iterator> i = rootElement.getDescendants( elementFilter ); i.hasNext(); )
+ {
+ Element e = (Element) i.next();
+ e.setNamespace( pomNamespace );
+ }
+ }
+
+
+ try ( Writer writer = WriterFactory.newXmlWriter( pomFile ) )
+ {
+ if ( intro != null )
+ {
+ writer.write( intro );
+ }
+
+ Format format = Format.getRawFormat();
+ format.setLineSeparator( ls );
+ XMLOutputter out = new XMLOutputter( format );
+ out.output( document.getRootElement(), writer );
+
+ if ( outtro != null )
+ {
+ writer.write( outtro );
+ }
+ }
+ catch ( IOException e )
+ {
+ throw new ReleaseExecutionException( "Error writing POM: " + e.getMessage(), e );
+ }
+ }
+
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomModelETLFactory.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomModelETLFactory.java
new file mode 100644
index 000000000..26ebe5de5
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomModelETLFactory.java
@@ -0,0 +1,46 @@
+package org.apache.maven.shared.release.transform.jdom;
+
+/*
+ * 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 org.apache.maven.shared.release.transform.ModelETLRequest;
+import org.apache.maven.shared.release.transform.ModelETLFactory;
+import org.codehaus.plexus.component.annotations.Component;
+
+/**
+ * @author Robert Scholte
+ * @since 3.0
+ */
+@Component( role = ModelETLFactory.class, hint = JDomModelETLFactory.ROLE_HINT )
+public class JDomModelETLFactory implements ModelETLFactory
+{
+ public static final String ROLE_HINT = "jdom-sax";
+
+ @Override
+ public JDomModelETL newInstance( ModelETLRequest request )
+ {
+ JDomModelETL result = new JDomModelETL();
+
+ result.setLs( request.getLineSeparator() );
+ result.setProject( request.getProject() );
+ result.setReleaseDescriptor( request.getReleaseDescriptor() );
+
+ return result;
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomParent.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomParent.java
new file mode 100644
index 000000000..be345df4c
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomParent.java
@@ -0,0 +1,93 @@
+package org.apache.maven.shared.release.transform.jdom;
+
+/*
+ * 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 org.apache.maven.model.Parent;
+import org.jdom.Element;
+
+/**
+ * JDom implementation of poms PARENT element
+ *
+ * @author Robert Scholte
+ * @since 3.0
+ */
+public class JDomParent extends Parent
+{
+ private Element parent;
+
+ public JDomParent( Element parent )
+ {
+ this.parent = parent;
+ }
+
+ @Override
+ public String getVersion()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setVersion( String version )
+ {
+ JDomUtils.rewriteElement( "version", version, parent, parent.getNamespace() );
+ }
+
+ @Override
+ public String getArtifactId()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getGroupId()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getRelativePath()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setArtifactId( String artifactId )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setGroupId( String groupId )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setRelativePath( String relativePath )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getId()
+ {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomPlugin.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomPlugin.java
new file mode 100644
index 000000000..bf21eeab2
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomPlugin.java
@@ -0,0 +1,193 @@
+package org.apache.maven.shared.release.transform.jdom;
+
+/*
+ * 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.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Plugin;
+import org.apache.maven.model.PluginExecution;
+import org.apache.maven.shared.release.transform.MavenCoordinate;
+import org.jdom.Element;
+
+/**
+ * JDom implementation of poms PLUGIN element
+ *
+ * @author Robert Scholte
+ * @since 3.0
+ */
+public class JDomPlugin extends Plugin implements MavenCoordinate
+{
+ private Element plugin;
+ private final MavenCoordinate coordinate;
+
+ public JDomPlugin( Element plugin )
+ {
+ this.plugin = plugin;
+ this.coordinate = new JDomMavenCoordinate( plugin );
+ }
+
+ @Override
+ public void addDependency( Dependency dependency )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addExecution( PluginExecution pluginExecution )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getArtifactId()
+ {
+ return coordinate.getArtifactId();
+ }
+
+ @Override
+ public List getDependencies()
+ {
+ Element dependenciesElm = plugin.getChild( "dependencies", plugin.getNamespace() );
+ if ( dependenciesElm == null )
+ {
+ return Collections.emptyList();
+ }
+ else
+ {
+ List dependencyElms =
+ dependenciesElm.getChildren( "dependency", plugin.getNamespace() );
+
+ List dependencies = new ArrayList<>( dependencyElms.size() );
+
+ for ( Element dependencyElm : dependencyElms )
+ {
+ dependencies.add( new JDomDependency( dependencyElm ) );
+ }
+
+ return dependencies;
+ }
+ }
+
+ @Override
+ public List getExecutions()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object getGoals()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getGroupId()
+ {
+ return coordinate.getGroupId();
+ }
+
+ @Override
+ public String getVersion()
+ {
+ return coordinate.getVersion();
+ }
+
+ @Override
+ public boolean isExtensions()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeDependency( Dependency dependency )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeExecution( PluginExecution pluginExecution )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setArtifactId( String artifactId )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setDependencies( List dependencies )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setExecutions( List executions )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setExtensions( boolean extensions )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setGoals( Object goals )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setGroupId( String groupId )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setVersion( String version )
+ {
+ coordinate.setVersion( version );
+ }
+
+ @Override
+ public void flushExecutionMap()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Map getExecutionsAsMap()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getName()
+ {
+ return "plugin";
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomPluginManagement.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomPluginManagement.java
new file mode 100644
index 000000000..b997bde20
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomPluginManagement.java
@@ -0,0 +1,98 @@
+package org.apache.maven.shared.release.transform.jdom;
+
+/*
+ * 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.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.maven.model.Plugin;
+import org.apache.maven.model.PluginManagement;
+import org.jdom.Element;
+
+/**
+ * JDom implementation of poms PLUGINMANAGEMENT element
+ *
+ * @author Robert Scholte
+ * @since 3.0
+ */
+public class JDomPluginManagement extends PluginManagement
+{
+ private final Element pluginManagement;
+
+ public JDomPluginManagement( Element pluginManagement )
+ {
+ this.pluginManagement = pluginManagement;
+ }
+
+ @Override
+ public void addPlugin( Plugin plugin )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List getPlugins()
+ {
+ Element pluginsElm = pluginManagement.getChild( "plugins", pluginManagement.getNamespace() );
+ if ( pluginsElm == null )
+ {
+ return Collections.emptyList();
+ }
+ else
+ {
+ List pluginElms = pluginsElm.getChildren( "plugin", pluginManagement.getNamespace() );
+
+ List plugins = new ArrayList<>( pluginElms.size() );
+
+ for ( Element pluginElm : pluginElms )
+ {
+ plugins.add( new JDomPlugin( pluginElm ) );
+ }
+
+ return plugins;
+ }
+ }
+
+ @Override
+ public void removePlugin( Plugin plugin )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setPlugins( List plugins )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void flushPluginMap()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Map getPluginsAsMap()
+ {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomProfile.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomProfile.java
new file mode 100644
index 000000000..0bcc4bb70
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomProfile.java
@@ -0,0 +1,63 @@
+package org.apache.maven.shared.release.transform.jdom;
+
+/*
+ * 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.util.List;
+
+import org.apache.maven.model.BuildBase;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.DependencyManagement;
+import org.apache.maven.model.Profile;
+import org.jdom.Element;
+
+/**
+ * JDom implementation of poms PROFILE element
+ *
+ * @author Robert Scholte
+ * @since 3.0
+ */
+public class JDomProfile
+ extends Profile
+{
+ private final JDomModelBase modelBase;
+
+ public JDomProfile( Element profile )
+ {
+ this.modelBase = new JDomModelBase( profile ) ;
+ }
+
+ @Override
+ public BuildBase getBuild()
+ {
+ return modelBase.getBuild();
+ }
+
+ @Override
+ public List getDependencies()
+ {
+ return modelBase.getDependencies();
+ }
+
+ @Override
+ public DependencyManagement getDependencyManagement()
+ {
+ return modelBase.getDependencyManagement();
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomProperties.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomProperties.java
new file mode 100644
index 000000000..a0f4c75c9
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomProperties.java
@@ -0,0 +1,161 @@
+package org.apache.maven.shared.release.transform.jdom;
+
+/*
+ * 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.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Enumeration;
+import java.util.InvalidPropertiesFormatException;
+import java.util.Properties;
+import java.util.Set;
+
+import org.jdom.Element;
+
+/**
+ * JDom implementation of poms PROPERTIES element
+ *
+ * @author Robert Scholte
+ * @since 3.0
+ */
+public class JDomProperties extends Properties
+{
+ private final Element properties;
+
+ public JDomProperties( Element properties )
+ {
+ this.properties = properties;
+ }
+
+ @Override
+ public synchronized Object setProperty( String key, String value )
+ {
+ Element property = properties.getChild( key, properties.getNamespace() );
+
+ JDomUtils.rewriteValue( property, value );
+
+ // todo follow specs of Hashtable.put
+ return null;
+ }
+
+ @Override
+ public synchronized void load( Reader reader )
+ throws IOException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public synchronized void load( InputStream inStream )
+ throws IOException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void save( OutputStream out, String comments )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void store( Writer writer, String comments )
+ throws IOException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void store( OutputStream out, String comments )
+ throws IOException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public synchronized void loadFromXML( InputStream in )
+ throws IOException, InvalidPropertiesFormatException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void storeToXML( OutputStream os, String comment )
+ throws IOException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void storeToXML( OutputStream os, String comment, String encoding )
+ throws IOException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getProperty( String key )
+ {
+ Element property = properties.getChild( key, properties.getNamespace() );
+
+ if ( property == null )
+ {
+ return null;
+ }
+ else
+ {
+ return property.getTextTrim();
+ }
+ }
+
+ @Override
+ public String getProperty( String key, String defaultValue )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Enumeration> propertyNames()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Set stringPropertyNames()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void list( PrintStream out )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void list( PrintWriter out )
+ {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomReportPlugin.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomReportPlugin.java
new file mode 100644
index 000000000..b781ef116
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomReportPlugin.java
@@ -0,0 +1,165 @@
+package org.apache.maven.shared.release.transform.jdom;
+
+/*
+ * 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.util.List;
+import java.util.Map;
+
+import org.apache.maven.model.ReportPlugin;
+import org.apache.maven.model.ReportSet;
+import org.apache.maven.shared.release.transform.MavenCoordinate;
+import org.jdom.Element;
+
+/**
+ * JDom implementation of poms reports PLUGIN element
+ *
+ * @author Robert Scholte
+ * @since 3.0
+ */
+public class JDomReportPlugin
+ extends ReportPlugin implements MavenCoordinate
+{
+ private final MavenCoordinate coordinate;
+
+ public JDomReportPlugin( Element reportPlugin )
+ {
+ this.coordinate = new JDomMavenCoordinate( reportPlugin );
+ }
+
+ @Override
+ public void addReportSet( ReportSet reportSet )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getArtifactId()
+ {
+ return coordinate.getArtifactId();
+ }
+
+ @Override
+ public Object getConfiguration()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getGroupId()
+ {
+ return coordinate.getGroupId();
+ }
+
+ @Override
+ public String getInherited()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List getReportSets()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getVersion()
+ {
+ return coordinate.getVersion();
+ }
+
+ @Override
+ public void removeReportSet( ReportSet reportSet )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setArtifactId( String artifactId )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setConfiguration( Object configuration )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setGroupId( String groupId )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setInherited( String inherited )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setReportSets( List reportSets )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setVersion( String version )
+ {
+ coordinate.setVersion( version );
+ }
+
+ @Override
+ public void flushReportSetMap()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Map getReportSetsAsMap()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getKey()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unsetInheritanceApplied()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isInheritanceApplied()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getName()
+ {
+ return "plugin";
+ }
+}
diff --git a/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomReporting.java b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomReporting.java
new file mode 100644
index 000000000..2a07f15a1
--- /dev/null
+++ b/Java-base/maven-release/src/maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom/JDomReporting.java
@@ -0,0 +1,123 @@
+package org.apache.maven.shared.release.transform.jdom;
+
+/*
+ * 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.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.maven.model.ReportPlugin;
+import org.apache.maven.model.Reporting;
+import org.jdom.Element;
+
+/**
+ * JDom implementation of poms REPORTING element
+ *
+ * @author Robert Scholte
+ * @since 3.0
+ */
+public class JDomReporting extends Reporting
+{
+
+ private final Element reporting;
+
+ public JDomReporting( Element reporting )
+ {
+ this.reporting = reporting;
+ }
+
+ @Override
+ public void addPlugin( ReportPlugin reportPlugin )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getOutputDirectory()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List getPlugins()
+ {
+ Element pluginsElm = reporting.getChild( "plugins", reporting.getNamespace() );
+ if ( pluginsElm == null )
+ {
+ return Collections.emptyList();
+ }
+ else
+ {
+ List