136 lines
5.8 KiB
Java
136 lines
5.8 KiB
Java
package p.packer.foundation;
|
|
|
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import p.packer.api.PackerProjectContext;
|
|
|
|
import java.io.IOException;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.util.ArrayList;
|
|
import java.util.Comparator;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
public final class FileSystemPackerRegistryRepository implements PackerRegistryRepository {
|
|
private static final ObjectMapper MAPPER = new ObjectMapper();
|
|
private static final int REGISTRY_SCHEMA_VERSION = 1;
|
|
|
|
@Override
|
|
public PackerRegistryState load(PackerProjectContext project) {
|
|
final Path registryPath = PackerWorkspacePaths.registryPath(project);
|
|
if (!Files.isRegularFile(registryPath)) {
|
|
return emptyState();
|
|
}
|
|
|
|
try {
|
|
final RegistryDocument document = MAPPER.readValue(registryPath.toFile(), RegistryDocument.class);
|
|
final int schemaVersion = document.schemaVersion <= 0 ? REGISTRY_SCHEMA_VERSION : document.schemaVersion;
|
|
if (schemaVersion != REGISTRY_SCHEMA_VERSION) {
|
|
throw new PackerRegistryException("Unsupported registry schema_version: " + schemaVersion);
|
|
}
|
|
final List<PackerRegistryEntry> entries = new ArrayList<>();
|
|
if (document.assets != null) {
|
|
for (RegistryAssetDocument asset : document.assets) {
|
|
if (asset == null) {
|
|
continue;
|
|
}
|
|
entries.add(new PackerRegistryEntry(
|
|
asset.assetId,
|
|
asset.assetUuid,
|
|
normalizeRoot(asset.root),
|
|
asset.includedInBuild == null || asset.includedInBuild));
|
|
}
|
|
}
|
|
validateEntries(entries);
|
|
final int nextAssetId = document.nextAssetId > 0
|
|
? document.nextAssetId
|
|
: entries.stream().mapToInt(PackerRegistryEntry::assetId).max().orElse(0) + 1;
|
|
return new PackerRegistryState(
|
|
schemaVersion,
|
|
nextAssetId,
|
|
entries.stream().sorted(Comparator.comparingInt(PackerRegistryEntry::assetId)).toList());
|
|
} catch (IOException exception) {
|
|
throw new PackerRegistryException("Unable to load registry: " + registryPath, exception);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void save(PackerProjectContext project, PackerRegistryState state) {
|
|
final Path registryDirectory = PackerWorkspacePaths.registryDirectory(project);
|
|
final Path registryPath = PackerWorkspacePaths.registryPath(project);
|
|
validateEntries(state.assets());
|
|
try {
|
|
Files.createDirectories(registryDirectory);
|
|
final RegistryDocument document = new RegistryDocument();
|
|
document.schemaVersion = state.schemaVersion();
|
|
document.nextAssetId = state.nextAssetId();
|
|
document.assets = state.assets().stream()
|
|
.map(entry -> new RegistryAssetDocument(
|
|
entry.assetId(),
|
|
entry.assetUuid(),
|
|
entry.root(),
|
|
entry.includedInBuild()))
|
|
.toList();
|
|
MAPPER.writerWithDefaultPrettyPrinter().writeValue(registryPath.toFile(), document);
|
|
} catch (IOException exception) {
|
|
throw new PackerRegistryException("Unable to save registry: " + registryPath, exception);
|
|
}
|
|
}
|
|
|
|
private PackerRegistryState emptyState() {
|
|
return new PackerRegistryState(REGISTRY_SCHEMA_VERSION, 1, List.of());
|
|
}
|
|
|
|
private void validateEntries(List<PackerRegistryEntry> entries) {
|
|
final Set<Integer> assetIds = new HashSet<>();
|
|
final Set<String> assetUuids = new HashSet<>();
|
|
final Set<String> roots = new HashSet<>();
|
|
for (PackerRegistryEntry entry : entries) {
|
|
if (!assetIds.add(entry.assetId())) {
|
|
throw new PackerRegistryException("Duplicate asset_id in registry: " + entry.assetId());
|
|
}
|
|
if (!assetUuids.add(entry.assetUuid())) {
|
|
throw new PackerRegistryException("Duplicate asset_uuid in registry: " + entry.assetUuid());
|
|
}
|
|
if (!roots.add(entry.root())) {
|
|
throw new PackerRegistryException("Duplicate asset root in registry: " + entry.root());
|
|
}
|
|
}
|
|
}
|
|
|
|
private String normalizeRoot(String root) {
|
|
if (root == null || root.isBlank()) {
|
|
throw new PackerRegistryException("Registry asset root must not be blank");
|
|
}
|
|
final String normalized = root.trim().replace('\\', '/');
|
|
final Path normalizedPath = Path.of(normalized).normalize();
|
|
if (normalizedPath.isAbsolute() || normalizedPath.startsWith("..")) {
|
|
throw new PackerRegistryException("Registry asset root is outside the trusted assets boundary: " + normalized);
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
private static final class RegistryDocument {
|
|
@JsonProperty("schema_version")
|
|
public int schemaVersion;
|
|
|
|
@JsonProperty("next_asset_id")
|
|
public int nextAssetId;
|
|
|
|
@JsonProperty("assets")
|
|
public List<RegistryAssetDocument> assets = List.of();
|
|
}
|
|
|
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
private record RegistryAssetDocument(
|
|
@JsonProperty("asset_id") int assetId,
|
|
@JsonProperty("asset_uuid") String assetUuid,
|
|
@JsonProperty("root") String root,
|
|
@JsonProperty("included_in_build") Boolean includedInBuild) {
|
|
}
|
|
}
|