/*
* #%L
* Nazgul Project: nazgul-core-quickstart-api
* %%
* Copyright (C) 2010 - 2017 jGuru Europe AB
* %%
* Licensed under the jGuru Europe AB license (the "License"), based
* on Apache License, Version 2.0; you may not use this file except
* in compliance with the License.
*
* You may obtain a copy of the License at
*
* http://www.jguru.se/licenses/jguruCorporateSourceLicense-2.0.txt
*
* 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.
* #L%
*
*/
package se.jguru.nazgul.core.quickstart.api.generator.parser;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import se.jguru.nazgul.core.parser.api.DefaultTokenParser;
import se.jguru.nazgul.core.parser.api.SingleBracketTokenDefinitions;
import se.jguru.nazgul.core.parser.api.TokenParser;
import se.jguru.nazgul.core.parser.api.agent.DefaultParserAgent;
import se.jguru.nazgul.core.parser.api.agent.HostNameParserAgent;
import se.jguru.nazgul.core.quickstart.api.PomType;
import se.jguru.nazgul.core.quickstart.api.generator.SoftwareComponentPart;
import se.jguru.nazgul.core.quickstart.model.Name;
import se.jguru.nazgul.core.quickstart.model.Project;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedMap;
import java.util.StringTokenizer;
import java.util.TreeMap;
/**
* Builder class to simplify creating a TokenParser tailored for maven POM and project data token substitution.
* The returned TokenParsers will use {@code SingleBracketTokenDefinitions} for token definitions, to avoid confusing
* the parser tokens with maven's default variable form. (I.e. it will use tokens on the form [token] to
* avoid clashing with ${token}).
*
* @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>, jGuru Europe AB
*/
public final class SingleBracketPomTokenParserFactory {
// Our log
private static final Logger log = LoggerFactory.getLogger(SingleBracketPomTokenParserFactory.class.getName());
/**
* The key for the token holding the PomType verbatim.
*/
public static final String POMTYPE_KEY = "POMTYPE";
/**
* The key for the token holding the {@code pomType.name().toLowerCase().replace("_", "-")} value.
*/
public static final String LOWERCASE_POMTYPE_KEY = "pomtype";
/**
* The name of the directory containing all parent POM projects.
*/
public static final String POMS_DIRECTORY_NAME = "poms";
/*
* Hide factory class constructors.
*/
private SingleBracketPomTokenParserFactory() {
// Do nothing
}
/**
* Factory builder method entry method, creating the internal Builder and assigning the supplied tokens.
*
* @param pomType The POM type for which this SingleBracketPomTokenParserFactory should create a TokenParser.
* @param project The non-null Project used by this SingleBracketPomTokenParserFactory to derive tokens for the
* created compound TokenParser.
* @return The Builder used to create the TokenParser.
*/
public static DirectoryPrefixEnricher create(final PomType pomType,
final Project project) {
// Check sanity
Validate.notNull(pomType, "Cannot handle null pomType argument.");
Validate.notNull(project, "Cannot handle null project argument.");
// Derive the project properties.
final SortedMap<String, String> pomTokens = new TreeMap<>();
pomTokens.put(POMTYPE_KEY, pomType.name());
pomTokens.put(LOWERCASE_POMTYPE_KEY, pomType.name().toLowerCase().replace("_", "-"));
// All done.
return new DirectoryPrefixEnricher(project, pomType, pomTokens, false);
}
/**
* Abstract specification for a BuilderStep class in the controlled Builder pattern.
*/
abstract static class BuilderStep {
// Internal state
private boolean parentPomType;
private boolean requiresProjectSuffix = false;
private boolean addProjectNameAsDirectoryPrefix;
protected Project project;
protected PomType pomType;
protected SortedMap<String, String> pomTokens;
/**
* Creates a new BuilderStep wrapping the supplied data. Each can be {@code null}.
*
* @param project The Project of this BuilderStep.
* @param pomType The POM type of this BuilderStep.
* @param pomTokens The pomTokens storage.
* @param addProjectNameAsDirectoryPrefix set to {@code true} if directory names and artifactIDs should
* add/prepend the project name as prefix
* (i.e. yield {@code project.getName()-} prepended to directory names).
*/
protected BuilderStep(final Project project,
final PomType pomType,
final SortedMap<String, String> pomTokens,
final boolean addProjectNameAsDirectoryPrefix) {
this.project = project;
this.pomType = pomType;
this.pomTokens = pomTokens;
this.addProjectNameAsDirectoryPrefix = addProjectNameAsDirectoryPrefix;
// Check sanity
if (pomType != null) {
for (SoftwareComponentPart current : SoftwareComponentPart.values()) {
if (pomType == current.getComponentPomType()) {
requiresProjectSuffix = current.isSuffixRequired();
}
}
parentPomType = pomType.name().contains("PARENT");
}
}
/**
* Validates that the given key is present within the pomTokens,
* and that it has a non-null value.
*
* @param key The token key.
*/
protected void validateRequiredKeyValuePair(final String key) {
// Check sanity
Validate.isTrue(pomTokens.containsKey(key), "Required key [" + key + "] not present in tokenMap.");
Validate.notEmpty(pomTokens.get(key),
"Cannot handle null or empty value for key [" + key + "] in tokenMap.");
}
/**
* @return {@code} true if this Builder should have a project suffix, due to the PomType.
*/
protected final boolean isProjectSuffixRequired() {
return requiresProjectSuffix;
}
/**
* @return {@code true} if the PomType of this BuilderStep is a Parent POM (as opposed to a Component POM).
*/
protected final boolean isParentPomType() {
return parentPomType;
}
/**
* @return {@code true} if this BuilderStep should add prefixes with the Project's name
* to local project directory names. This means that SoftwareComponent directories should be
* given names on the form {@code foo-finance-api} instead of {@code finance-api},
* given that the project's name is {@code foo} and the SoftwareComponent's {@code finance}.
*/
protected boolean isProjectNameAddedToDirectories() {
return addProjectNameAsDirectoryPrefix;
}
/**
* Converts the supplied path to a List of segments, assuming that the path uses '/' chars to separate
* directory names. Returning a List containing all directory names (i.e. path parts)
* in their order of occurrence.
*
* @param path The path to tokenize, assumed to use '/' as separator chars.
* @return A List whose elements contain the directory names within the supplied path.
*/
protected List<String> tokenize(final String path) {
final List<String> toReturn = new ArrayList<>();
if (path != null) {
final StringTokenizer tok = new StringTokenizer(path, "/", false);
while (tok.hasMoreTokens()) {
toReturn.add(tok.nextToken());
}
}
return toReturn;
}
}
/**
* Class which defines whether or not the name of the Project should be added as a prefix to
* SoftwareComponent directories when their names are synthesized. Given a SoftwareComponent
* {@code configuration} in a Project with the name {@code foo}, its API project directory name is
* <ul>
* <li>{@code configuration-api} if this DirectoryPrefixEnricher is given a false argument
* (or the method {@code withoutProjectNameAsDirectoryPrefix()} is called.</li>
* <li>{@code foo-configuration-api} if this DirectoryPrefixEnricher is given a true argument
* (or the method {@code useProjectNameAsDirectoryPrefix()} is called.</li>
* </ul>
*/
public static class DirectoryPrefixEnricher extends BuilderStep {
public DirectoryPrefixEnricher(final Project project,
final PomType pomType,
final SortedMap<String, String> pomTokens,
final boolean addProjectNameAsDirectoryPrefix) {
super(project, pomType, pomTokens, addProjectNameAsDirectoryPrefix);
}
/**
* Indicate if the Project's name should be prepended as a prefix to software component directory names.
* Given a SoftwareComponent {@code configuration} in a Project with the name {@code foo},
* its API project directory name is
* <ul>
* <li>{@code configuration-api} if this method is called with the argument {@code false}.</li>
* <li>{@code foo-configuration-api} if this method is called with the argument {@code true}.</li>
* </ul>
*
* @param yes true to indicate that the project name should be added as a prefix to SoftwareComponent
* directories, and false otherwise.
* @return The next BuilderStep subtype in the builder chain.
*/
public RelativePathEnricher prependProjectNameAsDirectoryPrefix(final boolean yes) {
return new RelativePathEnricher(project, pomType, pomTokens, yes);
}
/**
* Convenience method calling {@code return prependProjectNameAsDirectoryPrefix(true);}
*
* @return The next BuilderStep subtype in the builder chain.
* @see #prependProjectNameAsDirectoryPrefix(boolean)
*/
public RelativePathEnricher useProjectNameAsDirectoryPrefix() {
return prependProjectNameAsDirectoryPrefix(true);
}
/**
* Convenience method calling {@code return prependProjectNameAsDirectoryPrefix(false);}
*
* @return The next BuilderStep subtype in the builder chain.
* @see #prependProjectNameAsDirectoryPrefix(boolean)
*/
public RelativePathEnricher withoutProjectNameAsDirectoryPrefix() {
return prependProjectNameAsDirectoryPrefix(false);
}
}
/**
* Class which adds the relative path to a SoftwareComponent, as well as temporary pomTokens
* {@code SOFTWARE_COMPONENT_RELATIVE_PATH} and {@code SOFTWARE_COMPONENT_NAME}.
* Adds the relative path to the reactor of the SoftwareComponent in which the active Maven project
* which should be tokenized by the resulting TokenParser resides. The reactor for a SoftwareComponentPart project
* is its parent directory. (I.e. for the {@code finance-api} project residing within the SoftwareComponent
* {@code finance}, the relative path should be {@code services/finance} assuming that we would like the
* finance SoftwareComponent to reside in the {@code services} VCS path).
*/
public static class RelativePathEnricher extends BuilderStep {
/**
* The pomTokens key holding the relative path to the SoftwareComponent containing the active Maven project.
*/
public static final String SOFTWARE_COMPONENT_RELATIVE_PATH = "softwareComponentRelativePath";
/**
* The pomTokens key holding the relative name of the SoftwareComponent containing the active Maven project.
*/
public static final String SOFTWARE_COMPONENT_NAME = "softwareComponentName";
/**
* The string name of the PomType for the parent of the active Maven project.
* (I.e. {@code parentPomType.name()}).
*/
public static final String PARENT_POMTYPE = "parent_pomType";
RelativePathEnricher(final Project project,
final PomType pomType,
final SortedMap<String, String> pomTokens,
final boolean addProjectNameAsDirectoryPrefix) {
super(project, pomType, pomTokens, addProjectNameAsDirectoryPrefix);
}
/**
* Parent projects are not part of a SoftwareComponent; therefore one should call this method instead of
* the {@code inSoftwareComponentWithRelativePath()} method.
*
* @return The next BuilderStep subtype in the builder chain.
*/
public PrefixEnricher isParentProject() {
// Check sanity
Validate.isTrue(isParentPomType(),
"isParentProject should only be called for PARENT PomTypes.");
// Since all parent poms are located in the [projName]/poms/[xyz-]projName-[something-]parent
// directory, we really need not define the software component path at all.
//
// pomTokens.put(SOFTWARE_COMPONENT_RELATIVE_PATH, "poms");
// Add the PomType of the parent, which is non-null for PomTypes other than PomType.PARENT.
final PomType parentPomType = getParentPomType(2);
if (parentPomType != null) {
pomTokens.put(PARENT_POMTYPE, parentPomType.name());
// Add the relative path to the parent POM directory
final String parentPomDirName = (isProjectNameAddedToDirectories() || isParentPomType()
? project.getName() + Name.DEFAULT_SEPARATOR
: "") + parentPomType.name().toLowerCase().replace("_", Name.DEFAULT_SEPARATOR);
pomTokens.put(PomToken.PARENT_POM_RELATIVE_PATH.getToken(), "../" + parentPomDirName);
} else {
// This should happen if pomType is PomType.PARENT
pomTokens.put(PomToken.PARENT_POM_RELATIVE_PATH.getToken(), "");
}
// All Done.
return new PrefixEnricher(project, pomType, pomTokens, isProjectNameAddedToDirectories());
}
/**
* <p>Adds the relative path to the reactor of the SoftwareComponent in which the active Maven project which
* should be tokenized by the resulting TokenParser resides. The reactor for a SoftwareComponentPart project
* is its parent directory. (I.e. for the {@code finance-api} project residing within the SoftwareComponent
* {@code finance}, the relative path should be {@code services/finance} assuming that we would like the
* finance SoftwareComponent to reside in the {@code services} VCS path).
* This implies that the directory name of the project itself (such as {@code finance-api}) should not be
* provided in the componentRelativePath.</p>
* <p>Example:</p>
* <pre>
* <code>
* // Assume that we want to create the TokenParser for the project
* //
* // finance-api
* //
* // Then the componentRelativePath provided should be "services/finance"
* // and not "services/finance/finance-api"
* //
* final TokenParser tokenParser = SingleBracketPomTokenParserFactory
* .create(PomType.COMPONENT_API, acmeFooProject, false)
* .inSoftwareComponentWithRelativePath("services/finance")
* .withProjectGroupIdPrefix("org.acme", false)
* .withoutProjectSuffix()
* .build();
* </code>
* </pre>
*
* @param relativePathToSoftwareComponent Cannot be null or empty. The relative directory path from the project VCS
* root to the <strong>component directory</strong> containing the Maven
* project which should be tokenized by the resulting TokenParser.
* @return The next BuilderStep subtype in the builder chain.
*/
public PrefixEnricher inSoftwareComponentWithRelativePath(final String relativePathToSoftwareComponent) {
// Delegate if required
if (isParentPomType()) {
return isParentProject();
}
final String invalidRelativePathMsg = "Cannot handle null or empty "
+ "projectRelativeDirectoryPath argument for non-RootReactor projects.";
final List<String> pathSegments = tokenize(relativePathToSoftwareComponent);
// We have a non-parent project, so we might need to calculate the relative path.
switch (pomType) {
case ROOT_REACTOR:
pomTokens.put(SOFTWARE_COMPONENT_RELATIVE_PATH, "");
// pomTokens.put(PomToken.PARENT_POM_RELATIVE_PATH.getToken(), "");
// pomTokens.put(SOFTWARE_COMPONENT_NAME, null);
break;
case REACTOR:
Validate.notEmpty(relativePathToSoftwareComponent, invalidRelativePathMsg);
final String pathSegmentSeparator = "/";
String paddedRelativePath = relativePathToSoftwareComponent.endsWith(pathSegmentSeparator)
? relativePathToSoftwareComponent.substring(0,
relativePathToSoftwareComponent.lastIndexOf(pathSegmentSeparator))
: relativePathToSoftwareComponent;
pomTokens.put(SOFTWARE_COMPONENT_RELATIVE_PATH, paddedRelativePath);
pomTokens.put(SOFTWARE_COMPONENT_NAME, pathSegments.get(pathSegments.size() - 1));
PomType parentPomType = getParentPomType(pathSegments.size());
pomTokens.put(PARENT_POMTYPE, parentPomType.name());
pomTokens.put(PomToken.PARENT_POM_RELATIVE_PATH.getToken(), "");
break;
default:
Validate.notEmpty(relativePathToSoftwareComponent, invalidRelativePathMsg);
pomTokens.put(SOFTWARE_COMPONENT_RELATIVE_PATH, relativePathToSoftwareComponent);
pomTokens.put(SOFTWARE_COMPONENT_NAME, pathSegments.get(pathSegments.size() - 1));
parentPomType = getParentPomType(pathSegments.size());
final String relative = getRelativePathToParentPom(relativePathToSoftwareComponent, parentPomType);
pomTokens.put(PARENT_POMTYPE, parentPomType.name());
pomTokens.put(PomToken.PARENT_POM_RELATIVE_PATH.getToken(), relative);
break;
}
// All Done.
return new PrefixEnricher(project, pomType, pomTokens, isProjectNameAddedToDirectories());
}
//
// Private helpers
//
private String getRelativePathToParentPom(final String relativePathToSoftwareComponent,
final PomType parentPomType) {
final List<String> tokenizedPath = tokenize(relativePathToSoftwareComponent);
final int numCdsUpwards = pomType == PomType.REACTOR
? tokenizedPath.size()
: tokenizedPath.size() + 1;
final StringBuilder relativePathBuilder = new StringBuilder();
for (int i = 0; i < numCdsUpwards; i++) {
relativePathBuilder.append("../");
}
relativePathBuilder.append(POMS_DIRECTORY_NAME + "/");
// Always prepend the project name to parent POM dirs (i.e. in the "poms/" directory).
final String parentPomDirName = project.getName() + Name.DEFAULT_SEPARATOR
+ parentPomType.name().replace("_", Name.DEFAULT_SEPARATOR).toLowerCase();
relativePathBuilder.append(parentPomDirName);
return relativePathBuilder.toString();
}
private PomType getParentPomType(final int numSegmentsInRelativePathToProjectRoot) {
PomType toReturn = null;
switch (pomType) {
case REACTOR:
toReturn = numSegmentsInRelativePathToProjectRoot < 2
? PomType.ROOT_REACTOR
: PomType.REACTOR;
break;
case COMPONENT_MODEL:
toReturn = PomType.MODEL_PARENT;
break;
case MODEL_PARENT:
case COMPONENT_SPI:
case COMPONENT_API:
toReturn = PomType.API_PARENT;
break;
case API_PARENT:
case WAR_PARENT:
case COMPONENT_TEST:
case COMPONENT_IMPLEMENTATION:
case OTHER_PARENT:
toReturn = PomType.PARENT;
break;
default:
if (log.isWarnEnabled()) {
log.warn("Could not determine parent PomType for pomType [" + pomType + "].");
}
toReturn = null;
break;
}
// All done.
return toReturn;
}
}
/**
* BuilderStep which adds the prefix of Maven GroupIDs for a relative path within a directory.
* A "groupIdPrefix" is normally identical to a reverse DNS of the project's organisation,
* similar to {@code se.jguru} or {@code org.acme}. Note that the groupIdPrefix should <strong>not</strong>
* include the project name (i.e. {@code project.getName()}), as it will be automatically appended by this builder.
* Given a SoftwareComponent with the relative path {@code services/finance}, the groupIdPrefix
* {@code se.jguru} and the project name {@code foo}, the full groupId of the finance service
* SoftwareComponent can be constructed as {@code se.jguru.foo.services.finance}.
* This, in turn, implies that the API project in the finance SoftwareComponent will retrieve the groupId
* {@code se.jguru.foo.services.finance.api}.
*/
public static class PrefixEnricher extends BuilderStep {
/**
* The property containing the {@code groupIdPrefix}, a.k.a. reverse organisation DNS,
* which should be prepended to the groupId and package properties.
* Given a SoftwareComponent with the relative path {@code services/finance}, the groupIdPrefix
* {@code se.jguru} and the project name {@code foo}, the full groupId of the finance service
* SoftwareComponent can be constructed as {@code se.jguru.foo.services.finance}.
* This, in turn, implies that the API project in the finance SoftwareComponent will retrieve the groupId
* {@code se.jguru.foo.services.finance.api}.
*/
public static final String REVERSE_DNS_OF_ORGANISATION = "reverseOrganisationDns";
PrefixEnricher(final Project project,
final PomType pomType,
final SortedMap<String, String> pomTokens,
final boolean addProjectNameAsDirectoryPrefix) {
super(project, pomType, pomTokens, addProjectNameAsDirectoryPrefix);
}
/**
* Adds prefix which should be prepended to the relative path to generate a groupId for the current project.
* A "projectGroupIdPrefix" is normally identical to a reverse DNS of the project's organisation,
* similar to {@code se.jguru} or {@code org.acme}. Note that the groupIdPrefix should <strong>not</strong>
* include the project name (i.e. {@code project.getName()}), as it will be automatically appended by this
* builder.
*
* @param projectGroupIdPrefix A non-null prefix string added to the relative path to generate a groupId for
* the current project. Empty values are permitted, but null are not. Normally
* identical to a reverse DNS of the project's organisation, similar to
* {@code se.jguru} or {@code org.acme}. Note that the groupIdPrefix should
* <strong>not</strong> include the project name (i.e. {@code project.getName()}),
* as it will be automatically appended by this builder.
* @return The next BuilderStep subtype in the builder chain.
*/
public SuffixEnricher withProjectGroupIdPrefix(final String projectGroupIdPrefix) {
// Check sanity
Validate.notEmpty(projectGroupIdPrefix, "Cannot handle null or empty projectGroupIdPrefix argument.");
final PomType parentPomType = pomTokens.get(RelativePathEnricher.PARENT_POMTYPE) == null
? null
: PomType.valueOf(pomTokens.get(RelativePathEnricher.PARENT_POMTYPE));
// Add the relevant pomTokens
pomTokens.put(REVERSE_DNS_OF_ORGANISATION, projectGroupIdPrefix);
pomTokens.put(PomToken.PARENT_GROUPID.getToken(), getParentGroupId(projectGroupIdPrefix, parentPomType));
pomTokens.put(PomToken.PARENT_ARTIFACTID.getToken(), getParentArtifactId(parentPomType));
// All done.
return new SuffixEnricher(project, pomType, pomTokens, isProjectNameAddedToDirectories());
}
//
// Private helpers
//
private String getParentGroupId(final String projectGroupIdPrefix, final PomType parentPomType) {
final String delimiter = ".";
final String projectPrefix = project.getPrefix() != null ? project.getPrefix() + delimiter : "";
StringBuilder groupIdBuilder = new StringBuilder(
projectGroupIdPrefix + delimiter + projectPrefix + project.getName() + delimiter);
// Calculate the Maven groupId for the parent POM
switch (pomType) {
case ROOT_REACTOR:
groupIdBuilder = new StringBuilder(project.getReactorParent().getGroupId());
break;
case PARENT:
groupIdBuilder = new StringBuilder(project.getParentParent().getGroupId());
break;
case REACTOR:
// Find the relative path of the parent reactor only for the case that the
// parent is not
//
// relativePath: osgi.launcher --> parent GID: [se.jguru.nazgul.core].osgi
if (parentPomType != PomType.ROOT_REACTOR) {
final String relPath = pomTokens.get(RelativePathEnricher.SOFTWARE_COMPONENT_RELATIVE_PATH);
final List<String> segments = tokenize(relPath);
Validate.isTrue(segments.size() > 0, "For a reactor POM not immediately below the root "
+ "reactor, the number of path segments should be at least 1. (Got: "
+ segments.size() + ")");
for (int i = 0; i < segments.size() - 1; i++) {
groupIdBuilder.append(segments.get(i));
if (i < segments.size() - 2) {
groupIdBuilder.append(delimiter);
}
}
} else {
// Remove the trailing '.'
groupIdBuilder.deleteCharAt(groupIdBuilder.length() - 1);
}
break;
default:
// Check sanity
Validate.notNull(parentPomType, "Parent PomType should not be null for pomType [" + pomType + "]");
final String projectPrefixSegment = project.getPrefix() != null
? project.getPrefix() + Name.DEFAULT_SEPARATOR
: "";
groupIdBuilder.append(POMS_DIRECTORY_NAME)
.append(delimiter)
.append(projectPrefixSegment)
.append(project.getName())
.append(Name.DEFAULT_SEPARATOR)
.append(parentPomType.name().replace("_", Name.DEFAULT_SEPARATOR).toLowerCase());
break;
}
// All done.
return groupIdBuilder.toString();
}
private String getParentArtifactId(final PomType parentPomType) {
StringBuilder artifactIdBuilder = new StringBuilder();
final String prefix = project.getPrefix() != null ? project.getPrefix() + Name.DEFAULT_SEPARATOR : "";
// Parent artifactIDs should be on the form
//
// [prefix-][name]-[parentPomType (transformed)]
artifactIdBuilder.append(prefix).append(project.getName()).append(Name.DEFAULT_SEPARATOR);
// Calculate the Maven groupId for the parent POM
switch (pomType) {
case ROOT_REACTOR:
artifactIdBuilder = new StringBuilder(project.getReactorParent().getArtifactId());
break;
case PARENT:
artifactIdBuilder = new StringBuilder(project.getParentParent().getArtifactId());
break;
case REACTOR:
//
// The following reactor G/A:
// <groupId>se.jguru.nazgul.core.osgi.launcher</groupId>
// <artifactId>nazgul-core-osgi-launcher-reactor</artifactId>
//
// ... yields the parent G/A as shown:
// <groupId>se.jguru.nazgul.core.osgi</groupId>
// <artifactId>nazgul-core-osgi-reactor</artifactId>
//
if (parentPomType != PomType.ROOT_REACTOR) {
final String relPath = pomTokens.get(RelativePathEnricher.SOFTWARE_COMPONENT_RELATIVE_PATH);
final List<String> segments = tokenize(relPath);
Validate.isTrue(segments.size() > 0, "For a reactor POM not immediately below the root "
+ "reactor, the number of path segments should be at least 1. (Got: "
+ segments.size() + ")");
for (int i = 0; i < segments.size() - 1; i++) {
artifactIdBuilder.append(segments.get(i)).append(Name.DEFAULT_SEPARATOR);
}
}
artifactIdBuilder.append("reactor");
break;
default:
// The parent artifactId should be on the form
// [prefix-][name-][parentPomType (adjusted)]
// nazgul-core-parent
artifactIdBuilder.append(parentPomType.name().replace("_", Name.DEFAULT_SEPARATOR).toLowerCase());
break;
}
// All done
return artifactIdBuilder.toString();
}
/*
// RelativeParentPomPath: Add the relativePath to the parent POM directory
final PomType parentPomType = pomTokens.get(RelativePathEnricher.PARENT_POMTYPE) == null
? null
: PomType.valueOf(pomTokens.get(RelativePathEnricher.PARENT_POMTYPE));
if (parentPomType != null) {
String relativePathToParentPomDir = getRelativePathToParentPomDir(
parentPomType, parentDirSegments, dirPrefix);
if (isParentPomType()) {
relativePathToParentPomDir = "../"
+ (isProjectNameAddedToDirectories() ? project.getName() + Name.DEFAULT_SEPARATOR : "")
+ parentPomType.name().toLowerCase().replace("_", Name.DEFAULT_SEPARATOR);
}
pomTokens.put(PomToken.PARENT_POM_RELATIVE_PATH.getToken(), relativePathToParentPomDir);
// Add the parent POM groupId and artifactId coordinates
switch (pomType) {
case ROOT_REACTOR:
final SimpleArtifact reactorParent = project.getReactorParent();
pomTokens.put(PomToken.PARENT_GROUPID.getToken(), reactorParent.getGroupId());
pomTokens.put(PomToken.PARENT_ARTIFACTID.getToken(), reactorParent.getArtifactId());
break;
case PARENT:
final SimpleArtifact parentParent = project.getParentParent();
pomTokens.put(PomToken.PARENT_GROUPID.getToken(), parentParent.getGroupId());
pomTokens.put(PomToken.PARENT_ARTIFACTID.getToken(), parentParent.getArtifactId());
break;
case API_PARENT:
case MODEL_PARENT:
case WAR_PARENT:
case OTHER_PARENT:
case OTHER:
//
// Expected form:
// acme-foo-api-parent, that is
// ([projectPrefix]-)[projectName]-[parentPomType]
//
final String parentArtifactId = artifactPrefix + project.getName() + Name.DEFAULT_SEPARATOR
+ parentPomType.name().toLowerCase().replace("_", "-");
//
// Expected form:
// org.acme.foo.poms.foo-api-parent, that is
// [projectGroupIdPrefix].[projectName].poms.[projectName]-[parentPomType]
//
final String parentGroupId = projectGroupIdPrefix + "." + project.getName() + ".poms."
+ project.getName() + Name.DEFAULT_SEPARATOR
+ parentPomType.name().toLowerCase().replace("_", "-");
pomTokens.put(PomToken.PARENT_ARTIFACTID.getToken(), parentArtifactId);
pomTokens.put(PomToken.PARENT_GROUPID.getToken(), parentGroupId);
break;
case REACTOR:
// Typical reactor-to-immediate_parent_reactor structure:
*/
/*
<parent>
<groupId>se.jguru.nazgul.core.osgi</groupId>
<artifactId>nazgul-core-osgi-reactor</artifactId>
<version>1.6.1-SNAPSHOT</version>
</parent>
<groupId>se.jguru.nazgul.core.osgi.launcher</groupId>
<artifactId>nazgul-core-osgi-launcher-reactor</artifactId>
*/
/*
//
// GroupID: as activeProject's groupID, minus the last segment.
// ArtifactID: ([projectPrefix]-)[projectName]-[parentDirRelativePathAsRawArtifactId]-reactor
//
final StringBuilder parentGroupIdBuilder = new StringBuilder(projectGroupIdPrefix);
final int numParentPathSegments = parentDirSegments.size() == 0
? 0
: parentDirSegments.size() - 1;
parentGroupIdBuilder.append(".").append(project.getName());
for (int i = 0; i < numParentPathSegments; i++) {
parentGroupIdBuilder.append(".").append(parentDirSegments.get(i));
}
final StringBuilder parentArtifactIdBuilder = new StringBuilder();
if (nonEmptyProjectPrefix) {
parentArtifactIdBuilder.append(project.getPrefix()).append("-");
}
parentArtifactIdBuilder.append(project.getName()).append("-");
for (int i = 0; i < numParentPathSegments; i++) {
parentArtifactIdBuilder.append(parentDirSegments.get(i)).append("-");
}
parentArtifactIdBuilder.append("reactor");
// We assume that all reactors will have the same version as the root reactor
pomTokens.put(PomToken.PARENT_GROUPID.getToken(), parentGroupIdBuilder.toString());
pomTokens.put(PomToken.PARENT_ARTIFACTID.getToken(), parentArtifactIdBuilder.toString());
break;
default:
// No idea how to treat this...
log.warn("PomType [" + pomType + "] is not handled by the [" + getClass().getSimpleName()
+ "] in respect to parent groupID (" + PomToken.PARENT_GROUPID.getToken() + ") and "
+ "artifactID (" + PomToken.PARENT_ARTIFACTID.getToken() + ") tokens.\n"
+ "This should not happen; please notify the developers ... nicely.");
break;
}
} else {
log.warn("Could not find parent PomType for PomType [" + pomType
+ "]. This implies that the [" + PomToken.PARENT_POM_RELATIVE_PATH.getToken()
+ "] token cannot be set. Validate that your template does not require it, "
+ "or set it manually.");
}
*/
}
/**
* Builder step which can add a project suffix, for project types which requires it
* (and no project suffix for project types that requires none).
*/
public static class SuffixEnricher extends BuilderStep {
SuffixEnricher(final Project project,
final PomType pomType,
final SortedMap<String, String> pomTokens,
final boolean addProjectNameAsDirectoryPrefix) {
super(project, pomType, pomTokens, addProjectNameAsDirectoryPrefix);
}
/**
* Adds a project suffix, which is used to calculate groupId and artifactId for the local
* project for ComponentTypes where suffix is required. If the {@code isProjectSuffixRequired()} yields
* {@code false}, the projectSuffix value is ignored (implying it may be null or empty in these cases).
*
* @param projectSuffix The suffix for the project. May be null or empty, depending on the active projectType.
* @return The next BuilderStep subtype in the builder chain.
* @see se.jguru.nazgul.core.quickstart.api.generator.SoftwareComponentPart#isSuffixRequired()
*/
public MavenVersionEnricher withProjectSuffix(final String projectSuffix) {
// Get some token parts
final String localProjectDirName = getProjectDirectoryName(projectSuffix);
final String relativePackage = getRelativePackage(projectSuffix);
String pathToParentDir = pomTokens.get(RelativePathEnricher.SOFTWARE_COMPONENT_RELATIVE_PATH);
if (pathToParentDir == null && isParentPomType()) {
pathToParentDir = POMS_DIRECTORY_NAME;
}
final String relativeDirPath = (pomType == PomType.REACTOR || pomType == PomType.ROOT_REACTOR)
? pathToParentDir
: pathToParentDir + "/" + localProjectDirName;
pomTokens.put(PomToken.RELATIVE_DIRPATH.getToken(), relativeDirPath);
pomTokens.put(PomToken.RELATIVE_PACKAGE.getToken(), relativePackage);
// Now find the groupId and artifactId for the active project.
final boolean nonEmptyProjectPrefix = project.getPrefix() != null && project.getPrefix().length() > 0;
final String artifactPrefix = nonEmptyProjectPrefix ? project.getPrefix() + Name.DEFAULT_SEPARATOR : "";
// ArtifactID: Calculate for the current project.
//
// Using the localProjectDirName, add the project name as a prefix if required.
// DirName: "finance-api" --> ArtifactID: "acme-foo-finance-api"
// DirName: "foo-finance-api" --> ArtifactID: "acme-foo-finance-api"
//
final String slice = artifactPrefix + project.getName() + Name.DEFAULT_SEPARATOR;
String rawProjectDirName = localProjectDirName.startsWith(slice)
? localProjectDirName.substring(slice.length())
: localProjectDirName;
String artifactId = artifactPrefix + project.getName() + Name.DEFAULT_SEPARATOR + rawProjectDirName;
if (pomType == PomType.REACTOR) {
artifactId = artifactId + "-reactor";
} else if (pomType == PomType.ROOT_REACTOR) {
artifactId = artifactPrefix + project.getName() + Name.DEFAULT_SEPARATOR + "reactor";
}
pomTokens.put(PomToken.ARTIFACTID.getToken(), artifactId);
//
// GroupID: Calculate for the current project, on the form:
//
// Component Project [xyz]:
// [reverseDns].[[projectPrefix].][projectName].[relativePathToComponentParent].[componentName].[type],
// i.e:
//
// Reverse DNS: org.acme
// Project Prefix: gnat
// Project Name: foo
// Relative Path: messaging
//
// a) Project type MODEL --> groupID: org.acme.gnat.foo.messaging.model
// b) Project type REACTOR --> groupID: org.acme.gnat.foo.messaging
// c) Project type SPI (jpa) --> groupID: org.acme.gnat.foo.messaging.spi.jpa
// d) Project type IMPL (pojo) --> groupID: org.acme.gnat.foo.messaging.impl.pojo
//
final String groupId = pomTokens.get(PrefixEnricher.REVERSE_DNS_OF_ORGANISATION) + "."
+ (nonEmptyProjectPrefix ? project.getPrefix() + "." : "")
+ project.getName()
+ ("".equals(relativePackage) ? "" : "." + relativePackage);
pomTokens.put(PomToken.GROUPID.getToken(), groupId);
// All done.
return new MavenVersionEnricher(project, pomType, pomTokens, isProjectNameAddedToDirectories());
}
/**
* Adds a {@code null} project suffix, and delegates to calculate groupId and artifactId for
* the local project for ComponentTypes where suffix is not used.
*
* @return The next BuilderStep subtype in the builder chain.
* @see #withProjectSuffix(String)
*/
public MavenVersionEnricher withoutProjectSuffix() {
// Check sanity
Validate.isTrue(!isProjectSuffixRequired(), "Project type [" + pomType + "] requires a project suffix.");
// Delegate
return withProjectSuffix(null);
}
//
// Private helpers
//
/**
* Retrieves the directory name for the active Maven project.
*
* @param projectSuffix The suffix for the project. May be null or empty, depending on the active projectType.
* @return The name of the directory for the active Maven project. Never null or empty.
*/
@SuppressWarnings("all")
private String getProjectDirectoryName(final String projectSuffix) {
// Check sanity
if (pomType == PomType.ROOT_REACTOR) {
// This is a special case
return project.getName();
}
final boolean addProjectSuffixToDirName = isProjectSuffixRequired()
&& projectSuffix != null
&& !projectSuffix.isEmpty();
final StringBuilder dirNameBuilder = new StringBuilder();
if (isProjectNameAddedToDirectories()) {
//
// In this case, the project dirName should be prepended with the
// project prefix (if it exists) and name, i.e:
//
// a) foo-gnat-messaging-model, or
// b) gnat-messaging-model (if no prefix is defined within the project).
//
if (project.getPrefix() != null && !"".equals(project.getPrefix())) {
dirNameBuilder.append(project.getPrefix()).append(Name.DEFAULT_SEPARATOR);
}
dirNameBuilder.append(project.getName()).append(Name.DEFAULT_SEPARATOR);
}
// For parent POMs, we should simply add the PomType as the name ...
if (!isParentPomType()) {
//
// The SoftwareComponent's name should not be null or empty for non-parent projects.
//
final String softwareComponentName = pomTokens.get(RelativePathEnricher.SOFTWARE_COMPONENT_NAME);
Validate.notEmpty(softwareComponentName, "SoftwareComponentName was null or empty for non-parent "
+ "pomType [" + pomType + "]. Cannot calculate project directory name. "
+ "This should not happen.");
dirNameBuilder.append(softwareComponentName);
}
// Append the project type to the directory name, minding that reactor POMs are a special case.
// Reactor project reside within the software component directory and simply ensure that all
// SoftwareComponentPart projects are included within the build reactor.
final String lcPomType = pomType.name().toLowerCase().replace("_", Name.DEFAULT_SEPARATOR);
if (pomType == PomType.REACTOR) {
// The reactor POM in the poms reactor is not part of a software component.
if (POMS_DIRECTORY_NAME.equals(pomTokens.get(RelativePathEnricher.SOFTWARE_COMPONENT_NAME))) {
return POMS_DIRECTORY_NAME;
}
// Standard component reactor; don't append anything.
// dirNameBuilder.append(Name.DEFAULT_SEPARATOR).append(lcPomType);
} else if (isParentPomType()) {
dirNameBuilder.append(lcPomType);
} else {
// This is a component PomType. Remove the "component-" part of the lcPomType.
final int stripOffLength = ("component" + Name.DEFAULT_SEPARATOR).length();
if (lcPomType.length() < stripOffLength) {
log.error("Got stripOffLength: [" + stripOffLength + "] and lcPomType.length(): ["
+ lcPomType.length() + "] for lcPomType [" + lcPomType + "]");
throw new IllegalStateException("This should not happen...");
}
dirNameBuilder.append(Name.DEFAULT_SEPARATOR).append(lcPomType.substring(stripOffLength));
}
if (addProjectSuffixToDirName) {
dirNameBuilder.append(Name.DEFAULT_SEPARATOR).append(projectSuffix);
}
// All done - but shorten the name of the directory somewhat.
return dirNameBuilder
.toString()
.replace("implementation", "impl");
}
private String getRelativePackage(final String localProjectSuffix) {
final String prefix = project.getPrefix() != null ? project.getPrefix() + Name.DEFAULT_SEPARATOR : "";
final StringBuilder relativePackageBuilder = new StringBuilder();
if (isParentPomType()) {
// The relative package should be something like
// poms.[project.prefix-][project.name]-[pomType],
//
// given that the pomType is made lowercase.
relativePackageBuilder
.append(POMS_DIRECTORY_NAME)
.append(".")
.append(prefix)
.append(project.getName())
.append(Name.DEFAULT_SEPARATOR)
.append(pomType.toString().toLowerCase().replace("_", Name.DEFAULT_SEPARATOR));
} else if (pomType == PomType.ROOT_REACTOR) {
return "";
} else {
// The first part of the relative package should be
//
// [relativePathToParent (converted)].[softwareComponent]
final String relativePathToSoftwareComponent = pomTokens.get(
RelativePathEnricher.SOFTWARE_COMPONENT_RELATIVE_PATH);
relativePackageBuilder.append(relativePathToSoftwareComponent.replace("/", "."));
if (pomType != PomType.REACTOR) {
// A relative path of:
// 1) "services/finance/finance-api", or
// 2) "services/finance/foo-finance-api"
//
// ... should yield relative package:
// "services.finance.api"
//
// ... where the last package segment is retrieved from the PomType.
final String lcPomTypeName = pomType.name().toLowerCase();
final String pomTypePackage = lcPomTypeName.substring(lcPomTypeName.lastIndexOf("_") + 1);
relativePackageBuilder.append(".").append(pomTypePackage);
// Append the project suffix, if required
if (isProjectSuffixRequired()) {
relativePackageBuilder.append(".").append(localProjectSuffix);
}
}
}
// All done.
return relativePackageBuilder.toString();
}
}
/**
* Either of two Maven versions are generally required to tokenize the <parent>
* element within a POM. For {@code PomType.REACTOR} and {@code PomType.ROOT_REACTOR} projects,
* the {@code reactorPomMavenVersion} is used, and for other projects the {@code topParentMavenVersion}
* version is used.
*/
public static class MavenVersionEnricher extends BuilderStep {
MavenVersionEnricher(final Project project,
final PomType pomType,
final SortedMap<String, String> pomTokens,
final boolean addProjectNameAsDirectoryPrefix) {
super(project, pomType, pomTokens, addProjectNameAsDirectoryPrefix);
}
/**
* Assigns the two Maven versions used to create POM Tokens within this Builder. For {@code PomType.REACTOR}
* and {@code PomType.ROOT_REACTOR} projects, the {@code reactorPomMavenVersion} is used, and for other
* projects the {@code topParentMavenVersion} version is used.
* This BuilderStep provides the possibility to use different Maven versions for the parent POM and the
* current POM, which is required in root/top reactor and root/top parent poms. For example, a typical
* tokenization for a root reactor template POM uses the following structure:
* <pre>
* <code>
* <parent>
* <groupId>[parentGroupId]</groupId>
* <artifactId>[parentArtifactId]</artifactId>
* <strong><version>[project:reactorParent.mavenVersion]</version></strong>
* <relativePath>[parentPomRelativePath]</relativePath>
* </parent>
*
*
* <groupId>[groupId]</groupId>
* <artifactId>[artifactId]</artifactId>
* <strong><version>[mavenVersion]</version></strong>
* </code>
* </pre>
*
* @param reactorPomMavenVersion The version of the reactor POMs. Note that this is <strong>not</strong>
* identical to {@code project.getReactorParent().getMavenVersion()},
* since the reactor parent's maven version is the version of the
* <strong>parent</strong> element for the root/topmost reactor POM in the
* project.
* @param topParentMavenVersion The version of the
* @return The next BuilderStep subtype in the builder chain.
*/
public Builder withMavenVersions(final String reactorPomMavenVersion,
final String topParentMavenVersion) {
// Check sanity
Validate.notEmpty(reactorPomMavenVersion, "Cannot handle null or empty reactorPomMavenVersion argument.");
Validate.notEmpty(topParentMavenVersion, "Cannot handle null or empty topParentMavenVersion argument.");
// Use the correct parent Maven version
switch (pomType) {
case ROOT_REACTOR:
pomTokens.put(PomToken.PARENT_VERSION.getToken(), project.getReactorParent().getMavenVersion());
pomTokens.put(PomToken.VERSION.getToken(), reactorPomMavenVersion);
break;
case PARENT:
pomTokens.put(PomToken.PARENT_VERSION.getToken(), project.getParentParent().getMavenVersion());
pomTokens.put(PomToken.VERSION.getToken(), topParentMavenVersion);
break;
case REACTOR:
pomTokens.put(PomToken.PARENT_VERSION.getToken(), reactorPomMavenVersion);
pomTokens.put(PomToken.VERSION.getToken(), reactorPomMavenVersion);
break;
default:
pomTokens.put(PomToken.PARENT_VERSION.getToken(), topParentMavenVersion);
pomTokens.put(PomToken.VERSION.getToken(), topParentMavenVersion);
break;
}
// All done.
return new Builder(project, pomType, pomTokens, isProjectNameAddedToDirectories());
}
}
/**
* Builder class to simplify creating a SortedMap whose keys contains the tokens of each StandardToken.
* This Map can be used for token replacements in text template files.
*/
public static class Builder extends BuilderStep {
Builder(final Project project,
final PomType pomType,
final SortedMap<String, String> pomTokens,
final boolean addProjectNameAsDirectoryPrefix) {
super(project, pomType, pomTokens, addProjectNameAsDirectoryPrefix);
}
/**
* Standard additive token addition method.
*
* @param pomToken The non-null PomToken for which to add a value.
* @param value The non-empty value to add.
* @return This Builder object, for call chaining.
*/
public Builder addPomToken(final PomToken pomToken, final String value) {
// Check sanity
Validate.notNull(pomToken, "Cannot handle null pomToken argument.");
// Delegate and return
addToken(pomToken.getToken(), value);
return this;
}
/**
* Adds an arbitrary key/value pair to this SingleBracketPomTokenParserFactory.
*
* @param key The non-empty token key.
* @param value The non-empty value to add.
* @return This Builder object, for call chaining.
*/
public Builder addToken(final String key, final String value) {
// Check sanity
Validate.notNull(key, "Cannot handle null key argument.");
Validate.notEmpty(value, "Cannot handle null or empty value argument.");
// Add the pomToken
pomTokens.put(key, value);
return this;
}
/**
* @return A fully set-up TokenParser sporting 3 ParserAgents (i.e. DefaultParserAgent,
* HostNameParserAgent and a FactoryParserAgent).
*/
public TokenParser build() {
// Add the ParserAgents and return.
final TokenParser toReturn = new DefaultTokenParser();
toReturn.addAgent(new DefaultParserAgent());
toReturn.addAgent(new HostNameParserAgent());
toReturn.addAgent(new FactoryParserAgent(project, pomTokens));
// All done.
toReturn.initialize(new SingleBracketTokenDefinitions());
return toReturn;
}
}
}