From b9851eecaf742981b44837477f549d72cdaec288 Mon Sep 17 00:00:00 2001 From: Walter Oggioni Date: Fri, 28 Jul 2023 09:15:24 +0800 Subject: [PATCH] bugfix --- gradle.properties | 2 +- .../api/service/PacmanServiceRemote.java | 3 + settings.gradle | 8 +-- .../woggioni/jpacrepo/cache/PackageCache.java | 37 ++++++++++ .../woggioni/jpacrepo/config/AppConfig.java | 4 -- .../jpacrepo/factory/BeanFactory.java | 6 ++ .../jpacrepo/service/PacmanServiceEJB.java | 68 +++++++++++++------ .../jpacrepo/service/PacmanWebService.java | 55 +++------------ 8 files changed, 104 insertions(+), 79 deletions(-) create mode 100644 src/main/java/net/woggioni/jpacrepo/cache/PackageCache.java diff --git a/gradle.properties b/gradle.properties index 64ee63e..a65f733 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ jpacrepo.version=2023.07 -lys.version=2023.07.20 +lys.version=2023.07.28 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 91aec28..6244aa7 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 @@ -8,6 +8,7 @@ import net.woggioni.jpacrepo.api.model.PkgData; import net.woggioni.jpacrepo.api.model.PkgId; import net.woggioni.jpacrepo.api.wire.PkgTuple; +import java.io.InputStream; import java.util.Collection; import java.util.List; import java.util.NavigableMap; @@ -44,4 +45,6 @@ public interface PacmanServiceRemote { Set missingFiles(Collection fileNames); NavigableMap getPkgMap(); + + boolean addPackage(String fileName, InputStream inputStream); } \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 9f24758..751ce4a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,11 +1,5 @@ pluginManagement { repositories { - mavenLocal { - content { - includeGroup 'net.woggioni.gradle' - includeGroup 'net.woggioni.gradle.lombok' - } - } maven { url = 'https://woggioni.net/mvn/' content { @@ -41,4 +35,4 @@ rootProject.name = 'jpacrepo' include 'jpacrepo-api' include 'jpacrepo-impl' include 'jpacrepo-client' -include 'jpacrepo-frontend' \ No newline at end of file +include 'jpacrepo-frontend' diff --git a/src/main/java/net/woggioni/jpacrepo/cache/PackageCache.java b/src/main/java/net/woggioni/jpacrepo/cache/PackageCache.java new file mode 100644 index 0000000..46542cd --- /dev/null +++ b/src/main/java/net/woggioni/jpacrepo/cache/PackageCache.java @@ -0,0 +1,37 @@ +package net.woggioni.jpacrepo.cache; + +import net.woggioni.jpacrepo.api.model.PkgId; +import net.woggioni.jpacrepo.api.wire.PkgTuple; + +import java.util.NavigableMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; + +public class PackageCache { + private NavigableMap cachedMap; + private final AtomicBoolean invalidateCache = new AtomicBoolean(true); + + public NavigableMap getCachedMap(Supplier> mapSupplier) { + NavigableMap result = null; + if (!invalidateCache.get()) { + result = cachedMap; + } + if (result == null) { + synchronized(this) { + if (!invalidateCache.get()) { + result = cachedMap; + } + if (result == null) { + cachedMap = mapSupplier.get(); + invalidateCache.set(false); + result = cachedMap; + } + } + } + return result; + } + + public void invalidateCache() { + invalidateCache.set(true); + } +} diff --git a/src/main/java/net/woggioni/jpacrepo/config/AppConfig.java b/src/main/java/net/woggioni/jpacrepo/config/AppConfig.java index dd9e0f4..2815de4 100644 --- a/src/main/java/net/woggioni/jpacrepo/config/AppConfig.java +++ b/src/main/java/net/woggioni/jpacrepo/config/AppConfig.java @@ -12,7 +12,6 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.Properties; -import java.util.concurrent.atomic.AtomicBoolean; @RequiredArgsConstructor(access = AccessLevel.PUBLIC) public class AppConfig { @@ -26,9 +25,6 @@ public class AppConfig { @Getter private final String dataSourceJndi; - @Getter - private final AtomicBoolean invalidateCache = new AtomicBoolean(true); - public Path getFile(PkgData pkg) { return repoFolder.resolve(pkg.getFileName()); } diff --git a/src/main/java/net/woggioni/jpacrepo/factory/BeanFactory.java b/src/main/java/net/woggioni/jpacrepo/factory/BeanFactory.java index 1fe276e..e750169 100644 --- a/src/main/java/net/woggioni/jpacrepo/factory/BeanFactory.java +++ b/src/main/java/net/woggioni/jpacrepo/factory/BeanFactory.java @@ -3,6 +3,7 @@ package net.woggioni.jpacrepo.factory; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Produces; import jakarta.enterprise.inject.spi.InjectionPoint; +import net.woggioni.jpacrepo.cache.PackageCache; import net.woggioni.jpacrepo.config.AppConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,4 +23,9 @@ public class BeanFactory { public Logger createLogger(InjectionPoint injectionPoint) { return LoggerFactory.getLogger(injectionPoint.getMember().getDeclaringClass().getName()); } + @Produces + @ApplicationScoped + public PackageCache createCache() { + return new PackageCache(); + } } \ No newline at end of file diff --git a/src/main/java/net/woggioni/jpacrepo/service/PacmanServiceEJB.java b/src/main/java/net/woggioni/jpacrepo/service/PacmanServiceEJB.java index a43e859..0f47339 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.Singleton; +import jakarta.ejb.Stateless; import jakarta.ejb.TransactionAttribute; import jakarta.ejb.TransactionAttributeType; import jakarta.ejb.TransactionManagement; @@ -19,20 +19,20 @@ import jakarta.ejb.TransactionManagementType; import jakarta.enterprise.concurrent.ManagedExecutorService; import jakarta.inject.Inject; import jakarta.persistence.EntityManager; -import jakarta.persistence.EntityManagerFactory; -import jakarta.persistence.TypedQuery; +import jakarta.persistence.PersistenceContext; import lombok.SneakyThrows; import net.woggioni.jpacrepo.api.model.CompressionFormat; import net.woggioni.jpacrepo.api.model.PkgData; import net.woggioni.jpacrepo.api.model.PkgId; import net.woggioni.jpacrepo.api.service.PacmanServiceLocal; import net.woggioni.jpacrepo.api.service.PacmanServiceRemote; +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.persistence.QueryEngine; import net.woggioni.jpacrepo.service.jpa.Queries; -import net.woggioni.jpacrepo.api.wire.PkgTuple; import net.woggioni.jpacrepo.version.PkgIdComparator; import net.woggioni.jwo.CollectionUtils; import net.woggioni.jwo.Con; @@ -41,8 +41,12 @@ import net.woggioni.jwo.Sup; import net.woggioni.jwo.Tuple2; import org.slf4j.Logger; +import java.io.InputStream; +import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Collection; @@ -59,15 +63,15 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; -@Singleton +@Stateless @Lock(LockType.READ) @TransactionManagement(TransactionManagementType.CONTAINER) @ConcurrencyManagement(ConcurrencyManagementType.CONTAINER) @Local({PacmanServiceLocal.class}) @Remote({PacmanServiceRemote.class}) public class PacmanServiceEJB implements PacmanServiceLocal { - @Inject - private EntityManagerFactory emf; + @PersistenceContext + private EntityManager em; @Inject private AppConfig ctx; @@ -78,6 +82,9 @@ public class PacmanServiceEJB implements PacmanServiceLocal { @Resource(name = "DefaultManagedExecutorService") private ManagedExecutorService executor; + @Inject + private PackageCache packageCache; + private void deletePkgData(EntityManager em, PkgData pkgData) { em.remove(pkgData); } @@ -88,7 +95,6 @@ public class PacmanServiceEJB implements PacmanServiceLocal { @TransactionAttribute(TransactionAttributeType.REQUIRED) @Schedule(hour = "4", minute = "00", persistent = false) public void syncDB() { - EntityManager em = emf.createEntityManager(); try { logger.info("Starting repository cleanup"); //Removes from DB the packages that have been deleted from filesystem @@ -153,7 +159,7 @@ public class PacmanServiceEJB implements PacmanServiceLocal { logger.info("Removing obsolete packages"); deleteOld(em); logger.info("Repository cleanup completed successfully"); - ctx.getInvalidateCache().set(true); + packageCache.invalidateCache(); } finally { em.close(); } @@ -173,7 +179,6 @@ public class PacmanServiceEJB implements PacmanServiceLocal { @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void deletePackage(String filename) { - EntityManager em = emf.createEntityManager(); deletePackage(em, filename); logger.info("Package {} has been deleted", filename); } @@ -198,7 +203,6 @@ public class PacmanServiceEJB implements PacmanServiceLocal { @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public long countResults(String name, String version, String arch) { - EntityManager em = emf.createEntityManager(); return QueryEngine.countResults(em, name, version, arch); } @@ -211,7 +215,6 @@ public class PacmanServiceEJB implements PacmanServiceLocal { String fileName, int pageNumber, int pageSize) { - EntityManager em = emf.createEntityManager(); return Queries.searchPackage(em, name, version, arch, compressionFormat, fileName) .setMaxResults(pageSize) .setFirstResult(pageNumber * pageSize) @@ -220,31 +223,26 @@ public class PacmanServiceEJB implements PacmanServiceLocal { @Override public List searchName(@Nonnull String name) { - EntityManager em = emf.createEntityManager(); return Queries.searchPackageName(em, name).getResultList(); } @Override public List searchByFileName(@Nonnull String fileName) { - EntityManager em = emf.createEntityManager(); return Queries.searchPackagesByFileName(em, fileName).getResultList(); } @Override public List searchByHash(@Nonnull String hash) { - EntityManager em = emf.createEntityManager(); return Queries.searchPackagesByHash(em, hash).getResultList(); } @Override public List listFiles() { - EntityManager em = emf.createEntityManager(); return Queries.listFiles(em).getResultList(); } @Override public List listHashes() { - EntityManager em = emf.createEntityManager(); return Queries.listHashes(em).getResultList(); } @@ -252,7 +250,6 @@ public class PacmanServiceEJB implements PacmanServiceLocal { @TransactionAttribute(TransactionAttributeType.SUPPORTS) public List searchPkgId( String name, String version, String arch, CompressionFormat compressionFormat) { - EntityManager em = emf.createEntityManager(); return Queries.searchPackages(em, name, version, arch, compressionFormat).getResultList(); } @@ -266,7 +263,6 @@ public class PacmanServiceEJB implements PacmanServiceLocal { } public Set missingFiles(Collection fileNames) { - EntityManager em = emf.createEntityManager(); Stream result = fileNames.stream(); Set existing = Queries.getExistingFiles(em, fileNames) .getResultStream().collect(CollectionUtils.toUnmodifiableTreeSet()); @@ -274,8 +270,12 @@ public class PacmanServiceEJB implements PacmanServiceLocal { .collect(CollectionUtils.toUnmodifiableTreeSet()); } + @Override public NavigableMap getPkgMap() { - EntityManager em = emf.createEntityManager(); + return packageCache.getCachedMap(this::computePackageMap); + } + + private NavigableMap computePackageMap() { return Queries.getPkgMap(em).getResultStream().map(tuple -> { PkgId pkgId = tuple.get(0, PkgId.class); String filename = tuple.get(1, String.class); @@ -298,7 +298,33 @@ public class PacmanServiceEJB implements PacmanServiceLocal { @Nullable @Override public PkgData getPackage(PkgId pkgId) { - EntityManager em = emf.createEntityManager(); return em.find(PkgData.class, pkgId); } + + @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; + else { + try(OutputStream output = Files.newOutputStream(file)) { + JWO.copy(input, output, 0x10000); + PkgData pkg = PkgDataImpl.parseFile(file, + CompressionFormatImpl.guess(Paths.get(fileName))); + pkg.setFileName(fileName); + Optional.ofNullable(em.find(PkgData.class, pkg.getId())).ifPresent((Con) (pkgData -> { + em.remove(pkgData); + Files.delete(ctx.getRepoFolder().resolve(pkgData.getFileName())); + })); + logger.info("Persisting package {}", pkg.getFileName()); + em.persist(pkg); + Files.move(file, ctx.getRepoFolder().resolve(fileName), StandardCopyOption.ATOMIC_MOVE); + packageCache.invalidateCache(); + return true; + } catch (Throwable t) { + Files.delete(file); + throw t; + } + } + } } \ No newline at end of file diff --git a/src/main/java/net/woggioni/jpacrepo/service/PacmanWebService.java b/src/main/java/net/woggioni/jpacrepo/service/PacmanWebService.java index e680bc9..e01579b 100644 --- a/src/main/java/net/woggioni/jpacrepo/service/PacmanWebService.java +++ b/src/main/java/net/woggioni/jpacrepo/service/PacmanWebService.java @@ -78,9 +78,6 @@ import java.util.stream.Collectors; @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) @TransactionManagement(TransactionManagementType.CONTAINER) public class PacmanWebService { - - private NavigableMap cachedMap; - @Inject private EntityManagerFactory emf; @@ -93,24 +90,6 @@ public class PacmanWebService { @Inject private AppConfig ctx; - private NavigableMap getCachedMap() { - NavigableMap result = null; - if (!ctx.getInvalidateCache().get()) { - result = cachedMap; - } - if (result == null) { - synchronized(this) { - result = cachedMap; - if (result == null) { - cachedMap = service.getPkgMap(); - ctx.getInvalidateCache().set(false); - result = cachedMap; - } - } - } - return result; - } - private Response manageQueryResult(List list) { return manageQueryResult(list, false); } @@ -163,6 +142,7 @@ public class PacmanWebService { EntityTag etag = new EntityTag(Integer.toString(cachedMap.hashCode()), false); Response.ResponseBuilder builder = request.evaluatePreconditions(etag); if (builder == null) { + TreeMap>>> result = cachedMap.entrySet().stream().collect( Collectors.groupingBy((Map.Entry entry) -> entry.getKey().getArch(), TreeMap::new, @@ -221,7 +201,8 @@ public class PacmanWebService { cc.setMaxAge(86400); cc.setMustRevalidate(true); cc.setNoCache(true); - EntityTag etag = new EntityTag(Integer.toString(getCachedMap().hashCode())); + NavigableMap cachedMap = service.getPkgMap(); + EntityTag etag = new EntityTag(Integer.toString(cachedMap.hashCode())); Response.ResponseBuilder builder = request.evaluatePreconditions(etag); if (builder == null) { Long size = service.getFileSize(fileName); @@ -277,31 +258,13 @@ public class PacmanWebService { @Context UriInfo uriInfo) { EntityManager em = emf.createEntityManager(); if (filename == null) throw new BadRequestException(); - java.nio.file.Path file = Files.createTempFile(ctx.getRepoFolder(), filename, null); - List savedFiles = service.searchByFileName(filename); Response result; - if (savedFiles.size() > 0) result = Response.notModified().build(); - else { - try(OutputStream output = Files.newOutputStream(file)) { - JWO.copy(input, output, 0x10000); - PkgData pkg = PkgDataImpl.parseFile(file, - CompressionFormatImpl.guess(Paths.get(filename))); - pkg.setFileName(filename); - Optional.ofNullable(em.find(PkgData.class, pkg.getId())).ifPresent((Con) (pkgData -> { - em.remove(pkgData); - Files.delete(ctx.getRepoFolder().resolve(pkgData.getFileName())); - })); - log.info("Persisting package {}", pkg.getFileName()); - em.persist(pkg); - URI pkgUri = uriInfo.getAbsolutePathBuilder().path(pkg.getFileName()).build(); - Files.move(file, ctx.getRepoFolder().resolve(filename), StandardCopyOption.ATOMIC_MOVE); - ctx.getInvalidateCache().set(true); - cachedMap = null; - result = Response.created(pkgUri).build(); - } catch (Throwable t) { - Files.delete(file); - throw t; - } + boolean added = service.addPackage(filename, input); + if(!added) { + result = Response.notModified().build(); + } else { + URI pkgUri = uriInfo.getAbsolutePathBuilder().path(filename).build(); + result = Response.created(pkgUri).build(); } return result; }