Add metadata for snapshot + events

This commit is contained in:
Andreas Dinauer 2026-04-25 17:28:32 +02:00
parent 667480f5e3
commit b25c5500b9
18 changed files with 278 additions and 17 deletions

View File

@ -1,20 +1,80 @@
package dev.dinauer.maven;
import dev.dinauer.maven.maven.core.release.ReleaseService;
import at.favre.lib.crypto.bcrypt.BCrypt;
import dev.dinauer.maven.maven.core.MavenService;
import dev.dinauer.maven.maven.token.TokenEntity;
import dev.dinauer.maven.maven.token.TokenRepo;
import dev.dinauer.maven.maven.token.TokenService;
import dev.dinauer.maven.maven.token.dto.TokenCreation;
import dev.dinauer.maven.maven.token.dto.TokenSecret;
import io.quarkus.arc.profile.IfBuildProfile;
import io.quarkus.narayana.jta.QuarkusTransaction;
import io.quarkus.oidc.client.Tokens;
import io.quarkus.runtime.Startup;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.SecurityIdentityAugmentor;
import io.quarkus.security.runtime.QuarkusPrincipal;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.control.ActivateRequestContext;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.core.SecurityContext;
import org.jspecify.annotations.NonNull;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.io.InputStream;
import java.time.LocalDate;
import java.time.ZonedDateTime;
import java.util.Objects;
import java.util.UUID;
@jakarta.ws.rs.Path("/dev")
@ApplicationScoped
@IfBuildProfile("dev")
public class Dev
{
@GET
public void init()
private static final BCrypt.Hasher HASHER = BCrypt.withDefaults();
@Inject
TokenRepo tokenRepo;
@Inject
MavenService mavenService;
@Startup
public void create() throws IOException
{
QuarkusTransaction.begin();
TokenEntity entity = new TokenEntity()
.setName("test")
.setToken(HASHER.hashToString(11, "c693b486-0f3a-4fe4-8716-de5a5a7fb566".toCharArray()))
.setUserId("test")
.setExpiresAt(LocalDate.now().plusDays(2))
.setCreatedAt(ZonedDateTime.now());
tokenRepo.persist(entity);
QuarkusTransaction.commit();
uploadSnapshot_01();
uploadSnapshot_02();
}
private void uploadSnapshot_01() throws IOException
{
mavenService.upload("/org/postgresql/postgresql/42.7.9-SNAPSHOT/postgresql-42.7.9-20250419.123456-1.jar", readFile("/jar/postgresql-42.7.9.jar"));
mavenService.upload("/org/postgresql/postgresql/42.7.9-SNAPSHOT/postgresql-42.7.9-20250419.123456-1.pom", readFile("/jar/postgresql-42.7.9.pom"));
}
private void uploadSnapshot_02() throws IOException
{
mavenService.upload("/org/postgresql/postgresql/42.7.9-SNAPSHOT/postgresql-42.7.9-20250419.133456-1.jar", readFile("/jar/postgresql-42.7.9.jar"));
mavenService.upload("/org/postgresql/postgresql/42.7.9-SNAPSHOT/postgresql-42.7.9-20250419.133456-1-javadoc.jar", readFile("/jar/postgresql-42.7.9.jar"));
mavenService.upload("/org/postgresql/postgresql/42.7.9-SNAPSHOT/postgresql-42.7.9-20250419.133456-1-tests.jar", readFile("/jar/postgresql-42.7.9.jar"));
mavenService.upload("/org/postgresql/postgresql/42.7.9-SNAPSHOT/postgresql-42.7.9-20250419.133456-1.pom", readFile("/jar/postgresql-42.7.9.pom"));
}
private byte[] readFile(String path) throws IOException
{
return Objects.requireNonNull(getClass().getResourceAsStream(path)).readAllBytes();
}
}

View File

@ -19,6 +19,9 @@ public class Resource
private String version;
@Column(name = "is_snapshot")
private boolean isSnapshot;
@OneToOne(mappedBy = "resource")
@JsonBackReference
private Event event;
@ -77,4 +80,15 @@ public class Resource
this.event = event;
return this;
}
public boolean isSnapshot()
{
return isSnapshot;
}
public Resource setSnapshot(boolean snapshot)
{
isSnapshot = snapshot;
return this;
}
}

View File

@ -3,6 +3,8 @@ package dev.dinauer.maven.maven.core;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.ZonedDateTime;
import java.util.Optional;
@ -10,6 +12,8 @@ import java.util.Optional;
@ApplicationScoped
public class ArtifactService
{
private static final Logger LOG = LoggerFactory.getLogger(ArtifactService.class);
@Inject
ArtifactRepo artifactRepo;
@ -43,6 +47,7 @@ public class ArtifactService
}
else
{
LOG.error("Cannot find artifact {}:{}", groupId, artifactId);
throw new NotFoundException();
}
}

