This commit is contained in:
2023-07-28 09:15:24 +08:00
parent ab81022bce
commit b9851eecaf
8 changed files with 104 additions and 79 deletions

View File

@@ -1,3 +1,3 @@
jpacrepo.version=2023.07 jpacrepo.version=2023.07
lys.version=2023.07.20 lys.version=2023.07.28

View File

@@ -8,6 +8,7 @@ import net.woggioni.jpacrepo.api.model.PkgData;
import net.woggioni.jpacrepo.api.model.PkgId; import net.woggioni.jpacrepo.api.model.PkgId;
import net.woggioni.jpacrepo.api.wire.PkgTuple; import net.woggioni.jpacrepo.api.wire.PkgTuple;
import java.io.InputStream;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.NavigableMap; import java.util.NavigableMap;
@@ -44,4 +45,6 @@ public interface PacmanServiceRemote {
Set<String> missingFiles(Collection<String> fileNames); Set<String> missingFiles(Collection<String> fileNames);
NavigableMap<PkgId, PkgTuple> getPkgMap(); NavigableMap<PkgId, PkgTuple> getPkgMap();
boolean addPackage(String fileName, InputStream inputStream);
} }

View File

@@ -1,11 +1,5 @@
pluginManagement { pluginManagement {
repositories { repositories {
mavenLocal {
content {
includeGroup 'net.woggioni.gradle'
includeGroup 'net.woggioni.gradle.lombok'
}
}
maven { maven {
url = 'https://woggioni.net/mvn/' url = 'https://woggioni.net/mvn/'
content { content {
@@ -41,4 +35,4 @@ rootProject.name = 'jpacrepo'
include 'jpacrepo-api' include 'jpacrepo-api'
include 'jpacrepo-impl' include 'jpacrepo-impl'
include 'jpacrepo-client' include 'jpacrepo-client'
include 'jpacrepo-frontend' include 'jpacrepo-frontend'

View File

@@ -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<PkgId, PkgTuple> cachedMap;
private final AtomicBoolean invalidateCache = new AtomicBoolean(true);
public NavigableMap<PkgId, PkgTuple> getCachedMap(Supplier<NavigableMap<PkgId, PkgTuple>> mapSupplier) {
NavigableMap<PkgId, PkgTuple> 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);
}
}

View File

@@ -12,7 +12,6 @@ import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
@RequiredArgsConstructor(access = AccessLevel.PUBLIC) @RequiredArgsConstructor(access = AccessLevel.PUBLIC)
public class AppConfig { public class AppConfig {
@@ -26,9 +25,6 @@ public class AppConfig {
@Getter @Getter
private final String dataSourceJndi; private final String dataSourceJndi;
@Getter
private final AtomicBoolean invalidateCache = new AtomicBoolean(true);
public Path getFile(PkgData pkg) { public Path getFile(PkgData pkg) {
return repoFolder.resolve(pkg.getFileName()); return repoFolder.resolve(pkg.getFileName());
} }

View File

@@ -3,6 +3,7 @@ package net.woggioni.jpacrepo.factory;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces; import jakarta.enterprise.inject.Produces;
import jakarta.enterprise.inject.spi.InjectionPoint; import jakarta.enterprise.inject.spi.InjectionPoint;
import net.woggioni.jpacrepo.cache.PackageCache;
import net.woggioni.jpacrepo.config.AppConfig; import net.woggioni.jpacrepo.config.AppConfig;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -22,4 +23,9 @@ public class BeanFactory {
public Logger createLogger(InjectionPoint injectionPoint) { public Logger createLogger(InjectionPoint injectionPoint) {
return LoggerFactory.getLogger(injectionPoint.getMember().getDeclaringClass().getName()); return LoggerFactory.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
} }
@Produces
@ApplicationScoped
public PackageCache createCache() {
return new PackageCache();
}
} }

View File

@@ -11,7 +11,7 @@ import jakarta.ejb.Lock;
import jakarta.ejb.LockType; import jakarta.ejb.LockType;
import jakarta.ejb.Remote; import jakarta.ejb.Remote;
import jakarta.ejb.Schedule; import jakarta.ejb.Schedule;
import jakarta.ejb.Singleton; import jakarta.ejb.Stateless;
import jakarta.ejb.TransactionAttribute; import jakarta.ejb.TransactionAttribute;
import jakarta.ejb.TransactionAttributeType; import jakarta.ejb.TransactionAttributeType;
import jakarta.ejb.TransactionManagement; import jakarta.ejb.TransactionManagement;
@@ -19,20 +19,20 @@ import jakarta.ejb.TransactionManagementType;
import jakarta.enterprise.concurrent.ManagedExecutorService; import jakarta.enterprise.concurrent.ManagedExecutorService;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.PersistenceContext;
import jakarta.persistence.TypedQuery;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import net.woggioni.jpacrepo.api.model.CompressionFormat; import net.woggioni.jpacrepo.api.model.CompressionFormat;
import net.woggioni.jpacrepo.api.model.PkgData; import net.woggioni.jpacrepo.api.model.PkgData;
import net.woggioni.jpacrepo.api.model.PkgId; import net.woggioni.jpacrepo.api.model.PkgId;
import net.woggioni.jpacrepo.api.service.PacmanServiceLocal; import net.woggioni.jpacrepo.api.service.PacmanServiceLocal;
import net.woggioni.jpacrepo.api.service.PacmanServiceRemote; 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.config.AppConfig;
import net.woggioni.jpacrepo.impl.model.CompressionFormatImpl; import net.woggioni.jpacrepo.impl.model.CompressionFormatImpl;
import net.woggioni.jpacrepo.impl.model.PkgDataImpl; import net.woggioni.jpacrepo.impl.model.PkgDataImpl;
import net.woggioni.jpacrepo.persistence.QueryEngine; import net.woggioni.jpacrepo.persistence.QueryEngine;
import net.woggioni.jpacrepo.service.jpa.Queries; import net.woggioni.jpacrepo.service.jpa.Queries;
import net.woggioni.jpacrepo.api.wire.PkgTuple;
import net.woggioni.jpacrepo.version.PkgIdComparator; import net.woggioni.jpacrepo.version.PkgIdComparator;
import net.woggioni.jwo.CollectionUtils; import net.woggioni.jwo.CollectionUtils;
import net.woggioni.jwo.Con; import net.woggioni.jwo.Con;
@@ -41,8 +41,12 @@ import net.woggioni.jwo.Sup;
import net.woggioni.jwo.Tuple2; import net.woggioni.jwo.Tuple2;
import org.slf4j.Logger; import org.slf4j.Logger;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.Collection; import java.util.Collection;
@@ -59,15 +63,15 @@ import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@Singleton @Stateless
@Lock(LockType.READ) @Lock(LockType.READ)
@TransactionManagement(TransactionManagementType.CONTAINER) @TransactionManagement(TransactionManagementType.CONTAINER)
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER) @ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
@Local({PacmanServiceLocal.class}) @Local({PacmanServiceLocal.class})
@Remote({PacmanServiceRemote.class}) @Remote({PacmanServiceRemote.class})
public class PacmanServiceEJB implements PacmanServiceLocal { public class PacmanServiceEJB implements PacmanServiceLocal {
@Inject @PersistenceContext
private EntityManagerFactory emf; private EntityManager em;
@Inject @Inject
private AppConfig ctx; private AppConfig ctx;
@@ -78,6 +82,9 @@ public class PacmanServiceEJB implements PacmanServiceLocal {
@Resource(name = "DefaultManagedExecutorService") @Resource(name = "DefaultManagedExecutorService")
private ManagedExecutorService executor; private ManagedExecutorService executor;
@Inject
private PackageCache packageCache;
private void deletePkgData(EntityManager em, PkgData pkgData) { private void deletePkgData(EntityManager em, PkgData pkgData) {
em.remove(pkgData); em.remove(pkgData);
} }
@@ -88,7 +95,6 @@ public class PacmanServiceEJB implements PacmanServiceLocal {
@TransactionAttribute(TransactionAttributeType.REQUIRED) @TransactionAttribute(TransactionAttributeType.REQUIRED)
@Schedule(hour = "4", minute = "00", persistent = false) @Schedule(hour = "4", minute = "00", persistent = false)
public void syncDB() { public void syncDB() {
EntityManager em = emf.createEntityManager();
try { try {
logger.info("Starting repository cleanup"); logger.info("Starting repository cleanup");
//Removes from DB the packages that have been deleted from filesystem //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"); logger.info("Removing obsolete packages");
deleteOld(em); deleteOld(em);
logger.info("Repository cleanup completed successfully"); logger.info("Repository cleanup completed successfully");
ctx.getInvalidateCache().set(true); packageCache.invalidateCache();
} finally { } finally {
em.close(); em.close();
} }
@@ -173,7 +179,6 @@ public class PacmanServiceEJB implements PacmanServiceLocal {
@Override @Override
@TransactionAttribute(TransactionAttributeType.REQUIRED) @TransactionAttribute(TransactionAttributeType.REQUIRED)
public void deletePackage(String filename) { public void deletePackage(String filename) {
EntityManager em = emf.createEntityManager();
deletePackage(em, filename); deletePackage(em, filename);
logger.info("Package {} has been deleted", filename); logger.info("Package {} has been deleted", filename);
} }
@@ -198,7 +203,6 @@ public class PacmanServiceEJB implements PacmanServiceLocal {
@Override @Override
@TransactionAttribute(TransactionAttributeType.SUPPORTS) @TransactionAttribute(TransactionAttributeType.SUPPORTS)
public long countResults(String name, String version, String arch) { public long countResults(String name, String version, String arch) {
EntityManager em = emf.createEntityManager();
return QueryEngine.countResults(em, name, version, arch); return QueryEngine.countResults(em, name, version, arch);
} }
@@ -211,7 +215,6 @@ public class PacmanServiceEJB implements PacmanServiceLocal {
String fileName, String fileName,
int pageNumber, int pageNumber,
int pageSize) { int pageSize) {
EntityManager em = emf.createEntityManager();
return Queries.searchPackage(em, name, version, arch, compressionFormat, fileName) return Queries.searchPackage(em, name, version, arch, compressionFormat, fileName)
.setMaxResults(pageSize) .setMaxResults(pageSize)
.setFirstResult(pageNumber * pageSize) .setFirstResult(pageNumber * pageSize)
@@ -220,31 +223,26 @@ public class PacmanServiceEJB implements PacmanServiceLocal {
@Override @Override
public List<String> searchName(@Nonnull String name) { public List<String> searchName(@Nonnull String name) {
EntityManager em = emf.createEntityManager();
return Queries.searchPackageName(em, name).getResultList(); return Queries.searchPackageName(em, name).getResultList();
} }
@Override @Override
public List<PkgData> searchByFileName(@Nonnull String fileName) { public List<PkgData> searchByFileName(@Nonnull String fileName) {
EntityManager em = emf.createEntityManager();
return Queries.searchPackagesByFileName(em, fileName).getResultList(); return Queries.searchPackagesByFileName(em, fileName).getResultList();
} }
@Override @Override
public List<PkgData> searchByHash(@Nonnull String hash) { public List<PkgData> searchByHash(@Nonnull String hash) {
EntityManager em = emf.createEntityManager();
return Queries.searchPackagesByHash(em, hash).getResultList(); return Queries.searchPackagesByHash(em, hash).getResultList();
} }
@Override @Override
public List<String> listFiles() { public List<String> listFiles() {
EntityManager em = emf.createEntityManager();
return Queries.listFiles(em).getResultList(); return Queries.listFiles(em).getResultList();
} }
@Override @Override
public List<String> listHashes() { public List<String> listHashes() {
EntityManager em = emf.createEntityManager();
return Queries.listHashes(em).getResultList(); return Queries.listHashes(em).getResultList();
} }
@@ -252,7 +250,6 @@ public class PacmanServiceEJB implements PacmanServiceLocal {
@TransactionAttribute(TransactionAttributeType.SUPPORTS) @TransactionAttribute(TransactionAttributeType.SUPPORTS)
public List<PkgId> searchPkgId( public List<PkgId> searchPkgId(
String name, String version, String arch, CompressionFormat compressionFormat) { String name, String version, String arch, CompressionFormat compressionFormat) {
EntityManager em = emf.createEntityManager();
return Queries.searchPackages(em, name, version, arch, compressionFormat).getResultList(); return Queries.searchPackages(em, name, version, arch, compressionFormat).getResultList();
} }
@@ -266,7 +263,6 @@ public class PacmanServiceEJB implements PacmanServiceLocal {
} }
public Set<String> missingFiles(Collection<String> fileNames) { public Set<String> missingFiles(Collection<String> fileNames) {
EntityManager em = emf.createEntityManager();
Stream<String> result = fileNames.stream(); Stream<String> result = fileNames.stream();
Set<String> existing = Queries.getExistingFiles(em, fileNames) Set<String> existing = Queries.getExistingFiles(em, fileNames)
.getResultStream().collect(CollectionUtils.toUnmodifiableTreeSet()); .getResultStream().collect(CollectionUtils.toUnmodifiableTreeSet());
@@ -274,8 +270,12 @@ public class PacmanServiceEJB implements PacmanServiceLocal {
.collect(CollectionUtils.toUnmodifiableTreeSet()); .collect(CollectionUtils.toUnmodifiableTreeSet());
} }
@Override
public NavigableMap<PkgId, PkgTuple> getPkgMap() { public NavigableMap<PkgId, PkgTuple> getPkgMap() {
EntityManager em = emf.createEntityManager(); return packageCache.getCachedMap(this::computePackageMap);
}
private NavigableMap<PkgId, PkgTuple> computePackageMap() {
return Queries.getPkgMap(em).getResultStream().map(tuple -> { return Queries.getPkgMap(em).getResultStream().map(tuple -> {
PkgId pkgId = tuple.get(0, PkgId.class); PkgId pkgId = tuple.get(0, PkgId.class);
String filename = tuple.get(1, String.class); String filename = tuple.get(1, String.class);
@@ -298,7 +298,33 @@ public class PacmanServiceEJB implements PacmanServiceLocal {
@Nullable @Nullable
@Override @Override
public PkgData getPackage(PkgId pkgId) { public PkgData getPackage(PkgId pkgId) {
EntityManager em = emf.createEntityManager();
return em.find(PkgData.class, pkgId); 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<PkgData> 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>) (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;
}
}
}
} }

View File

@@ -78,9 +78,6 @@ import java.util.stream.Collectors;
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@TransactionManagement(TransactionManagementType.CONTAINER) @TransactionManagement(TransactionManagementType.CONTAINER)
public class PacmanWebService { public class PacmanWebService {
private NavigableMap<PkgId, PkgTuple> cachedMap;
@Inject @Inject
private EntityManagerFactory emf; private EntityManagerFactory emf;
@@ -93,24 +90,6 @@ public class PacmanWebService {
@Inject @Inject
private AppConfig ctx; private AppConfig ctx;
private NavigableMap<PkgId, PkgTuple> getCachedMap() {
NavigableMap<PkgId, PkgTuple> 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<PkgData> list) { private Response manageQueryResult(List<PkgData> list) {
return manageQueryResult(list, false); return manageQueryResult(list, false);
} }
@@ -163,6 +142,7 @@ public class PacmanWebService {
EntityTag etag = new EntityTag(Integer.toString(cachedMap.hashCode()), false); EntityTag etag = new EntityTag(Integer.toString(cachedMap.hashCode()), false);
Response.ResponseBuilder builder = request.evaluatePreconditions(etag); Response.ResponseBuilder builder = request.evaluatePreconditions(etag);
if (builder == null) { if (builder == null) {
TreeMap<String, TreeMap<String, Map<String, NavigableSet<PkgTuple>>>> result = cachedMap.entrySet().stream().collect( TreeMap<String, TreeMap<String, Map<String, NavigableSet<PkgTuple>>>> result = cachedMap.entrySet().stream().collect(
Collectors.groupingBy((Map.Entry<PkgId, PkgTuple> entry) -> entry.getKey().getArch(), Collectors.groupingBy((Map.Entry<PkgId, PkgTuple> entry) -> entry.getKey().getArch(),
TreeMap::new, TreeMap::new,
@@ -221,7 +201,8 @@ public class PacmanWebService {
cc.setMaxAge(86400); cc.setMaxAge(86400);
cc.setMustRevalidate(true); cc.setMustRevalidate(true);
cc.setNoCache(true); cc.setNoCache(true);
EntityTag etag = new EntityTag(Integer.toString(getCachedMap().hashCode())); NavigableMap<PkgId, PkgTuple> cachedMap = service.getPkgMap();
EntityTag etag = new EntityTag(Integer.toString(cachedMap.hashCode()));
Response.ResponseBuilder builder = request.evaluatePreconditions(etag); Response.ResponseBuilder builder = request.evaluatePreconditions(etag);
if (builder == null) { if (builder == null) {
Long size = service.getFileSize(fileName); Long size = service.getFileSize(fileName);
@@ -277,31 +258,13 @@ public class PacmanWebService {
@Context UriInfo uriInfo) { @Context UriInfo uriInfo) {
EntityManager em = emf.createEntityManager(); EntityManager em = emf.createEntityManager();
if (filename == null) throw new BadRequestException(); if (filename == null) throw new BadRequestException();
java.nio.file.Path file = Files.createTempFile(ctx.getRepoFolder(), filename, null);
List<PkgData> savedFiles = service.searchByFileName(filename);
Response result; Response result;
if (savedFiles.size() > 0) result = Response.notModified().build(); boolean added = service.addPackage(filename, input);
else { if(!added) {
try(OutputStream output = Files.newOutputStream(file)) { result = Response.notModified().build();
JWO.copy(input, output, 0x10000); } else {
PkgData pkg = PkgDataImpl.parseFile(file, URI pkgUri = uriInfo.getAbsolutePathBuilder().path(filename).build();
CompressionFormatImpl.guess(Paths.get(filename))); result = Response.created(pkgUri).build();
pkg.setFileName(filename);
Optional.ofNullable(em.find(PkgData.class, pkg.getId())).ifPresent((Con<PkgData>) (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;
}
} }
return result; return result;
} }