/* * #%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; 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.analyzer.NamingStrategy; import se.jguru.nazgul.core.quickstart.api.analyzer.PomAnalyzer; import se.jguru.nazgul.core.quickstart.model.Name; import java.io.File; import java.io.IOException; import java.io.Serializable; /** * Default DefaultStructureNavigator implementation. * * @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>, jGuru Europe AB */ public class DefaultStructureNavigator implements StructureNavigator, Serializable { // Our log private static final Logger log = LoggerFactory.getLogger(DefaultStructureNavigator.class.getName()); // Internal state private File projectRootDirectoryCache; private File projectTopmostParentPomDirectoryCache; private NamingStrategy namingStrategy; private PomAnalyzer pomAnalyzer; /** * Creates a new DefaultStructureNavigator using the supplied ProjectNamingStrategy * to identify pieces of the project. * * @param namingStrategy The non-null ProjectNamingStrategy used to identify parts of the project. * @param pomAnalyzer The non-null PomAnalyzer used to check POMs for validity. */ public DefaultStructureNavigator(final NamingStrategy namingStrategy, final PomAnalyzer pomAnalyzer) { // Check sanity Validate.notNull(namingStrategy, "Cannot handle null namingStrategy argument."); Validate.notNull(pomAnalyzer, "Cannot handle null pomAnalyzer argument."); // Assign internal state this.namingStrategy = namingStrategy; this.pomAnalyzer = pomAnalyzer; } /** * {@inheritDoc} */ @SuppressWarnings("all") @Override public File getProjectRootDirectory(final File fileOrDirectory) throws InvalidStructureException { // Check sanity Validate.notNull(fileOrDirectory, "Cannot handle null fileOrDirectory argument."); // Result already cached? if (projectRootDirectoryCache != null) { final String cachedProjectRootPath = FileUtils.getCanonicalPath(projectRootDirectoryCache); final String givenFileOrDir = FileUtils.getCanonicalPath(fileOrDirectory); if (givenFileOrDir.startsWith(cachedProjectRootPath)) { return projectRootDirectoryCache; } } // If the supplied fileOrDirectory is a File, originate from its parent directory. final File topmostNonNullPom = getRootReactorPomFile(fileOrDirectory); final File toReturn = topmostNonNullPom == null ? null : topmostNonNullPom.getParentFile(); if (toReturn != null) { // 1) Validate that we have a properly formed Root Reactor pom. final Model rootReactorCandidate = FileUtils.getPomModel(topmostNonNullPom); final Parent rootReactorParent = rootReactorCandidate.getParent(); Model parentModel = new Model(); pomAnalyzer.validateRootReactorPom(rootReactorCandidate); // 2) Acquire the project Name and the corresponding project folder prefix final Name projectName = Name.parse(rootReactorCandidate.getArtifactId()); namingStrategy.validate(projectName, PomType.ROOT_REACTOR); final String sep = projectName.getSeparator(); final String folderNamePrefix = namingStrategy.isPrefixRequiredOnAllFolders() ? projectName.getPrefix() + sep + projectName.getName() + sep : projectName.getName() + sep; // 2) Ensure that we have a poms directory final File pomsDir = getRelativeFileOrDirectory(toReturn, "poms", false); validateDirectoryExists(pomsDir); // 3) ... and a correctly formed reactor POM in the poms directory final File pomsReactorFile = new File(pomsDir, "pom.xml"); validateFileExists(pomsReactorFile); final Model pomsReactor = FileUtils.getPomModel(pomsReactorFile); pomAnalyzer.validate(pomsReactor, PomType.REACTOR, rootReactorCandidate); // 4) Validate that we have a correct setup of parent POM directories final File parentPomDir = new File(pomsDir, folderNamePrefix + "parent"); final File apiParentPomDir = new File(pomsDir, folderNamePrefix + "api" + sep + "parent"); final File modelParentPomDir = new File(pomsDir, folderNamePrefix + "model" + sep + "parent"); validateDirectoryExists(parentPomDir); validateDirectoryExists(apiParentPomDir); validateDirectoryExists(modelParentPomDir); // 5) Ensure that the required parent POMs exist final File parentPomFile = new File(parentPomDir, "pom.xml"); final File apiParentPomFile = new File(apiParentPomDir, "pom.xml"); final File modelParentPomFile = new File(modelParentPomDir, "pom.xml"); validateFileExists(parentPomFile); validateFileExists(apiParentPomFile); validateFileExists(modelParentPomFile); // 6) Ensure that the Parent POMs are valid, in terms of parent relations. final Model parentPomModel = FileUtils.getPomModel(parentPomFile); final Model apiParentPomModel = FileUtils.getPomModel(apiParentPomFile); final Model modelParentPomModel = FileUtils.getPomModel(modelParentPomFile); pomAnalyzer.validate(parentPomModel, PomType.PARENT, null); pomAnalyzer.validate(apiParentPomModel, PomType.API_PARENT, parentPomModel); pomAnalyzer.validate(modelParentPomModel, PomType.MODEL_PARENT, apiParentPomModel); // All done. projectRootDirectoryCache = toReturn; projectTopmostParentPomDirectoryCache = parentPomDir; return toReturn; } else { throw new InvalidStructureException("File or directory [" + fileOrDirectory.getAbsolutePath() + "] is not located inside a Maven project."); } } /** * {@inheritDoc} */ @Override public String getRelativePath(final File directory, final boolean usePackageSeparator) throws InvalidStructureException { // Check sanity Validate.notNull(directory, "Cannot handle null directory argument."); if (directory.exists() && !directory.isDirectory()) { throw new IllegalArgumentException("If the directory target exists, " + "it must be a directory. Found incorrect: [" + FileUtils.getCanonicalPath(directory) + "]"); } final File rootDirPath = getProjectRootDirectory(directory); final String rootPath = FileUtils.getCanonicalPath(rootDirPath); final String leafPath = FileUtils.getCanonicalPath(directory); final int lastIndex = leafPath.lastIndexOf(rootPath); if (lastIndex == -1) { throw new InvalidStructureException("Leaf path [" + leafPath + "] was not below [" + rootPath + "]"); } String toReturn = leafPath.substring(lastIndex + rootPath.length() + 1); if (usePackageSeparator) { toReturn = toReturn.replace(File.separator, PACKAGE_SEPARATOR); } if (log.isDebugEnabled()) { log.debug("RootPath [" + rootPath + "] and leafPath [" + leafPath + "] yields [" + toReturn + "], given usePackageSeparator [" + usePackageSeparator + "]"); } // All done. return toReturn; } /** * {@inheritDoc} */ @Override public File getParentPomDirectory(final File fileOrDirectory) throws InvalidStructureException { // Delegate getProjectRootDirectory(fileOrDirectory); // All done. return projectTopmostParentPomDirectoryCache; } // // Private helpers // private void validateDirectoryExists(final File aDir) { boolean directoryExists = aDir != null && aDir.exists() && aDir.isDirectory(); if (!directoryExists) { final String msg = aDir == null ? "Required directory nonexistent." : "Required directory [" + FileUtils.getCanonicalPath(aDir) + "] nonexistent."; throw new InvalidStructureException(msg); } } private void validateFileExists(final File aFile) { boolean fileExists = aFile != null && aFile.exists() && aFile.isFile(); if (!fileExists) { final String msg = aFile == null ? "Required file nonexistent." : "Required file [" + FileUtils.getCanonicalPath(aFile) + "] nonexistent."; throw new InvalidStructureException(msg); } } private File getRootReactorPomFile(final File fileOrDirectory) { File toReturn = null; for (File current = fileOrDirectory.isFile() ? fileOrDirectory.getParentFile() : fileOrDirectory; current != null; current = current.getParentFile()) { // Done? if (!current.exists()) { break; } File tmp = getPomFile(current); if (tmp == null) { break; } // Iterate upwards toReturn = tmp; } return toReturn; } private File getPomFile(final File aDirectory) { if (aDirectory.exists() && aDirectory.isDirectory()) { final File[] files = aDirectory.listFiles(POM_FILE_FILTER); if (files != null && files.length > 0) { if (files.length > 1) { log.warn("More than one file in [" + aDirectory.getAbsolutePath() + "] has the lowercase name 'pom.xml'. " + "This is not recommended, as it confuses tooling. Returning the first one found."); } // All done, return files[0]; } } // No pom file found. return null; } private File getRelativeFileOrDirectory(final File rootReactorDirectory, final String relativePath, final boolean isFile) { final File shouldExist = new File(rootReactorDirectory, relativePath); boolean existsProperly = shouldExist.exists() && (isFile ? shouldExist.isFile() : shouldExist.isDirectory()); if (!existsProperly) { try { final String path = shouldExist.getCanonicalPath(); log.warn("Required " + (isFile ? "file" : "directory") + " [" + path + "] does not exist."); } catch (IOException e) { log.error("Could not retrieve canonical path for [" + shouldExist.getAbsolutePath() + "]", e); } } // All done. return shouldExist; } }