View File

@ -1,28 +1,23 @@
package dev.dinauer.maven.maven.core;
import com.fasterxml.jackson.core.JsonProcessingException;
import dev.dinauer.maven.maven.core.context.ArtifactContext;
import dev.dinauer.maven.maven.core.context.ReleaseContext;
import dev.dinauer.maven.maven.core.context.SnapshotContext;
import dev.dinauer.maven.maven.core.context.VersionContext;
import dev.dinauer.maven.maven.core.model.*;
import dev.dinauer.maven.maven.core.model.Version;
import dev.dinauer.maven.maven.core.release.ReleaseService;
import dev.dinauer.maven.maven.core.release.parser.ReleaseFile;
import dev.dinauer.maven.maven.core.release.parser.ReleaseFileParser;
import dev.dinauer.maven.maven.core.snapshot.SnapshotFile;
import dev.dinauer.maven.maven.core.snapshot.SnapshotPom;
import dev.dinauer.maven.maven.core.snapshot.SnapshotService;
import dev.dinauer.maven.maven.core.snapshot.metadata.SnapshotMetadataService;
import dev.dinauer.maven.maven.core.snapshot.parser.SnapshotFileParser;
import dev.dinauer.maven.maven.shared.Extensions;
import dev.dinauer.maven.maven.shared.MavenCoordinates;
import dev.dinauer.maven.maven.shared.ParserResult;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.apache.commons.lang3.NotImplementedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -39,6 +34,8 @@ public class MavenService
@Inject
SnapshotService snapshotService;
@Inject
SnapshotMetadataService snapshotMetadataService;
public void upload(String path, byte[] body)
{
@ -87,7 +84,7 @@ public class MavenService
}
if (Objects.equals(FileExt.XML, context.extensions().ext()))
{
return Response.status(404).build();
return Response.status(200).type(MediaType.APPLICATION_XML).entity(snapshotMetadataService.generate(versionContext)).build();
}
}
if (context.getClass() == ArtifactContext.class)
@ -99,7 +96,7 @@ public class MavenService
private SnapshotContext toSnapshotContext(VersionContext versionContext)
{
ParserResult<SnapshotFile> result = new SnapshotFileParser(versionContext.artifactId(), versionContext.base()).parse(versionContext.filename());
ParserResult<SnapshotFile> result = new SnapshotFileParser(versionContext.artifactId(), versionContext.plainVersion()).parse(versionContext.filename());
return new SnapshotContext(
versionContext.extensions(),
versionContext.groupId(),
@ -115,7 +112,7 @@ public class MavenService
private ReleaseContext toReleaseContext(VersionContext versionContext)
{
ParserResult<ReleaseFile> result = new ReleaseFileParser(versionContext.artifactId(), versionContext.base()).parse(versionContext.filename());
ParserResult<ReleaseFile> result = new ReleaseFileParser(versionContext.artifactId(), versionContext.plainVersion()).parse(versionContext.filename());
return new ReleaseContext(
versionContext.extensions(),
versionContext.groupId(),

View File

@ -25,7 +25,7 @@ public class VersionContext extends ArtifactContext
return Strings.CI.endsWith(version, SNAPSHOT_SUFFIX);
}
public String base()
public String plainVersion()
{
return Strings.CI.removeEnd(version, SNAPSHOT_SUFFIX);
}

View File

@ -1,5 +1,9 @@
package dev.dinauer.maven.maven.core.release;
import dev.dinauer.maven.event.Event;
import dev.dinauer.maven.event.EventType;
import dev.dinauer.maven.event.Resource;
import dev.dinauer.maven.event.repo.EventRepo;
import dev.dinauer.maven.maven.core.context.ReleaseContext;
import dev.dinauer.maven.maven.core.context.SnapshotContext;
import dev.dinauer.maven.maven.core.model.FileHash;
@ -10,6 +14,7 @@ import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.NotImplementedException;
import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
@ApplicationScoped
public class ReleasePomService
@ -19,6 +24,8 @@ public class ReleasePomService
@Inject
ReleaseVersionService releaseVersionService;
@Inject
EventRepo eventRepo;
public void store(ReleaseContext releaseContext, byte[] body)
{
@ -26,6 +33,7 @@ public class ReleasePomService
if (jar == null)
{
create(releaseContext, body);
createEvent(releaseContext);
}
else
{
@ -59,4 +67,9 @@ public class ReleasePomService
releasePom.setFilename(releaseContext.filename());
releasePomRepo.persist(releasePom);
}
private void createEvent(ReleaseContext releaseContext)
{
eventRepo.persist(new Event().setType(EventType.UPLOAD).setTimestamp(ZonedDateTime.now()).setResource(new Resource().setGroupId(releaseContext.groupId()).setArtifactId(releaseContext.artifactId()).setVersion(releaseContext.version()).setSnapshot(false)));
}
}

View File

@ -45,6 +45,11 @@ public class SnapshotBundle
return this;
}
public String getDateTime()
{
return String.format("%s.%s", date, time);
}
public String getDate()
{
return date;

View File

@ -1,5 +1,7 @@
package dev.dinauer.maven.maven.core.snapshot;
import dev.dinauer.maven.maven.core.context.VersionContext;
import dev.dinauer.maven.maven.core.snapshot.metadata.utils.SnapshotComparator;
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ -12,4 +14,12 @@ public class SnapshotBundleRepo implements PanacheRepositoryBase<SnapshotBundle,
{
return find("snapshotVersion = :snapshotVersion AND date = :date AND time = :time AND buildNumber = :buildNumber", Map.ofEntries(Map.entry("snapshotVersion", version), Map.entry("date", date), Map.entry("time", time), Map.entry("buildNumber", buildNumber))).firstResult();
}
public SnapshotBundle latest(VersionContext versionContext)
{
return list("snapshotVersion.version = ?1 AND snapshotVersion.artifact.artifactId = ?2 AND snapshotVersion.artifact.group.groupId = ?3", versionContext.version(), versionContext.artifactId(), versionContext.groupId()).stream()
.sorted(new SnapshotComparator())
.toList()
.getFirst();
}
}

View File

@ -1,5 +1,10 @@
package dev.dinauer.maven.maven.core.snapshot;
import dev.dinauer.maven.event.Event;
import dev.dinauer.maven.event.EventType;
import dev.dinauer.maven.event.Resource;
import dev.dinauer.maven.event.repo.EventRepo;
import dev.dinauer.maven.maven.core.context.ReleaseContext;
import dev.dinauer.maven.maven.core.context.SnapshotContext;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@ -8,6 +13,7 @@ import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.NotImplementedException;
import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
@ApplicationScoped
public class SnapshotPomService
@ -15,6 +21,9 @@ public class SnapshotPomService
@Inject
SnapshotPomRepo snapshotPomRepo;
@Inject
EventRepo eventRepo;
@Inject
SnapshotBundleService snapshotBundleService;
@ -25,6 +34,7 @@ public class SnapshotPomService
if (snapshotPom == null)
{
create(snapshotBundle, body);
createEvent(snapshotContext);
}
else
{
@ -59,4 +69,9 @@ public class SnapshotPomService
snapshotPom.setSha1(DigestUtils.sha1Hex(content));
snapshotPomRepo.persist(snapshotPom);
}
private void createEvent(SnapshotContext snapshotContext)
{
eventRepo.persist(new Event().setType(EventType.UPLOAD).setTimestamp(ZonedDateTime.now()).setResource(new Resource().setGroupId(snapshotContext.groupId()).setArtifactId(snapshotContext.artifactId()).setVersion(snapshotContext.plainVersion()).setSnapshot(true)));
}
}

View File

@ -4,6 +4,7 @@ import dev.dinauer.maven.maven.core.ArtifactId;
import dev.dinauer.maven.maven.core.ArtifactService;
import dev.dinauer.maven.maven.core.context.SnapshotContext;
import dev.dinauer.maven.maven.core.context.VersionContext;
import dev.dinauer.maven.maven.core.snapshot.metadata.utils.SnapshotComparator;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;

View File

@ -0,0 +1,70 @@
package dev.dinauer.maven.maven.core.snapshot.metadata;
import com.ctc.wstx.shaded.msv_core.util.LightStack;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import dev.dinauer.maven.maven.core.context.VersionContext;
import dev.dinauer.maven.maven.core.snapshot.*;
import dev.dinauer.maven.maven.core.snapshot.metadata.model.Snapshot;
import dev.dinauer.maven.maven.core.snapshot.metadata.model.SnapshotMetadata;
import dev.dinauer.maven.maven.core.snapshot.metadata.model.SnapshotVersion;
import dev.dinauer.maven.maven.core.snapshot.metadata.model.SnapshotVersioning;
import dev.dinauer.maven.maven.core.snapshot.metadata.utils.SnapshotComparator;
import dev.dinauer.maven.metadata.Metadata;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.InternalServerErrorException;
import org.apache.commons.lang3.Strings;
import java.util.List;
import java.util.stream.Stream;
@ApplicationScoped
public class SnapshotMetadataService
{
private static final XmlMapper XML_MAPPER = new XmlMapper();
@Inject
SnapshotBundleRepo snapshotBundleRepo;
public String generate(VersionContext versionContext)
{
try
{
return XML_MAPPER.writeValueAsString(generateMetadata(versionContext));
}
catch (JsonProcessingException e)
{
throw new InternalServerErrorException();
}
}
private SnapshotMetadata generateMetadata(VersionContext versionContext)
{
SnapshotBundle latest = snapshotBundleRepo.latest(versionContext);
Snapshot snapshot = new Snapshot(String.format("%s.%s", latest.getDate(), latest.getTime()), latest.getBuildNumber());
List<SnapshotVersion> versions = toVersions(versionContext.plainVersion(), latest);
return new SnapshotMetadata(versionContext.groupId(), versionContext.artifactId(), versionContext.version(), new SnapshotVersioning(snapshot, latest.getDate() + latest.getTime(), versions));
}
private List<SnapshotVersion> toVersions(String version, SnapshotBundle bundle)
{
SnapshotPom pom = bundle.getPom();
if (pom != null)
{
SnapshotVersion pomVersion = new SnapshotVersion(null, "pom", formatValue(version, bundle.getDate(), bundle.getTime(), bundle.getBuildNumber()), bundle.getDateTime());
return Stream.concat(bundle.getJars().stream().map(item -> jarToVersion(version, bundle, item)), Stream.of(pomVersion)).toList();
}
throw new InternalServerErrorException();
}
private SnapshotVersion jarToVersion(String version, SnapshotBundle bundle, SnapshotJar jar)
{
return new SnapshotVersion(jar.getClassifier(), "jar", formatValue(version, bundle.getDate(), bundle.getTime(), bundle.getBuildNumber()), bundle.getDateTime());
}
private String formatValue(String version, String date, String time, int buildNumber)
{
return String.format("%s-%s.%s-%s", version, date, time, buildNumber);
}
}

View File

@ -0,0 +1,5 @@
package dev.dinauer.maven.maven.core.snapshot.metadata.model;
public record Snapshot(String timestamp, int buildNumber)
{
}

View File

@ -0,0 +1,12 @@
package dev.dinauer.maven.maven.core.snapshot.metadata.model;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import java.util.List;
@JacksonXmlRootElement(localName = "metadata")
public record SnapshotMetadata(String groupId, String artifactId, String version, SnapshotVersioning versioning)
{
}

View File

@ -0,0 +1,9 @@
package dev.dinauer.maven.maven.core.snapshot.metadata.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@JsonPropertyOrder({"classifier", "extension", "value", "updated"})
public record SnapshotVersion(@JsonInclude(JsonInclude.Include.NON_NULL) String classifier, String extension, String value, String updated)
{
}

View File

@ -0,0 +1,10 @@
package dev.dinauer.maven.maven.core.snapshot.metadata.model;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import java.util.List;
public record SnapshotVersioning(Snapshot snapshot, String lastUpdated, @JacksonXmlElementWrapper(localName = "snapshotVersions") @JacksonXmlProperty(localName = "snapshotVersion") List<SnapshotVersion> snapshotVersions)
{
}

View File

@ -0,0 +1,25 @@
package dev.dinauer.maven.maven.core.snapshot.metadata.utils;
import dev.dinauer.maven.maven.core.snapshot.SnapshotBundle;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
public class SnapshotComparator implements Comparator<SnapshotBundle>
{
private static final DateTimeFormatter MAVEN_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd.HHmmss");
@Override
public int compare(SnapshotBundle b0, SnapshotBundle b1)
{
long b0DateTime = LocalDateTime.parse(String.format("%s.%s", b0.getDate(), b0.getTime()), MAVEN_DATE_TIME_FORMATTER).toEpochSecond(ZoneOffset.UTC);
long b1DateTime = LocalDateTime.parse(String.format("%s.%s", b1.getDate(), b1.getTime()), MAVEN_DATE_TIME_FORMATTER).toEpochSecond(ZoneOffset.UTC);
if (b0DateTime == b1DateTime)
{
return Integer.compare(b1.getBuildNumber(), b0.getBuildNumber());
}
return Long.compare(b1DateTime, b0DateTime);
}
}

View File

@ -0,0 +1,9 @@
package dev.dinauer.maven.maven.shared;
public class PomNotFoundException extends RuntimeException
{
public PomNotFoundException()
{
super();
}
}

View File

@ -75,6 +75,7 @@ create table resource
(
artifact_id varchar(255),
group_id varchar(255),
is_snapshot boolean,
id varchar(255) not null
constraint resource_pkey
primary key,