com.artipie.composer.AstoRepository Maven / Gradle / Ivy
/*
* The MIT License (MIT) Copyright (c) 2020-2023 artipie.com
* https://github.com/artipie/artipie/blob/master/LICENSE.txt
*/
package com.artipie.composer;
import com.artipie.asto.Content;
import com.artipie.asto.Key;
import com.artipie.asto.Storage;
import com.artipie.composer.http.Archive;
import javax.json.Json;
import javax.json.JsonObject;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
/**
* PHP Composer repository that stores packages in a {@link Storage}.
*/
public final class AstoRepository implements Repository {
/**
* Key to all packages.
*/
public static final Key ALL_PACKAGES = new AllPackages();
/**
* The storage.
*/
private final Storage asto;
/**
* Prefix with url for uploaded archive.
*/
private final Optional prefix;
/**
* Ctor.
* @param storage Storage to store all repository data.
*/
public AstoRepository(final Storage storage) {
this(storage, Optional.empty());
}
/**
* Ctor.
* @param storage Storage to store all repository data.
* @param prefix Prefix with url for uploaded archive.
*/
public AstoRepository(final Storage storage, final Optional prefix) {
this.asto = storage;
this.prefix = prefix;
}
@Override
public CompletionStage> packages() {
return this.packages(AstoRepository.ALL_PACKAGES);
}
@Override
public CompletionStage> packages(final Name name) {
return this.packages(name.key());
}
@Override
public CompletableFuture addJson(final Content content, final Optional vers) {
final Key key = new Key.From(UUID.randomUUID().toString());
return this.asto.save(key, content).thenCompose(
nothing -> this.asto.value(key)
.thenCompose(Content::asBytesFuture)
.thenCompose(bytes -> {
final Package pack = new JsonPackage(bytes);
return CompletableFuture.allOf(
this.packages().thenCompose(
packages -> packages.orElse(new JsonPackages())
.add(pack, vers)
.thenCompose(
pkgs -> pkgs.save(
this.asto, AstoRepository.ALL_PACKAGES
)
)
).toCompletableFuture(),
pack.name().thenCompose(
name -> this.packages(name).thenCompose(
packages -> packages.orElse(new JsonPackages())
.add(pack, vers)
.thenCompose(
pkgs -> pkgs.save(this.asto, name.key())
)
)
).toCompletableFuture()
).thenCompose(
ignored -> this.asto.delete(key)
);
})
);
}
@Override
public CompletableFuture addArchive(final Archive archive, final Content content) {
final Key key = archive.name().artifact();
final Key rand = new Key.From(UUID.randomUUID().toString());
final Key tmp = new Key.From(rand, archive.name().full());
return this.asto.save(key, content)
.thenCompose(
nothing -> this.asto.value(key)
.thenCompose(
cont -> archive.composerFrom(cont)
.thenApply(
compos -> AstoRepository.addVersion(compos, archive.name())
).thenCombine(
this.asto.value(key),
(compos, cnt) -> archive.replaceComposerWith(
cnt,
compos.toString()
.getBytes(StandardCharsets.UTF_8)
).thenCompose(arch -> this.asto.save(tmp, arch))
.thenCompose(noth -> this.asto.delete(key))
.thenCompose(noth -> this.asto.move(tmp, key))
.thenCombine(
this.packages(),
(noth, packages) -> packages.orElse(new JsonPackages())
.add(
new JsonPackage(this.addDist(compos, key)),
Optional.empty()
)
.thenCompose(
pkgs -> pkgs.save(
this.asto, AstoRepository.ALL_PACKAGES
)
)
).thenCompose(Function.identity())
).thenCompose(Function.identity())
)
);
}
@Override
public CompletableFuture value(final Key key) {
return this.asto.value(key);
}
@Override
public Storage storage() {
return this.asto;
}
@Override
public CompletableFuture exists(final Key key) {
return this.asto.exists(key);
}
@Override
public CompletableFuture save(final Key key, final Content content) {
return this.asto.save(key, content);
}
@Override
public CompletionStage exclusively(
final Key key,
final Function> operation
) {
return this.asto.exclusively(key, operation);
}
@Override
public CompletableFuture move(final Key source, final Key destination) {
return this.asto.move(source, destination);
}
@Override
public CompletableFuture delete(final Key key) {
return this.asto.delete(key);
}
/**
* Add version field to composer json.
* @param compos Composer json file
* @param name Instance of name for obtaining version
* @return Composer json with added version.
*/
private static JsonObject addVersion(final JsonObject compos, final Archive.Name name) {
return Json.createObjectBuilder(compos)
.add(JsonPackage.VRSN, name.version())
.build();
}
/**
* Add `dist` field to composer json.
* @param compos Composer json file
* @param path Prefix path for uploading tgz archive
* @return Composer json with added `dist` field.
*/
private byte[] addDist(final JsonObject compos, final Key path) {
final String url = this.prefix.orElseThrow(
() -> new IllegalStateException("Prefix url for `dist` for uploaded archive was empty.")
).replaceAll("/$", "");
try {
return Json.createObjectBuilder(compos).add(
"dist", Json.createObjectBuilder()
.add("url", new URI(String.format("%s/%s", url, path.string())).toString())
.add("type", "zip")
.build()
).build()
.toString()
.getBytes(StandardCharsets.UTF_8);
} catch (final URISyntaxException exc) {
throw new IllegalStateException(
String.format("Failed to combine url `%s` with path `%s`", url, path.string()),
exc
);
}
}
/**
* Reads packages description from storage.
*
* @param key Content location in storage.
* @return Packages found by name, might be empty.
*/
private CompletionStage> packages(final Key key) {
return this.asto.exists(key).thenCompose(
exists -> {
final CompletionStage> packages;
if (exists) {
packages = this.asto.value(key)
.thenApply(JsonPackages::new)
.thenApply(Optional::of);
} else {
packages = CompletableFuture.completedFuture(Optional.empty());
}
return packages;
}
);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy