org.artifactory.util.RepoLayoutUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.artifactory.util.RepoLayoutUtils.java

Source

/*
 * Artifactory is a binaries repository manager.
 * Copyright (C) 2012 JFrog Ltd.
 *
 * Artifactory is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Artifactory is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Artifactory.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.artifactory.util;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.lang.StringUtils;
import org.artifactory.descriptor.repo.RepoLayout;
import org.artifactory.descriptor.repo.RepoLayoutBuilder;
import org.artifactory.util.layouts.token.BaseTokenFilter;
import org.artifactory.util.layouts.token.OrganizationPathTokenFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Noam Y. Tenne
 */
public abstract class RepoLayoutUtils {
    private static final Logger log = LoggerFactory.getLogger(RepoLayoutUtils.class);

    /**
     * LAYOUT PRESETS
     */
    public static final String MAVEN_2_DEFAULT_NAME = "maven-2-default";
    public static final String MAVEN_1_DEFAULT_NAME = "maven-1-default";
    public static final String IVY_DEFAULT_NAME = "ivy-default";
    public static final String GRADLE_DEFAULT_NAME = "gradle-default";
    public static final String NUGET_DEFAULT_NAME = "nuget-default";
    public static final String SBT_DEFAULT_NAME = "sbt-default";
    public static final String NPM_DEFAULT_NAME = "npm-default";
    public static final String BOWER_DEFAULT_NAME = "bower-default";
    public static final String VSC_DEFAULT_NAME = "vcs-default";
    public static final String SIMPLE_DEFAULT_NAME = "simple-default";

    public static List<String> DEFAULT_LAYOUTS = new LinkedList<String>() {
        {
            add(MAVEN_2_DEFAULT_NAME);
            add(MAVEN_1_DEFAULT_NAME);
            add(IVY_DEFAULT_NAME);
            add(GRADLE_DEFAULT_NAME);
            add(NUGET_DEFAULT_NAME);
            add(SBT_DEFAULT_NAME);
            add(NPM_DEFAULT_NAME);
            add(BOWER_DEFAULT_NAME);
            add(VSC_DEFAULT_NAME);
            add(SIMPLE_DEFAULT_NAME);
        }
    };

    public static final RepoLayout MAVEN_2_DEFAULT = new RepoLayoutBuilder().name(MAVEN_2_DEFAULT_NAME)
            .artifactPathPattern("[orgPath]/[module]/[baseRev](-[folderItegRev])/"
                    + "[module]-[baseRev](-[fileItegRev])(-[classifier]).[ext]")
            .distinctiveDescriptorPathPattern(true)
            .descriptorPathPattern("[orgPath]/[module]/[baseRev](-[folderItegRev])/"
                    + "[module]-[baseRev](-[fileItegRev])(-[classifier]).pom")
            .folderIntegrationRevisionRegExp("SNAPSHOT")
            .fileIntegrationRevisionRegExp("SNAPSHOT|(?:(?:[0-9]{8}.[0-9]{6})-(?:[0-9]+))").build();
    public static final RepoLayout IVY_DEFAULT = new RepoLayoutBuilder().name(IVY_DEFAULT_NAME)
            .artifactPathPattern("[org]/[module]/[baseRev](-[folderItegRev])/[type]s/"
                    + "[module](-[classifier])-[baseRev](-[fileItegRev]).[ext]")
            .distinctiveDescriptorPathPattern(true)
            .descriptorPathPattern(
                    "[org]/[module]/[baseRev](-[folderItegRev])/[type]s/" + "ivy-[baseRev](-[fileItegRev]).xml")
            .folderIntegrationRevisionRegExp("\\d{14}").fileIntegrationRevisionRegExp("\\d{14}").build();
    public static final RepoLayout GRADLE_DEFAULT = new RepoLayoutBuilder().name(GRADLE_DEFAULT_NAME)
            .artifactPathPattern("[org]/[module]/[baseRev](-[folderItegRev])/"
                    + "[module]-[baseRev](-[fileItegRev])(-[classifier]).[ext]")
            .distinctiveDescriptorPathPattern(true)
            .descriptorPathPattern("[org]/[module]/ivy-[baseRev](-[fileItegRev]).xml")
            .folderIntegrationRevisionRegExp("\\d{14}").fileIntegrationRevisionRegExp("\\d{14}").build();
    public static final RepoLayout MAVEN_1_DEFAULT = new RepoLayoutBuilder().name(MAVEN_1_DEFAULT_NAME)
            .artifactPathPattern("[org]/[type]s/[module]-[baseRev](-[fileItegRev])" + "(-[classifier]).[ext]")
            .distinctiveDescriptorPathPattern(true)
            .descriptorPathPattern("[org]/[type]s/[module]-[baseRev](-[fileItegRev]).pom")
            .folderIntegrationRevisionRegExp(".+").fileIntegrationRevisionRegExp(".+").build();

