OpenApiSpecVersions.java
/*
* #%L
* wcm.io
* %%
* Copyright (C) 2023 wcm.io
* %%
* Licensed 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.
* #L%
*/
package io.wcm.siteapi.openapi.validator;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ScanResult;
/**
* Get available versions of Site API specification.
*
* <p>
* By default, spec files are expected in the classpath at <code>/site-api-spec</code>
* with filenames named following this pattern:
* </p>
*
* <pre>
* site-api.yaml
* site-api-v1.yaml
* site-api-v2.yaml
* ...
* </pre>
*
* <p>
* But you can also specify a custom path and file name pattern.
* </p>
*
* <p>
* The spec versions are derived from the file names. If no version is detected in the filename (e.g.
* <code>site-api.yaml</code> an empty string is used as versions. Otherwise the version from the file name
* is returned (e.g. <code>site-api-v1.yaml</code> -> <code>v1</code>). This versions reflects the
* "major version" of the spec with expected full backward compatibility within this version.
* </p>
*/
public final class OpenApiSpecVersions {
/**
* Default classpath path to look for Site API spec files.
*/
public static final String DEFAULT_RESOURCE_PATH = "site-api-spec";
/**
* Default pattern for Site API spec files. Last group is expected to return the actual version.
*/
public static final Pattern DEFAULT_FILENAME_PATTERN = Pattern.compile("site-api(-(\\w+))?.yaml");
private final SortedSet<String> versions;
private final Map<String, URL> urls;
/**
* Get all Site API Specs detected in classpath matching the default path and pattern.
*/
public OpenApiSpecVersions() {
this(DEFAULT_RESOURCE_PATH, DEFAULT_FILENAME_PATTERN, null);
}
/**
* Get all Site API Specs detected in classpath matching given path and filename pattern.
* @param path Directory in classpath
* @param filenamePattern File name pattern (last group is expected to return the actual version).
* @param versionComparator Comparator for versions ("highest" version is last version) -
* or null to use standard string sorting
*/
public OpenApiSpecVersions(@NotNull String path, @NotNull Pattern filenamePattern,
@Nullable Comparator<String> versionComparator) {
versions = new TreeSet<>(versionComparator);
urls = new HashMap<>();
// get all matching spec files from classpath
try (ScanResult scanResult = new ClassGraph().acceptPathsNonRecursive(path).scan()) {
scanResult.getAllResources().forEach(resource -> {
String filename = FilenameUtils.getName(resource.getPath());
Matcher matcher = filenamePattern.matcher(filename);
if (matcher.matches()) {
String version = StringUtils.defaultString(matcher.group(matcher.groupCount()));
versions.add(version);
urls.put(version, resource.getURL());
}
});
}
if (versions.isEmpty()) {
throw new IllegalArgumentException("No Site API spec found in classpath at '" + path + "' "
+ "with pattern: " + filenamePattern);
}
}
/**
* Get all Site API versions.
* @return Versions
*/
public @NotNull Collection<String> getAllVersions() {
return Collections.unmodifiableCollection(versions);
}
/**
* Get latest version.
* @return Version
*/
public @NotNull String getLatestVersion() {
return versions.last();
}
/**
* Returns Site API specification for highest version number.
* @return Site API specification.
*/
public @NotNull OpenApiSpec getLatest() {
return get(getLatestVersion());
}
/**
* Returns Site API specification.
* @param version Requested spec version
* @return Site API specification.
*/
public @NotNull OpenApiSpec get(@NotNull String version) {
URL url = urls.get(version);
if (url == null) {
throw new IllegalArgumentException("Invalid version: " + version);
}
return new OpenApiSpec(url, version);
}
}