package io.airlift.airship.coordinator;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.io.ByteProcessor;
import com.google.common.io.ByteSource;
import com.google.common.io.Resources;
import io.airlift.airship.shared.Repository;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.net.URI;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.google.common.collect.Sets.newHashSet;
import static io.airlift.airship.shared.HttpUriBuilder.uriBuilderFrom;
import static io.airlift.airship.shared.MavenCoordinates.DEFAULT_BINARY_PACKAGING;
import static io.airlift.airship.shared.MavenCoordinates.DEFAULT_CONFIG_PACKAGING;
public class HttpRepository
implements Repository
{
private final List<URI> baseUris;
private final Pattern configShortNamePattern;
private final Pattern configVersionPattern;
private final Pattern binaryVersionPattern;
@Inject
public HttpRepository(CoordinatorConfig config)
{
this(
Lists.transform(config.getRepositories(), new Function<String, URI>()
{
@Override
public URI apply(@Nullable String uri)
{
return URI.create(uri);
}
}),
config.getHttpShortNamePattern(),
config.getHttpRepoConfigVersionPattern(),
config.getHttpRepoBinaryVersionPattern());
}
public HttpRepository(Iterable<URI> baseUris, String configShortNamePattern, String configVersionPattern, String binaryVersionPattern)
{
Preconditions.checkNotNull(baseUris, "baseUris is null");
this.baseUris = ImmutableList.copyOf(baseUris);
if (configShortNamePattern != null) {
this.configShortNamePattern = Pattern.compile(configShortNamePattern);
Preconditions.checkArgument(this.configShortNamePattern.matcher("").groupCount() == 1, "configShortNamePattern must have one capturing group");
}
else {
this.configShortNamePattern = null;
}
if (configVersionPattern != null) {
this.configVersionPattern = Pattern.compile(configVersionPattern);
Preconditions.checkArgument(this.configVersionPattern.matcher("").groupCount() >= 1, "configVersionPattern must have at least one capturing group");
}
else {
this.configVersionPattern = null;
}
if (binaryVersionPattern != null) {
this.binaryVersionPattern = Pattern.compile(binaryVersionPattern);
Preconditions.checkArgument(this.configVersionPattern.matcher("").groupCount() >= 1, "configVersionPattern must have at least one capturing group");
}
else {
this.binaryVersionPattern = null;
}
}
@Override
public String configShortName(String config)
{
if (!config.startsWith("@")) {
return null;
}
config = config.substring(1);
if (configShortNamePattern != null) {
Matcher matcher = configShortNamePattern.matcher(config);
if (matcher.matches()) {
return matcher.group(1).replaceAll("[:%/ !$]", "_");
}
}
return config;
}
@Override
public String configRelativize(String config)
{
if (!config.startsWith("@")) {
return null;
}
config = config.substring(1);
for (URI baseUri : baseUris) {
String baseUriString = baseUri.toASCIIString();
if (config.startsWith(baseUriString)) {
return config.substring(0, baseUriString.length());
}
}
return null;
}
@Override
public String configResolve(String config)
{
if (!config.startsWith("@")) {
return null;
}
config = config.substring(1);
URI uri = toHttpUri(config, DEFAULT_CONFIG_PACKAGING);
if (uri != null) {
return "@" + config;
}
return null;
}
@Override
public String configUpgrade(String config, String version)
{
if (!config.startsWith("@") || !version.startsWith("@")) {
return null;
}
config = config.substring(1);
version = version.substring(1);
String upgrade = upgrade(config, version, configVersionPattern, DEFAULT_CONFIG_PACKAGING);
if (upgrade != null) {
return "@" + upgrade;
}
return null;
}
@Override
public boolean configEqualsIgnoreVersion(String config1, String config2)
{
return config1.startsWith("@") &&
config2.startsWith("@") &&
equalsIgnoreVersion(config1, config2, configVersionPattern);
}
@Override
public URI configToHttpUri(String config)
{
if (!config.startsWith("@")) {
return null;
}
config = config.substring(1);
return toHttpUri(config, DEFAULT_CONFIG_PACKAGING);
}
@Override
public String binaryRelativize(String binary)
{
for (URI baseUri : baseUris) {
String baseUriString = baseUri.toASCIIString();
if (binary.startsWith(baseUriString)) {
return binary.substring(0, baseUriString.length());
}
}
return null;
}
@Override
public String binaryResolve(String binary)
{
URI uri = toHttpUri(binary, DEFAULT_BINARY_PACKAGING);
if (uri != null) {
return binary;
}
return null;
}
@Override
public String binaryUpgrade(String binary, String version)
{
return upgrade(binary, version, binaryVersionPattern, DEFAULT_BINARY_PACKAGING);
}
@Override
public boolean binaryEqualsIgnoreVersion(String binary1, String binary2)
{
return equalsIgnoreVersion(binary1, binary2, binaryVersionPattern);
}
@Override
public URI binaryToHttpUri(String binary)
{
return toHttpUri(binary, DEFAULT_BINARY_PACKAGING);
}
private String upgrade(String path, String version, Pattern versionPattern, String defaultPackaging)
{
// try to replace version in existing config
if (versionPattern != null) {
String newConfig = upgradePath(path, version, versionPattern);
if (newConfig != null && toHttpUri(newConfig, defaultPackaging) != null) {
return newConfig;
}
}
// version pattern did not match, so check if new version is an absolute uri
URI uri = toHttpUri(version, defaultPackaging);
if (uri != null) {
return version;
}
return null;
}
private boolean equalsIgnoreVersion(String path1, String path2, Pattern versionPattern)
{
if (versionPattern == null) {
return false;
}
String path1NoVersion;
Matcher matcher1 = versionPattern.matcher(path1);
if (!matcher1.find()) {
return false;
}
path1NoVersion = matcher1.replaceAll("");
String path2NoVersion;
Matcher matcher2 = versionPattern.matcher(path2);
if (!matcher2.find()) {
return false;
}
path2NoVersion = matcher2.replaceAll("");
return path1NoVersion.equals(path2NoVersion);
}
private URI toHttpUri(String path, String defaultExtension)
{
try {
URI uri = URI.create(path);
if (uri.isAbsolute()) {
if (isValidLocation(uri)) {
return uri;
}
else {
uri = uri.resolve("." + defaultExtension);
if (isValidLocation(uri)) {
return uri;
}
else {
return null;
}
}
}
}
catch (Exception ignored) {
}
Set<URI> uris = newHashSet();
for (URI baseUri : baseUris) {
try {
URI uri = uriBuilderFrom(baseUri).appendPath(path).build();
if (isValidLocation(uri)) {
uris.add(uri);
}
else {
uri = uri.resolve("." + defaultExtension);
if (isValidLocation(uri)) {
uris.add(uri);
}
}
}
catch (Exception ignored) {
}
}
if (uris.size() > 1) {
throw new RuntimeException("Ambiguous spec " + path + " matched " + uris);
}
if (uris.isEmpty()) {
return null;
}
return uris.iterator().next();
}
private boolean isValidLocation(URI uri)
{
try {
ByteSource byteSource = Resources.asByteSource(uri.toURL());
byteSource.read(new ByteProcessor<Void>()
{
private int count;
public boolean processBytes(byte[] buffer, int offset, int length)
{
count += length;
// make sure we got at least 10 bytes
return count < 10;
}
public Void getResult()
{
return null;
}
});
return true;
}
catch (Exception ignored) {
}
return false;
}
public static String upgradePath(String spec, String version, Pattern versionPattern)
{
Matcher matcher = versionPattern.matcher(spec);
StringBuilder out = new StringBuilder();
int end = 0;
while (matcher.find()) {
for (int group = 0; group < matcher.groupCount(); group++) {
out.append(spec.substring(end, matcher.start(group + 1))).append(version);
end = matcher.end(group + 1);
}
}
// no matches
if (end == 0) {
return null;
}
out.append(spec.substring(end));
return out.toString();
}
@Override
public String toString()
{
final StringBuilder sb = new StringBuilder();
sb.append("HttpRepository");
sb.append("{baseUris=").append(baseUris);
sb.append(", configShortNamePattern=").append(configShortNamePattern);
sb.append(", configVersionPattern=").append(configVersionPattern);
sb.append(", binaryVersionPattern=").append(binaryVersionPattern);
sb.append('}');
return sb.toString();
}
}