    /**
     * LAYOUT TOKENS
     */
    public static final String ORGANIZATION = "org";
    public static final String ORGANIZATION_PATH = "orgPath";
    public static final String MODULE = "module";
    public static final String BASE_REVISION = "baseRev";
    public static final String FOLDER_INTEGRATION_REVISION = "folderItegRev";
    public static final String FILE_INTEGRATION_REVISION = "fileItegRev";
    public static final String CLASSIFIER = "classifier";
    public static final String EXT = "ext";
    public static final String TYPE = "type";
    public static final String RELEASE = "RELEASE";

    public static final Set<String> TOKENS = Sets.newHashSet(ORGANIZATION, ORGANIZATION_PATH, MODULE, BASE_REVISION,
            FOLDER_INTEGRATION_REVISION, FILE_INTEGRATION_REVISION, CLASSIFIER, EXT, TYPE, RELEASE);

    public static final Map<String, BaseTokenFilter> TOKEN_FILTERS;

    private static final Set<Character> REGEX_SPECIAL_TOKENS = Sets.newHashSet('.', '+', '?', '*', '{', '}', '^',
            '$');
    private static final Pattern OPTIONAL_AREA_PATTERN = Pattern.compile("\\([^\\(]*\\)");
    private static final Pattern REPLACED_OPTIONAL_TOKEN_PATTERN = Pattern.compile("\\([^\\[\\(]*\\)");
    private static final Pattern CUSTOM_TOKEN_PATTERN = Pattern.compile("<[^<]*>");

    static {
        Map<String, BaseTokenFilter> temp = Maps.newHashMap();
        temp.put(RepoLayoutUtils.ORGANIZATION_PATH, OrganizationPathTokenFilter.getInstance());
        TOKEN_FILTERS = Collections.unmodifiableMap(temp);
    }

    private RepoLayoutUtils() {
    }

    public static boolean isReservedName(String layoutName) {
        return DEFAULT_LAYOUTS.contains(layoutName);
    }

    public static boolean isDefaultM2(RepoLayout repoLayout) {
        return MAVEN_2_DEFAULT.equals(repoLayout);
    }

    public static boolean isDefaultSimple(RepoLayout repoLayout) {
        if (repoLayout == null) {
            return false;
        }
        String name = repoLayout.getName();
        return StringUtils.equals(name, "simple-default") || StringUtils.equals(name, "art-simple-default");
    }

    public static boolean isDefaultIvy(RepoLayout repoLayout) {
        return IVY_DEFAULT.equals(repoLayout);
    }

    public static boolean isDefaultGradle(RepoLayout repoLayout) {
        return GRADLE_DEFAULT.equals(repoLayout);
    }

