diff --git a/build.gradle b/build.gradle index fdfe967..4c98238 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,13 @@ allprojects { } tasks.withType(JavaCompile) { - options.release = 17 + options.release = 21 + } + + java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } } tasks.withType(Test) { @@ -76,11 +82,12 @@ dependencies { implementation catalog.commons.compress implementation catalog.jna + implementation catalog.liquibase.cdi.jakarta testImplementation catalog.jackson.module.jakarta.xmlbind.annotations testImplementation catalog.jboss.ejb.client testImplementation catalog.weld.se.core - testImplementation catalog.h2 +// testImplementation catalog.h2 testImplementation catalog.hibernate.core testImplementation catalog.resteasy.client testImplementation catalog.resteasy.jackson2.provider @@ -113,13 +120,14 @@ Provider nimCompileTaskProvider = tasks.register("compileNim", Exec) { workingDir(nimDir) } -Provider warTaskProvider = tasks.named('war', War) { +Provider warTaskProvider = tasks.named('war', War) { War it -> from staticDir from nimCompileTaskProvider + archiveVersion = '' } tasks.named('deploy2Wildfly', Deploy2WildflyTask) { d2w -> - d2w.rpcPort = 1234 + d2w.rpcPort = 9990 d2w.rpcUsername = 'woggioni' // d2w.rpcUsername = 'admin' d2w.rpcPassword = '123456' diff --git a/gradle.properties b/gradle.properties index 7a50a16..7e11d75 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -jpacrepo.version=2023.10.04 +jpacrepo.version=2024.02.09 -lys.version=2023.10.01 +lys.version=2024.02.09 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 033e24c..7f93135 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ac72c34..a80b22c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index fcb6fca..0adc8e1 100755 --- a/gradlew +++ b/gradlew @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/jpacrepo-api/build.gradle b/jpacrepo-api/build.gradle index ac9ed4a..1eb8efa 100644 --- a/jpacrepo-api/build.gradle +++ b/jpacrepo-api/build.gradle @@ -7,9 +7,13 @@ dependencies { compileOnly catalog.jakarta.persistence.api compileOnly catalog.jakarta.inject.api compileOnly catalog.jakarta.ejb.api + compileOnly catalog.jakarta.enterprise.cdi.api compileOnly catalog.jakarta.json.bind.api + compileOnly catalog.jakarta.json.api compileOnly catalog.jakarta.annotation.api - + compileOnly catalog.slf4j.api +// compileOnly catalog.hibernate.core + implementation catalog.liquibase.core annotationProcessor catalog.hibernate.jpamodelgen } diff --git a/jpacrepo-api/src/main/java/module-info.java b/jpacrepo-api/src/main/java/module-info.java index a927d19..4ffd11d 100644 --- a/jpacrepo-api/src/main/java/module-info.java +++ b/jpacrepo-api/src/main/java/module-info.java @@ -5,6 +5,8 @@ module net.woggioni.jpacrepo.api { requires jakarta.persistence; requires jakarta.annotation; requires jakarta.json.bind; + requires jakarta.cdi; + requires jakarta.json; exports net.woggioni.jpacrepo.api.model; exports net.woggioni.jpacrepo.api.service; diff --git a/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/jsonb/NamedEntitySerializer.java b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/jsonb/NamedEntitySerializer.java new file mode 100644 index 0000000..ac1841f --- /dev/null +++ b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/jsonb/NamedEntitySerializer.java @@ -0,0 +1,16 @@ +package net.woggioni.jpacrepo.api.jsonb; + +import jakarta.json.bind.serializer.JsonbSerializer; +import jakarta.json.bind.serializer.SerializationContext; +import jakarta.json.stream.JsonGenerator; +import net.woggioni.jpacrepo.api.model.NamedEntity; + +public class NamedEntitySerializer implements JsonbSerializer { + @Override + public void serialize( + NamedEntity namedEntity, + JsonGenerator jsonGenerator, + SerializationContext serializationContext) { + serializationContext.serialize(namedEntity.getName(), jsonGenerator); + } +} diff --git a/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/jsonb/SetSerializer.java b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/jsonb/SetSerializer.java new file mode 100644 index 0000000..5835dac --- /dev/null +++ b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/jsonb/SetSerializer.java @@ -0,0 +1,22 @@ +package net.woggioni.jpacrepo.api.jsonb; + +import jakarta.json.bind.serializer.JsonbSerializer; +import jakarta.json.bind.serializer.SerializationContext; +import jakarta.json.stream.JsonGenerator; +import net.woggioni.jpacrepo.api.model.NamedEntity; + +import java.util.Set; + +public class SetSerializer implements JsonbSerializer> { + @Override + public void serialize( + Set namedEntities, + JsonGenerator jsonGenerator, + SerializationContext serializationContext) { + jsonGenerator.writeStartArray(); + for(NamedEntity namedEntity : namedEntities) { + serializationContext.serialize(namedEntity.getName(), jsonGenerator); + } + jsonGenerator.writeEnd(); + } +} diff --git a/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/Dependency.java b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/Dependency.java new file mode 100644 index 0000000..1a0a8f0 --- /dev/null +++ b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/Dependency.java @@ -0,0 +1,40 @@ +package net.woggioni.jpacrepo.api.model; + +import jakarta.json.bind.annotation.JsonbTypeSerializer; +import jakarta.persistence.Access; +import jakarta.persistence.AccessType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlRootElement; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import net.woggioni.jpacrepo.api.jsonb.NamedEntitySerializer; + +import java.util.Comparator; +import java.util.Objects; +import java.util.jar.Attributes; + +@Entity +@Access(AccessType.PROPERTY) +@XmlRootElement +@XmlAccessorType(XmlAccessType.PROPERTY) +@JsonbTypeSerializer(NamedEntitySerializer.class) +public class Dependency extends NamedEntity implements Comparable { + public static Dependency of(String name) { + var result = new Dependency(); + result.setName(name); + return result; + } + @Override + public int compareTo(Dependency o) { + return super.compareTo(o); + } +} diff --git a/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/License.java b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/License.java new file mode 100644 index 0000000..f966e54 --- /dev/null +++ b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/License.java @@ -0,0 +1,30 @@ +package net.woggioni.jpacrepo.api.model; + +import jakarta.json.bind.annotation.JsonbTypeSerializer; +import jakarta.persistence.Access; +import jakarta.persistence.AccessType; +import jakarta.persistence.Entity; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlRootElement; +import net.woggioni.jpacrepo.api.jsonb.NamedEntitySerializer; + +@Entity +@Access(AccessType.PROPERTY) +@XmlRootElement +@XmlAccessorType(XmlAccessType.PROPERTY) +@JsonbTypeSerializer(NamedEntitySerializer.class) +public class License extends NamedEntity implements Comparable { + public static License of(String name) { + var result = new License(); + result.setName(name); + return result; + } + + @Override + public int compareTo(License o) { + return super.compareTo(o); + } +} diff --git a/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/NamedEntity.java b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/NamedEntity.java new file mode 100644 index 0000000..77d3ccb --- /dev/null +++ b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/NamedEntity.java @@ -0,0 +1,56 @@ +package net.woggioni.jpacrepo.api.model; + +import jakarta.json.bind.annotation.JsonbTypeSerializer; +import jakarta.persistence.Access; +import jakarta.persistence.AccessType; +import jakarta.persistence.Column; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import jakarta.xml.bind.annotation.XmlTransient; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import net.woggioni.jpacrepo.api.jsonb.NamedEntitySerializer; + +import java.util.Comparator; +import java.util.Objects; + + +@Getter +@Setter +@Access(AccessType.PROPERTY) +@NoArgsConstructor +@MappedSuperclass +@JsonbTypeSerializer(NamedEntitySerializer.class) +public abstract class NamedEntity { + @Getter(onMethod_ = { + @Column(unique = true, length = 1024), + }) + private String name; + + @Getter(onMethod_ = { + @Id, + @GeneratedValue, + @XmlTransient + }) + private long id; + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(obj.getClass() == getClass()) { + NamedEntity other = (NamedEntity) obj; + return Objects.equals(getName(), other.getName()); + } else { + return false; + } + } + public int compareTo(NamedEntity o) { + return Comparator.naturalOrder().compare(getName(), o.getName()); + } +} diff --git a/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/Packager.java b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/Packager.java new file mode 100644 index 0000000..00432fa --- /dev/null +++ b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/Packager.java @@ -0,0 +1,28 @@ +package net.woggioni.jpacrepo.api.model; + +import jakarta.json.bind.annotation.JsonbTypeSerializer; +import jakarta.persistence.Access; +import jakarta.persistence.AccessType; +import jakarta.persistence.Entity; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlRootElement; +import net.woggioni.jpacrepo.api.jsonb.NamedEntitySerializer; + +@Entity +@Access(AccessType.PROPERTY) +@XmlRootElement +@XmlAccessorType(XmlAccessType.PROPERTY) +@JsonbTypeSerializer(NamedEntitySerializer.class) +public class Packager extends NamedEntity implements Comparable { + public static Packager of(String name) { + var result = new Packager(); + result.setName(name); + return result; + } + + @Override + public int compareTo(Packager o) { + return super.compareTo(o); + } +} diff --git a/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/PkgBase.java b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/PkgBase.java new file mode 100644 index 0000000..34fd349 --- /dev/null +++ b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/PkgBase.java @@ -0,0 +1,31 @@ +package net.woggioni.jpacrepo.api.model; + +import jakarta.json.bind.annotation.JsonbTypeSerializer; +import jakarta.persistence.Access; +import jakarta.persistence.AccessType; +import jakarta.persistence.Entity; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlRootElement; +import net.woggioni.jpacrepo.api.jsonb.NamedEntitySerializer; + +@Entity +@Access(AccessType.PROPERTY) +@XmlRootElement +@XmlAccessorType(XmlAccessType.PROPERTY) +@JsonbTypeSerializer(NamedEntitySerializer.class) +public class PkgBase extends NamedEntity implements Comparable { + + public static PkgBase of(String name) { + var result = new PkgBase(); + result.setName(name); + return result; + } + + @Override + public int compareTo(PkgBase o) { + return super.compareTo(o); + } +} diff --git a/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/PkgData.java b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/PkgData.java index 6e61692..23d9f46 100644 --- a/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/PkgData.java +++ b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/PkgData.java @@ -1,17 +1,18 @@ package net.woggioni.jpacrepo.api.model; import jakarta.json.bind.annotation.JsonbTransient; -import jakarta.json.bind.annotation.JsonbVisibility; -import jakarta.json.bind.config.PropertyVisibilityStrategy; +import jakarta.json.bind.annotation.JsonbTypeSerializer; import jakarta.persistence.Access; import jakarta.persistence.AccessType; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; import jakarta.persistence.Index; -import jakarta.persistence.NamedQueries; -import jakarta.persistence.NamedQuery; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; import jakarta.persistence.PrePersist; import jakarta.persistence.PreUpdate; import jakarta.persistence.Table; @@ -19,73 +20,144 @@ import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; import jakarta.xml.bind.annotation.XmlRootElement; import jakarta.xml.bind.annotation.XmlTransient; -import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import net.woggioni.jpacrepo.api.jsonb.SetSerializer; import java.io.Serializable; import java.time.Instant; +import java.util.Objects; import java.util.Set; -@Data +@Setter +@Getter +@NoArgsConstructor @Entity -@Access(AccessType.FIELD) -@NamedQueries(value = { - @NamedQuery(name = "searchByFileName", query = "SELECT p FROM PkgData p WHERE p.fileName = :fileName"), - @NamedQuery(name = "searchByName", query = "SELECT p FROM PkgData p WHERE p.id.name = :name"), - @NamedQuery(name = "searchById", query = "SELECT p FROM PkgData p WHERE p.id = :id"), - @NamedQuery(name = "searchByHash", query = "SELECT p FROM PkgData p WHERE p.md5sum = :md5sum") -}) +@Access(AccessType.PROPERTY) @Table(indexes = { @Index(columnList = "md5sum", unique = true), - @Index(columnList = "fileName", unique = true) + @Index(columnList = "fileName", unique = true), + @Index(columnList = "name, version, arch, compressionformat", unique = true) }) @XmlRootElement @XmlAccessorType(XmlAccessType.PROPERTY) public class PkgData implements Serializable { - @EmbeddedId - private PkgId id; + @Getter(onMethod_ = { + @Embedded, + @Column(unique = true) + }) + private PkgId pkgId; - private String base; + @Getter(onMethod_ = { + @Id, + @GeneratedValue, + @XmlTransient, + @JsonbTransient + }) + private long id; + @Getter(onMethod_ = { + @ManyToOne + }) + private PkgBase base; + + @Getter( + onMethod_ = { + @Column(length = 1024) + }) private String description; + @Getter( + onMethod_ = { + @Column(length = 1024) + }) private String url; private Instant buildDate; - private String packager; + @Getter(onMethod_ = { + @ManyToOne + }) + private Packager packager; private long size; - private String license; + @Getter(onMethod_ = { + @ManyToMany, + @JoinTable(name = "PkgData_license") + }) + @JsonbTypeSerializer(SetSerializer.class) + private Set license; - private String md5sum; + @Getter( + onMethod_ = { + @Column(length = 16) + }) + private byte[] md5sum; + @Getter( + onMethod_ = { + @Column(length = 1024) + }) private String fileName; - @ElementCollection(fetch = FetchType.EAGER) - private Set replaces; + @Getter(onMethod_ = { + @ManyToMany, + @JoinTable(name = "PkgData_replaces") + }) + @JsonbTypeSerializer(SetSerializer.class) + private Set replaces; - @ElementCollection(fetch = FetchType.EAGER) - private Set conflict; + @Getter(onMethod_ = { + @ManyToMany, + @JoinTable(name = "PkgData_conflict") + }) + @JsonbTypeSerializer(SetSerializer.class) + private Set conflict; - @ElementCollection(fetch = FetchType.EAGER) - private Set provides; + @Getter(onMethod_ = { + @ManyToMany, + @JoinTable(name = "PkgData_provides") + }) + @JsonbTypeSerializer(SetSerializer.class) + private Set provides; - @ElementCollection(fetch = FetchType.EAGER) - private Set depend; + @Getter(onMethod_ = { + @ManyToMany, + @JoinTable(name = "PkgData_depend") + }) + @JsonbTypeSerializer(SetSerializer.class) + private Set depend; - @ElementCollection(fetch = FetchType.EAGER) - private Set optdepend; + @Getter(onMethod_ = { + @ManyToMany, + @JoinTable(name = "PkgData_optdepend") + }) + @JsonbTypeSerializer(SetSerializer.class) + private Set optdepend; - @ElementCollection(fetch = FetchType.EAGER) - private Set makedepend; + @Getter(onMethod_ = { + @ManyToMany, + @JoinTable(name = "PkgData_makedepend") + }) + @JsonbTypeSerializer(SetSerializer.class) + private Set makedepend; - @ElementCollection(fetch = FetchType.EAGER) - private Set makeopkgopt; + @Getter(onMethod_ = { + @ManyToMany, + @JoinTable(name = "PkgData_makepkgopt"), + }) + @JsonbTypeSerializer(SetSerializer.class) + private Set makepkgopt; - @ElementCollection(fetch = FetchType.EAGER) - private Set backup; + @Getter(onMethod_ = { + @ManyToMany, + @JoinTable(name = "PkgData_backup"), + }) + @JsonbTypeSerializer(SetSerializer.class) + private Set backup; @XmlTransient @JsonbTransient @@ -96,5 +168,19 @@ public class PkgData implements Serializable { private void writeTimestamp() { updTimestamp = Instant.now(); } + + @Override + public int hashCode() { + return pkgId.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof PkgData other) { + return Objects.equals(id, other.id); + } else { + return false; + } + } } diff --git a/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/PkgId.java b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/PkgId.java index e262e95..892703c 100644 --- a/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/PkgId.java +++ b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/model/PkgId.java @@ -2,6 +2,7 @@ package net.woggioni.jpacrepo.api.model; import jakarta.persistence.Access; import jakarta.persistence.AccessType; +import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; @@ -9,21 +10,41 @@ import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; import jakarta.xml.bind.annotation.XmlRootElement; import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; import java.io.Serializable; +import java.util.Objects; + @Data @Embeddable -@Access(AccessType.FIELD) +@Access(AccessType.PROPERTY) @XmlRootElement @XmlAccessorType(XmlAccessType.PROPERTY) public class PkgId implements Serializable { + @Getter( + onMethod_ = { + @Column(length = 64) + }) private String name; + @Getter( + onMethod_ = { + @Column(length = 64) + }) private String version; + @Getter( + onMethod_ = { + @Column(length = 16) + }) private String arch; - @Enumerated(EnumType.ORDINAL) + @Getter( + onMethod_ = { + @Enumerated(EnumType.ORDINAL) + }) private CompressionFormat compressionFormat; } diff --git a/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/service/PacmanServiceRemote.java b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/service/PacmanServiceRemote.java index f1b695d..984500f 100644 --- a/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/service/PacmanServiceRemote.java +++ b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/service/PacmanServiceRemote.java @@ -15,6 +15,7 @@ import java.io.InputStream; import java.util.Collection; import java.util.List; import java.util.NavigableMap; +import java.util.Optional; import java.util.Set; @Remote @@ -41,7 +42,7 @@ public interface PacmanServiceRemote { CompressionFormat compressionFormat); @Nullable - PkgData getPackage(PkgId pkgId); + Optional getPackage(PkgId pkgId); @Nullable Long getFileSize(String fileName); diff --git a/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/wire/PkgIdList.java b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/wire/PkgIdList.java new file mode 100644 index 0000000..7bcf153 --- /dev/null +++ b/jpacrepo-api/src/main/java/net/woggioni/jpacrepo/api/wire/PkgIdList.java @@ -0,0 +1,35 @@ +package net.woggioni.jpacrepo.api.wire; + + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlRootElement; +import net.woggioni.jpacrepo.api.model.PkgId; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@XmlRootElement(name = "pkgIds") +@XmlAccessorType(XmlAccessType.PROPERTY) +public class PkgIdList extends ArrayList { + public PkgIdList() {} + public PkgIdList(Collection c) { + super(c); + } + + public PkgIdList(PkgId... elements) { + for (PkgId el : elements) add(el); + } + + @XmlElement(name = "pkgId") + public List getItems() { + return this; + } + + public void setItems(List pkgs) { + this.clear(); + this.addAll(pkgs); + } +} diff --git a/jpacrepo-experimental/build.gradle b/jpacrepo-experimental/build.gradle new file mode 100644 index 0000000..e69de29 diff --git a/jpacrepo-experimental/src/test/java/net/woggioni/jpacrepo/experimental/JPQLTest.java b/jpacrepo-experimental/src/test/java/net/woggioni/jpacrepo/experimental/JPQLTest.java new file mode 100644 index 0000000..be1cb7a --- /dev/null +++ b/jpacrepo-experimental/src/test/java/net/woggioni/jpacrepo/experimental/JPQLTest.java @@ -0,0 +1,11 @@ +package net.woggioni.jpacrepo.experimental; + +import org.junit.jupiter.api.Test; + +public class JPQLTest { + + @Test + public void test() { + + } +} diff --git a/jpacrepo-impl/build.gradle b/jpacrepo-impl/build.gradle index c101b25..b5126f9 100644 --- a/jpacrepo-impl/build.gradle +++ b/jpacrepo-impl/build.gradle @@ -4,9 +4,9 @@ plugins { dependencies { compileOnly catalog.jakarta.persistence.api + compileOnly catalog.slf4j.api implementation catalog.xz - implementation catalog.slf4j.api implementation catalog.jzstd implementation catalog.jwo implementation catalog.commons.compress diff --git a/jpacrepo-impl/src/main/java/module-info.java b/jpacrepo-impl/src/main/java/module-info.java index 1377853..18f3610 100644 --- a/jpacrepo-impl/src/main/java/module-info.java +++ b/jpacrepo-impl/src/main/java/module-info.java @@ -4,6 +4,7 @@ module net.woggioni.jpacrepo.impl { requires net.woggioni.jpacrepo.api; requires net.woggioni.jwo; requires net.woggioni.jzstd; + requires org.slf4j; requires org.apache.commons.compress; exports net.woggioni.jpacrepo.impl.model; diff --git a/jpacrepo-impl/src/main/java/net/woggioni/jpacrepo/impl/model/PkgDataImpl.java b/jpacrepo-impl/src/main/java/net/woggioni/jpacrepo/impl/model/PkgDataImpl.java deleted file mode 100644 index 14773cb..0000000 --- a/jpacrepo-impl/src/main/java/net/woggioni/jpacrepo/impl/model/PkgDataImpl.java +++ /dev/null @@ -1,164 +0,0 @@ -package net.woggioni.jpacrepo.impl.model; - -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import lombok.SneakyThrows; -import net.woggioni.jpacrepo.api.model.PkgData; -import net.woggioni.jpacrepo.api.model.PkgId; -import net.woggioni.jwo.CollectionUtils; -import net.woggioni.jwo.Fun; -import net.woggioni.jwo.Hash; -import net.woggioni.jwo.JWO; -import net.woggioni.jwo.Tuple2; -import net.woggioni.jwo.UncloseableInputStream; -import net.woggioni.jzstd.ZstdInputStream; -import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; -import org.apache.commons.compress.compressors.xz.XZCompressorInputStream; - -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.text.ParseException; -import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.TreeMap; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.zip.GZIPInputStream; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class PkgDataImpl { - - @SneakyThrows - public static PkgData parseFile(Path file, net.woggioni.jpacrepo.api.model.CompressionFormat compressionFormat) { - Fun decompressorStreamConstructor; - switch (compressionFormat) { - case XZ: - decompressorStreamConstructor = XZCompressorInputStream::new; - break; - case Z_STANDARD: - decompressorStreamConstructor = ZstdInputStream::from; - break; - case GZIP: - decompressorStreamConstructor = GZIPInputStream::new; - break; - default: - throw JWO.newThrowable(ParseException.class, - "Unsupported compression format '%s'", compressionFormat); - } - try(TarArchiveInputStream is = new TarArchiveInputStream( - decompressorStreamConstructor.apply( - new BufferedInputStream( - Files.newInputStream(file))))) { - var archiveEntry = is.getNextEntry(); - while (archiveEntry != null) { - if (Objects.equals(".PKGINFO", archiveEntry.getName())) { - try(BufferedReader reader = - new BufferedReader( - new InputStreamReader( - new UncloseableInputStream(is)))) { - Map> metadata = reader.lines().map(String::trim) - .filter(Predicate.not(String::isEmpty)) - .filter(line -> !line.startsWith("#")) - .map((Fun>) line -> { - int equals = line.indexOf("="); - if (equals < 0) { - throw JWO.newThrowable(ParseException.class, - "Error parsing .PKGINFO file in '%s'", file); - } else { - return Tuple2.newInstance( - line.substring(0, equals).trim(), - line.substring(equals + 1).trim()); - } - }).collect( - Collectors.groupingBy( - Tuple2::get_1, - TreeMap::new, - Collectors.mapping(Tuple2::get_2, - Collectors.toUnmodifiableList()))); - PkgData data = new PkgData(); - data.setId(new PkgId()); - data.getId().setCompressionFormat(compressionFormat); - - for (Map.Entry> entry : metadata.entrySet()) { - String key = entry.getKey(); - List value = entry.getValue(); - switch (key) { - case "size": - data.setSize(Long.parseLong(value.get(0))); - break; - case "arch": - data.getId().setArch(value.get(0)); - break; - case "replaces": - data.setReplaces(value.stream().collect(CollectionUtils.toUnmodifiableTreeSet())); - break; - case "packager": - data.setPackager(value.get(0)); - break; - case "url": - data.setUrl(value.get(0)); - break; - case "pkgname": - data.getId().setName(value.get(0)); - break; - case "builddate": - data.setBuildDate( - Instant.ofEpochSecond(Long.parseLong(value.get(0)))); - break; - case "license": - data.setLicense(value.get(0)); - break; - case "pkgver": - data.getId().setVersion(value.get(0)); - break; - case "pkgdesc": - data.setDescription(value.get(0)); - break; - case "provides": - data.setProvides(value.stream().collect(CollectionUtils.toUnmodifiableTreeSet())); - break; - case "conflict": - data.setConflict(value.stream().collect(CollectionUtils.toUnmodifiableTreeSet())); - break; - case "backup": - data.setBackup(value.stream().collect(CollectionUtils.toUnmodifiableTreeSet())); - break; - case "optdepend": - data.setOptdepend(value.stream().collect(CollectionUtils.toUnmodifiableTreeSet())); - break; - case "depend": - data.setDepend(value.stream().collect(CollectionUtils.toUnmodifiableTreeSet())); - break; - case "makedepend": - data.setMakedepend(value.stream().collect(CollectionUtils.toUnmodifiableTreeSet())); - break; - case "makepkgopt": - data.setMakeopkgopt(value.stream().collect(CollectionUtils.toUnmodifiableTreeSet())); - break; - case "pkgbase": - data.setBase(value.get(0)); - break; - default: - break; - } - } - try(InputStream fis = Files.newInputStream(file)) { - data.setMd5sum(Hash.hash(Hash.Algorithm.MD5, fis).toString()); - } - data.setFileName(file.getFileName().toString()); - return data; - } - } else { - archiveEntry = is.getNextEntry(); - } - } - throw JWO.newThrowable(ParseException.class, ".PKGINFO file not found in '%s'", file); - } - } -} diff --git a/jpacrepo-impl/src/main/java/net/woggioni/jpacrepo/impl/model/PkgDataParser.java b/jpacrepo-impl/src/main/java/net/woggioni/jpacrepo/impl/model/PkgDataParser.java new file mode 100644 index 0000000..f968797 --- /dev/null +++ b/jpacrepo-impl/src/main/java/net/woggioni/jpacrepo/impl/model/PkgDataParser.java @@ -0,0 +1,296 @@ +package net.woggioni.jpacrepo.impl.model; + +import jakarta.persistence.EntityManager; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import net.woggioni.jpacrepo.api.model.CompressionFormat; +import net.woggioni.jpacrepo.api.model.Dependency; +import net.woggioni.jpacrepo.api.model.License; +import net.woggioni.jpacrepo.api.model.NamedEntity; +import net.woggioni.jpacrepo.api.model.NamedEntity_; +import net.woggioni.jpacrepo.api.model.Packager; +import net.woggioni.jpacrepo.api.model.PkgBase; +import net.woggioni.jpacrepo.api.model.PkgData; +import net.woggioni.jpacrepo.api.model.PkgId; +import net.woggioni.jwo.CollectionUtils; +import net.woggioni.jwo.Fun; +import net.woggioni.jwo.Hash; +import net.woggioni.jwo.JWO; +import net.woggioni.jwo.Tuple2; +import net.woggioni.jwo.UncloseableInputStream; +import net.woggioni.jzstd.ZstdInputStream; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.commons.compress.compressors.xz.XZCompressorInputStream; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.ParseException; +import java.time.Instant; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.zip.GZIPInputStream; + + +@Slf4j +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +class NamedEntityFinder { + private final Map cache; + private final EntityManager em; + private final Class cls; + private final Function ctor; + + public NamedEntityFinder(EntityManager em, Class cls, Function ctor) { + this(createLruCache(0x40000), em, cls, ctor); + } + + private static Map createLruCache(int maxSize) { + return new LinkedHashMap<>() { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() >= maxSize; + } + }; + } + + private Optional queryNamedEntity(String name) { + if(log.isDebugEnabled()) { + log.debug("Querying named entity '{}' with name {}", cls.getName(), name); + } + var cb = em.getCriteriaBuilder(); + var tq = cb.createQuery(cls); + var root = tq.from(cls); + tq.select(root).where(cb.equal(root.get(NamedEntity_.NAME), name)); + var query = em.createQuery(tq); + query.setMaxResults(1); + return Optional.of(query.getResultList()) + .filter(JWO.not(List::isEmpty)) + .map(it -> it.get(0)); + } + + public T getByName(String name) { + return + cache.computeIfAbsent( + name, + it -> queryNamedEntity(it) + .orElseGet(() -> { + var newEntity = ctor.apply(it); + em.persist(newEntity); + return newEntity; + }) + ); + } +} + +public class PkgDataParser { + private final NamedEntityFinder dependencyFinder; + private final NamedEntityFinder packagerFinder; + private final NamedEntityFinder licenseFinder; + private final NamedEntityFinder pkgBaseFinder; + + public PkgDataParser(EntityManager em) { + dependencyFinder = new NamedEntityFinder<>(em, Dependency.class, Dependency::of); + packagerFinder = new NamedEntityFinder<>(em, Packager.class, Packager::of); + licenseFinder = new NamedEntityFinder<>(em, License.class, License::of); + pkgBaseFinder = new NamedEntityFinder<>(em, PkgBase.class, PkgBase::of); + } + + private static T hydrate( + NamedEntityFinder namedEntityFinder, T entity + ) { + return Optional.ofNullable(entity) + .map(NamedEntity::getName) + .map(namedEntityFinder::getByName) + .orElse(null); + } + + private static Set hydrate( + NamedEntityFinder namedEntityFinder, Set entites) { + return entites + .stream() + .map(it -> hydrate(namedEntityFinder, it)) + .filter(Objects::nonNull) + .collect(Collectors.toUnmodifiableSet()); + } + + public PkgData hydrateJPA(PkgData pkgData) { + pkgData.setBase(hydrate(pkgBaseFinder, pkgData.getBase())); + pkgData.setDepend(hydrate(dependencyFinder, pkgData.getDepend())); + pkgData.setOptdepend(hydrate(dependencyFinder, pkgData.getOptdepend())); + pkgData.setProvides(hydrate(dependencyFinder, pkgData.getProvides())); + pkgData.setReplaces(hydrate(dependencyFinder, pkgData.getReplaces())); + pkgData.setBackup(hydrate(dependencyFinder, pkgData.getBackup())); + pkgData.setConflict(hydrate(dependencyFinder, pkgData.getConflict())); + pkgData.setMakedepend(hydrate(dependencyFinder, pkgData.getMakedepend())); + pkgData.setMakepkgopt(hydrate(dependencyFinder, pkgData.getMakepkgopt())); + pkgData.setPackager(hydrate(packagerFinder, pkgData.getPackager())); + pkgData.setLicense(hydrate(licenseFinder, pkgData.getLicense())); + return pkgData; + } + + @SneakyThrows + public static PkgData parseFile(Path file, CompressionFormat compressionFormat) { + Fun decompressorStreamConstructor = switch (compressionFormat) { + case XZ -> XZCompressorInputStream::new; + case Z_STANDARD -> ZstdInputStream::from; + case GZIP -> GZIPInputStream::new; + default -> throw JWO.newThrowable(ParseException.class, + "Unsupported compression format '%s'", compressionFormat); + }; + try (TarArchiveInputStream is = new TarArchiveInputStream( + decompressorStreamConstructor.apply( + new BufferedInputStream( + Files.newInputStream(file))))) { + var archiveEntry = is.getNextEntry(); + while (archiveEntry != null) { + if (Objects.equals(".PKGINFO", archiveEntry.getName())) { + try (BufferedReader reader = + new BufferedReader( + new InputStreamReader( + new UncloseableInputStream(is)))) { + Map> metadata = reader.lines().map(String::trim) + .filter(Predicate.not(String::isEmpty)) + .filter(line -> !line.startsWith("#")) + .map((Fun>) line -> { + int equals = line.indexOf("="); + if (equals < 0) { + throw JWO.newThrowable(ParseException.class, + "Error parsing .PKGINFO file in '%s'", file); + } else { + return Tuple2.newInstance( + line.substring(0, equals).trim(), + line.substring(equals + 1).trim()); + } + }).collect( + Collectors.groupingBy( + Tuple2::get_1, + TreeMap::new, + Collectors.mapping(Tuple2::get_2, + Collectors.toUnmodifiableList()))); + PkgData data = new PkgData(); + data.setPkgId(new PkgId()); + data.getPkgId().setCompressionFormat(compressionFormat); + data.setDepend(Collections.emptySet()); + data.setOptdepend(Collections.emptySet()); + data.setMakedepend(Collections.emptySet()); + data.setMakepkgopt(Collections.emptySet()); + data.setProvides(Collections.emptySet()); + data.setConflict(Collections.emptySet()); + data.setLicense(Collections.emptySet()); + data.setReplaces(Collections.emptySet()); + data.setBackup(Collections.emptySet()); + + for (Map.Entry> entry : metadata.entrySet()) { + String key = entry.getKey(); + List value = entry.getValue(); + switch (key) { + case "size": + data.setSize(Long.parseLong(value.get(0))); + break; + case "arch": + data.getPkgId().setArch(value.get(0)); + break; + case "replaces": + data.setReplaces(value.stream() + .map(Dependency::of) + .collect(CollectionUtils.toUnmodifiableTreeSet())); + break; + case "packager": + data.setPackager(Packager.of(value.get(0))); + break; + case "url": + data.setUrl(value.get(0)); + break; + case "pkgname": + data.getPkgId().setName(value.get(0)); + break; + case "builddate": + data.setBuildDate( + Instant.ofEpochSecond(Long.parseLong(value.get(0)))); + break; + case "license": + data.setLicense(value.stream() + .map(License::of) + .collect(CollectionUtils.toUnmodifiableTreeSet())); + break; + case "pkgver": + data.getPkgId().setVersion(value.get(0)); + break; + case "pkgdesc": + data.setDescription(value.get(0)); + break; + case "provides": + data.setProvides(value.stream() + .map(Dependency::of) + .collect(CollectionUtils.toUnmodifiableTreeSet())); + break; + case "conflict": + data.setConflict(value + .stream() + .map(Dependency::of) + .collect(CollectionUtils.toUnmodifiableTreeSet())); + break; + case "backup": + data.setBackup(value + .stream() + .map(Dependency::of) + .collect(CollectionUtils.toUnmodifiableTreeSet())); + break; + case "optdepend": + data.setOptdepend(value + .stream() + .map(Dependency::of) + .collect(CollectionUtils.toUnmodifiableTreeSet())); + break; + case "depend": + data.setDepend(value.stream() + .map(Dependency::of) + .collect(CollectionUtils.toUnmodifiableTreeSet())); + break; + case "makedepend": + data.setMakedepend(value + .stream() + .map(Dependency::of) + .collect(CollectionUtils.toUnmodifiableTreeSet())); + break; + case "makepkgopt": + data.setMakepkgopt(value + .stream() + .map(Dependency::of) + .collect(CollectionUtils.toUnmodifiableTreeSet())); + break; + case "pkgbase": + data.setBase(PkgBase.of(value.get(0))); + break; + default: + break; + } + } + try (InputStream fis = Files.newInputStream(file)) { + data.setMd5sum(Hash.hash(Hash.Algorithm.MD5, fis).getBytes()); + } + data.setFileName(file.getFileName().toString()); + return data; + } + } else { + archiveEntry = is.getNextEntry(); + } + } + throw JWO.newThrowable(ParseException.class, ".PKGINFO file not found in '%s'", file); + } + } +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index c37bd53..11de57b 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -9,6 +9,8 @@ open module net.woggioni.jpacrepo { requires static jakarta.cdi; requires static jakarta.ws.rs; + requires liquibase.core; + requires liquibase.jakarta.cdi; requires net.woggioni.jwo; requires net.woggioni.jpacrepo.impl; requires org.slf4j; diff --git a/src/main/java/net/woggioni/jpacrepo/factory/LiquibaseFactory.java b/src/main/java/net/woggioni/jpacrepo/factory/LiquibaseFactory.java new file mode 100644 index 0000000..07dc35e --- /dev/null +++ b/src/main/java/net/woggioni/jpacrepo/factory/LiquibaseFactory.java @@ -0,0 +1,49 @@ +package net.woggioni.jpacrepo.factory; + +import jakarta.annotation.Resource; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +import liquibase.integration.jakarta.cdi.CDILiquibaseConfig; +import liquibase.integration.jakarta.cdi.annotations.LiquibaseType; +import liquibase.resource.ClassLoaderResourceAccessor; +import liquibase.resource.ResourceAccessor; +import lombok.SneakyThrows; + +@ApplicationScoped +public class LiquibaseFactory { + + @Resource + private DataSource myDataSource; + + @Produces + @LiquibaseType + public CDILiquibaseConfig createConfig() { + CDILiquibaseConfig config = new CDILiquibaseConfig(); + config.setChangeLog("/META-INF/liquibase/jpacrepo.xml"); + config.setDefaultSchema("jpacrepo"); + config.setShouldRun(true); + return config; + } + + @Produces + @LiquibaseType + @SneakyThrows + public DataSource createDataSource() { + Connection conn = myDataSource.getConnection(); + Statement stmt = conn.createStatement(); + stmt.execute("CREATE SCHEMA IF NOT EXISTS \"jpacrepo\";"); + return myDataSource; + } + + @Produces + @LiquibaseType + public ResourceAccessor create() { + return new ClassLoaderResourceAccessor(getClass().getClassLoader()); + } +} diff --git a/src/main/java/net/woggioni/jpacrepo/factory/PersistenceUnitFactory.java b/src/main/java/net/woggioni/jpacrepo/factory/PersistenceUnitFactory.java index c9343bb..99102fe 100644 --- a/src/main/java/net/woggioni/jpacrepo/factory/PersistenceUnitFactory.java +++ b/src/main/java/net/woggioni/jpacrepo/factory/PersistenceUnitFactory.java @@ -1,22 +1,39 @@ package net.woggioni.jpacrepo.factory; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.RequestScoped; +import jakarta.enterprise.inject.Default; +import jakarta.enterprise.inject.Disposes; import jakarta.enterprise.inject.Produces; +import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.Persistence; import net.woggioni.jpacrepo.config.AppConfig; import java.util.Properties; -@ApplicationScoped +//@ApplicationScoped public class PersistenceUnitFactory { - - @Produces - private EntityManagerFactory createEntityManagerFactory(AppConfig appConfig) { - Properties properties = new Properties(); - properties.put("jakarta.persistence.schema-generation.database.action", - appConfig.getInitialSchemaAction().getValue()); - properties.put("jakarta.persistence.jtaDataSource", appConfig.getDataSourceJndi()); - return Persistence.createEntityManagerFactory("jpacrepo_pu", properties); - } +// +// @Produces +// private EntityManagerFactory createEntityManagerFactory(AppConfig appConfig) { +// Properties properties = new Properties(); +// properties.put("jakarta.persistence.schema-generation.database.action", +// appConfig.getInitialSchemaAction().getValue()); +// properties.put("jakarta.persistence.jtaDataSource", appConfig.getDataSourceJndi()); +// return Persistence.createEntityManagerFactory("jpacrepo_pu", properties); +// } +// +// @Produces +// @Default +// @RequestScoped +// public EntityManager create(EntityManagerFactory emf) { +// return emf.createEntityManager(); +// } +// +// public void dispose(@Disposes @Default EntityManager entityManager) { +// if (entityManager.isOpen()) { +// entityManager.close(); +// } +// } } diff --git a/src/main/java/net/woggioni/jpacrepo/service/PacmanServiceEJB.java b/src/main/java/net/woggioni/jpacrepo/service/PacmanServiceEJB.java index 3ba13ab..b1b88cb 100644 --- a/src/main/java/net/woggioni/jpacrepo/service/PacmanServiceEJB.java +++ b/src/main/java/net/woggioni/jpacrepo/service/PacmanServiceEJB.java @@ -11,7 +11,7 @@ import jakarta.ejb.Lock; import jakarta.ejb.LockType; import jakarta.ejb.Remote; import jakarta.ejb.Schedule; -import jakarta.ejb.Stateless; +import jakarta.ejb.Singleton; import jakarta.ejb.TransactionAttribute; import jakarta.ejb.TransactionAttributeType; import jakarta.ejb.TransactionManagement; @@ -30,12 +30,13 @@ import net.woggioni.jpacrepo.api.wire.PkgTuple; import net.woggioni.jpacrepo.cache.PackageCache; import net.woggioni.jpacrepo.config.AppConfig; import net.woggioni.jpacrepo.impl.model.CompressionFormatImpl; -import net.woggioni.jpacrepo.impl.model.PkgDataImpl; +import net.woggioni.jpacrepo.impl.model.PkgDataParser; import net.woggioni.jpacrepo.persistence.QueryEngine; import net.woggioni.jpacrepo.service.jpa.Queries; import net.woggioni.jpacrepo.version.PkgIdComparator; import net.woggioni.jwo.CollectionUtils; import net.woggioni.jwo.Con; +import net.woggioni.jwo.Hash; import net.woggioni.jwo.JWO; import net.woggioni.jwo.Sup; import net.woggioni.jwo.Tuple2; @@ -63,7 +64,9 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; -@Stateless +import static java.util.function.Predicate.not; + +@Singleton @Lock(LockType.READ) @TransactionManagement(TransactionManagementType.CONTAINER) @ConcurrencyManagement(ConcurrencyManagementType.CONTAINER) @@ -71,7 +74,7 @@ import java.util.stream.Stream; @Remote({PacmanServiceRemote.class}) public class PacmanServiceEJB implements PacmanServiceLocal { - @PersistenceContext + @PersistenceContext(name = "jpacrepo_pu") private EntityManager em; @Inject @@ -123,9 +126,10 @@ public class PacmanServiceEJB implements PacmanServiceLocal { }); long[] count = new long[]{0}; long totalPackages = fileListStreamSupplier.get().count(); + var parser = new PkgDataParser(em); Con persistPackages = (Boolean drain) -> { - while ((drain && inProgress.size() > 0) || inProgress.size() > maxInProgress) { + while ((drain && !inProgress.isEmpty()) || inProgress.size() > maxInProgress) { Optional.ofNullable(completionService.poll(1, TimeUnit.SECONDS)) .ifPresent((Con>) future -> { inProgress.remove(future); @@ -135,7 +139,7 @@ public class PacmanServiceEJB implements PacmanServiceLocal { } catch (ExecutionException ee) { throw ee.getCause(); } - persistPackage(em, pkgData, ++count[0], totalPackages); + persistPackage(em, parser, pkgData, ++count[0], totalPackages); }); } }; @@ -146,7 +150,11 @@ public class PacmanServiceEJB implements PacmanServiceLocal { }).get()) { inProgress.add(completionService.submit(() -> { try { - return PkgDataImpl.parseFile(file, CompressionFormatImpl.guess(file)); + var pkgData = PkgDataParser.parseFile(file, CompressionFormatImpl.guess(file)); + if(logger.isDebugEnabled()) { + logger.debug("Parsed package file {}", file); + } + return pkgData; } catch (Exception ex) { logger.error(String.format("Error parsing '%s'", file.toAbsolutePath()), ex); throw ex; @@ -162,12 +170,12 @@ public class PacmanServiceEJB implements PacmanServiceLocal { packageCache.invalidateCache(); } - private void persistPackage(EntityManager em, PkgData pkgData, long count, long totalPackages) { + private void persistPackage(EntityManager em, PkgDataParser parser, PkgData pkgData, long count, long totalPackages) { if (Queries.countPackagesByHash(em, pkgData.getMd5sum()).getSingleResult() == 0) { Queries.getPackageByFileName(em, pkgData.getFileName()) .getResultList() .forEach(p -> deletePkgData(em, p)); - em.persist(pkgData); + em.persist(parser.hydrateJPA(pkgData)); logger.info("({}/{}) Persisting package {}", count, totalPackages, pkgData.getFileName()); } } @@ -231,7 +239,7 @@ public class PacmanServiceEJB implements PacmanServiceLocal { @Override public List searchByHash(@Nonnull String hash) { - return Queries.searchPackagesByHash(em, hash).getResultList(); + return Queries.searchPackagesByHash(em, Hash.hexToBytes(hash)).getResultList(); } @Override @@ -241,7 +249,9 @@ public class PacmanServiceEJB implements PacmanServiceLocal { @Override public List listHashes() { - return Queries.listHashes(em).getResultList(); + return Queries.listHashes(em) + .getResultStream().map(JWO::bytesToHex) + .toList(); } @Override @@ -278,8 +288,7 @@ public class PacmanServiceEJB implements PacmanServiceLocal { PkgId pkgId = tuple.get(0, PkgId.class); String filename = tuple.get(1, String.class); long size = tuple.get(2, Long.class); - String md5sum = tuple.get(3, String.class); - ; + String md5sum = JWO.bytesToHex(tuple.get(3, byte[].class)); PkgTuple pkgTuple = new PkgTuple(); pkgTuple.setFileName(filename); pkgTuple.setSize(size); @@ -296,22 +305,26 @@ public class PacmanServiceEJB implements PacmanServiceLocal { @Nullable @Override - public PkgData getPackage(PkgId pkgId) { - return em.find(PkgData.class, pkgId); + public Optional getPackage(PkgId pkgId) { + var query = Queries.getPackageById(em, pkgId); + query.setMaxResults(1); + return Optional.of(query.getResultList()) + .filter(not(List::isEmpty)) + .map(l -> l.get(0)); } @SneakyThrows public boolean addPackage(String fileName, InputStream input) { java.nio.file.Path file = Files.createTempFile(ctx.getRepoFolder(), fileName, null); List savedFiles = searchByFileName(fileName); - if (savedFiles.size() > 0) return false; + if (!savedFiles.isEmpty()) return false; else { try (OutputStream output = Files.newOutputStream(file)) { JWO.copy(input, output, 0x10000); - PkgData pkg = PkgDataImpl.parseFile(file, + PkgData pkg = PkgDataParser.parseFile(file, CompressionFormatImpl.guess(Paths.get(fileName))); pkg.setFileName(fileName); - Optional.ofNullable(em.find(PkgData.class, pkg.getId())).ifPresent((Con) (pkgData -> { + getPackage(pkg.getPkgId()).ifPresent((Con) (pkgData -> { em.remove(pkgData); Files.delete(ctx.getRepoFolder().resolve(pkgData.getFileName())); })); diff --git a/src/main/java/net/woggioni/jpacrepo/service/PacmanWebService.java b/src/main/java/net/woggioni/jpacrepo/service/PacmanWebService.java index 6a1cd7c..ede0ffe 100644 --- a/src/main/java/net/woggioni/jpacrepo/service/PacmanWebService.java +++ b/src/main/java/net/woggioni/jpacrepo/service/PacmanWebService.java @@ -39,6 +39,7 @@ import net.woggioni.jpacrepo.api.model.PkgId; import net.woggioni.jpacrepo.api.security.Roles; import net.woggioni.jpacrepo.api.service.PacmanServiceLocal; import net.woggioni.jpacrepo.api.wire.PkgDataList; +import net.woggioni.jpacrepo.api.wire.PkgIdList; import net.woggioni.jpacrepo.api.wire.PkgTuple; import net.woggioni.jpacrepo.api.wire.StringList; import net.woggioni.jpacrepo.config.AppConfig; @@ -121,7 +122,9 @@ public class PacmanWebService { @QueryParam("arch") String arch, @QueryParam("compressionFormat") CompressionFormat compressionFormat ) { - return Response.ok(service.searchPkgId(name, version, arch, compressionFormat)).build(); + return Response.ok( + new PkgIdList(service.searchPkgId(name, version, arch, compressionFormat)) + ).build(); } @GET diff --git a/src/main/java/net/woggioni/jpacrepo/service/jpa/Queries.java b/src/main/java/net/woggioni/jpacrepo/service/jpa/Queries.java index dd51248..d29e8fe 100644 --- a/src/main/java/net/woggioni/jpacrepo/service/jpa/Queries.java +++ b/src/main/java/net/woggioni/jpacrepo/service/jpa/Queries.java @@ -1,5 +1,6 @@ package net.woggioni.jpacrepo.service.jpa; +import jakarta.persistence.EntityGraph; import jakarta.persistence.EntityManager; import jakarta.persistence.Tuple; import jakarta.persistence.TypedQuery; @@ -26,6 +27,7 @@ import java.util.Optional; @NoArgsConstructor(access = AccessLevel.PRIVATE) public class Queries { + private static final String ENTITY_GRAPH_PROPERTY = "jakarta.persistence.fetchgraph"; private interface PredicateSupplier { Predicate get(CriteriaBuilder cb, Root root); } @@ -47,7 +49,9 @@ public class Queries { Root root = criteriaQuery.from(entity); Predicate predicate = cb.equal(root.get(PkgData_.fileName), fileName); criteriaQuery.select(root).where(predicate); - return em.createQuery(criteriaQuery); + var query = em.createQuery(criteriaQuery); + query.setHint(ENTITY_GRAPH_PROPERTY, getPkgDataEntityGraph(em)); + return query; } public static TypedQuery getUpdateTimestampByFileName(EntityManager em, String fileName) { @@ -72,7 +76,7 @@ public class Queries { Root pkgDataRoot = criteriaQuery.from(entity); Subquery subQuery = criteriaQuery.subquery(String.class); Root pkgDataRootSub = subQuery.from(entity); - Path pkgIdPathSub = pkgDataRootSub.get(PkgData_.id); + Path pkgIdPathSub = pkgDataRootSub.get(PkgData_.pkgId); Predicate havingPredicate = cb.greaterThan(cb.count( pkgIdPathSub.get(PkgId_.version)), minNumberOfDifferentVersions); subQuery.select(pkgIdPathSub.get(PkgId_.name)) @@ -82,7 +86,7 @@ public class Queries { ).having(havingPredicate); Predicate predicate = cb.and( cb.lessThan(pkgDataRoot.get(PkgData_.buildDate), cutoff), - pkgDataRoot.get(PkgData_.id).get(PkgId_.name).in(subQuery.getSelection()) + pkgDataRoot.get(PkgData_.pkgId).get(PkgId_.name).in(subQuery.getSelection()) ); criteriaQuery.select(pkgDataRoot.get(PkgData_.fileName)) .where(predicate) @@ -90,7 +94,7 @@ public class Queries { return em.createQuery(criteriaQuery); } - public static TypedQuery countPackagesByHash(EntityManager em, String hash) { + public static TypedQuery countPackagesByHash(EntityManager em, byte[] hash) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery criteriaQuery = cb.createQuery(Long.class); Metamodel metamodel = em.getMetamodel(); @@ -109,10 +113,12 @@ public class Queries { EntityType entity = metamodel.entity(PkgData.class); Root pkgDataRoot = criteriaQuery.from(entity); criteriaQuery.select(pkgDataRoot).where(predicateSupplier.get(cb, pkgDataRoot)); - return em.createQuery(criteriaQuery); + var query = em.createQuery(criteriaQuery); + query.setHint(ENTITY_GRAPH_PROPERTY, getPkgDataEntityGraph(em)); + return query; } - public static TypedQuery searchPackagesByHash(EntityManager em, String hash) { + public static TypedQuery searchPackagesByHash(EntityManager em, byte[] hash) { return searchPackagesByPredicate(em, (cb, root) -> cb.equal(root.get(PkgData_.md5sum), hash)); } @@ -131,13 +137,28 @@ public class Queries { return em.createQuery(criteriaQuery); } + public static TypedQuery getPackageById(EntityManager em, PkgId pkgId) { + var cb = em.getCriteriaBuilder(); + var cq = cb.createQuery(PkgData.class); + var root = cq.from(PkgData_.class_); + cq.select(root).where( + cb.equal( + root.get(PkgData_.pkgId), + pkgId + ) + ); + var query = em.createQuery(cq); + query.setHint(ENTITY_GRAPH_PROPERTY, getPkgDataEntityGraph(em)); + return query; + } + public static TypedQuery searchPackageName(EntityManager em, String needle) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery criteriaQuery = cb.createQuery(String.class); Metamodel metamodel = em.getMetamodel(); EntityType entity = metamodel.entity(PkgData.class); Root root = criteriaQuery.from(entity); - Path pkgIdPath = root.get(PkgData_.id); + Path pkgIdPath = root.get(PkgData_.pkgId); Predicate predicate = cb.like( cb.lower(pkgIdPath.get(PkgId_.name)), "%%" + needle + "%%" @@ -159,9 +180,9 @@ public class Queries { return em.createQuery(criteriaQuery); } - public static TypedQuery listHashes(EntityManager em) { + public static TypedQuery listHashes(EntityManager em) { CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery criteriaQuery = cb.createQuery(String.class); + CriteriaQuery criteriaQuery = cb.createQuery(byte[].class); Metamodel metamodel = em.getMetamodel(); EntityType entity = metamodel.entity(PkgData.class); Root root = criteriaQuery.from(entity); @@ -180,7 +201,7 @@ public class Queries { Metamodel metamodel = em.getMetamodel(); EntityType entity = metamodel.entity(PkgData.class); Root root = criteriaQuery.from(entity); - Path pkgIdRoot = root.get(PkgData_.id); + Path pkgIdRoot = root.get(PkgData_.pkgId); Predicate[] predicates = JWO.streamCat( Optional.ofNullable(name) .map(it -> cb.equal(pkgIdRoot.get(PkgId_.name), it)).stream(), @@ -219,7 +240,7 @@ public class Queries { Metamodel metamodel = em.getMetamodel(); EntityType entity = metamodel.entity(PkgData.class); Root root = criteriaQuery.from(entity); - Path idPath = root.get(PkgData_.id); + Path idPath = root.get(PkgData_.pkgId); criteriaQuery.multiselect( idPath, root.get(PkgData_.fileName), @@ -244,7 +265,7 @@ public class Queries { CriteriaBuilder builder = em.getCriteriaBuilder(); criteriaQuery = builder.createQuery(PkgData.class); Root entity = criteriaQuery.from(PkgData.class); - Path pkgIdPath = entity.get(PkgData_.id); + Path pkgIdPath = entity.get(PkgData_.pkgId); Predicate predicate = builder.and(JWO.streamCat( JWO.optional2Stream( Optional.ofNullable(name) @@ -275,7 +296,25 @@ public class Queries { builder.asc(pkgIdPath.get(PkgId_.compressionFormat)), builder.asc(entity.get(PkgData_.fileName)) ); - return em.createQuery(criteriaQuery); + var query = em.createQuery(criteriaQuery); + query.setHint(ENTITY_GRAPH_PROPERTY, getPkgDataEntityGraph(em)); + return query; + } + + private static EntityGraph getPkgDataEntityGraph(EntityManager em) { + var graph = em.createEntityGraph(PkgData.class); + graph.addAttributeNodes(PkgData_.base); + graph.addAttributeNodes(PkgData_.packager); + graph.addAttributeNodes(PkgData_.license); + graph.addSubgraph(PkgData_.depend); + graph.addSubgraph(PkgData_.backup); + graph.addSubgraph(PkgData_.conflict); + graph.addSubgraph(PkgData_.replaces); + graph.addSubgraph(PkgData_.makedepend); + graph.addSubgraph(PkgData_.makepkgopt); + graph.addSubgraph(PkgData_.optdepend); + graph.addSubgraph(PkgData_.provides); + return graph; } } diff --git a/src/main/resources/META-INF/liquibase/jpacrepo.xml b/src/main/resources/META-INF/liquibase/jpacrepo.xml new file mode 100644 index 0000000..c14c5df --- /dev/null +++ b/src/main/resources/META-INF/liquibase/jpacrepo.xml @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml index 6da4456..cac01cb 100644 --- a/src/main/resources/META-INF/persistence.xml +++ b/src/main/resources/META-INF/persistence.xml @@ -5,12 +5,18 @@ version="2.1"> - net.woggioni.jpacrepo.api.model.PkgData net.woggioni.jpacrepo.api.model.PkgId + net.woggioni.jpacrepo.api.model.PkgData + net.woggioni.jpacrepo.api.model.License + net.woggioni.jpacrepo.api.model.PkgBase + net.woggioni.jpacrepo.api.model.Dependency + net.woggioni.jpacrepo.api.model.Packager true + @@ -18,7 +24,31 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/ClientTest.java b/src/test/java/ClientTest.java index 0f40962..da96c6a 100644 --- a/src/test/java/ClientTest.java +++ b/src/test/java/ClientTest.java @@ -12,7 +12,7 @@ import lombok.SneakyThrows; import net.woggioni.jpacrepo.api.model.PkgData; import net.woggioni.jpacrepo.api.service.PacmanServiceRemote; import net.woggioni.jpacrepo.impl.model.CompressionFormatImpl; -import net.woggioni.jpacrepo.impl.model.PkgDataImpl; +import net.woggioni.jpacrepo.impl.model.PkgDataParser; import net.woggioni.jwo.Con; import net.woggioni.jwo.Hash; import net.woggioni.jwo.JWO; @@ -98,7 +98,7 @@ public class ClientTest { try(InputStream is = Files.newInputStream(tmpFile)) { hash = Hash.md5(is); } - PkgData p = PkgDataImpl.parseFile(tmpFile, CompressionFormatImpl.guess(tmpFile)); + PkgData p = PkgDataParser.parseFile(tmpFile, CompressionFormatImpl.guess(tmpFile)); Assertions.assertEquals(JWO.bytesToHex(hash.getBytes()), p.getMd5sum()); }); } @@ -124,8 +124,8 @@ public class ClientTest { prop.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming"); prop.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory"); - prop.put(Context.PROVIDER_URL, "http-remoting://localhost:7080"); -// prop.put(Context.PROVIDER_URL, "http-remoting://localhost:8080"); +// prop.put(Context.PROVIDER_URL, "http-remoting://localhost:1234"); + prop.put(Context.PROVIDER_URL, "http-remoting://localhost:8080"); // prop.put(Context.PROVIDER_URL, "remote://odroid-u3:4447"); prop.put(Context.SECURITY_PRINCIPAL, "walter"); prop.put(Context.SECURITY_CREDENTIALS, "27ff5990757d1d"); @@ -138,7 +138,7 @@ public class ClientTest { traverseJndiNode("/", context); // final PacmanService stateService = (PacmanService) ctx.lookup("/jpacrepo-1.0/remote/PacmanServiceEJB!service.PacmanService"); final PacmanServiceRemote service = (PacmanServiceRemote) ctx.lookup( - "/jpacrepo-2023.07/PacmanServiceEJB!net.woggioni.jpacrepo.api.service.PacmanServiceRemote" + "/jpacrepo/PacmanServiceEJB!net.woggioni.jpacrepo.api.service.PacmanServiceRemote" ); // List pkgs = service.searchPackage("google-earth", null, null, 1, 10); // System.out.println(new XStream().toXML(pkgs)); diff --git a/src/test/java/ParseTest.java b/src/test/java/ParseTest.java index 76bba48..3411a16 100644 --- a/src/test/java/ParseTest.java +++ b/src/test/java/ParseTest.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.module.jakarta.xmlbind.JakartaXmlBindAnnotationModu import lombok.SneakyThrows; import net.woggioni.jpacrepo.api.model.PkgData; import net.woggioni.jpacrepo.impl.model.CompressionFormatImpl; -import net.woggioni.jpacrepo.impl.model.PkgDataImpl; +import net.woggioni.jpacrepo.impl.model.PkgDataParser; import org.junit.jupiter.api.Test; import java.lang.reflect.ParameterizedType; @@ -31,7 +31,7 @@ public class ParseTest { Files.list(Paths.get("/var/cache/pacman/pkg")) .filter(Files::isRegularFile) .filter(p -> pattern.matcher(p.getFileName().toString()).matches()) - .map(path -> PkgDataImpl.parseFile(path, CompressionFormatImpl.guess(path))) + .map(path -> PkgDataParser.parseFile(path, CompressionFormatImpl.guess(path))) .limit(10) .map(new Function() { @Override