/*
* #%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.analyzer;
import org.apache.commons.lang3.Validate;
import org.apache.maven.model.Model;
import org.apache.maven.model.Parent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import se.jguru.nazgul.core.quickstart.api.InvalidStructureException;
import se.jguru.nazgul.core.quickstart.api.PomType;
import se.jguru.nazgul.core.quickstart.model.Name;
import java.util.ArrayList;
import java.util.List;
/**
* Abstract PomAnalyzer implementation, providing convenience methods for validating various
* properties of Maven POMs. Override this for a concrete PomAnalyzer class which can be applied
* along with a concrete NamingStrategy to analyze and validate the content of POMs for
* compliance with a given strategy.
*
* @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>, jGuru Europe AB
*/
public abstract class AbstractPomAnalyzer implements PomAnalyzer {
// Our log
private static final Logger log = LoggerFactory.getLogger(AbstractPomAnalyzer.class.getName());
// Internal state
private NamingStrategy namingStrategy;
/**
* Creates an AbstractPomAnalyzer using the supplied NamingStrategy to create Names from Models and
* to validate the well-formedness of POMs.
*
* @param namingStrategy A non-null NamingStrategy used to validate Models.
*/
protected AbstractPomAnalyzer(final NamingStrategy namingStrategy) {
Validate.notNull(namingStrategy, "Cannot handle null namingStrategy argument.");
this.namingStrategy = namingStrategy;
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("all")
public final void validateRootReactorPom(final Model aRootReactorPom) throws InvalidStructureException {
// Validate that the supplied Model complies with the naming strategy.
final Name name = namingStrategy.createName(aRootReactorPom);
try {
namingStrategy.validate(name, PomType.ROOT_REACTOR);
} catch (IllegalArgumentException e) {
final StringBuilder builder = getErrorMessageBuilder("Incompatible POM name for strategy ["
+ namingStrategy.getClass().getSimpleName() + "]: " + e.getMessage(), aRootReactorPom);
throw new InvalidStructureException(builder.toString());
}
// Delegate
performCustomRootReactorPomValidation(aRootReactorPom);
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("all")
public final void validateTopmostParentPom(final Model topmostParentPom) throws InvalidStructureException {
// Validate that the supplied Model complies with the naming strategy.
final Name name = namingStrategy.createName(topmostParentPom);
try {
namingStrategy.validate(name, PomType.PARENT);
} catch (IllegalArgumentException e) {
final StringBuilder builder = getErrorMessageBuilder("Incompatible POM name for strategy ["
+ namingStrategy.getClass().getSimpleName() + "]: " + e.getMessage(), topmostParentPom);
throw new InvalidStructureException(builder.toString());
}
// Parent POMs should not define Modules
verifyNoModules(topmostParentPom);
// Delegate
performCustomTopmostParentPomValidation(topmostParentPom);
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("all")
public final void validate(final Model aPOM, final PomType expectedType, final Model parentOrNull) throws
InvalidStructureException {
// Check sanity
Validate.notNull(aPOM, "Cannot handle null aPOM argument.");
Validate.notNull(expectedType, "Cannot handle null expectedType argument.");
// Validate that the supplied Model complies with the naming strategy.
final Name name = namingStrategy.createName(aPOM);
try {
namingStrategy.validate(name, expectedType);
} catch (IllegalArgumentException e) {
final StringBuilder builder = getErrorMessageBuilder("Incompatible POM name for strategy ["
+ namingStrategy.getClass().getSimpleName() + "]: " + e.getMessage(), aPOM);
throw new InvalidStructureException(builder.toString());
}
switch (expectedType) {
case ROOT_REACTOR:
validateRootReactorPom(aPOM);
break;
case PARENT:
// Delegate
validateTopmostParentPom(aPOM);
break;
case API_PARENT:
case MODEL_PARENT:
case WAR_PARENT:
case OTHER_PARENT:
// Check sanity
Validate.notNull(parentOrNull, "Cannot handle null parentOrNull argument for expected type ["
+ expectedType + "].");
// Delegate
validateParentPom(aPOM, expectedType, parentOrNull);
break;
case REACTOR:
// Check sanity
Validate.notNull(parentOrNull, "Cannot handle null parentOrNull argument for expected type ["
+ expectedType + "].");
validateReactorPom(aPOM, parentOrNull);
break;
default:
throw new UnsupportedOperationException("Cannot handle expectedType [" + expectedType + "]");
}
}
/**
* Verifies that the supplied toValidate Model does not contain any defined Modules, and
* that the supplied nonNullParent has the same GAV coordinates as the supplied toValidate POM.
*
* @param toValidate The POM model to validate.
* @param expectedType The expected type of POM to validate the supplied Model against.
* @param parentOrNull A Model which holds the expected Maven GAV coordinates for the aPOM's Parent POM.
* @throws InvalidStructureException if the Model does not conform to structure specifications.
*/
protected void validateParentPom(final Model toValidate, final PomType expectedType, final Model parentOrNull)
throws InvalidStructureException {
// Parent POMs should not define Modules
verifyNoModules(toValidate);
// Is the supplied parentOrNull correct?
if (parentOrNull != null) {
verifyParentPom(toValidate, parentOrNull);
}
}
/**
* Verifies that the supplied toValidate Model does not contain any defined Modules, and
* that the supplied nonNullParent has the same GAV coordinates as the supplied toValidate POM.
*
* @param toValidate The POM model to validate.
* @param nonNullParent A Model which holds the expected Maven GAV coordinates for the aPOM's Parent POM.
* @throws InvalidStructureException if the Model does not conform to structure specifications, i.e. if the
* reactor POM was invalid.
*/
protected void validateReactorPom(final Model toValidate, final Model nonNullParent)
throws InvalidStructureException {
// Is the supplied nonNullParent correct?
verifyParentPom(toValidate, nonNullParent);
}
/**
* Override this method to perform custom validation of the Maven Model from the root reactor POM.
* The NamingStrategy has already been used to validate maven GAV coordinates for the supplied root reactor
* when this method is invoked.
*
* @param rootReactorModel the root rootReactorModel which should be validated in a custom way.
* @throws InvalidStructureException if the supplied aRootReactorModel was invalid.
*/
protected void performCustomRootReactorPomValidation(final Model rootReactorModel)
throws InvalidStructureException {
// Do nothing.
}
/**
* Override this method to perform custom validation of the Maven Model from the topmost parent POM.
* The NamingStrategy has already been used to validate maven GAV coordinates for the supplied topmost
* parent POM when this method is invoked.
*
* @param topmostParentModel the root rootReactorModel which should be validated in a custom way.
* @throws InvalidStructureException if the supplied topmostParentModel was invalid.
*/
protected void performCustomTopmostParentPomValidation(final Model topmostParentModel)
throws InvalidStructureException {
// Do nothing.
}
//
// Private helpers
//
/**
* Verifies that the supplied toValidate Model has identical groupId, artifactId and version
* of the supplied requiredParent Model.
*
* @param toVerify The Model to verify.
* @param requiredParent The Maven Model containing data of the required Parent for the supplied toVerify Model.
* @throws InvalidStructureException if the GAV coordinates of the supplied toVerify Model did not match those of
* the supplied requiredParent Module.
*/
protected final void verifyParentPom(final Model toVerify, final Model requiredParent)
throws InvalidStructureException {
final List<String> errors = new ArrayList<>();
//
// Note that the version of the toValidate project might be null
// (in case the version is inherited from the parent, and therefore matching).
//
final Parent parentToValidate = toVerify.getParent();
final boolean versionMatches = toVerify.getVersion() == null
|| requiredParent.getVersion().equals(parentToValidate.getVersion());
if (!versionMatches) {
errors.add("Required version mismatch (Required: " + requiredParent.getVersion() + ", Actual: "
+ parentToValidate.getVersion() + ")");
}
if (!requiredParent.getGroupId().equals(parentToValidate.getGroupId())) {
errors.add("Required groupId mismatch (Required: " + requiredParent.getGroupId() + ", Actual: "
+ parentToValidate.getGroupId() + ")");
}
if (!requiredParent.getArtifactId().equals(parentToValidate.getArtifactId())) {
errors.add("Required artifactId mismatch (Required: " + requiredParent.getArtifactId() + ", Actual: "
+ parentToValidate.getArtifactId() + ")");
}
if (errors.size() > 0) {
final StringBuilder builder = getErrorMessageBuilder("Incorrect parent relationship", toVerify);
for (int i = 0; i < errors.size(); i++) {
builder.append("\n " + (i + 1) + ") ").append(errors.get(i));
}
// All done.
throw new InvalidStructureException(builder.toString());
}
}
/**
* Verifies that the supplied Model does not have any defined Modules.
* This is a requirement for all Parent POMs, since they should be the direct parents of
* artifact/leaf projects within a multi-module reactor.
*
* @param toVerify The Model to verify.
* @throws InvalidStructureException if the supplied Model contained Module definitions.
*/
protected void verifyNoModules(final Model toVerify) throws InvalidStructureException {
final List<String> modules = toVerify.getModules();
if (modules != null && modules.size() > 0) {
final StringBuilder builder = getErrorMessageBuilder("POM should not contain Modules", toVerify);
throw new InvalidStructureException(builder.toString());
}
}
/**
* Retrieves the expected suffix for an artifactID of a project Model with the supplied type.
*
* @param type The PomType of a project.
* @return the expected suffix for an artifactID of a project Model with the supplied type, or {@code null}
* if none could be provided. (This is the case for a OTHER_PARENT only).
*/
protected String getExpectedArtifactIdSuffix(final PomType type) {
// Check sanity
Validate.notNull(type, "Cannot handle null type argument.");
String toReturn = null;
switch (type) {
case ROOT_REACTOR:
case REACTOR:
toReturn = AbstractNamingStrategy.REACTOR_SUFFIX;
break;
case PARENT:
case API_PARENT:
case MODEL_PARENT:
case WAR_PARENT:
toReturn = type.name().toLowerCase().replace('_', '-');
break;
case OTHER_PARENT:
toReturn = AbstractNamingStrategy.PARENT_SUFFIX;
break;
default:
log.warn("PomType [" + type + "] not registered.");
break;
}
// All done.
return toReturn;
}
private StringBuilder getErrorMessageBuilder(final String mainError, final Model model) {
// Check sanity
Validate.notEmpty(mainError, "Cannot handle null or empty mainError argument.");
Validate.notNull(model, "Cannot handle null model argument.");
final String effectiveVersion = model.getVersion() == null
? model.getParent().getVersion()
: model.getVersion();
return new StringBuilder(mainError + " for POM (" + model.getGroupId() + "/" + model.getArtifactId()
+ "/" + effectiveVersion + "): ");
}
}