    /**
     * Indicates whether the given layout contains the orgPath token, equal to Ivy's M2 compatibility,
     *
     * @param repoLayout Layout to check
     * @return True if the layout contains the orgPath token
     */
    public static boolean layoutContainsOrgPathToken(RepoLayout repoLayout) {
        if (repoLayout == null) {
            throw new IllegalArgumentException("Cannot check a null layout for token existence.");
        }

        String artifactPathPattern = repoLayout.getArtifactPathPattern();
        String descriptorPathPattern = repoLayout.getDescriptorPathPattern();

        return ((artifactPathPattern != null) && artifactPathPattern.contains(ORGANIZATION_PATH))
                || (repoLayout.isDistinctiveDescriptorPathPattern() && (descriptorPathPattern != null)
                        && descriptorPathPattern.contains(ORGANIZATION_PATH));
    }

    /**
     * Find all optional areas that their token values were provided and remove the "optional" brackets that surround
     * them.
     *
     * @param itemPathTemplate     Item path template to modify
     * @param removeBracketContent True if the content of the optional bracket should be disposed
     * @return Modified item path template
     */
    public static String removeReplacedTokenOptionalBrackets(String itemPathTemplate,
            boolean removeBracketContent) {
        Matcher matcher = REPLACED_OPTIONAL_TOKEN_PATTERN.matcher(itemPathTemplate);

        int latestGroupEnd = 0;
        StringBuilder newPathBuilder = new StringBuilder();

        while (matcher.find()) {
            int replacedOptionalTokenAreaStart = matcher.start();
            int replacedOptionalTokenAreaEnd = matcher.end();
            String replacedOptionalTokenValue = matcher.group(0);

            newPathBuilder.append(itemPathTemplate.substring(latestGroupEnd, replacedOptionalTokenAreaStart));

            if (!removeBracketContent) {
                newPathBuilder.append(replacedOptionalTokenValue.replaceAll("[\\(\\)]", ""));
            }

            //Path after optional area
            latestGroupEnd = replacedOptionalTokenAreaEnd;
        }

        if ((latestGroupEnd != 0) && latestGroupEnd < itemPathTemplate.length()) {
            newPathBuilder.append(itemPathTemplate.substring(latestGroupEnd));
        }

        if (newPathBuilder.length() == 0) {
            return itemPathTemplate;
        }

        return newPathBuilder.toString();
    }

    /**
     * Find all remaining optional areas that were left with un-replaced tokens and remove them completely
     *
     * @param itemPathTemplate Item path template to modify
     * @return Modified item path template
     */
    public static String removeUnReplacedTokenOptionalBrackets(String itemPathTemplate) {
        Matcher matcher = OPTIONAL_AREA_PATTERN.matcher(itemPathTemplate);

        int latestGroupEnd = 0;
        StringBuilder newPathBuilder = new StringBuilder();

        while (matcher.find()) {
            int optionalAreaStart = matcher.start();
            int optionalAreaEnd = matcher.end();
            String optionalAreaValue = matcher.group(0);

            if (optionalAreaValue.contains("[")) {
                newPathBuilder.append(itemPathTemplate.substring(latestGroupEnd, optionalAreaStart));
                latestGroupEnd = optionalAreaEnd;
            }
        }

        if ((latestGroupEnd != 0) && latestGroupEnd < itemPathTemplate.length()) {
            newPathBuilder.append(itemPathTemplate.substring(latestGroupEnd));
        }

        if (newPathBuilder.length() == 0) {
            return itemPathTemplate;
        }

        return newPathBuilder.toString();
    }

    /**
     * Creates a regular expression based on the given path pattern and layout
     *
     * @param repoLayout   Repo layout to target
     * @param patternToUse Pattern to translate
     * @return Regular expression of given path
     */
    public static String generateRegExpFromPattern(RepoLayout repoLayout, String patternToUse) {
        return generateRegExpFromPattern(repoLayout, patternToUse, false, false);
    }

    /**
     * Creates a regular expression based on the given path pattern and layout
     * the pattern may contain version tokens ([RELEASE] or [INTEGRATION])
     *
     * @param repoLayout         Repo layout to target
     * @param patternToUse       Pattern to translate
     * @param failOnUnknownToken Throw exception if the pattern contains an unknown token (neither reserved nor custom)
     * @return Regular expression of given path
     */
    public static String generateRegExpFromPattern(RepoLayout repoLayout, String patternToUse,
            boolean failOnUnknownToken) {
        return generateRegExpFromPattern(repoLayout, patternToUse, failOnUnknownToken, false);
    }

    /**
     * Creates a regular expression based on the given path pattern and layout
     *
     * @param repoLayout         Repo layout to target
     * @param patternToUse       Pattern to translate
     * @param failOnUnknownToken Throw exception if the pattern contains an unknown token (neither reserved nor custom)
     * @param hasVersionTokens   indicates if the pattern contains version tokens
     * @return Regular expression of given path
     */
    public static String generateRegExpFromPattern(RepoLayout repoLayout, String patternToUse,
            boolean failOnUnknownToken, boolean hasVersionTokens) {
        List<String> tokenAppearance = Lists.newArrayList();
        StringBuilder itemPathPatternRegExpBuilder = new StringBuilder();

        boolean withinToken = false;
        boolean withinCustomToken = false;
        StringBuilder currentTokenBuilder = new StringBuilder();
        StringBuilder customRegExTokenBuilder = new StringBuilder();
        for (char c : patternToUse.toCharArray()) {
            if (('[' == c) && !withinToken && !withinCustomToken) {
                withinToken = true;
            } else if ((']' == c) && withinToken && !withinCustomToken) {
                withinToken = false;
                String currentToken = currentTokenBuilder.toString();
                currentTokenBuilder.delete(0, currentTokenBuilder.length());
                if (isReservedToken(currentToken)) {
                    appendToken(itemPathPatternRegExpBuilder, currentToken, tokenAppearance,
                            getTokenRegExp(currentToken, repoLayout, hasVersionTokens));
                } else if (customRegExTokenBuilder.length() != 0) {
                    appendToken(itemPathPatternRegExpBuilder, currentToken, tokenAppearance,
                            customRegExTokenBuilder.toString());
                    customRegExTokenBuilder.delete(0, customRegExTokenBuilder.length());
                } else {
                    String errorMessage = "The token '[" + currentToken + "]' is unknown. If this is not intended, "
                            + "please verify the token name for correctness or add a mapping for this token using the "
                            + "'[$NAME&lt;REGEXP&gt;]' syntax.";
                    if (log.isDebugEnabled()) {
                        log.debug("Error occurred while generating regular expressions from the repository layout "
                                + "pattern '{}': {}", patternToUse, errorMessage);
                    }
                    if (failOnUnknownToken) {
                        throw new IllegalArgumentException(errorMessage);
                    }
                }
            } else if ('<' == c) {
                withinCustomToken = true;
            } else if ('>' == c) {
                withinCustomToken = false;
            } else if (withinCustomToken) {
                customRegExTokenBuilder.append(c);
            } else if (!withinToken) {
                appendNonReservedToken(itemPathPatternRegExpBuilder, Character.toString(c));
            } else {
                currentTokenBuilder.append(c);
            }
        }
        return itemPathPatternRegExpBuilder.toString();
    }

    /**
     * Indicates whether the given token has a value filter assigned to it
     *
     * @param tokenName Name of token check
     * @return True if the token relies on a filter
     */
    public static boolean tokenHasFilter(String tokenName) {
        return TOKEN_FILTERS.containsKey(tokenName);
    }

    /**
     * Returns the Ivy pattern representation of the layout's artifact patten
     *
     * @param repoLayout Layout to "translate"
     * @return Ivy pattern
     */
    public static String getArtifactLayoutAsIvyPattern(RepoLayout repoLayout) {
        return getItemLayoutAsIvyPattern(repoLayout, false);
    }

    /**
     * Returns the Ivy pattern representation of the layout's descriptor patten
     *
     * @param repoLayout Layout to "translate"
     * @return Ivy pattern
     */
    public static String getDescriptorLayoutAsIvyPattern(RepoLayout repoLayout) {
        return getItemLayoutAsIvyPattern(repoLayout, true);
    }

    /**
     * Wraps the given keyword with the token parentheses ('[', ']')
     *
     * @param keyword Keyword to wrap
     * @return Wrapped keyword
     */
    public static String wrapKeywordAsToken(String keyword) {
        return "[" + keyword + "]";
    }

    /**
     * Indicates whether the compared layouts are fully compatible (don't miss any tokens when crossed)
     *
     * @param first  Layout to compare
     * @param second Layout to compare
     * @return True if no tokens are missed between the layouts
     */
    public static boolean layoutsAreCompatible(RepoLayout first, RepoLayout second) {
        String firstArtifactPathPattern = first.getArtifactPathPattern();
        String secondArtifactPathPattern = second.getArtifactPathPattern();

        if (foundMissingTokens(firstArtifactPathPattern, secondArtifactPathPattern)) {
            return false;
        }
        if (foundMissingTokens(secondArtifactPathPattern, firstArtifactPathPattern)) {
            return false;
        }

        if (first.isDistinctiveDescriptorPathPattern() && second.isDistinctiveDescriptorPathPattern()) {

            String firstDescriptorPathPattern = first.getDescriptorPathPattern();
            String secondDescriptorPathPattern = second.getDescriptorPathPattern();

            if (foundMissingTokens(firstDescriptorPathPattern, secondDescriptorPathPattern)) {
                return false;
            }
            if (foundMissingTokens(secondDescriptorPathPattern, firstDescriptorPathPattern)) {
                return false;
            }
        }
        return true;
    }

    public static String clearCustomTokenRegEx(String path) {
        return CUSTOM_TOKEN_PATTERN.matcher(path).replaceAll("");
    }

    private static void appendNonReservedToken(StringBuilder itemPathPatternRegExpBuilder,
            String itemPathPatternElement) {
        char[] splitPathPatternElement = itemPathPatternElement.toCharArray();
        for (char elementToken : splitPathPatternElement) {
            // Escaping special regex characters
            if (REGEX_SPECIAL_TOKENS.contains(elementToken)) {
                itemPathPatternRegExpBuilder.append("\\");
            }

            itemPathPatternRegExpBuilder.append(elementToken);

            if ('(' == elementToken) {
                itemPathPatternRegExpBuilder.append("?:");
            }

            //Append the '?' character to the end of the parenthesis - optional group
            if (')' == elementToken) {
                itemPathPatternRegExpBuilder.append("?");
            }
        }
    }

    private static boolean isReservedToken(String pathElement) {
        return TOKENS.contains(pathElement);
    }

    private static String getTokenRegExp(String tokenName, RepoLayout repoLayout, boolean hasVersionTokens) {
        if (ORGANIZATION.equals(tokenName)) {
            return "[^/]+?";
        } else if (ORGANIZATION_PATH.equals(tokenName)) {
            return ".+?";
        } else if (MODULE.equals(tokenName)) {
            return "[^/]+";
        } else if (BASE_REVISION.equals(tokenName)) {
            return "[^/]+?";
        } else if (FOLDER_INTEGRATION_REVISION.equals(tokenName)) {
            String regExp = repoLayout.getFolderIntegrationRevisionRegExp();
            if (hasVersionTokens) {
                regExp = regExp + "|\\[INTEGRATION\\]" + "|\\[RELEASE\\]";
            }
            return regExp;
        } else if (FILE_INTEGRATION_REVISION.equals(tokenName)) {
            String regExp = repoLayout.getFileIntegrationRevisionRegExp();
            if (hasVersionTokens) {
                regExp = regExp + "|\\[INTEGRATION\\]" + "|\\[RELEASE\\]";
            }
            return regExp;
        } else if (CLASSIFIER.equals(tokenName)) {
            return "[^/]+?";
        } else if (EXT.equals(tokenName)) {
            return "(?:(?!\\d))[^\\-/]+";
        } else if (TYPE.equals(tokenName)) {
            return "[^/]+?";
        } else if (hasVersionTokens && RELEASE.equals(tokenName)) {
            return "[^/]+?";
        }
        return null;
    }

    private static String getItemLayoutAsIvyPattern(RepoLayout repoLayout, boolean descriptor) {
        if (repoLayout == null) {
            throw new IllegalArgumentException("Cannot translate a null layout.");
        }

        String layoutToTranslate;

        if (descriptor && repoLayout.isDistinctiveDescriptorPathPattern()) {
            layoutToTranslate = repoLayout.getDescriptorPathPattern();
        } else {
            layoutToTranslate = repoLayout.getArtifactPathPattern();
        }

        String organizationToken = wrapKeywordAsToken("organization");
        layoutToTranslate = layoutToTranslate.replaceAll("\\[" + ORGANIZATION_PATH + "\\]", organizationToken);
        layoutToTranslate = layoutToTranslate.replaceAll("\\[" + ORGANIZATION + "\\]", organizationToken);
        layoutToTranslate = layoutToTranslate.replaceAll("\\[" + BASE_REVISION + "\\]",
                wrapKeywordAsToken("revision"));
        layoutToTranslate = layoutToTranslate.replaceAll("\\[" + FOLDER_INTEGRATION_REVISION + "\\]", "");
        layoutToTranslate = layoutToTranslate.replaceAll("\\[" + FILE_INTEGRATION_REVISION + "\\]", "");
        layoutToTranslate = removeReplacedTokenOptionalBrackets(layoutToTranslate, true);

        return layoutToTranslate;
    }

    private static boolean foundMissingTokens(String firstPattern, String secondPattern) {
        boolean withinToken = false;
        boolean withinCustomToken = false;
        StringBuilder currentTokenValue = new StringBuilder();
        for (char c : firstPattern.toCharArray()) {
            if (('[' == c) && !withinCustomToken) {
                currentTokenValue.append("[");
                withinToken = true;
            } else if ((']' == c) && !withinCustomToken) {
                withinToken = false;
                currentTokenValue.append("]");
                String currentToken = currentTokenValue.toString();
                currentTokenValue.delete(0, currentTokenValue.length());
                if (!secondPattern.contains(currentToken)) {

                    /**
                     * If the unfound token is orgPath but org is found, or the opposite, don't consider as missing,
                     * they are interchangeable
                     */
                    if ((wrapKeywordAsToken(ORGANIZATION_PATH).equals(currentToken)
                            && secondPattern.contains(wrapKeywordAsToken(ORGANIZATION)))
                            || (wrapKeywordAsToken(ORGANIZATION).equals(currentToken)
                                    && secondPattern.contains(wrapKeywordAsToken(ORGANIZATION_PATH)))) {
                        continue;
                    }
                    return true;
                }
            } else if (('<' == c) && withinToken) {
                withinCustomToken = true;
                currentTokenValue.append("<");
            } else if (('>' == c) && withinToken) {
                withinCustomToken = false;
                currentTokenValue.append(">");
            } else if (withinToken) {
                currentTokenValue.append(c);
            }
        }

        return false;
    }

    private static void appendToken(StringBuilder itemPathPatternRegExpBuilder, String tokenName,
            List<String> tokenAppearance, String tokenValue) {
        itemPathPatternRegExpBuilder.append("(?<").append(tokenName).append(">");
        if (tokenAppearance.contains(tokenName)) {
            itemPathPatternRegExpBuilder.append("\\").append(tokenAppearance.indexOf(tokenName) + 1);
        } else {
            itemPathPatternRegExpBuilder.append(tokenValue);
            tokenAppearance.add(tokenName);
        }
        itemPathPatternRegExpBuilder.append(")");
    }
}