java rewrite
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
build
|
||||||
|
target
|
||||||
|
.idea
|
||||||
|
.gradle
|
||||||
|
|
117
build.gradle
Normal file
117
build.gradle
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
plugins {
|
||||||
|
id 'net.woggioni.gradle.lombok' apply false
|
||||||
|
id 'net.woggioni.gradle.wildfly'
|
||||||
|
id 'war'
|
||||||
|
}
|
||||||
|
|
||||||
|
import net.woggioni.gradle.wildfly.Deploy2WildflyTask
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
apply plugin: 'net.woggioni.gradle.lombok'
|
||||||
|
|
||||||
|
group = 'net.woggioni'
|
||||||
|
version = getProperty('jpacrepo.version')
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url = 'https://woggioni.net/mvn/'
|
||||||
|
content {
|
||||||
|
includeGroup 'net.woggioni'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
lombok {
|
||||||
|
version = getProperty('lombok.version')
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile) {
|
||||||
|
options.release = 11
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(Test) {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(':jpacrepo-impl')
|
||||||
|
implementation group: 'net.woggioni', name: 'jwo', version: getProperty('jwo.version')
|
||||||
|
implementation group: 'org.slf4j', name: 'slf4j-api', version: getProperty('slf4j.version')
|
||||||
|
|
||||||
|
compileOnly group: 'org.hibernate',
|
||||||
|
name: 'hibernate-jpamodelgen',
|
||||||
|
version: getProperty('hibernate.version')
|
||||||
|
// compileOnly group: 'jakarta.persistence',
|
||||||
|
// name: 'jakarta.persistence-api', version: getProperty('jakarta.persistence.version')
|
||||||
|
// compileOnly group: 'jakarta.annotation',
|
||||||
|
// name: 'jakarta.annotation-api',
|
||||||
|
// version: getProperty('jakarta.annotation.version')
|
||||||
|
// compileOnly group: 'jakarta.platform',
|
||||||
|
// name: 'jakarta.jakartaee-web-api',
|
||||||
|
// version: getProperty('jakarta.ee.version')
|
||||||
|
compileOnly group: 'jakarta.platform',
|
||||||
|
name: 'jakarta.jakartaee-api',
|
||||||
|
version: getProperty('jakarta.ee.version')
|
||||||
|
implementation group: 'org.apache.commons',
|
||||||
|
name: 'commons-compress',
|
||||||
|
version: getProperty('apache.commons.compress.version')
|
||||||
|
implementation group: 'net.java.dev.jna', name: 'jna', version: getProperty('jna.version')
|
||||||
|
|
||||||
|
// compileOnly group: 'jakarta.inject',
|
||||||
|
// name: 'jakarta.inject-api', version: getProperty('jakarta.inject.version')
|
||||||
|
// compileOnly group: 'jakarta.enterprise', name: 'jakarta.enterprise.cdi-api', version: getProperty('jakarta.cdi.version')
|
||||||
|
|
||||||
|
testImplementation group: 'jakarta.platform',
|
||||||
|
name: 'jakarta.jakartaee-api',
|
||||||
|
version: getProperty('jakarta.ee.version')
|
||||||
|
|
||||||
|
// testImplementation group: 'jakarta.platform',
|
||||||
|
// name: 'jakarta.jakartaee-web-api',
|
||||||
|
// version: getProperty('jakarta.ee.version')
|
||||||
|
testImplementation group: 'org.jboss',
|
||||||
|
name: 'jboss-ejb-client', version: getProperty('jboss.ejb.client.version')
|
||||||
|
testImplementation group: 'org.jboss.weld.se',
|
||||||
|
name: 'weld-se-core', version: getProperty('weld.version')
|
||||||
|
testImplementation group: 'com.h2database',
|
||||||
|
name: 'h2', version: getProperty('h2.version')
|
||||||
|
testImplementation group: 'org.hibernate',
|
||||||
|
name: 'hibernate-core',
|
||||||
|
version: getProperty('hibernate.version')
|
||||||
|
testImplementation group: 'org.jboss.resteasy',
|
||||||
|
name: 'resteasy-client', version: getProperty('resteasy.version')
|
||||||
|
testImplementation group: 'org.jboss.resteasy',
|
||||||
|
name: 'resteasy-jackson2-provider', version: getProperty('resteasy.version')
|
||||||
|
testRuntimeOnly group: 'org.slf4j',
|
||||||
|
name: 'slf4j-simple', version: getProperty('slf4j.version')
|
||||||
|
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: getProperty('junit.jupiter.version')
|
||||||
|
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: getProperty('junit.jupiter.version')
|
||||||
|
testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: getProperty('junit.jupiter.version')
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper {
|
||||||
|
distributionType = Wrapper.DistributionType.BIN
|
||||||
|
gradleVersion = getProperty('gradle.version')
|
||||||
|
}
|
||||||
|
|
||||||
|
File nimDir = project.file('nim')
|
||||||
|
File srcDir = new File(nimDir, 'src')
|
||||||
|
File staticDir = new File(nimDir, 'static')
|
||||||
|
|
||||||
|
Provider<Exec> nimCompileTaskProvider = tasks.register("compileNim", Exec) {
|
||||||
|
inputs.files(project.fileTree(srcDir), project.fileTree(staticDir))
|
||||||
|
File outputFile = new File(temporaryDir, "jpacrepo.js")
|
||||||
|
outputs.file(outputFile)
|
||||||
|
commandLine 'nim', 'js', "-o:$outputFile", 'src/jpacrepo.nim'
|
||||||
|
workingDir(nimDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
Provider<War> warTaskProvider = tasks.named('war', War) {
|
||||||
|
from staticDir
|
||||||
|
from nimCompileTaskProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('deploy2Wildfly', Deploy2WildflyTask) {
|
||||||
|
}
|
73
build.sbt
73
build.sbt
@@ -1,73 +0,0 @@
|
|||||||
import net.woggioni.sbt.{NimPlugin, WildflyPlugin}
|
|
||||||
import net.woggioni.sbt.ConfigurationFile._
|
|
||||||
|
|
||||||
name := "jpacrepo"
|
|
||||||
|
|
||||||
organization := "net.woggioni"
|
|
||||||
|
|
||||||
version := "2.0"
|
|
||||||
|
|
||||||
resolvers += Resolver.mavenLocal
|
|
||||||
|
|
||||||
scalaVersion := "2.13.2"
|
|
||||||
|
|
||||||
libraryDependencies += "org.tukaani" % "xz" % Versions.xz
|
|
||||||
libraryDependencies += "org.slf4j" % "slf4j-api" % Versions.slf4j
|
|
||||||
libraryDependencies += "net.woggioni" % "jzstd" % Versions.jzstd
|
|
||||||
libraryDependencies += "net.woggioni" % "jwo" % Versions.jwo
|
|
||||||
libraryDependencies += "org.apache.commons" % "commons-compress" % Versions.`common-compress`
|
|
||||||
|
|
||||||
libraryDependencies += "org.hibernate" % "hibernate-jpamodelgen" % Versions.hibernate % Provided
|
|
||||||
libraryDependencies += "org.projectlombok" % "lombok" % Versions.lombok % Provided
|
|
||||||
libraryDependencies += "javax" % "javaee-api" % Versions.javaee % Provided
|
|
||||||
|
|
||||||
libraryDependencies += "org.jboss" % "jboss-ejb-client" % Versions.jbossEjbClient % Test
|
|
||||||
libraryDependencies += "org.apache.logging.log4j" % "log4j-slf4j-impl" % Versions.log4j % Test
|
|
||||||
|
|
||||||
libraryDependencies += "org.jboss.weld.se" % "weld-se-core" % Versions.weld % Test
|
|
||||||
libraryDependencies += "com.h2database" % "h2" % Versions.h2 % Test
|
|
||||||
libraryDependencies += "org.hibernate" % "hibernate-core" % Versions.hibernate % Test
|
|
||||||
|
|
||||||
libraryDependencies += "org.jboss.resteasy" % "resteasy-client" % Versions.restEasy % Test
|
|
||||||
libraryDependencies += "org.jboss.resteasy" % "resteasy-jackson2-provider" % Versions.restEasy % Test
|
|
||||||
libraryDependencies += "org.scalatest" %% "scalatest" % Versions.scalatest % Test
|
|
||||||
|
|
||||||
enablePlugins(WarPlugin)
|
|
||||||
enablePlugins(WildflyPlugin)
|
|
||||||
enablePlugins(NimPlugin)
|
|
||||||
|
|
||||||
nimCompilerParameters in CompileNim := Seq("-d:release", "-d:serverURL=")
|
|
||||||
sources in CompileNim := Seq(baseDirectory.value / "nim" / "src" / "jpacrepo.nim")
|
|
||||||
|
|
||||||
webappWebInfClasses := true
|
|
||||||
|
|
||||||
|
|
||||||
Test / run / javaOptions += "-Dnet.woggioni.jpacrepo.configuration.file=conf/server.properties"
|
|
||||||
Test / run / fork := true
|
|
||||||
|
|
||||||
webappPostProcess := {
|
|
||||||
val baseDir = baseDirectory.value / "nim" / "static"
|
|
||||||
val nimOutput = (compileNim in CompileNim).value
|
|
||||||
println(nimOutput)
|
|
||||||
webappDir: File => {
|
|
||||||
IO.copyFile(baseDir / "index.html", webappDir / "index.html")
|
|
||||||
IO.copyFile(baseDir / "jpacrepo.css", webappDir / "jpacrepo.css")
|
|
||||||
nimOutput.foreach(file => IO.copyFile(file, webappDir / file.getName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
lazy val datasourceJNDI = SettingKey[String]("datasource-jndi", "JNDI name of the application datasource")
|
|
||||||
lazy val databaseAction = SettingKey[String]("database-action",
|
|
||||||
"value of the property \"javax.persistence.schema-generation.database.action\" in the persistence unit")
|
|
||||||
|
|
||||||
datasourceJNDI := "java:/PostgresDS"
|
|
||||||
databaseAction := "none"
|
|
||||||
|
|
||||||
resourceGenerators in Compile += Def.task({
|
|
||||||
Seq(IO.configureFile(
|
|
||||||
(sourceDirectory in Compile).value / "resources-template" / "persistence.xml",
|
|
||||||
(resourceManaged in Compile).value / "META-INF" / "persistence.xml",
|
|
||||||
Map("dataSourceJNDI" -> datasourceJNDI.value,
|
|
||||||
"dataBaseAction" -> databaseAction.value)))
|
|
||||||
}).taskValue
|
|
29
gradle.properties
Normal file
29
gradle.properties
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
gradle.version=7.4.2
|
||||||
|
|
||||||
|
net.woggioni.gradle.lombok.version=0.1
|
||||||
|
net.woggioni.gradle.wildfly.version=0.1
|
||||||
|
net.woggioni.gradle.envelope.version=1.0-SNAPSHOT
|
||||||
|
|
||||||
|
jpacrepo.version=2.0-SNAPSHOT
|
||||||
|
|
||||||
|
lombok.version=1.18.22
|
||||||
|
slf4j.version=1.7.32
|
||||||
|
xz.version=1.9
|
||||||
|
apache.commons.compress.version=1.21
|
||||||
|
hibernate.version=5.6.5.Final
|
||||||
|
jzstd.version=0.1-SNAPSHOT
|
||||||
|
jwo.version=1.0-SNAPSHOT
|
||||||
|
jakarta.persistence.version=3.0.0
|
||||||
|
jakarta.inject.version=2.0.0
|
||||||
|
jakarta.cdi.version=3.0.1
|
||||||
|
jakarta.annotation.version=2.0.0
|
||||||
|
jakarta.ee.version=8.0.0
|
||||||
|
jaxb.api.version=2.3.1
|
||||||
|
|
||||||
|
jboss.ejb.client.version=4.0.43.Final
|
||||||
|
log4j.version=2.17.2
|
||||||
|
weld.version=4.0.3.Final
|
||||||
|
h2.version=2.1.210
|
||||||
|
resteasy.version=6.0.0.Final
|
||||||
|
jna.version=5.10.0
|
||||||
|
junit.jupiter.version=5.8.2
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
234
gradlew
vendored
Executable file
234
gradlew
vendored
Executable file
@@ -0,0 +1,234 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015-2021 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command;
|
||||||
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
# double quotes to make sure that they get re-expanded; and
|
||||||
|
# * put everything else in single quotes, so that it's not re-expanded.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
89
gradlew.bat
vendored
Normal file
89
gradlew.bat
vendored
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
16
jpacrepo-api/build.gradle
Normal file
16
jpacrepo-api/build.gradle
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java-library'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly group: 'jakarta.platform',
|
||||||
|
name: 'jakarta.jakartaee-api',
|
||||||
|
version: getProperty('jakarta.ee.version')
|
||||||
|
compileOnly group: 'javax.xml.bind', name: 'jaxb-api', version: getProperty('jaxb.api.version')
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) { JavaCompile j ->
|
||||||
|
options.compilerArgs += [
|
||||||
|
'--add-reads', 'net.woggioni.jpacrepo.api=ALL-UNNAMED'
|
||||||
|
]
|
||||||
|
}
|
6
jpacrepo-api/src/main/java/module-info.java
Normal file
6
jpacrepo-api/src/main/java/module-info.java
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module net.woggioni.jpacrepo.api {
|
||||||
|
requires static lombok;
|
||||||
|
requires static java.xml.bind;
|
||||||
|
exports net.woggioni.jpacrepo.api.model;
|
||||||
|
exports net.woggioni.jpacrepo.api.service;
|
||||||
|
}
|
@@ -0,0 +1,12 @@
|
|||||||
|
package net.woggioni.jpacrepo.api.model;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum CompressionFormat {
|
||||||
|
XZ("xz"), GZIP("gz"), Z_STANDARD("zst");
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final String value;
|
||||||
|
}
|
@@ -0,0 +1,93 @@
|
|||||||
|
package net.woggioni.jpacrepo.api.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.persistence.Access;
|
||||||
|
import javax.persistence.AccessType;
|
||||||
|
import javax.persistence.ElementCollection;
|
||||||
|
import javax.persistence.EmbeddedId;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.Index;
|
||||||
|
import javax.persistence.NamedQueries;
|
||||||
|
import javax.persistence.NamedQuery;
|
||||||
|
import javax.persistence.PrePersist;
|
||||||
|
import javax.persistence.PreUpdate;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@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")
|
||||||
|
})
|
||||||
|
@Table(indexes = {
|
||||||
|
@Index(columnList = "md5sum", unique = true),
|
||||||
|
@Index(columnList = "fileName", unique = true)
|
||||||
|
})
|
||||||
|
@XmlRootElement
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
public class PkgData {
|
||||||
|
|
||||||
|
@EmbeddedId
|
||||||
|
private PkgId id;
|
||||||
|
|
||||||
|
private String base;
|
||||||
|
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
private OffsetDateTime buildDate;
|
||||||
|
|
||||||
|
private String packager;
|
||||||
|
|
||||||
|
private long size;
|
||||||
|
|
||||||
|
private String license;
|
||||||
|
|
||||||
|
private String md5sum;
|
||||||
|
|
||||||
|
private String fileName;
|
||||||
|
|
||||||
|
@ElementCollection(fetch = FetchType.EAGER)
|
||||||
|
private Set<String> replaces;
|
||||||
|
|
||||||
|
@ElementCollection(fetch = FetchType.EAGER)
|
||||||
|
private Set<String> conflict;
|
||||||
|
|
||||||
|
@ElementCollection(fetch = FetchType.EAGER)
|
||||||
|
private Set<String> provides;
|
||||||
|
|
||||||
|
@ElementCollection(fetch = FetchType.EAGER)
|
||||||
|
private Set<String> depend;
|
||||||
|
|
||||||
|
@ElementCollection(fetch = FetchType.EAGER)
|
||||||
|
private Set<String> optdepend;
|
||||||
|
|
||||||
|
@ElementCollection(fetch = FetchType.EAGER)
|
||||||
|
private Set<String> makedepend;
|
||||||
|
|
||||||
|
@ElementCollection(fetch = FetchType.EAGER)
|
||||||
|
private Set<String> makeopkgopt;
|
||||||
|
|
||||||
|
@ElementCollection(fetch = FetchType.EAGER)
|
||||||
|
private Set<String> backup;
|
||||||
|
|
||||||
|
private OffsetDateTime updTimestamp;
|
||||||
|
|
||||||
|
@PreUpdate
|
||||||
|
@PrePersist
|
||||||
|
private void writeTimestamp() {
|
||||||
|
updTimestamp = OffsetDateTime.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@@ -0,0 +1,30 @@
|
|||||||
|
package net.woggioni.jpacrepo.api.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.persistence.Access;
|
||||||
|
import javax.persistence.AccessType;
|
||||||
|
import javax.persistence.Embeddable;
|
||||||
|
import javax.persistence.EnumType;
|
||||||
|
import javax.persistence.Enumerated;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Embeddable
|
||||||
|
@Access(AccessType.FIELD)
|
||||||
|
@XmlRootElement
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
public class PkgId implements Serializable {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
private String arch;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.ORDINAL)
|
||||||
|
private CompressionFormat compressionFormat;
|
||||||
|
}
|
@@ -0,0 +1,13 @@
|
|||||||
|
package net.woggioni.jpacrepo.api.service;
|
||||||
|
|
||||||
|
|
||||||
|
import net.woggioni.jpacrepo.api.model.PkgData;
|
||||||
|
|
||||||
|
import javax.ejb.Local;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Local
|
||||||
|
public interface PacmanServiceLocal extends PacmanServiceRemote {
|
||||||
|
long countResults(String name, String version, String arch);
|
||||||
|
List<PkgData> searchPackage(String name, String version, String arch, int page, int pageSize, String fileName);
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
package net.woggioni.jpacrepo.api.service;
|
||||||
|
|
||||||
|
import javax.ejb.Remote;
|
||||||
|
|
||||||
|
@Remote
|
||||||
|
public interface PacmanServiceRemote {
|
||||||
|
void syncDB();
|
||||||
|
void deletePackage(String filename);
|
||||||
|
}
|
50
jpacrepo-client/build.gradle
Normal file
50
jpacrepo-client/build.gradle
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java-library'
|
||||||
|
id "net.woggioni.gradle.envelope"
|
||||||
|
}
|
||||||
|
|
||||||
|
import net.woggioni.gradle.envelope.EnvelopeJarTask
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
implementation group: 'org.slf4j', name: 'slf4j-api', version: getProperty('slf4j.version')
|
||||||
|
implementation group: 'org.apache.logging.log4j',
|
||||||
|
name: 'log4j-slf4j-impl',
|
||||||
|
version: getProperty('log4j.version')
|
||||||
|
|
||||||
|
implementation project(':jpacrepo-api')
|
||||||
|
runtimeOnly group: 'org.jboss',
|
||||||
|
name: 'jboss-ejb-client', version: getProperty('jboss.ejb.client.version')
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
modularity.inferModulePath = true
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) { JavaCompile j ->
|
||||||
|
options.javaModuleMainClass = 'net.woggioni.jpacrepo.client.Main'
|
||||||
|
// options.compilerArgs += [
|
||||||
|
// '--add-reads', 'javax.ejb=ALL-UNNAMED'
|
||||||
|
// ]
|
||||||
|
}
|
||||||
|
|
||||||
|
Provider<EnvelopeJarTask> envelopeJarTaskProvider = tasks.named('envelopeJar', EnvelopeJarTask.class) {
|
||||||
|
mainClass = 'net.woggioni.jpacrepo.client.Main'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('run', JavaExec) { JavaExec t ->
|
||||||
|
classpath(project.sourceSets.main.output.dirs)
|
||||||
|
classpath(project.sourceSets.main.runtimeClasspath)
|
||||||
|
mainModule = 'net.woggioni.jpacrepo.client'
|
||||||
|
mainClass = 'net.woggioni.jpacrepo.client.Main'
|
||||||
|
// String modulePath = project.sourceSets.main.runtimeClasspath.files.stream()
|
||||||
|
// .map(File::toString)
|
||||||
|
// .collect(Collectors.joining(System.getProperty('path.separator')))
|
||||||
|
|
||||||
|
// jvmArgs = [
|
||||||
|
// '--add-reads', 'net.woggioni.jpacrepo.client=ALL-UNNAMED',
|
||||||
|
// '--module-path', modulePath
|
||||||
|
// ]
|
||||||
|
modularity.inferModulePath = true
|
||||||
|
}
|
8
jpacrepo-client/src/main/java/module-info.java
Normal file
8
jpacrepo-client/src/main/java/module-info.java
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
module net.woggioni.jpacrepo.client {
|
||||||
|
requires static lombok;
|
||||||
|
requires jdk.unsupported;
|
||||||
|
requires java.naming;
|
||||||
|
requires org.slf4j;
|
||||||
|
requires org.apache.logging.log4j;
|
||||||
|
requires net.woggioni.jpacrepo.api;
|
||||||
|
}
|
@@ -0,0 +1,58 @@
|
|||||||
|
package net.woggioni.jpacrepo.client;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.woggioni.jpacrepo.api.service.PacmanServiceRemote;
|
||||||
|
|
||||||
|
import javax.naming.Context;
|
||||||
|
import javax.naming.InitialContext;
|
||||||
|
import javax.naming.NameClassPair;
|
||||||
|
import javax.naming.NamingEnumeration;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class Main {
|
||||||
|
|
||||||
|
private static void traverseJndiNode(String nodeName, Context context) {
|
||||||
|
try {
|
||||||
|
NamingEnumeration<NameClassPair> list = context.list(nodeName);
|
||||||
|
while (list.hasMore()) {
|
||||||
|
String childName = nodeName + "" + list.next().getName();
|
||||||
|
System.out.println(childName);
|
||||||
|
traverseJndiNode(childName, context);
|
||||||
|
}
|
||||||
|
} catch (NamingException ex) {
|
||||||
|
// We reached a leaf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Properties prop = new Properties();
|
||||||
|
// InputStream in = Main.class.getClassLoader().getResourceAsStream("jboss-ejb-client.properties");
|
||||||
|
// prop.load(in);
|
||||||
|
prop.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
|
||||||
|
|
||||||
|
prop.put(Context.INITIAL_CONTEXT_FACTORY, "org.wildfly.naming.client.WildFlyInitialContextFactory");
|
||||||
|
prop.put(Context.PROVIDER_URL, "http-remoting://localhost:8080");
|
||||||
|
// prop.put(Context.PROVIDER_URL, "http-remoting://nuc:8080");
|
||||||
|
// prop.put(Context.PROVIDER_URL, "remote://odroid-u3:4447");
|
||||||
|
prop.put(Context.SECURITY_PRINCIPAL, "walter");
|
||||||
|
prop.put(Context.SECURITY_CREDENTIALS, "27ff5990757d1d");
|
||||||
|
// prop.put(Context.SECURITY_PRINCIPAL, "luser");
|
||||||
|
// prop.put(Context.SECURITY_CREDENTIALS, "123456");
|
||||||
|
|
||||||
|
prop.put("jboss.naming.client.ejb.context", true);
|
||||||
|
Context context = new InitialContext(prop);
|
||||||
|
Context ctx = new InitialContext(prop);
|
||||||
|
traverseJndiNode("/", context);
|
||||||
|
// final PacmanService stateService = (PacmanService) ctx.lookup("/jpacrepo-1.0/remote/PacmanServiceEJB!service.PacmanService");
|
||||||
|
final PacmanServiceRemote service = (PacmanServiceRemote) ctx.lookup(
|
||||||
|
"/jpacrepo-2.0-SNAPSHOT/PacmanServiceEJB!net.woggioni.jpacrepo.api.service.PacmanServiceRemote"
|
||||||
|
);
|
||||||
|
// List<PkgData> pkgs = service.searchPackage("google-earth", null, null, 1, 10);
|
||||||
|
// System.out.println(new XStream().toXML(pkgs));
|
||||||
|
service.syncDB();
|
||||||
|
}
|
||||||
|
}
|
14
jpacrepo-client/src/main/resources/log4j2.xml
Normal file
14
jpacrepo-client/src/main/resources/log4j2.xml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Configuration status="INFO">
|
||||||
|
<Appenders>
|
||||||
|
<Console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="%d{HH:mm:ss,SSS} %highlight{[%p]} (%t) %c: %m%n"/>
|
||||||
|
</Console>
|
||||||
|
</Appenders>
|
||||||
|
|
||||||
|
<Loggers>
|
||||||
|
<Root level="INFO">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</Root>
|
||||||
|
</Loggers>
|
||||||
|
</Configuration>
|
19
jpacrepo-impl/build.gradle
Normal file
19
jpacrepo-impl/build.gradle
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java-library'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly group: 'jakarta.platform',
|
||||||
|
name: 'jakarta.jakartaee-api',
|
||||||
|
version: getProperty('jakarta.ee.version')
|
||||||
|
|
||||||
|
implementation group: 'org.tukaani', name: 'xz', version: getProperty('xz.version')
|
||||||
|
implementation group: 'org.slf4j', name: 'slf4j-api', version: getProperty('slf4j.version')
|
||||||
|
implementation group: 'net.woggioni', name: 'jzstd', version: getProperty('jzstd.version')
|
||||||
|
implementation group: 'net.woggioni', name: 'jwo', version: getProperty('jwo.version')
|
||||||
|
implementation group: 'org.apache.commons',
|
||||||
|
name: 'commons-compress',
|
||||||
|
version: getProperty('apache.commons.compress.version')
|
||||||
|
|
||||||
|
api project(':jpacrepo-api')
|
||||||
|
}
|
9
jpacrepo-impl/src/main/java/module-info.java
Normal file
9
jpacrepo-impl/src/main/java/module-info.java
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
module net.woggioni.jpacrepo.impl {
|
||||||
|
requires static lombok;
|
||||||
|
requires net.woggioni.jpacrepo.api;
|
||||||
|
requires net.woggioni.jwo;
|
||||||
|
requires net.woggioni.jzstd;
|
||||||
|
requires org.apache.commons.compress;
|
||||||
|
|
||||||
|
exports net.woggioni.jpacrepo.impl.model;
|
||||||
|
}
|
@@ -0,0 +1,41 @@
|
|||||||
|
package net.woggioni.jpacrepo.impl.model;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import net.woggioni.jpacrepo.api.model.CompressionFormat;
|
||||||
|
import net.woggioni.jwo.JWO;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class CompressionFormatImpl {
|
||||||
|
private static final Map<String, CompressionFormat> valueMap;
|
||||||
|
|
||||||
|
static {
|
||||||
|
Map<String, CompressionFormat> result = new TreeMap<>();
|
||||||
|
for(CompressionFormat format : CompressionFormat.values()) {
|
||||||
|
result.put(format.getValue(), format);
|
||||||
|
}
|
||||||
|
valueMap = Collections.unmodifiableMap(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static CompressionFormat guess(Path file) {
|
||||||
|
String extension = JWO.splitExtension(file.getFileName())
|
||||||
|
.orElseThrow(() -> JWO.newThrowable(ParseException.class,
|
||||||
|
"Unable to parse file extension for '%s'", file.getFileName()))
|
||||||
|
.get_2().substring(1);
|
||||||
|
|
||||||
|
CompressionFormat result = valueMap.get(extension);
|
||||||
|
if(result == null) {
|
||||||
|
throw JWO.newThrowable(IllegalArgumentException.class,
|
||||||
|
"Unknown compression format for file extension '%s'", extension);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,166 @@
|
|||||||
|
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.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
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<InputStream, InputStream> 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<String, List<String>> metadata = reader.lines().map(String::trim)
|
||||||
|
.filter(Predicate.not(String::isEmpty))
|
||||||
|
.filter(line -> !line.startsWith("#"))
|
||||||
|
.map((Fun<String, Tuple2<String, String>>) 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<String, String>::get_1,
|
||||||
|
TreeMap::new,
|
||||||
|
Collectors.mapping(Tuple2<String, String>::get_2,
|
||||||
|
Collectors.toUnmodifiableList())));
|
||||||
|
PkgData data = new PkgData();
|
||||||
|
data.setId(new PkgId());
|
||||||
|
data.getId().setCompressionFormat(compressionFormat);
|
||||||
|
|
||||||
|
for (Map.Entry<String, List<String>> entry : metadata.entrySet()) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
List<String> 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(OffsetDateTime.ofInstant(
|
||||||
|
Instant.ofEpochSecond(Long.parseLong(value.get(0))), ZoneOffset.UTC));
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -9,7 +9,7 @@ from sequtils import map, apply
|
|||||||
|
|
||||||
var pkgMap : JsonNode
|
var pkgMap : JsonNode
|
||||||
|
|
||||||
const serverURL {.strdefine.}: string = "http://woggioni.net/jpacrepo/"
|
const serverURL {.strdefine.}: string = ""
|
||||||
|
|
||||||
proc last[T](s : seq[T]) : T = s[s.len - 1]
|
proc last[T](s : seq[T]) : T = s[s.len - 1]
|
||||||
proc formatByteSize(size : BiggestInt) : string = size.float64.formatEng(precision=1, siPrefix=true, unit = "B")
|
proc formatByteSize(size : BiggestInt) : string = size.float64.formatEng(precision=1, siPrefix=true, unit = "B")
|
||||||
@@ -73,6 +73,7 @@ proc newDownloadPanel(parent : Element) : DownloadPanel =
|
|||||||
let tf= document.createElement("input")
|
let tf= document.createElement("input")
|
||||||
tf.setAttribute("name", "pkgs")
|
tf.setAttribute("name", "pkgs")
|
||||||
let txt = sequtils.foldl(pkglist, a & " " & b)
|
let txt = sequtils.foldl(pkglist, a & " " & b)
|
||||||
|
echo txt
|
||||||
tf.value(txt)
|
tf.value(txt)
|
||||||
form.appendChild(tf)
|
form.appendChild(tf)
|
||||||
document.body.appendChild(form)
|
document.body.appendChild(form)
|
||||||
@@ -123,16 +124,18 @@ proc addPkg(dp : DownloadPanel, pkgfile : string) =
|
|||||||
req.send()
|
req.send()
|
||||||
dp.updateBadge
|
dp.updateBadge
|
||||||
|
|
||||||
proc readTableRow(row : Element) : JsonNode =
|
proc readTableRow(arch : string, row : Element) : JsonNode =
|
||||||
let pkgname = $row.querySelector("td:nth-child(2)").textContent
|
let pkgname = $row.querySelector("td:nth-child(2)").textContent
|
||||||
let version = $row.querySelector("td:nth-child(3) button").textContent
|
let version = $row.querySelector("td:nth-child(3) button").textContent
|
||||||
let arch = $row.querySelector("td:nth-child(4) button").textContent
|
let filename = $row.querySelector("td:nth-child(4) button").textContent
|
||||||
pkgMap[pkgname][version][arch]
|
for candidate in pkgMap[arch][pkgname][version]:
|
||||||
|
if filename == candidate["filename"].getStr:
|
||||||
|
return candidate
|
||||||
|
|
||||||
proc createDropdown(parent : Element, data :seq[string], onchange : proc(value : string)) =
|
proc createDropdown(parent : Element, data :seq[string], onchange : proc(value : string)) =
|
||||||
|
|
||||||
htmlTreeAppend(parent):
|
htmlTreeAppend(parent):
|
||||||
"div":
|
"span":
|
||||||
var button : Element
|
var button : Element
|
||||||
classList = ["dropdown"]
|
classList = ["dropdown"]
|
||||||
"button":
|
"button":
|
||||||
@@ -158,18 +161,18 @@ proc createDropdown(parent : Element, data :seq[string], onchange : proc(value :
|
|||||||
type PkgTable = ref object
|
type PkgTable = ref object
|
||||||
addButton : Element
|
addButton : Element
|
||||||
|
|
||||||
proc newPkgTable(parent: Element, searchString : string) : PkgTable =
|
proc newPkgTable(parent: Element, arch: string, searchString : string, addButtonCallback : proc(e : Event)) : PkgTable =
|
||||||
var pkgtable = PkgTable()
|
var pkgtable = PkgTable()
|
||||||
var fragments = newSeq[string]()
|
var fragments = newSeq[string]()
|
||||||
for fragment in searchString.splitWhitespace():
|
for fragment in searchString.splitWhitespace():
|
||||||
fragments.add(fragment)
|
fragments.add(fragment)
|
||||||
var searchResult = newOrderedTable[string, JsonNode]()
|
var searchResult = newOrderedTable[string, JsonNode]()
|
||||||
for key, value in pkgMap:
|
for key, value in pkgMap[arch]:
|
||||||
for fragment in fragments:
|
for fragment in fragments:
|
||||||
if fragment in key:
|
if fragment in key:
|
||||||
searchResult[key] = value
|
searchResult[key] = value
|
||||||
for table in document.querySelectorAll("table.pkgtable"):
|
for node in parent.querySelectorAll("table.pkgtable"):
|
||||||
table.parentNode.removeChild(table)
|
parent.removeChild(node)
|
||||||
htmlTreeAppend(parent):
|
htmlTreeAppend(parent):
|
||||||
"table":
|
"table":
|
||||||
classList = ["table", "table-striped","pkgtable"]
|
classList = ["table", "table-striped","pkgtable"]
|
||||||
@@ -190,21 +193,22 @@ proc newPkgTable(parent: Element, searchString : string) : PkgTable =
|
|||||||
"th":
|
"th":
|
||||||
text = "Version"
|
text = "Version"
|
||||||
"th":
|
"th":
|
||||||
text = "Arch"
|
text = "File name"
|
||||||
"th":
|
"th":
|
||||||
text = "Installed size"
|
text = "Installed size"
|
||||||
"tbody":
|
"tbody":
|
||||||
cb:
|
cb:
|
||||||
var i = 0
|
var i = 0
|
||||||
for name, versions in searchResult:
|
for name, versions in searchResult.mpairs:
|
||||||
|
|
||||||
closureScope:
|
closureScope:
|
||||||
htmlTreeAppend(elem):
|
htmlTreeAppend(elem):
|
||||||
"tr":
|
"tr":
|
||||||
var row : Element
|
var row : Element
|
||||||
var archCell : Element
|
var fileNameCell : Element
|
||||||
var sizeCell : Element
|
var sizeCell : Element
|
||||||
let size_change_callback = proc(newValue : string) =
|
let size_change_callback = proc(newValue : string) =
|
||||||
sizeCell.textContent = readTableRow(row)["size"].getInt.formatByteSize
|
sizeCell.textContent = readTableRow(arch, row)["size"].getInt.formatByteSize
|
||||||
|
|
||||||
"td":
|
"td":
|
||||||
"div":
|
"div":
|
||||||
@@ -221,36 +225,46 @@ proc newPkgTable(parent: Element, searchString : string) : PkgTable =
|
|||||||
data.add(version)
|
data.add(version)
|
||||||
let vs = versions
|
let vs = versions
|
||||||
let change_callback = proc(newValue : string) =
|
let change_callback = proc(newValue : string) =
|
||||||
archCell.removeChildren()
|
fileNameCell.removeChildren()
|
||||||
var newdata = newSeq[string]()
|
var newdata = newSeq[string]()
|
||||||
for arch, pkgname in vs[newValue]:
|
for arch, pkgname in vs[newValue]:
|
||||||
newdata.add(arch)
|
newdata.add(arch)
|
||||||
createDropdown(archCell, newdata, size_change_callback)
|
createDropdown(fileNameCell, newdata, size_change_callback)
|
||||||
size_change_callback(newValue)
|
size_change_callback(newValue)
|
||||||
createDropdown(elem, data, change_callback)
|
createDropdown(elem, data, change_callback)
|
||||||
"td":
|
"td":
|
||||||
cb:
|
cb:
|
||||||
archCell = elem
|
fileNameCell = elem
|
||||||
var data = newSeq[string]()
|
var data = newSeq[string]()
|
||||||
var arches : JsonNode
|
var files : JsonNode
|
||||||
for v, a in versions:
|
for v, f in versions:
|
||||||
arches = a
|
files = f
|
||||||
break
|
break
|
||||||
for arch, pkgname in arches:
|
for file in files:
|
||||||
data.add(arch)
|
data.add(file["filename"].getStr)
|
||||||
createDropdown(elem, data, size_change_callback)
|
createDropdown(elem, data, size_change_callback)
|
||||||
"td":
|
"td":
|
||||||
cb:
|
cb:
|
||||||
sizeCell = elem
|
sizeCell = elem
|
||||||
for v, arches in versions:
|
for v, files in versions:
|
||||||
for key, value in arches:
|
for file in files:
|
||||||
elem.textContent = value["size"].getInt.formatByteSize
|
elem.textContent = file["size"].getInt.formatByteSize
|
||||||
return
|
return
|
||||||
cb:
|
cb:
|
||||||
row = elem
|
row = elem
|
||||||
|
pkgtable.addButton.addEventListener("click", addButtonCallback)
|
||||||
pkgtable
|
pkgtable
|
||||||
|
|
||||||
|
var selectedArch : string
|
||||||
|
var archButtons : Element
|
||||||
|
var table : Element
|
||||||
|
var searchString : string
|
||||||
|
var add2DownloadList : proc(e : Event)
|
||||||
|
|
||||||
|
proc updateTable(e : Event) =
|
||||||
|
if pkgMap.len == 0 or searchString.len < 2: return
|
||||||
|
discard newPkgTable(table, selectedArch, searchString, add2DownloadList)
|
||||||
|
|
||||||
var dp : DownloadPanel
|
var dp : DownloadPanel
|
||||||
# var pkgTable : PkgTable
|
# var pkgTable : PkgTable
|
||||||
htmlTreeappend document.body:
|
htmlTreeappend document.body:
|
||||||
@@ -264,7 +278,6 @@ htmlTreeappend document.body:
|
|||||||
# "z-index" : "-10"
|
# "z-index" : "-10"
|
||||||
# }
|
# }
|
||||||
"div":
|
"div":
|
||||||
var table : Element
|
|
||||||
style = {"background-color" : "rgba(255, 255, 255, 0.25)"}
|
style = {"background-color" : "rgba(255, 255, 255, 0.25)"}
|
||||||
classList = ["container"]
|
classList = ["container"]
|
||||||
"div":
|
"div":
|
||||||
@@ -291,17 +304,15 @@ htmlTreeappend document.body:
|
|||||||
classList = ["form-control"]
|
classList = ["form-control"]
|
||||||
attrs = {"type" : "text"}
|
attrs = {"type" : "text"}
|
||||||
cb :
|
cb :
|
||||||
proc add2DownloadList(e : Event) =
|
add2DownloadList = proc(e : Event) =
|
||||||
let rows = table.querySelectorAll("tbody tr")
|
let rows = table.querySelectorAll("tbody tr")
|
||||||
for row in rows:
|
for row in rows:
|
||||||
let cbox = row.querySelector("td:first-child input")
|
let cbox = row.querySelector("td:first-child input")
|
||||||
if cbox.checked:
|
if cbox.checked:
|
||||||
dp.addPkg(readTableRow(row)["filename"].getStr)
|
dp.addPkg(readTableRow(selectedArch, row)["filename"].getStr)
|
||||||
proc oninput(e : Event) =
|
oninput:
|
||||||
if pkgMap.len == 0 or elem.value.len < 2: return
|
searchString = $elem.value
|
||||||
let pkgtable = newPkgTable(table, $elem.value)
|
updateTable(event)
|
||||||
pkgtable.addButton.addEventListener("click", add2DownloadList)
|
|
||||||
elem.addEventListener("input", oninput)
|
|
||||||
"div":
|
"div":
|
||||||
classList = ["row"]
|
classList = ["row"]
|
||||||
"div":
|
"div":
|
||||||
@@ -309,6 +320,11 @@ htmlTreeappend document.body:
|
|||||||
cb:
|
cb:
|
||||||
dp = newDownloadPanel(elem)
|
dp = newDownloadPanel(elem)
|
||||||
"div":
|
"div":
|
||||||
|
"div":
|
||||||
|
classList = ["btn-group", "btn-group-justified"]
|
||||||
|
attrs = { "role" : "group" }
|
||||||
|
cb:
|
||||||
|
archButtons = elem
|
||||||
classList = ["col-sm-9"]
|
classList = ["col-sm-9"]
|
||||||
cb:
|
cb:
|
||||||
table = elem
|
table = elem
|
||||||
@@ -317,7 +333,42 @@ htmlTreeappend document.body:
|
|||||||
|
|
||||||
let r = newXMLHTTPRequest()
|
let r = newXMLHTTPRequest()
|
||||||
let load_cb = proc(e : Event) =
|
let load_cb = proc(e : Event) =
|
||||||
pkgMap = parseJson($r.responseText)
|
pkgMap = parseJson($r.responseText)
|
||||||
|
var buttons = newSeq[Element]()
|
||||||
|
var index = 0
|
||||||
|
for arch in pkgMap.keys:
|
||||||
|
closureScope:
|
||||||
|
htmlTreeAppend(archButtons):
|
||||||
|
"div":
|
||||||
|
classList = ["btn-group"]
|
||||||
|
attrs = { "role" : "group"}
|
||||||
|
"button":
|
||||||
|
classList = ["btn", "btn-default"]
|
||||||
|
attrs = {
|
||||||
|
"type": "button",
|
||||||
|
}
|
||||||
|
text = arch
|
||||||
|
let thisButtonIndex = index
|
||||||
|
let thisButtonArch = arch
|
||||||
|
onclick:
|
||||||
|
let update = thisButtonArch != selectedArch
|
||||||
|
selectedArch = thisButtonArch
|
||||||
|
for i in 0..<buttons.len:
|
||||||
|
let btn = buttons[i]
|
||||||
|
if(i == thisButtonIndex):
|
||||||
|
btn.classList.toggle("active")
|
||||||
|
else:
|
||||||
|
btn.classList.remove("active")
|
||||||
|
if update:
|
||||||
|
updateTable(event)
|
||||||
|
cb:
|
||||||
|
buttons.add(elem)
|
||||||
|
if index == 0:
|
||||||
|
selectedArch = arch
|
||||||
|
elem.classList.add("active")
|
||||||
|
inc(index)
|
||||||
|
# createDropdown(archDropDown, data, arch_change_callback)
|
||||||
|
|
||||||
r.addEventListener("load", load_cb)
|
r.addEventListener("load", load_cb)
|
||||||
r.open("get", serverURL & "api/pkg/map")
|
r.open("get", serverURL & "api/pkg/map")
|
||||||
r.setRequestHeader("Accept", "application/json")
|
r.setRequestHeader("Accept", "application/json")
|
||||||
|
26
settings.gradle
Normal file
26
settings.gradle
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url = 'https://woggioni.net/mvn/'
|
||||||
|
content {
|
||||||
|
includeGroup 'net.woggioni.gradle'
|
||||||
|
includeGroup 'net.woggioni.gradle.lombok'
|
||||||
|
includeGroup 'net.woggioni.gradle.wildfly'
|
||||||
|
includeGroup 'net.woggioni.gradle.envelope'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id "net.woggioni.gradle.lombok" version getProperty('net.woggioni.gradle.lombok.version')
|
||||||
|
id "net.woggioni.gradle.wildfly" version getProperty('net.woggioni.gradle.wildfly.version')
|
||||||
|
id "net.woggioni.gradle.envelope" version getProperty('net.woggioni.gradle.envelope.version')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = 'jpacrepo'
|
||||||
|
|
||||||
|
include 'jpacrepo-api'
|
||||||
|
include 'jpacrepo-impl'
|
||||||
|
include 'jpacrepo-client'
|
62
src/main/java/net/woggioni/jpacrepo/config/AppConfig.java
Normal file
62
src/main/java/net/woggioni/jpacrepo/config/AppConfig.java
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package net.woggioni.jpacrepo.config;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import net.woggioni.jpacrepo.api.model.PkgData;
|
||||||
|
import net.woggioni.jpacrepo.persistence.InitialSchemaAction;
|
||||||
|
import net.woggioni.jwo.BiFun;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final Path repoFolder;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final InitialSchemaAction initialSchemaAction;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final String dataSourceJndi;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final AtomicBoolean invalidateCache = new AtomicBoolean(true);
|
||||||
|
|
||||||
|
public Path getFile(PkgData pkg) {
|
||||||
|
return repoFolder.resolve(pkg.getFileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getFile(String fileName) {
|
||||||
|
return repoFolder.resolve(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static AppConfig newInstance(String propertyFile) {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
var path = Path.of(propertyFile);
|
||||||
|
if (Files.exists(path)) {
|
||||||
|
try(InputStream is = Files.newInputStream(path)) {
|
||||||
|
properties.load(is);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BiFun<String, String, String> getProperty = (String key, String defaultValue) -> {
|
||||||
|
key = "net.woggioni.jpacrepo." + key;
|
||||||
|
String result = System.getProperty(key);
|
||||||
|
if(result == null) {
|
||||||
|
result = properties.getProperty(key, defaultValue);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
return new AppConfig(
|
||||||
|
Path.of(getProperty.apply("RepoFolder", "/var/cache/pacman/pkg")),
|
||||||
|
InitialSchemaAction.from(getProperty.apply("InitialSchemaAction", "none")),
|
||||||
|
getProperty.apply("dataSourceJndi", "java:comp/DefaultDataSource"));
|
||||||
|
}
|
||||||
|
}
|
23
src/main/java/net/woggioni/jpacrepo/factory/BeanFactory.java
Normal file
23
src/main/java/net/woggioni/jpacrepo/factory/BeanFactory.java
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package net.woggioni.jpacrepo.factory;
|
||||||
|
|
||||||
|
import net.woggioni.jpacrepo.config.AppConfig;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.enterprise.inject.Produces;
|
||||||
|
import javax.enterprise.inject.spi.InjectionPoint;
|
||||||
|
|
||||||
|
public class BeanFactory {
|
||||||
|
|
||||||
|
@Produces
|
||||||
|
public AppConfig newAppConfig() {
|
||||||
|
return AppConfig.newInstance(
|
||||||
|
System.getProperty("net.woggioni.jpacrepo.configuration.file",
|
||||||
|
"/etc/jpacrepo/server.properties"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Produces
|
||||||
|
public Logger createLogger(InjectionPoint injectionPoint) {
|
||||||
|
return LoggerFactory.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,22 @@
|
|||||||
|
package net.woggioni.jpacrepo.factory;
|
||||||
|
|
||||||
|
import net.woggioni.jpacrepo.config.AppConfig;
|
||||||
|
|
||||||
|
import javax.enterprise.context.ApplicationScoped;
|
||||||
|
import javax.enterprise.inject.Produces;
|
||||||
|
import javax.persistence.EntityManagerFactory;
|
||||||
|
import javax.persistence.Persistence;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
public class PersistenceUnitFactory {
|
||||||
|
|
||||||
|
@Produces
|
||||||
|
@ApplicationScoped
|
||||||
|
private EntityManagerFactory createEntityManagerFactory(AppConfig appConfig) {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.put("javax.persistence.schema-generation.database.action",
|
||||||
|
appConfig.getInitialSchemaAction().getValue());
|
||||||
|
properties.put("javax.persistence.jtaDataSource", appConfig.getDataSourceJndi());
|
||||||
|
return Persistence.createEntityManagerFactory("jpacrepo_pu", properties);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,45 @@
|
|||||||
|
package net.woggioni.jpacrepo.persistence;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import net.woggioni.jwo.JWO;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum InitialSchemaAction {
|
||||||
|
NONE("none"), CREATE("create"), DROP("drop"), DROP_AND_CREATE("drop-and-create");
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Map<String, InitialSchemaAction> valueMap;
|
||||||
|
|
||||||
|
static {
|
||||||
|
Map<String, InitialSchemaAction> result = new TreeMap<>();
|
||||||
|
for(InitialSchemaAction initialSchemaAction : values()) {
|
||||||
|
result.put(initialSchemaAction.value, initialSchemaAction);
|
||||||
|
}
|
||||||
|
valueMap = Collections.unmodifiableMap(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static InitialSchemaAction from(String s) {
|
||||||
|
InitialSchemaAction result = valueMap.get(s);
|
||||||
|
if(result == null) {
|
||||||
|
throw JWO.newThrowable(IllegalArgumentException.class,
|
||||||
|
"Unknown initial schema action '%s'", s);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@@ -1,6 +1,7 @@
|
|||||||
package net.woggioni.jpacrepo.persistence;
|
package net.woggioni.jpacrepo.persistence;
|
||||||
|
|
||||||
import net.woggioni.jpacrepo.pacbase.PkgData;
|
|
||||||
|
import net.woggioni.jpacrepo.api.model.PkgData;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
import javax.persistence.TypedQuery;
|
import javax.persistence.TypedQuery;
|
||||||
@@ -11,66 +12,52 @@ import javax.persistence.criteria.Root;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by walter on 29/03/15.
|
|
||||||
*/
|
|
||||||
public class QueryEngine
|
public class QueryEngine
|
||||||
{
|
{
|
||||||
private String entityName;
|
private String entityName;
|
||||||
private String query;
|
private String query;
|
||||||
private List<String> where;
|
private List<String> where;
|
||||||
|
|
||||||
public QueryEngine(Class<?> cls)
|
public QueryEngine(Class<?> cls) {
|
||||||
{
|
|
||||||
query = String.format("SELECT e FROM %s e", cls.getSimpleName());
|
query = String.format("SELECT e FROM %s e", cls.getSimpleName());
|
||||||
this.entityName = cls.getSimpleName();
|
this.entityName = cls.getSimpleName();
|
||||||
where = new ArrayList<>();
|
where = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryEngine(String entityName)
|
public QueryEngine(String entityName) {
|
||||||
{
|
|
||||||
query = String.format("SELECT e FROM %s e", entityName);
|
query = String.format("SELECT e FROM %s e", entityName);
|
||||||
where = new ArrayList<>();
|
where = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryEngine select(String... fields)
|
public QueryEngine select(String... fields) {
|
||||||
{
|
|
||||||
String[] strarr = new String[fields.length];
|
String[] strarr = new String[fields.length];
|
||||||
for (int i = 0; i < fields.length; i++)
|
for (int i = 0; i < fields.length; i++) {
|
||||||
{
|
|
||||||
strarr[i] = "e." + fields[i];
|
strarr[i] = "e." + fields[i];
|
||||||
}
|
}
|
||||||
query = "SELECT " + String.join(",", strarr) + " FROM " + entityName + " e";
|
query = "SELECT " + String.join(",", strarr) + " FROM " + entityName + " e";
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryEngine select()
|
public QueryEngine select() {
|
||||||
{
|
|
||||||
query = String.format("SELECT e FROM %s e", entityName);
|
query = String.format("SELECT e FROM %s e", entityName);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryEngine where(String field, String operator, String value)
|
public QueryEngine where(String field, String operator, String value) {
|
||||||
{
|
|
||||||
where.add(String.format("e.%s %s '%s'", field, operator, value));
|
where.add(String.format("e.%s %s '%s'", field, operator, value));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String build()
|
public String build() {
|
||||||
{
|
if (where.isEmpty()) {
|
||||||
if (where.isEmpty())
|
|
||||||
{
|
|
||||||
return query;
|
return query;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
return query + " WHERE " + String.join(" AND ", where);
|
return query + " WHERE " + String.join(" AND ", where);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<PkgData> searchPackage(
|
public static List<PkgData> searchPackage(
|
||||||
EntityManager em, String name, String version, String arch, int pageNumber, int pageSize, String fileName)
|
EntityManager em, String name, String version, String arch, int pageNumber, int pageSize, String fileName) {
|
||||||
{
|
|
||||||
CriteriaBuilder builder;
|
CriteriaBuilder builder;
|
||||||
CriteriaQuery<PkgData> criteriaQuery;
|
CriteriaQuery<PkgData> criteriaQuery;
|
||||||
Root<PkgData> entity;
|
Root<PkgData> entity;
|
||||||
@@ -80,49 +67,39 @@ public class QueryEngine
|
|||||||
entity = criteriaQuery.from(PkgData.class);
|
entity = criteriaQuery.from(PkgData.class);
|
||||||
Predicate finalPredicate = null, p;
|
Predicate finalPredicate = null, p;
|
||||||
|
|
||||||
if (name != null && !name.isEmpty())
|
if (name != null && !name.isEmpty()) {
|
||||||
{
|
|
||||||
p = builder.equal(entity.get("name").get("id"), name);
|
p = builder.equal(entity.get("name").get("id"), name);
|
||||||
finalPredicate = p;
|
finalPredicate = p;
|
||||||
}
|
}
|
||||||
if (version != null && !version.isEmpty())
|
if (version != null && !version.isEmpty()) {
|
||||||
{
|
|
||||||
p = builder.equal(entity.get("version"), version);
|
p = builder.equal(entity.get("version"), version);
|
||||||
finalPredicate = finalPredicate != null ? builder.and(finalPredicate, p) : p;
|
finalPredicate = finalPredicate != null ? builder.and(finalPredicate, p) : p;
|
||||||
}
|
}
|
||||||
if (arch != null && !arch.isEmpty())
|
if (arch != null && !arch.isEmpty()) {
|
||||||
{
|
|
||||||
p = builder.equal(entity.get("arch"), arch);
|
p = builder.equal(entity.get("arch"), arch);
|
||||||
finalPredicate = finalPredicate != null ? builder.and(finalPredicate, p) : p;
|
finalPredicate = finalPredicate != null ? builder.and(finalPredicate, p) : p;
|
||||||
}
|
}
|
||||||
if (fileName != null && !fileName.isEmpty())
|
if (fileName != null && !fileName.isEmpty()) {
|
||||||
{
|
|
||||||
p = builder.equal(entity.get("fileName"), fileName);
|
p = builder.equal(entity.get("fileName"), fileName);
|
||||||
finalPredicate = finalPredicate != null ? builder.and(finalPredicate, p) : p;
|
finalPredicate = finalPredicate != null ? builder.and(finalPredicate, p) : p;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (finalPredicate != null)
|
if (finalPredicate != null) {
|
||||||
{
|
|
||||||
criteriaQuery.select(entity).where(finalPredicate).orderBy(builder.asc(entity.get("fileName")));
|
criteriaQuery.select(entity).where(finalPredicate).orderBy(builder.asc(entity.get("fileName")));
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
criteriaQuery.select(entity).orderBy(builder.asc(entity.get("fileName")));
|
criteriaQuery.select(entity).orderBy(builder.asc(entity.get("fileName")));
|
||||||
}
|
}
|
||||||
TypedQuery<PkgData> query = em.createQuery(criteriaQuery);
|
TypedQuery<PkgData> query = em.createQuery(criteriaQuery);
|
||||||
if (pageNumber >= 0)
|
if (pageNumber >= 0) {
|
||||||
{
|
|
||||||
query.setFirstResult(pageNumber * pageSize);
|
query.setFirstResult(pageNumber * pageSize);
|
||||||
}
|
}
|
||||||
if (pageSize > 0)
|
if (pageSize > 0) {
|
||||||
{
|
|
||||||
query.setMaxResults(pageSize);
|
query.setMaxResults(pageSize);
|
||||||
}
|
}
|
||||||
return query.getResultList();
|
return query.getResultList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long countResults(EntityManager em, String name, String version, String arch)
|
public static long countResults(EntityManager em, String name, String version, String arch) {
|
||||||
{
|
|
||||||
CriteriaBuilder builder;
|
CriteriaBuilder builder;
|
||||||
CriteriaQuery<Long> criteriaQuery;
|
CriteriaQuery<Long> criteriaQuery;
|
||||||
Root<PkgData> entity;
|
Root<PkgData> entity;
|
||||||
@@ -132,28 +109,22 @@ public class QueryEngine
|
|||||||
entity = criteriaQuery.from(PkgData.class);
|
entity = criteriaQuery.from(PkgData.class);
|
||||||
Predicate finalPredicate = null, p;
|
Predicate finalPredicate = null, p;
|
||||||
|
|
||||||
if (name != null && !name.isEmpty())
|
if (name != null && !name.isEmpty()) {
|
||||||
{
|
|
||||||
p = builder.equal(entity.get("name").get("id"), name);
|
p = builder.equal(entity.get("name").get("id"), name);
|
||||||
finalPredicate = p;
|
finalPredicate = p;
|
||||||
}
|
}
|
||||||
if (version != null && !version.isEmpty())
|
if (version != null && !version.isEmpty()) {
|
||||||
{
|
|
||||||
p = builder.equal(entity.get("version"), version);
|
p = builder.equal(entity.get("version"), version);
|
||||||
finalPredicate = finalPredicate != null ? builder.and(finalPredicate, p) : p;
|
finalPredicate = finalPredicate != null ? builder.and(finalPredicate, p) : p;
|
||||||
}
|
}
|
||||||
if (arch != null && !arch.isEmpty())
|
if (arch != null && !arch.isEmpty()) {
|
||||||
{
|
|
||||||
p = builder.equal(entity.get("arch"), arch);
|
p = builder.equal(entity.get("arch"), arch);
|
||||||
finalPredicate = finalPredicate != null ? builder.and(finalPredicate, p) : p;
|
finalPredicate = finalPredicate != null ? builder.and(finalPredicate, p) : p;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (finalPredicate != null)
|
if (finalPredicate != null) {
|
||||||
{
|
|
||||||
criteriaQuery.select(builder.count(entity)).where(finalPredicate);
|
criteriaQuery.select(builder.count(entity)).where(finalPredicate);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
criteriaQuery.select(builder.count(entity));
|
criteriaQuery.select(builder.count(entity));
|
||||||
}
|
}
|
||||||
return em.createQuery(criteriaQuery).getSingleResult();
|
return em.createQuery(criteriaQuery).getSingleResult();
|
||||||
|
@@ -0,0 +1,213 @@
|
|||||||
|
package net.woggioni.jpacrepo.service;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import net.woggioni.jpacrepo.api.model.PkgData;
|
||||||
|
import net.woggioni.jpacrepo.api.service.PacmanServiceLocal;
|
||||||
|
import net.woggioni.jpacrepo.api.service.PacmanServiceRemote;
|
||||||
|
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.jwo.Con;
|
||||||
|
import net.woggioni.jwo.JWO;
|
||||||
|
import net.woggioni.jwo.Sup;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import javax.ejb.Asynchronous;
|
||||||
|
import javax.ejb.ConcurrencyManagement;
|
||||||
|
import javax.ejb.ConcurrencyManagementType;
|
||||||
|
import javax.ejb.Local;
|
||||||
|
import javax.ejb.Lock;
|
||||||
|
import javax.ejb.LockType;
|
||||||
|
import javax.ejb.Remote;
|
||||||
|
import javax.ejb.Schedule;
|
||||||
|
import javax.ejb.Singleton;
|
||||||
|
import javax.ejb.Startup;
|
||||||
|
import javax.ejb.TransactionAttribute;
|
||||||
|
import javax.ejb.TransactionAttributeType;
|
||||||
|
import javax.ejb.TransactionManagement;
|
||||||
|
import javax.ejb.TransactionManagementType;
|
||||||
|
import javax.enterprise.concurrent.ManagedExecutorService;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.EntityManagerFactory;
|
||||||
|
import javax.persistence.TypedQuery;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CompletionService;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorCompletionService;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Startup
|
||||||
|
@Singleton
|
||||||
|
@Lock(LockType.READ)
|
||||||
|
@TransactionManagement(TransactionManagementType.CONTAINER)
|
||||||
|
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
|
||||||
|
@Local({PacmanServiceLocal.class})
|
||||||
|
@Remote({PacmanServiceRemote.class})
|
||||||
|
public class PacmanServiceEJB implements PacmanServiceLocal {
|
||||||
|
@Inject
|
||||||
|
private EntityManagerFactory emf;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private AppConfig ctx;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private Logger logger;
|
||||||
|
|
||||||
|
@Resource(name = "DefaultManagedExecutorService")
|
||||||
|
private ManagedExecutorService executor;
|
||||||
|
|
||||||
|
private final static String nameQuery = "SELECT pname FROM PkgName pname WHERE id = :name";
|
||||||
|
private final static String hashQuery = "SELECT pdata FROM PkgData pdata WHERE md5sum = :md5sum";
|
||||||
|
private final static String hashQueryCount = "SELECT count(pdata) FROM PkgData pdata WHERE md5sum = :md5sum";
|
||||||
|
|
||||||
|
private void deletePkgData(EntityManager em, PkgData pkgData) {
|
||||||
|
em.remove(pkgData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
@Asynchronous
|
||||||
|
@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
|
||||||
|
logger.info("Searching for packages that are no more in the filesystem");
|
||||||
|
List<String> resultList = em.createQuery("SELECT p.fileName FROM PkgData p", String.class)
|
||||||
|
.getResultList();
|
||||||
|
logger.info("Got list of filenames from db");
|
||||||
|
Set<String> knownPkg = resultList.stream().filter(fileName -> {
|
||||||
|
Path file = ctx.getFile(fileName);
|
||||||
|
boolean result = Files.exists(file);
|
||||||
|
if (!result) {
|
||||||
|
logger.info("Removing package {} which was not found in filesystem", file.getFileName());
|
||||||
|
em.createQuery("SELECT p FROM PkgData p WHERE p.fileName = :fileName", PkgData.class)
|
||||||
|
.setParameter("fileName", file.getFileName().toString())
|
||||||
|
.getResultList().forEach(pkgData -> deletePkgData(em, pkgData));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}).collect(Collectors.toUnmodifiableSet());
|
||||||
|
logger.info("Searching for new packages or packages that were modified after being added to the database");
|
||||||
|
CompletionService<PkgData> completionService = new ExecutorCompletionService<>(executor);
|
||||||
|
final Set<Future<PkgData>> inProgress = new HashSet<>();
|
||||||
|
final int maxInProgress = Runtime.getRuntime().availableProcessors() * 5;
|
||||||
|
Con<Boolean> persistPackages = (Boolean drain) -> {
|
||||||
|
while ((drain && inProgress.size() > 0) || inProgress.size() > maxInProgress) {
|
||||||
|
Future<PkgData> future = completionService.poll(1, TimeUnit.SECONDS);
|
||||||
|
inProgress.remove(future);
|
||||||
|
PkgData pkgData;
|
||||||
|
try {
|
||||||
|
pkgData = future.get();
|
||||||
|
} catch (ExecutionException ee) {
|
||||||
|
throw ee.getCause();
|
||||||
|
}
|
||||||
|
persistPackage(em, pkgData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Files.list(ctx.getRepoFolder()).filter((Path file) -> {
|
||||||
|
String name = file.getFileName().toString();
|
||||||
|
return name.endsWith(".pkg.tar.xz") || name.endsWith(".pkg.tar.zst") || name.endsWith(".pkg.tar.gz");
|
||||||
|
}).forEach((Con<Path>) file -> {
|
||||||
|
if (!knownPkg.contains(file.getFileName().toString()) || ((Sup<Boolean>) () -> {
|
||||||
|
TypedQuery<Date> query = em.createQuery("SELECT p.updTimestamp FROM PkgData p WHERE filename = :filename", Date.class);
|
||||||
|
query.setParameter("filename", file.getFileName().toString());
|
||||||
|
Date result = query.getSingleResult();
|
||||||
|
return Files.getLastModifiedTime(file).toMillis() > result.getTime();
|
||||||
|
}).get()) {
|
||||||
|
inProgress.add(completionService.submit(() -> {
|
||||||
|
try {
|
||||||
|
return PkgDataImpl.parseFile(file, CompressionFormatImpl.guess(file));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
logger.error(String.format("Error parsing '%s'", file.toAbsolutePath()), ex);
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
persistPackages.accept(false);
|
||||||
|
});
|
||||||
|
persistPackages.accept(true);
|
||||||
|
logger.info("Removing obsolete packages");
|
||||||
|
deleteOld(em);
|
||||||
|
logger.info("Repository cleanup completed successfully");
|
||||||
|
ctx.getInvalidateCache().set(true);
|
||||||
|
} finally {
|
||||||
|
em.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void persistPackage(EntityManager em, PkgData pkgData) {
|
||||||
|
TypedQuery<Long> hquery = em.createQuery(hashQueryCount, Long.class);
|
||||||
|
hquery.setParameter("md5sum", pkgData.getMd5sum());
|
||||||
|
if (hquery.getSingleResult() == 0) {
|
||||||
|
TypedQuery<PkgData> fquery =
|
||||||
|
em.createQuery("SELECT p FROM PkgData p WHERE p.fileName = :fileName", PkgData.class);
|
||||||
|
fquery.setParameter("fileName", pkgData.getFileName());
|
||||||
|
fquery.getResultList().forEach(p -> deletePkgData(em, p));
|
||||||
|
em.persist(pkgData);
|
||||||
|
logger.info("Persisting package {}", pkgData.getFileName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@TransactionAttribute(TransactionAttributeType.REQUIRED)
|
||||||
|
public void deletePackage(String filename) {
|
||||||
|
EntityManager em = emf.createEntityManager();
|
||||||
|
deletePackage(em, filename);
|
||||||
|
logger.info("Package {} has been deleted", filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
private void deletePackage(EntityManager em, String filename) {
|
||||||
|
TypedQuery<PkgData> fquery = em.createQuery("SELECT p FROM PkgData p WHERE fileName = :fileName", PkgData.class);
|
||||||
|
fquery.setParameter("fileName", filename);
|
||||||
|
List<PkgData> savedFiles = fquery.getResultList();
|
||||||
|
if (savedFiles.size() == 0) {
|
||||||
|
throw JWO.newThrowable(IllegalArgumentException.class, "Package with name %s not found", filename);
|
||||||
|
}
|
||||||
|
PkgData pkg = fquery.getResultList().get(0);
|
||||||
|
Files.delete(ctx.getFile(pkg));
|
||||||
|
em.remove(pkg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static private final String deleteQuery =
|
||||||
|
"SELECT p.fileName FROM PkgData p WHERE p.buildDate < :cutoff and p.id.name in \n" + "(SELECT p2.id.name FROM PkgData p2 GROUP BY p2.id.name HAVING count(p2.id.name) > :minVersions\n)";
|
||||||
|
|
||||||
|
private void deleteOld(EntityManager em) {
|
||||||
|
TypedQuery<String> query = em.createQuery(deleteQuery, String.class);
|
||||||
|
Calendar cutoff = Calendar.getInstance();
|
||||||
|
cutoff.add(Calendar.YEAR, -2);
|
||||||
|
query.setParameter("cutoff", OffsetDateTime.now());
|
||||||
|
query.setParameter("minVersions", 2L);
|
||||||
|
List<String> list = query.getResultList();
|
||||||
|
list.forEach(this::deletePackage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
|
||||||
|
public long countResults(String name, String version, String arch) {
|
||||||
|
EntityManager em = emf.createEntityManager();
|
||||||
|
return QueryEngine.countResults(em, name, version, arch);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
|
||||||
|
public List<PkgData> searchPackage(String name, String version, String arch, int pageNumber, int pageSize, String fileName) {
|
||||||
|
EntityManager em = emf.createEntityManager();
|
||||||
|
return QueryEngine.searchPackage(em, name, version, arch, pageNumber, pageSize, null);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,427 @@
|
|||||||
|
package net.woggioni.jpacrepo.service;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.val;
|
||||||
|
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.config.AppConfig;
|
||||||
|
import net.woggioni.jpacrepo.impl.model.CompressionFormatImpl;
|
||||||
|
import net.woggioni.jpacrepo.impl.model.PkgDataImpl;
|
||||||
|
import net.woggioni.jpacrepo.service.wire.PkgDataList;
|
||||||
|
import net.woggioni.jpacrepo.service.wire.PkgTuple;
|
||||||
|
import net.woggioni.jpacrepo.service.wire.StringList;
|
||||||
|
import net.woggioni.jpacrepo.version.PkgIdComparator;
|
||||||
|
import net.woggioni.jpacrepo.version.VersionComparator;
|
||||||
|
import net.woggioni.jwo.CollectionUtils;
|
||||||
|
import net.woggioni.jwo.Con;
|
||||||
|
import net.woggioni.jwo.JWO;
|
||||||
|
import net.woggioni.jwo.Tuple2;
|
||||||
|
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
||||||
|
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import javax.ejb.ConcurrencyManagement;
|
||||||
|
import javax.ejb.ConcurrencyManagementType;
|
||||||
|
import javax.ejb.Singleton;
|
||||||
|
import javax.ejb.TransactionAttribute;
|
||||||
|
import javax.ejb.TransactionAttributeType;
|
||||||
|
import javax.ejb.TransactionManagement;
|
||||||
|
import javax.ejb.TransactionManagementType;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.EntityManagerFactory;
|
||||||
|
import javax.persistence.NoResultException;
|
||||||
|
import javax.persistence.NonUniqueResultException;
|
||||||
|
import javax.persistence.TypedQuery;
|
||||||
|
import javax.ws.rs.BadRequestException;
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
|
import javax.ws.rs.FormParam;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.MatrixParam;
|
||||||
|
import javax.ws.rs.NotFoundException;
|
||||||
|
import javax.ws.rs.OPTIONS;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.QueryParam;
|
||||||
|
import javax.ws.rs.WebApplicationException;
|
||||||
|
import javax.ws.rs.core.CacheControl;
|
||||||
|
import javax.ws.rs.core.Context;
|
||||||
|
import javax.ws.rs.core.EntityTag;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Request;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.core.StreamingOutput;
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.NavigableMap;
|
||||||
|
import java.util.NavigableSet;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Path("/pkg")
|
||||||
|
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
|
||||||
|
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
|
||||||
|
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
|
||||||
|
@TransactionManagement(TransactionManagementType.CONTAINER)
|
||||||
|
public class PacmanWebService {
|
||||||
|
|
||||||
|
private NavigableMap<PkgId, PkgTuple> cachedMap;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private EntityManagerFactory emf;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private Logger log;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private PacmanServiceLocal service;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
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) {
|
||||||
|
EntityManager em = emf.createEntityManager();
|
||||||
|
TypedQuery<Object[]> query = em.createQuery(
|
||||||
|
"SELECT pkg.id.name, pkg.id.version, pkg.id.arch, pkg.fileName, pkg.size, pkg.md5sum " +
|
||||||
|
"FROM PkgData pkg ORDER BY pkg.id.name, pkg.id.version, pkg.id.arch",
|
||||||
|
Object[].class);
|
||||||
|
cachedMap = query.getResultStream()
|
||||||
|
.map((Object[] pkg) -> {
|
||||||
|
String name = (String) pkg[0];
|
||||||
|
String version = (String) pkg[1];
|
||||||
|
String arch = (String) pkg[2];
|
||||||
|
String filename = (String) pkg[3];
|
||||||
|
long size = (long) pkg[4];
|
||||||
|
String md5sum = (String) pkg[5];
|
||||||
|
PkgTuple tuple = new PkgTuple();
|
||||||
|
tuple.setFilename(filename);
|
||||||
|
tuple.setSize(size);
|
||||||
|
tuple.setMd5sum(md5sum);
|
||||||
|
PkgId id = new PkgId();
|
||||||
|
id.setName(name);
|
||||||
|
id.setVersion(version);
|
||||||
|
id.setArch(arch);
|
||||||
|
return Tuple2.newInstance(id, tuple);
|
||||||
|
}).collect(
|
||||||
|
CollectionUtils.toUnmodifiableTreeMap(
|
||||||
|
Tuple2<PkgId, PkgTuple>::get_1,
|
||||||
|
Tuple2<PkgId, PkgTuple>::get_2,
|
||||||
|
PkgIdComparator.getComparator())
|
||||||
|
);
|
||||||
|
ctx.getInvalidateCache().set(false);
|
||||||
|
result = cachedMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response manageQueryResult(List<PkgData> list) {
|
||||||
|
return manageQueryResult(list, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response manageQueryResult(List<PkgData> list, boolean singleResult) {
|
||||||
|
if (list.isEmpty()) throw new NotFoundException();
|
||||||
|
else if (singleResult) {
|
||||||
|
if (list.size() == 1) return Response.ok(list.get(0)).build();
|
||||||
|
else throw new NonUniqueResultException("The returned list does not contain a single element");
|
||||||
|
} else {
|
||||||
|
return Response.ok(new PkgDataList(list)).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("searchByName/{name}")
|
||||||
|
public Response searchByName(@PathParam("name") String name) {
|
||||||
|
val em = emf.createEntityManager();
|
||||||
|
if (name == null) throw new WebApplicationException(Response.Status.BAD_REQUEST);
|
||||||
|
String query = String.format("SELECT pkgId.name FROM PkgId pkgId WHERE LOWER(pkgId.name) LIKE '%%%s%%' ORDER BY pkgId.name", name);
|
||||||
|
return Response.ok(em.createQuery(query, String.class).getResultList()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("searchByHash/{md5sum}")
|
||||||
|
public Response searchByHash(@PathParam("md5sum") String md5sum) {
|
||||||
|
return getPackageByHash(md5sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("list/{name}")
|
||||||
|
public Response getPackage(@PathParam("name") String name) {
|
||||||
|
EntityManager em = emf.createEntityManager();
|
||||||
|
TypedQuery<String> query = em.createQuery("SELECT pkg.id.version FROM PkgData pkg WHERE pkg.id.name = :name ORDER BY pkg.id.version", String.class);
|
||||||
|
query.setParameter("name", name);
|
||||||
|
return Response.ok(new StringList(query.getResultList())).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("list/{name}/{version}")
|
||||||
|
public Response getPackage(@PathParam("name") String name, @PathParam("version") String version) {
|
||||||
|
EntityManager em = emf.createEntityManager();
|
||||||
|
TypedQuery<String> query = em.createQuery("SELECT pkg.arch FROM PkgData pkg WHERE pkg.id.name = :name AND pkg.id.version = :version ORDER BY pkg.id.arch", String.class);
|
||||||
|
query.setParameter("name", name);
|
||||||
|
query.setParameter("version", version);
|
||||||
|
return Response.ok(new StringList(query.getResultList())).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("list/{name}/{version}/{arch}")
|
||||||
|
public Response getPackage(@PathParam("name") String name, @PathParam("version") String version, @PathParam("arch") String arch) {
|
||||||
|
EntityManager em = emf.createEntityManager();
|
||||||
|
TypedQuery<PkgData> query = em.createQuery("SELECT pkg FROM PkgData pkg WHERE " + "pkg.id.name = :name AND " + "pkg.id.version = :version AND " + "pkg.id.arch = :arch " + "ORDER BY pkg.arch", PkgData.class);
|
||||||
|
query.setParameter("name", name);
|
||||||
|
query.setParameter("version", version);
|
||||||
|
query.setParameter("arch", arch);
|
||||||
|
return Response.ok(query.getSingleResult()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Path("map")
|
||||||
|
public Response getPackageMap(@Context Request request) {
|
||||||
|
CacheControl cc = new CacheControl();
|
||||||
|
cc.setMaxAge(86400);
|
||||||
|
cc.setMustRevalidate(true);
|
||||||
|
cc.setNoCache(true);
|
||||||
|
|
||||||
|
NavigableMap<PkgId, PkgTuple> cachedMap = getCachedMap();
|
||||||
|
EntityTag etag = new EntityTag(Integer.toString(cachedMap.hashCode()), false);
|
||||||
|
Response.ResponseBuilder builder = request.evaluatePreconditions(etag);
|
||||||
|
if (builder == null) {
|
||||||
|
TreeMap<String, TreeMap<String, Map<String, NavigableSet<PkgTuple>>>> result = cachedMap.entrySet().stream().collect(
|
||||||
|
Collectors.groupingBy((Map.Entry<PkgId, PkgTuple> entry) -> entry.getKey().getArch(),
|
||||||
|
TreeMap::new,
|
||||||
|
Collectors.groupingBy((Map.Entry<PkgId, PkgTuple> entry) -> entry.getKey().getName(),
|
||||||
|
TreeMap::new,
|
||||||
|
Collectors.groupingBy((Map.Entry<PkgId, PkgTuple> entry) -> entry.getKey().getVersion(),
|
||||||
|
() -> new TreeMap<>(VersionComparator.getInstance().reversed()),
|
||||||
|
Collectors.mapping(
|
||||||
|
Map.Entry::getValue,
|
||||||
|
CollectionUtils.toUnmodifiableTreeSet(
|
||||||
|
Comparator.comparing(PkgTuple::getFilename)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
builder = Response.ok(result);
|
||||||
|
builder.tag(etag);
|
||||||
|
}
|
||||||
|
builder.cacheControl(cc);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("hashes")
|
||||||
|
public Response getHashes() {
|
||||||
|
EntityManager em = emf.createEntityManager();
|
||||||
|
TypedQuery<String> query = em.createQuery("SELECT p.md5sum FROM PkgData p", String.class);
|
||||||
|
return Response.ok(new StringList(query.getResultList())).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("files")
|
||||||
|
public Response getFiles() {
|
||||||
|
EntityManager em = emf.createEntityManager();
|
||||||
|
TypedQuery<String> query = em.createQuery("SELECT p.fileName FROM PkgData p", String.class);
|
||||||
|
return Response.ok(new StringList(query.getResultList())).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response getPackageByHash(String md5sum) {
|
||||||
|
EntityManager em = emf.createEntityManager();
|
||||||
|
TypedQuery<PkgData> hquery = em.createNamedQuery("searchByHash", PkgData.class);
|
||||||
|
if (md5sum != null) hquery.setParameter("md5sum", md5sum);
|
||||||
|
return manageQueryResult(hquery.getResultList(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response getPackageByFileName(String file) {
|
||||||
|
EntityManager em = emf.createEntityManager();
|
||||||
|
TypedQuery<PkgData> fnquery = em.createNamedQuery("searchByFileName", PkgData.class);
|
||||||
|
fnquery.setParameter("fileName", file);
|
||||||
|
return manageQueryResult(fnquery.getResultList(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("filesize/{filename}")
|
||||||
|
@SneakyThrows
|
||||||
|
public Response getFileSize(@PathParam("filename")String fileName, @Context Request request) {
|
||||||
|
CacheControl cc = new CacheControl();
|
||||||
|
cc.setMaxAge(86400);
|
||||||
|
cc.setMustRevalidate(true);
|
||||||
|
cc.setNoCache(true);
|
||||||
|
EntityTag etag = new EntityTag(Integer.toString(getCachedMap().hashCode()));
|
||||||
|
Response.ResponseBuilder builder = request.evaluatePreconditions(etag);
|
||||||
|
if (builder == null) {
|
||||||
|
java.nio.file.Path res = ctx.getFile(fileName);
|
||||||
|
if (!Files.exists(res)) throw new NotFoundException(String.format("File '%s' was not found", fileName));
|
||||||
|
builder = Response.ok(Files.size(res));
|
||||||
|
builder.tag(etag);
|
||||||
|
}
|
||||||
|
builder.cacheControl(cc);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@GET
|
||||||
|
@Path("download/{filename}")
|
||||||
|
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
|
public Response downloadPackage(@PathParam("filename") String fileName) {
|
||||||
|
EntityManager em = emf.createEntityManager();
|
||||||
|
TypedQuery<PkgData> fnquery = em.createNamedQuery("searchByFileName", PkgData.class);
|
||||||
|
fnquery.setParameter("fileName", fileName);
|
||||||
|
try {
|
||||||
|
PkgData pkg = fnquery.getSingleResult();
|
||||||
|
StreamingOutput stream = (OutputStream output) -> {
|
||||||
|
try(InputStream is = Files.newInputStream(ctx.getFile(pkg))) {
|
||||||
|
JWO.copy(is, output, 0x10000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return Response.ok(stream).header("Content-Length", Files.size(ctx.getFile(pkg))).build();
|
||||||
|
} catch(NoResultException nre) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/doYouWantAny")
|
||||||
|
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
|
||||||
|
public Response doYouWantAny(List<String> filenames) {
|
||||||
|
EntityManager em = emf.createEntityManager();
|
||||||
|
Set<String> result = filenames.stream().collect(CollectionUtils.toTreeSet());
|
||||||
|
if (!result.isEmpty()) {
|
||||||
|
TypedQuery<String> query = em.createQuery("SELECT pkg.fileName from PkgData pkg WHERE pkg.fileName in :filenames", String.class);
|
||||||
|
query.setParameter("filenames", filenames);
|
||||||
|
Set<String> toBeRemoved = query.getResultStream().collect(CollectionUtils.toTreeSet());
|
||||||
|
result.removeAll(toBeRemoved);
|
||||||
|
return Response.ok(new StringList(result)).build();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Response.ok(result.toArray()).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/upload")
|
||||||
|
@SneakyThrows
|
||||||
|
@TransactionAttribute(TransactionAttributeType.REQUIRED)
|
||||||
|
@Consumes({"application/x-xz", "application/gzip", "application/x-tar", MediaType.APPLICATION_OCTET_STREAM})
|
||||||
|
public Response createPackage(
|
||||||
|
InputStream input,
|
||||||
|
@MatrixParam("filename") String filename,
|
||||||
|
@Context UriInfo uriInfo) {
|
||||||
|
EntityManager em = emf.createEntityManager();
|
||||||
|
if (filename == null) throw new BadRequestException();
|
||||||
|
java.nio.file.Path file = Files.createTempFile(ctx.getRepoFolder(), filename, null);
|
||||||
|
TypedQuery<PkgData> fquery = em.createNamedQuery("searchByFileName", PkgData.class);
|
||||||
|
fquery.setParameter("fileName", filename);
|
||||||
|
List<PkgData> savedFiles = fquery.getResultList();
|
||||||
|
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>) (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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/search")
|
||||||
|
public List<PkgData> searchPackage(
|
||||||
|
@QueryParam("name") String name,
|
||||||
|
@QueryParam("version") String version,
|
||||||
|
@QueryParam("arch") String arch,
|
||||||
|
@QueryParam("page") int pageNumber,
|
||||||
|
@QueryParam("pageSize") int pageSize,
|
||||||
|
@QueryParam("fileName") String fileName) {
|
||||||
|
return service.searchPackage(name, version, arch, pageNumber, pageSize, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OPTIONS
|
||||||
|
@Path("/downloadTar")
|
||||||
|
@Produces("text/plain; charset=UTF-8")
|
||||||
|
public Response options() {
|
||||||
|
return Response.ok("POST, OPTIONS").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/downloadTar")
|
||||||
|
@Produces("application/x-tar")
|
||||||
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
|
public Response downloadTar(@FormParam("pkgs") String formData) {
|
||||||
|
String[] files = URLDecoder.decode(formData, StandardCharsets.UTF_8).split(" ");
|
||||||
|
Arrays.stream(files)
|
||||||
|
.filter(fileName -> !Files.exists(ctx.getFile(fileName)))
|
||||||
|
.forEach(fileName -> {
|
||||||
|
throw JWO.newThrowable(NotFoundException.class, "Package file '{}' does not exist", fileName);
|
||||||
|
});
|
||||||
|
StreamingOutput stream = new StreamingOutput() {
|
||||||
|
private final byte[] buffer = new byte[0x10000];
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
public void write(OutputStream output) {
|
||||||
|
try(TarArchiveOutputStream taos = new TarArchiveOutputStream(output)) {
|
||||||
|
for (String fname : files) {
|
||||||
|
log.info(fname);
|
||||||
|
java.nio.file.Path file = ctx.getFile(fname);
|
||||||
|
try(InputStream input = Files.newInputStream(file)) {
|
||||||
|
TarArchiveEntry entry = new TarArchiveEntry(fname);
|
||||||
|
entry.setSize(Files.size(file));
|
||||||
|
taos.putArchiveEntry(entry);
|
||||||
|
JWO.copy(input, taos, buffer);
|
||||||
|
taos.closeArchiveEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error(ex.getMessage(), ex);
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return Response.ok(stream).header("Content-Disposition", "attachment; filename=pkgs.tar").build();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,17 @@
|
|||||||
|
package net.woggioni.jpacrepo.service;
|
||||||
|
|
||||||
|
import javax.ws.rs.ApplicationPath;
|
||||||
|
import javax.ws.rs.core.Application;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ApplicationPath("api")
|
||||||
|
public class WxRsApplication extends Application {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Class<?>> getClasses() {
|
||||||
|
Set<Class<?>> result = new HashSet<>();
|
||||||
|
result.add(PacmanWebService.class);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,31 @@
|
|||||||
|
package net.woggioni.jpacrepo.service.wire;
|
||||||
|
|
||||||
|
|
||||||
|
import net.woggioni.jpacrepo.api.model.PkgData;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@XmlRootElement
|
||||||
|
public class PkgDataList extends ArrayList<PkgData> {
|
||||||
|
|
||||||
|
public PkgDataList(List<PkgData> l) {
|
||||||
|
for (PkgData el : l) add(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PkgDataList(PkgData... elements) {
|
||||||
|
for (PkgData el : elements) add(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
@XmlElement(name = "pkgData")
|
||||||
|
List<PkgData> getItems() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setItems(List<PkgData> pkgs) {
|
||||||
|
this.clear();
|
||||||
|
this.addAll(pkgs);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
package net.woggioni.jpacrepo.service.wire;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@XmlRootElement
|
||||||
|
public class PkgTuple {
|
||||||
|
String md5sum;
|
||||||
|
|
||||||
|
String filename;
|
||||||
|
|
||||||
|
long size;
|
||||||
|
}
|
@@ -0,0 +1,28 @@
|
|||||||
|
package net.woggioni.jpacrepo.service.wire;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@XmlRootElement
|
||||||
|
public class StringList extends ArrayList<String> {
|
||||||
|
|
||||||
|
public StringList(Iterable<String> l) {
|
||||||
|
for (String el : l) add(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringList(String... strings) {
|
||||||
|
for (String el : strings) add(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
@XmlElement(name = "pkgData")
|
||||||
|
List<String> getItems() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setItems(List<String> strings) {
|
||||||
|
this.clear();
|
||||||
|
this.addAll(strings);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,12 +1,16 @@
|
|||||||
package net.woggioni.jpacrepo.servlet;
|
package net.woggioni.jpacrepo.servlet;
|
||||||
|
|
||||||
import javax.servlet.ServletContext;
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.ServletOutputStream;
|
import javax.servlet.ServletOutputStream;
|
||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.*;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.Channels;
|
import java.nio.channels.Channels;
|
||||||
@@ -97,8 +101,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
|||||||
* @author Bauke Scholtz
|
* @author Bauke Scholtz
|
||||||
* @since 2.2
|
* @since 2.2
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractFileServlet extends HttpServlet
|
public abstract class AbstractFileServlet extends HttpServlet {
|
||||||
{
|
|
||||||
|
|
||||||
// Constants ------------------------------------------------------------------------------------------------------
|
// Constants ------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -113,16 +116,13 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
private static final String CONTENT_DISPOSITION_HEADER = "%s;filename=\"%2$s\"; filename*=UTF-8''%2$s";
|
private static final String CONTENT_DISPOSITION_HEADER = "%s;filename=\"%2$s\"; filename*=UTF-8''%2$s";
|
||||||
private static final String MULTIPART_BOUNDARY = UUID.randomUUID().toString();
|
private static final String MULTIPART_BOUNDARY = UUID.randomUUID().toString();
|
||||||
|
|
||||||
private static long stream(InputStream input, OutputStream output) throws IOException
|
private static long stream(InputStream input, OutputStream output) throws IOException {
|
||||||
{
|
|
||||||
try (ReadableByteChannel inputChannel = Channels.newChannel(input);
|
try (ReadableByteChannel inputChannel = Channels.newChannel(input);
|
||||||
WritableByteChannel outputChannel = Channels.newChannel(output))
|
WritableByteChannel outputChannel = Channels.newChannel(output)) {
|
||||||
{
|
|
||||||
ByteBuffer buffer = ByteBuffer.allocateDirect(DEFAULT_STREAM_BUFFER_SIZE);
|
ByteBuffer buffer = ByteBuffer.allocateDirect(DEFAULT_STREAM_BUFFER_SIZE);
|
||||||
long size = 0;
|
long size = 0;
|
||||||
|
|
||||||
while (inputChannel.read(buffer) != -1)
|
while (inputChannel.read(buffer) != -1) {
|
||||||
{
|
|
||||||
buffer.flip();
|
buffer.flip();
|
||||||
size += outputChannel.write(buffer);
|
size += outputChannel.write(buffer);
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
@@ -145,32 +145,26 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
* @throws IOException When an I/O error occurs.
|
* @throws IOException When an I/O error occurs.
|
||||||
* @since 2.2
|
* @since 2.2
|
||||||
*/
|
*/
|
||||||
public static long stream(File file, OutputStream output, long start, long length) throws IOException
|
public static long stream(File file, OutputStream output, long start, long length) throws IOException {
|
||||||
{
|
if (start == 0 && length >= file.length()) {
|
||||||
if (start == 0 && length >= file.length())
|
|
||||||
{
|
|
||||||
return stream(new FileInputStream(file), output);
|
return stream(new FileInputStream(file), output);
|
||||||
}
|
}
|
||||||
|
|
||||||
try (FileChannel fileChannel = (FileChannel) Files.newByteChannel(file.toPath(), StandardOpenOption.READ))
|
try (FileChannel fileChannel = (FileChannel) Files.newByteChannel(file.toPath(), StandardOpenOption.READ)) {
|
||||||
{
|
|
||||||
WritableByteChannel outputChannel = Channels.newChannel(output);
|
WritableByteChannel outputChannel = Channels.newChannel(output);
|
||||||
ByteBuffer buffer = ByteBuffer.allocateDirect(DEFAULT_STREAM_BUFFER_SIZE);
|
ByteBuffer buffer = ByteBuffer.allocateDirect(DEFAULT_STREAM_BUFFER_SIZE);
|
||||||
long size = 0;
|
long size = 0;
|
||||||
|
|
||||||
while (fileChannel.read(buffer, start + size) != -1)
|
while (fileChannel.read(buffer, start + size) != -1) {
|
||||||
{
|
|
||||||
buffer.flip();
|
buffer.flip();
|
||||||
|
|
||||||
if (size + buffer.limit() > length)
|
if (size + buffer.limit() > length) {
|
||||||
{
|
|
||||||
buffer.limit((int) (length - size));
|
buffer.limit((int) (length - size));
|
||||||
}
|
}
|
||||||
|
|
||||||
size += outputChannel.write(buffer);
|
size += outputChannel.write(buffer);
|
||||||
|
|
||||||
if (size >= length)
|
if (size >= length) {
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,27 +176,20 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static String encodeURL(String string)
|
private static String encodeURL(String string) {
|
||||||
{
|
if (string == null) {
|
||||||
if (string == null)
|
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
return URLEncoder.encode(string, UTF_8.name());
|
return URLEncoder.encode(string, UTF_8.name());
|
||||||
}
|
} catch (UnsupportedEncodingException e) {
|
||||||
catch (UnsupportedEncodingException e)
|
|
||||||
{
|
|
||||||
throw new UnsupportedOperationException(ERROR_UNSUPPORTED_ENCODING, e);
|
throw new UnsupportedOperationException(ERROR_UNSUPPORTED_ENCODING, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String encodeURI(String string)
|
private static String encodeURI(String string) {
|
||||||
{
|
if (string == null) {
|
||||||
if (string == null)
|
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,74 +205,60 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
// Actions --------------------------------------------------------------------------------------------------------
|
// Actions --------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doHead(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
protected void doHead(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||||
{
|
|
||||||
doRequest(request, response, true);
|
doRequest(request, response, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||||
{
|
|
||||||
doRequest(request, response, false);
|
doRequest(request, response, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doRequest(HttpServletRequest request, HttpServletResponse response, boolean head) throws IOException
|
private void doRequest(HttpServletRequest request, HttpServletResponse response, boolean head) throws IOException {
|
||||||
{
|
|
||||||
response.reset();
|
response.reset();
|
||||||
Resource resource;
|
Resource resource;
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
resource = new Resource(getFile(request));
|
resource = new Resource(getFile(request));
|
||||||
}
|
} catch (IllegalArgumentException e) {
|
||||||
catch (IllegalArgumentException e)
|
|
||||||
{
|
|
||||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
|
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resource.file == null)
|
if (resource.file == null) {
|
||||||
{
|
|
||||||
handleFileNotFound(request, response);
|
handleFileNotFound(request, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preconditionFailed(request, resource))
|
if (preconditionFailed(request, resource)) {
|
||||||
{
|
|
||||||
response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
|
response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCacheHeaders(response, resource, getExpireTime(request, resource.file));
|
setCacheHeaders(response, resource, getExpireTime(request, resource.file));
|
||||||
|
|
||||||
if (notModified(request, resource))
|
if (notModified(request, resource)) {
|
||||||
{
|
|
||||||
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Range> ranges = getRanges(request, resource);
|
List<Range> ranges = getRanges(request, resource);
|
||||||
|
|
||||||
if (ranges == null)
|
if (ranges == null) {
|
||||||
{
|
|
||||||
response.setHeader("Content-Range", "bytes */" + resource.length);
|
response.setHeader("Content-Range", "bytes */" + resource.length);
|
||||||
response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
|
response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ranges.isEmpty())
|
if (!ranges.isEmpty()) {
|
||||||
{
|
|
||||||
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
|
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
ranges.add(new Range(0, resource.length - 1)); // Full content.
|
ranges.add(new Range(0, resource.length - 1)); // Full content.
|
||||||
}
|
}
|
||||||
|
|
||||||
String contentType = setContentHeaders(request, response, resource, ranges);
|
String contentType = setContentHeaders(request, response, resource, ranges);
|
||||||
|
|
||||||
if (head)
|
if (head) {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,8 +288,7 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
* @throws IOException When something fails at I/O level.
|
* @throws IOException When something fails at I/O level.
|
||||||
* @since 2.3
|
* @since 2.3
|
||||||
*/
|
*/
|
||||||
protected void handleFileNotFound(HttpServletRequest request, HttpServletResponse response) throws IOException
|
protected void handleFileNotFound(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||||
{
|
|
||||||
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,8 +301,7 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
* @param file The involved file.
|
* @param file The involved file.
|
||||||
* @return The client cache expire time in seconds (not milliseconds!).
|
* @return The client cache expire time in seconds (not milliseconds!).
|
||||||
*/
|
*/
|
||||||
protected long getExpireTime(HttpServletRequest request, File file)
|
protected long getExpireTime(HttpServletRequest request, File file) {
|
||||||
{
|
|
||||||
return DEFAULT_EXPIRE_TIME_IN_SECONDS;
|
return DEFAULT_EXPIRE_TIME_IN_SECONDS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,15 +315,11 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
* @param file The involved file.
|
* @param file The involved file.
|
||||||
* @return The content type associated with the given HTTP servlet request and file.
|
* @return The content type associated with the given HTTP servlet request and file.
|
||||||
*/
|
*/
|
||||||
protected String getContentType(HttpServletRequest request, File file)
|
protected String getContentType(HttpServletRequest request, File file) {
|
||||||
{
|
|
||||||
String type = request.getServletContext().getMimeType(file.getName());
|
String type = request.getServletContext().getMimeType(file.getName());
|
||||||
if (type != null)
|
if (type != null) {
|
||||||
{
|
|
||||||
return type;
|
return type;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
return "application/octet-stream";
|
return "application/octet-stream";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -370,8 +337,7 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
* @return <code>true</code> if we must force a "Save As" dialog based on the given HTTP servlet request and content
|
* @return <code>true</code> if we must force a "Save As" dialog based on the given HTTP servlet request and content
|
||||||
* type.
|
* type.
|
||||||
*/
|
*/
|
||||||
protected boolean isAttachment(HttpServletRequest request, String contentType)
|
protected boolean isAttachment(HttpServletRequest request, String contentType) {
|
||||||
{
|
|
||||||
String accept = request.getHeader("Accept");
|
String accept = request.getHeader("Accept");
|
||||||
return (!contentType.startsWith("text") && !contentType.startsWith("image")) && (accept == null || !accepts(accept, contentType));
|
return (!contentType.startsWith("text") && !contentType.startsWith("image")) && (accept == null || !accepts(accept, contentType));
|
||||||
}
|
}
|
||||||
@@ -387,8 +353,7 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
* @return The file name to be used in <code>Content-Disposition</code> header.
|
* @return The file name to be used in <code>Content-Disposition</code> header.
|
||||||
* @since 2.3
|
* @since 2.3
|
||||||
*/
|
*/
|
||||||
protected String getAttachmentName(HttpServletRequest request, File file)
|
protected String getAttachmentName(HttpServletRequest request, File file) {
|
||||||
{
|
|
||||||
return file.getName();
|
return file.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,8 +362,7 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
/**
|
/**
|
||||||
* Returns true if it's a conditional request which must return 412.
|
* Returns true if it's a conditional request which must return 412.
|
||||||
*/
|
*/
|
||||||
private boolean preconditionFailed(HttpServletRequest request, Resource resource)
|
private boolean preconditionFailed(HttpServletRequest request, Resource resource) {
|
||||||
{
|
|
||||||
String match = request.getHeader("If-Match");
|
String match = request.getHeader("If-Match");
|
||||||
long unmodified = request.getDateHeader("If-Unmodified-Since");
|
long unmodified = request.getDateHeader("If-Unmodified-Since");
|
||||||
return (match != null) ? !matches(match, resource.eTag) : (unmodified != -1 && modified(unmodified, resource.lastModified));
|
return (match != null) ? !matches(match, resource.eTag) : (unmodified != -1 && modified(unmodified, resource.lastModified));
|
||||||
@@ -407,8 +371,7 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
/**
|
/**
|
||||||
* Set cache headers.
|
* Set cache headers.
|
||||||
*/
|
*/
|
||||||
private void setCacheHeaders(HttpServletResponse response, Resource resource, long expires)
|
private void setCacheHeaders(HttpServletResponse response, Resource resource, long expires) {
|
||||||
{
|
|
||||||
//Servlets.setCacheHeaders(response, expires);
|
//Servlets.setCacheHeaders(response, expires);
|
||||||
response.setHeader("ETag", resource.eTag);
|
response.setHeader("ETag", resource.eTag);
|
||||||
response.setDateHeader("Last-Modified", resource.lastModified);
|
response.setDateHeader("Last-Modified", resource.lastModified);
|
||||||
@@ -417,8 +380,7 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
/**
|
/**
|
||||||
* Returns true if it's a conditional request which must return 304.
|
* Returns true if it's a conditional request which must return 304.
|
||||||
*/
|
*/
|
||||||
private boolean notModified(HttpServletRequest request, Resource resource)
|
private boolean notModified(HttpServletRequest request, Resource resource) {
|
||||||
{
|
|
||||||
String noMatch = request.getHeader("If-None-Match");
|
String noMatch = request.getHeader("If-None-Match");
|
||||||
long modified = request.getDateHeader("If-Modified-Since");
|
long modified = request.getDateHeader("If-Modified-Since");
|
||||||
return (noMatch != null) ? matches(noMatch, resource.eTag) : (modified != -1 && !modified(modified, resource.lastModified));
|
return (noMatch != null) ? matches(noMatch, resource.eTag) : (modified != -1 && !modified(modified, resource.lastModified));
|
||||||
@@ -427,45 +389,34 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
/**
|
/**
|
||||||
* Get requested ranges. If this is null, then we must return 416. If this is empty, then we must return full file.
|
* Get requested ranges. If this is null, then we must return 416. If this is empty, then we must return full file.
|
||||||
*/
|
*/
|
||||||
private List<Range> getRanges(HttpServletRequest request, Resource resource)
|
private List<Range> getRanges(HttpServletRequest request, Resource resource) {
|
||||||
{
|
|
||||||
List<Range> ranges = new ArrayList<>(1);
|
List<Range> ranges = new ArrayList<>(1);
|
||||||
String rangeHeader = request.getHeader("Range");
|
String rangeHeader = request.getHeader("Range");
|
||||||
|
|
||||||
if (rangeHeader == null)
|
if (rangeHeader == null) {
|
||||||
{
|
|
||||||
return ranges;
|
return ranges;
|
||||||
}
|
} else if (!RANGE_PATTERN.matcher(rangeHeader).matches()) {
|
||||||
else if (!RANGE_PATTERN.matcher(rangeHeader).matches())
|
|
||||||
{
|
|
||||||
return null; // Syntax error.
|
return null; // Syntax error.
|
||||||
}
|
}
|
||||||
|
|
||||||
String ifRange = request.getHeader("If-Range");
|
String ifRange = request.getHeader("If-Range");
|
||||||
|
|
||||||
if (ifRange != null && !ifRange.equals(resource.eTag))
|
if (ifRange != null && !ifRange.equals(resource.eTag)) {
|
||||||
{
|
try {
|
||||||
try
|
|
||||||
{
|
|
||||||
long ifRangeTime = request.getDateHeader("If-Range");
|
long ifRangeTime = request.getDateHeader("If-Range");
|
||||||
|
|
||||||
if (ifRangeTime != -1 && modified(ifRangeTime, resource.lastModified))
|
if (ifRangeTime != -1 && modified(ifRangeTime, resource.lastModified)) {
|
||||||
{
|
|
||||||
return ranges;
|
return ranges;
|
||||||
}
|
}
|
||||||
}
|
} catch (IllegalArgumentException ifRangeHeaderIsInvalid) {
|
||||||
catch (IllegalArgumentException ifRangeHeaderIsInvalid)
|
|
||||||
{
|
|
||||||
return ranges;
|
return ranges;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String rangeHeaderPart : rangeHeader.split("=")[1].split(","))
|
for (String rangeHeaderPart : rangeHeader.split("=")[1].split(",")) {
|
||||||
{
|
|
||||||
Range range = parseRange(rangeHeaderPart, resource.length);
|
Range range = parseRange(rangeHeaderPart, resource.length);
|
||||||
|
|
||||||
if (range == null)
|
if (range == null) {
|
||||||
{
|
|
||||||
return null; // Logic error.
|
return null; // Logic error.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,23 +429,18 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
/**
|
/**
|
||||||
* Parse range header part. Returns null if there's a logic error (i.e. start after end).
|
* Parse range header part. Returns null if there's a logic error (i.e. start after end).
|
||||||
*/
|
*/
|
||||||
private Range parseRange(String range, long length)
|
private Range parseRange(String range, long length) {
|
||||||
{
|
|
||||||
long start = sublong(range, 0, range.indexOf('-'));
|
long start = sublong(range, 0, range.indexOf('-'));
|
||||||
long end = sublong(range, range.indexOf('-') + 1, range.length());
|
long end = sublong(range, range.indexOf('-') + 1, range.length());
|
||||||
|
|
||||||
if (start == -1)
|
if (start == -1) {
|
||||||
{
|
|
||||||
start = length - end;
|
start = length - end;
|
||||||
end = length - 1;
|
end = length - 1;
|
||||||
}
|
} else if (end == -1 || end > length - 1) {
|
||||||
else if (end == -1 || end > length - 1)
|
|
||||||
{
|
|
||||||
end = length - 1;
|
end = length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start > end)
|
if (start > end) {
|
||||||
{
|
|
||||||
return null; // Logic error.
|
return null; // Logic error.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,27 +450,22 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
/**
|
/**
|
||||||
* Set content headers.
|
* Set content headers.
|
||||||
*/
|
*/
|
||||||
private String setContentHeaders(HttpServletRequest request, HttpServletResponse response, Resource resource, List<Range> ranges)
|
private String setContentHeaders(HttpServletRequest request, HttpServletResponse response, Resource resource, List<Range> ranges) {
|
||||||
{
|
|
||||||
String contentType = getContentType(request, resource.file);
|
String contentType = getContentType(request, resource.file);
|
||||||
String disposition = isAttachment(request, contentType) ? "attachment" : "inline";
|
String disposition = isAttachment(request, contentType) ? "attachment" : "inline";
|
||||||
String filename = encodeURI(getAttachmentName(request, resource.file));
|
String filename = encodeURI(getAttachmentName(request, resource.file));
|
||||||
response.setHeader("Content-Disposition", String.format(CONTENT_DISPOSITION_HEADER, disposition, filename));
|
response.setHeader("Content-Disposition", String.format(CONTENT_DISPOSITION_HEADER, disposition, filename));
|
||||||
response.setHeader("Accept-Ranges", "bytes");
|
response.setHeader("Accept-Ranges", "bytes");
|
||||||
|
|
||||||
if (ranges.size() == 1)
|
if (ranges.size() == 1) {
|
||||||
{
|
|
||||||
Range range = ranges.get(0);
|
Range range = ranges.get(0);
|
||||||
response.setContentType(contentType);
|
response.setContentType(contentType);
|
||||||
response.setHeader("Content-Length", String.valueOf(range.length));
|
response.setHeader("Content-Length", String.valueOf(range.length));
|
||||||
|
|
||||||
if (response.getStatus() == HttpServletResponse.SC_PARTIAL_CONTENT)
|
if (response.getStatus() == HttpServletResponse.SC_PARTIAL_CONTENT) {
|
||||||
{
|
|
||||||
response.setHeader("Content-Range", "bytes " + range.start + "-" + range.end + "/" + resource.length);
|
response.setHeader("Content-Range", "bytes " + range.start + "-" + range.end + "/" + resource.length);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
response.setContentType("multipart/byteranges; boundary=" + MULTIPART_BOUNDARY);
|
response.setContentType("multipart/byteranges; boundary=" + MULTIPART_BOUNDARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -534,19 +475,14 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
/**
|
/**
|
||||||
* Write given file to response with given content type and ranges.
|
* Write given file to response with given content type and ranges.
|
||||||
*/
|
*/
|
||||||
private void writeContent(HttpServletResponse response, Resource resource, List<Range> ranges, String contentType) throws IOException
|
private void writeContent(HttpServletResponse response, Resource resource, List<Range> ranges, String contentType) throws IOException {
|
||||||
{
|
|
||||||
ServletOutputStream output = response.getOutputStream();
|
ServletOutputStream output = response.getOutputStream();
|
||||||
|
|
||||||
if (ranges.size() == 1)
|
if (ranges.size() == 1) {
|
||||||
{
|
|
||||||
Range range = ranges.get(0);
|
Range range = ranges.get(0);
|
||||||
stream(resource.file, output, range.start, range.length);
|
stream(resource.file, output, range.start, range.length);
|
||||||
}
|
} else {
|
||||||
else
|
for (Range range : ranges) {
|
||||||
{
|
|
||||||
for (Range range : ranges)
|
|
||||||
{
|
|
||||||
output.println();
|
output.println();
|
||||||
output.println("--" + MULTIPART_BOUNDARY);
|
output.println("--" + MULTIPART_BOUNDARY);
|
||||||
output.println("Content-Type: " + contentType);
|
output.println("Content-Type: " + contentType);
|
||||||
@@ -564,8 +500,7 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
/**
|
/**
|
||||||
* Returns true if the given match header matches the given ETag value.
|
* Returns true if the given match header matches the given ETag value.
|
||||||
*/
|
*/
|
||||||
private static boolean matches(String matchHeader, String eTag)
|
private static boolean matches(String matchHeader, String eTag) {
|
||||||
{
|
|
||||||
String[] matchValues = matchHeader.split("\\s*,\\s*");
|
String[] matchValues = matchHeader.split("\\s*,\\s*");
|
||||||
Arrays.sort(matchValues);
|
Arrays.sort(matchValues);
|
||||||
return Arrays.binarySearch(matchValues, eTag) > -1
|
return Arrays.binarySearch(matchValues, eTag) > -1
|
||||||
@@ -575,8 +510,7 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
/**
|
/**
|
||||||
* Returns true if the given modified header is older than the given last modified value.
|
* Returns true if the given modified header is older than the given last modified value.
|
||||||
*/
|
*/
|
||||||
private static boolean modified(long modifiedHeader, long lastModified)
|
private static boolean modified(long modifiedHeader, long lastModified) {
|
||||||
{
|
|
||||||
return (modifiedHeader + ONE_SECOND_IN_MILLIS <= lastModified); // That second is because the header is in seconds, not millis.
|
return (modifiedHeader + ONE_SECOND_IN_MILLIS <= lastModified); // That second is because the header is in seconds, not millis.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -584,8 +518,7 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
* Returns a substring of the given string value from the given begin index to the given end index as a long.
|
* Returns a substring of the given string value from the given begin index to the given end index as a long.
|
||||||
* If the substring is empty, then -1 will be returned.
|
* If the substring is empty, then -1 will be returned.
|
||||||
*/
|
*/
|
||||||
private static long sublong(String value, int beginIndex, int endIndex)
|
private static long sublong(String value, int beginIndex, int endIndex) {
|
||||||
{
|
|
||||||
String substring = value.substring(beginIndex, endIndex);
|
String substring = value.substring(beginIndex, endIndex);
|
||||||
return substring.isEmpty() ? -1 : Long.parseLong(substring);
|
return substring.isEmpty() ? -1 : Long.parseLong(substring);
|
||||||
}
|
}
|
||||||
@@ -593,8 +526,7 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
/**
|
/**
|
||||||
* Returns true if the given accept header accepts the given value.
|
* Returns true if the given accept header accepts the given value.
|
||||||
*/
|
*/
|
||||||
private static boolean accepts(String acceptHeader, String toAccept)
|
private static boolean accepts(String acceptHeader, String toAccept) {
|
||||||
{
|
|
||||||
String[] acceptValues = acceptHeader.split("\\s*(,|;)\\s*");
|
String[] acceptValues = acceptHeader.split("\\s*(,|;)\\s*");
|
||||||
Arrays.sort(acceptValues);
|
Arrays.sort(acceptValues);
|
||||||
return Arrays.binarySearch(acceptValues, toAccept) > -1
|
return Arrays.binarySearch(acceptValues, toAccept) > -1
|
||||||
@@ -607,24 +539,19 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
/**
|
/**
|
||||||
* Convenience class for a file resource.
|
* Convenience class for a file resource.
|
||||||
*/
|
*/
|
||||||
private static class Resource
|
private static class Resource {
|
||||||
{
|
|
||||||
private final File file;
|
private final File file;
|
||||||
private final long length;
|
private final long length;
|
||||||
private final long lastModified;
|
private final long lastModified;
|
||||||
private final String eTag;
|
private final String eTag;
|
||||||
|
|
||||||
public Resource(File file)
|
public Resource(File file) {
|
||||||
{
|
if (file != null && file.isFile()) {
|
||||||
if (file != null && file.isFile())
|
|
||||||
{
|
|
||||||
this.file = file;
|
this.file = file;
|
||||||
length = file.length();
|
length = file.length();
|
||||||
lastModified = file.lastModified();
|
lastModified = file.lastModified();
|
||||||
eTag = String.format(ETAG, encodeURL(file.getName()), lastModified);
|
eTag = String.format(ETAG, encodeURL(file.getName()), lastModified);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
this.file = null;
|
this.file = null;
|
||||||
length = 0;
|
length = 0;
|
||||||
lastModified = 0;
|
lastModified = 0;
|
||||||
@@ -637,14 +564,12 @@ public abstract class AbstractFileServlet extends HttpServlet
|
|||||||
/**
|
/**
|
||||||
* Convenience class for a byte range.
|
* Convenience class for a byte range.
|
||||||
*/
|
*/
|
||||||
private static class Range
|
private static class Range {
|
||||||
{
|
|
||||||
private final long start;
|
private final long start;
|
||||||
private final long end;
|
private final long end;
|
||||||
private final long length;
|
private final long length;
|
||||||
|
|
||||||
public Range(long start, long end)
|
public Range(long start, long end) {
|
||||||
{
|
|
||||||
this.start = start;
|
this.start = start;
|
||||||
this.end = end;
|
this.end = end;
|
||||||
length = end - start + 1;
|
length = end - start + 1;
|
||||||
|
@@ -9,18 +9,16 @@ import java.io.File;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
@WebServlet("/archive/*")
|
@WebServlet("/archive/*")
|
||||||
public class FileServlet extends AbstractFileServlet
|
public class FileServlet extends AbstractFileServlet {
|
||||||
{
|
private final Path root;
|
||||||
private Path root;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public FileServlet(AppConfig ctx){
|
public FileServlet(AppConfig ctx){
|
||||||
root = ctx.repoFolder();
|
root = ctx.getRepoFolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected File getFile(HttpServletRequest request) throws IllegalArgumentException {
|
protected File getFile(HttpServletRequest request) throws IllegalArgumentException {
|
||||||
return root.resolve(request.getPathInfo().substring(1)).toFile();
|
return root.resolve(request.getPathInfo().substring(1)).toFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -1,21 +1,19 @@
|
|||||||
package net.woggioni.jpacrepo.version;
|
package net.woggioni.jpacrepo.version;
|
||||||
|
|
||||||
import net.woggioni.jpacrepo.pacbase.PkgId;
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import net.woggioni.jpacrepo.api.model.PkgId;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
|
||||||
public class PkgIdComparator implements Comparator<PkgId> {
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
private final Comparator<PkgId> comparator;
|
public class PkgIdComparator {
|
||||||
|
@Getter
|
||||||
public PkgIdComparator() {
|
private static final Comparator<PkgId> comparator;
|
||||||
VersionComparator vc = new VersionComparator();
|
static {
|
||||||
comparator = Comparator.comparing(PkgId::name)
|
comparator = Comparator.comparing(PkgId::getName)
|
||||||
.thenComparing(PkgId::version, vc)
|
.thenComparing(PkgId::getVersion, VersionComparator.getInstance())
|
||||||
.thenComparing(PkgId::arch);
|
.thenComparing(PkgId::getArch);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(PkgId id1, PkgId id2) {
|
|
||||||
return comparator.compare(id1, id2);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
package net.woggioni.jpacrepo.version;
|
package net.woggioni.jpacrepo.version;
|
||||||
|
|
||||||
|
|
||||||
import com.sun.jna.Native;
|
import com.sun.jna.Native;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
|
||||||
@@ -13,10 +17,12 @@ class AlpmLibrary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class VersionComparator implements Comparator<String> {
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
@Override
|
public class VersionComparator {
|
||||||
public int compare(String version1, String version2) {
|
@Getter
|
||||||
return AlpmLibrary.alpm_pkg_vercmp(version1, version2);
|
private static final Comparator<String> instance;
|
||||||
|
static {
|
||||||
|
instance = AlpmLibrary::alpm_pkg_vercmp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
24
src/main/resources/META-INF/persistence.xml
Normal file
24
src/main/resources/META-INF/persistence.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
|
||||||
|
version="2.1">
|
||||||
|
|
||||||
|
<persistence-unit name="jpacrepo_pu" transaction-type="JTA">
|
||||||
|
<class>net.woggioni.jpacrepo.api.model.PkgData</class>
|
||||||
|
<class>net.woggioni.jpacrepo.api.model.PkgId</class>
|
||||||
|
<exclude-unlisted-classes>true</exclude-unlisted-classes>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<property name="org.jboss.logging.provider" value="log4j2"/>
|
||||||
|
<property name="javax.persistence.schema-generation.create-source" value="script-then-metadata"/>
|
||||||
|
<property name="javax.persistence.schema-generation.create-script-source"
|
||||||
|
value="META-INF/sql/CreateSchema.sql"/>
|
||||||
|
<property name="javax.persistence.schema-generation.drop-source" value="metadata-then-script"/>
|
||||||
|
<property name="javax.persistence.schema-generation.drop-script-source"
|
||||||
|
value="META-INF/sql/DropSchema.sql"/>
|
||||||
|
<property name="hibernate.default_schema" value="jpacrepo"/>
|
||||||
|
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQL82Dialect"/>
|
||||||
|
</properties>
|
||||||
|
</persistence-unit>
|
||||||
|
</persistence>
|
@@ -1,41 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.config
|
|
||||||
|
|
||||||
import java.nio.file.{Files, Path, Paths}
|
|
||||||
import java.util.Properties
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
|
|
||||||
import net.woggioni.jpacrepo.pacbase.PkgData
|
|
||||||
import net.woggioni.jpacrepo.persistence.InitialSchemaAction
|
|
||||||
import net.woggioni.jpacrepo.utils.Utils._
|
|
||||||
|
|
||||||
object AppConfig {
|
|
||||||
|
|
||||||
def apply(propertyFile: String): AppConfig = {
|
|
||||||
val getProperty = new Properties().let { it =>
|
|
||||||
val path = Paths.get(propertyFile)
|
|
||||||
if (Files.exists(path)) {
|
|
||||||
Files.newInputStream(path).use { is =>
|
|
||||||
it.load(is)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(key : String, default : String) => System.getProperty(s"net.woggioni.jpacrepo.$key") match {
|
|
||||||
case null => it.getProperty(key, default)
|
|
||||||
case path: String => path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
new AppConfig(
|
|
||||||
repoFolder = getProperty("RepoFolder", "net.woggioni.jpacrepo.RepoFolder")
|
|
||||||
.let { it => Paths.get(it) },
|
|
||||||
initialSchemaAction = getProperty("InitialSchemaAction", "none").let(InitialSchemaAction(_)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case class AppConfig(val repoFolder: Path,
|
|
||||||
val initialSchemaAction: InitialSchemaAction) {
|
|
||||||
|
|
||||||
var invalidateCache = new AtomicBoolean(true)
|
|
||||||
|
|
||||||
def getFile(pkg: PkgData) = repoFolder.resolve(pkg.fileName)
|
|
||||||
|
|
||||||
def getFile(fileName: String) = repoFolder.resolve(fileName)
|
|
||||||
}
|
|
@@ -1,5 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.exception
|
|
||||||
|
|
||||||
class ParseException(msg : String, cause : Throwable) extends RuntimeException(msg, cause) {
|
|
||||||
def this(msg : String) = this(msg, null)
|
|
||||||
}
|
|
@@ -1,48 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.factory
|
|
||||||
|
|
||||||
import java.util.Properties
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct
|
|
||||||
import javax.enterprise.inject.Produces
|
|
||||||
import javax.enterprise.inject.spi.InjectionPoint
|
|
||||||
import javax.faces.bean.ApplicationScoped
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.persistence.{EntityManagerFactory, Persistence}
|
|
||||||
import net.woggioni.jpacrepo.config.AppConfig
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
|
|
||||||
class PersistenceUnitFactory {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private var appConfig : AppConfig = _
|
|
||||||
|
|
||||||
private var emf : EntityManagerFactory = _
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
def init(): Unit = {
|
|
||||||
val properties = new Properties()
|
|
||||||
properties.put("javax.persistence.schema-generation.database.action", appConfig.initialSchemaAction.value)
|
|
||||||
emf = Persistence.createEntityManagerFactory("jpacrepo_pu", properties)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Produces
|
|
||||||
private def createEntityManagerFactory = emf
|
|
||||||
}
|
|
||||||
|
|
||||||
class BeanFactory {
|
|
||||||
|
|
||||||
@Produces
|
|
||||||
@ApplicationScoped
|
|
||||||
def produce: AppConfig = {
|
|
||||||
val ctx = AppConfig(
|
|
||||||
System.getProperty("net.woggioni.jpacrepo.configuration.file",
|
|
||||||
"/etc/jpacrepo/server.properties"))
|
|
||||||
ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
@Produces
|
|
||||||
private def createLogger(injectionPoint: InjectionPoint) =
|
|
||||||
LoggerFactory.getLogger(injectionPoint.getMember.getDeclaringClass.getName)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@@ -1,46 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.model
|
|
||||||
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.security.MessageDigest
|
|
||||||
|
|
||||||
object Hasher {
|
|
||||||
|
|
||||||
private val md5 = MessageDigest.getInstance("MD5")
|
|
||||||
|
|
||||||
private def getHash(md : MessageDigest, is: InputStream): Array[Byte] = {
|
|
||||||
val buffer = new Array[Byte](4096)
|
|
||||||
var read = 0
|
|
||||||
while ( {
|
|
||||||
read = is.read(buffer, 0, buffer.length)
|
|
||||||
read >= 0
|
|
||||||
}) {
|
|
||||||
md.update(buffer, 0, read)
|
|
||||||
}
|
|
||||||
md.digest
|
|
||||||
}
|
|
||||||
|
|
||||||
def computeMD5(is: InputStream): String = Hasher.bytesToHex(getHash(md5, is))
|
|
||||||
|
|
||||||
private val hexArray = "0123456789ABCDEF".toCharArray
|
|
||||||
|
|
||||||
def bytesToHex(bytes: Array[Byte]): String = {
|
|
||||||
val hexChars = new Array[Char](bytes.length * 2)
|
|
||||||
var j = 0
|
|
||||||
while (j < bytes.length) {
|
|
||||||
val v = bytes(j) & 0xFF
|
|
||||||
hexChars(j * 2) = hexArray(v >>> 4)
|
|
||||||
hexChars(j * 2 + 1) = hexArray(v & 0x0F)
|
|
||||||
j += 1
|
|
||||||
}
|
|
||||||
new String(hexChars)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Hasher(val algorithm: String) {
|
|
||||||
|
|
||||||
private val md = MessageDigest.getInstance(algorithm)
|
|
||||||
|
|
||||||
def getHash(is : InputStream) = Hasher.getHash(md, is)
|
|
||||||
|
|
||||||
def getHashString(is: InputStream): String = Hasher.bytesToHex(getHash(is))
|
|
||||||
}
|
|
@@ -1,11 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.model
|
|
||||||
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.security.{DigestInputStream, MessageDigest}
|
|
||||||
|
|
||||||
class MD5InputStream(val is: InputStream) extends DigestInputStream(is, MessageDigest.getInstance("md5")) {
|
|
||||||
def digest(): String = {
|
|
||||||
val digest = getMessageDigest.digest
|
|
||||||
Hasher.bytesToHex(digest)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,114 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.model
|
|
||||||
|
|
||||||
import java.io.{BufferedInputStream, InputStream}
|
|
||||||
import java.nio.file.{Files, Path}
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.zip.GZIPInputStream
|
|
||||||
|
|
||||||
import net.woggioni.jpacrepo.exception.ParseException
|
|
||||||
import net.woggioni.jpacrepo.pacbase.{CompressionFormat, PkgData, PkgId}
|
|
||||||
import net.woggioni.jpacrepo.utils.Utils._
|
|
||||||
import net.woggioni.jzstd.ZstdInputStream
|
|
||||||
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream
|
|
||||||
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream
|
|
||||||
|
|
||||||
import scala.io.Source
|
|
||||||
import scala.jdk.CollectionConverters._
|
|
||||||
|
|
||||||
object Parser {
|
|
||||||
|
|
||||||
def parseFile(file: Path, compressionFormat : CompressionFormat): PkgData = {
|
|
||||||
val hasher = new Hasher("MD5")
|
|
||||||
val decompressorStreamConstructor = compressionFormat match {
|
|
||||||
case CompressionFormat.XZ => arg : InputStream => new XZCompressorInputStream(arg)
|
|
||||||
case CompressionFormat.Z_STANDARD => is : InputStream => ZstdInputStream.from(is)
|
|
||||||
case CompressionFormat.GZIP => is : InputStream => new GZIPInputStream(is)
|
|
||||||
case format => throw new ParseException(s"Unsupported compression format '$format'")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val is = new TarArchiveInputStream(
|
|
||||||
decompressorStreamConstructor(
|
|
||||||
new BufferedInputStream(
|
|
||||||
Files.newInputStream(file))))
|
|
||||||
try {
|
|
||||||
var archiveEntry = is.getNextEntry
|
|
||||||
while (archiveEntry != null) {
|
|
||||||
if (archiveEntry.getName == ".PKGINFO") {
|
|
||||||
val buffer = new Array[Byte](archiveEntry.getSize.toInt)
|
|
||||||
is.read(buffer)
|
|
||||||
val metadata = Source.fromBytes(buffer)
|
|
||||||
.getLines()
|
|
||||||
.map(_.trim)
|
|
||||||
.filter(!_.isEmpty)
|
|
||||||
.filter(!_.startsWith("#"))
|
|
||||||
.map(line => {
|
|
||||||
val equals = line.indexOf("=")
|
|
||||||
if (equals < 0) {
|
|
||||||
throw new ParseException(s"Error parsing .PKGINFO file in '${file}'")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
(line.substring(0, equals).trim, line.substring(equals + 1, line.length).trim)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.to(LazyList)
|
|
||||||
.groupBy(_._1)
|
|
||||||
.map(pair => pair._1 -> pair._2.map(_._2).toList)
|
|
||||||
val data = new PkgData
|
|
||||||
data.id = new PkgId
|
|
||||||
for (pair <- metadata) {
|
|
||||||
val (key, value) = pair
|
|
||||||
key match {
|
|
||||||
case "size" =>
|
|
||||||
data.size = value.head.toLong
|
|
||||||
case "arch" =>
|
|
||||||
data.id.arch = value.head
|
|
||||||
case "replaces" =>
|
|
||||||
data.replaces = value.toSet.asJava
|
|
||||||
case "packager" =>
|
|
||||||
data.packager = value.head
|
|
||||||
case "url" =>
|
|
||||||
data.url = value.head
|
|
||||||
case "pkgname" =>
|
|
||||||
data.id.name = value.head
|
|
||||||
case "builddate" =>
|
|
||||||
data.buildDate = new Date(value.head.toLong * 1000)
|
|
||||||
case "license" =>
|
|
||||||
data.license = value.head
|
|
||||||
case "pkgver" =>
|
|
||||||
data.id.version = value.head
|
|
||||||
case "pkgdesc" =>
|
|
||||||
data.description = value.head
|
|
||||||
case "provides" =>
|
|
||||||
data.provides = value.toSet.asJava
|
|
||||||
case "conflict" =>
|
|
||||||
data.conflict = value.toSet.asJava
|
|
||||||
case "backup" =>
|
|
||||||
data.backup = value.toSet.asJava
|
|
||||||
case "optdepend" =>
|
|
||||||
data.optdepend = value.toSet.asJava
|
|
||||||
case "depend" =>
|
|
||||||
data.depend = value.toSet.asJava
|
|
||||||
case "makedepend" =>
|
|
||||||
data.makedepend = value.toSet.asJava
|
|
||||||
case "makepkgopt" =>
|
|
||||||
data.makeopkgopt = value.toSet.asJava
|
|
||||||
case "pkgbase" =>
|
|
||||||
data.base = value.head
|
|
||||||
case _ =>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data.md5sum = Files.newInputStream(file).use(hasher.getHashString)
|
|
||||||
data.fileName = file.getFileName.toString
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
archiveEntry = is.getNextEntry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new ParseException(s".PKGINFO file not found in '$file'")
|
|
||||||
} finally {
|
|
||||||
is.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,32 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.pacbase
|
|
||||||
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
import net.woggioni.jpacrepo.exception.ParseException
|
|
||||||
import net.woggioni.jwo.JWO
|
|
||||||
|
|
||||||
sealed class CompressionFormat(val extension : String) {
|
|
||||||
override def toString: String = extension
|
|
||||||
}
|
|
||||||
|
|
||||||
object CompressionFormat {
|
|
||||||
private val map = LazyList(XZ, GZIP, Z_STANDARD)
|
|
||||||
.map(v => v.extension -> v).toMap
|
|
||||||
|
|
||||||
def apply(text : String) : Option[CompressionFormat] = map.get(text)
|
|
||||||
def values() = map.values
|
|
||||||
|
|
||||||
def guess(file : Path) = {
|
|
||||||
val extension = JWO.splitExtension(file).orElseThrow(() =>
|
|
||||||
new ParseException(s"Unable to parse file extension for '${file.getFileName}'")
|
|
||||||
)._2
|
|
||||||
CompressionFormat(extension.substring(1)) match {
|
|
||||||
case Some(compressionFormat) => compressionFormat
|
|
||||||
case None => throw new IllegalArgumentException(s"Unknown compression format for file extension '$extension'")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case object XZ extends CompressionFormat("xz")
|
|
||||||
case object GZIP extends CompressionFormat("gz")
|
|
||||||
case object Z_STANDARD extends CompressionFormat("zst")
|
|
||||||
}
|
|
@@ -1,8 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.pacbase
|
|
||||||
|
|
||||||
import net.woggioni.jpacrepo.version.VersionComparator
|
|
||||||
|
|
||||||
class VersionOrdering extends Ordering[String] {
|
|
||||||
private val comparator = new VersionComparator
|
|
||||||
override def compare(x: String, y: String): Int = comparator.compare(x, y)
|
|
||||||
}
|
|
@@ -1,78 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.pacbase
|
|
||||||
|
|
||||||
import java.util
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
import javax.persistence._
|
|
||||||
import javax.xml.bind.annotation.{XmlAccessType, XmlAccessorType, XmlRootElement}
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Access(AccessType.FIELD)
|
|
||||||
@NamedQueries(value = Array[NamedQuery](
|
|
||||||
new NamedQuery(name = "searchByFileName", query = "SELECT p FROM PkgData p WHERE p.fileName = :fileName"),
|
|
||||||
new NamedQuery(name = "searchByName", query = "SELECT p FROM PkgData p WHERE p.id.name = :name"),
|
|
||||||
new NamedQuery(name = "searchById", query = "SELECT p FROM PkgData p WHERE p.id = :id"),
|
|
||||||
new NamedQuery(name = "searchByHash", query = "SELECT p FROM PkgData p WHERE p.md5sum = :md5sum")
|
|
||||||
))
|
|
||||||
@Table(indexes = Array(
|
|
||||||
new Index(columnList = "md5sum", unique = true),
|
|
||||||
new Index(columnList = "fileName", unique = true))
|
|
||||||
)
|
|
||||||
@XmlRootElement
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
class PkgData {
|
|
||||||
|
|
||||||
@EmbeddedId
|
|
||||||
var id: PkgId = _
|
|
||||||
|
|
||||||
var base: String = _
|
|
||||||
|
|
||||||
var description: String = _
|
|
||||||
|
|
||||||
var url: String = _
|
|
||||||
|
|
||||||
@Temporal(TemporalType.TIMESTAMP)
|
|
||||||
var buildDate: Date = _
|
|
||||||
|
|
||||||
var packager: String = _
|
|
||||||
|
|
||||||
var size = 0L
|
|
||||||
|
|
||||||
var license: String = _
|
|
||||||
|
|
||||||
var md5sum: String = _
|
|
||||||
|
|
||||||
var fileName: String = _
|
|
||||||
|
|
||||||
@ElementCollection(fetch = FetchType.EAGER)
|
|
||||||
var replaces: util.Set[String] = _
|
|
||||||
|
|
||||||
@ElementCollection(fetch = FetchType.EAGER)
|
|
||||||
var conflict: util.Set[String] = _
|
|
||||||
|
|
||||||
@ElementCollection(fetch = FetchType.EAGER)
|
|
||||||
var provides: util.Set[String] = _
|
|
||||||
|
|
||||||
@ElementCollection(fetch = FetchType.EAGER)
|
|
||||||
var depend: util.Set[String] = _
|
|
||||||
|
|
||||||
@ElementCollection(fetch = FetchType.EAGER)
|
|
||||||
var optdepend: util.Set[String] = _
|
|
||||||
|
|
||||||
@ElementCollection(fetch = FetchType.EAGER)
|
|
||||||
var makedepend: util.Set[String] = _
|
|
||||||
|
|
||||||
@ElementCollection(fetch = FetchType.EAGER)
|
|
||||||
var makeopkgopt: util.Set[String] = _
|
|
||||||
|
|
||||||
@ElementCollection(fetch = FetchType.EAGER)
|
|
||||||
var backup: util.Set[String] = _
|
|
||||||
|
|
||||||
@Temporal(TemporalType.TIMESTAMP)
|
|
||||||
var updTimestamp: Date = _
|
|
||||||
|
|
||||||
@PreUpdate
|
|
||||||
@PrePersist
|
|
||||||
private def writeTimestamp(): Unit = updTimestamp = new Date
|
|
||||||
}
|
|
||||||
|
|
@@ -1,31 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.pacbase
|
|
||||||
|
|
||||||
import javax.persistence.{Access, AccessType, Embeddable}
|
|
||||||
import javax.xml.bind.annotation.{XmlAccessType, XmlAccessorType, XmlRootElement}
|
|
||||||
|
|
||||||
@Embeddable
|
|
||||||
@Access(AccessType.FIELD)
|
|
||||||
@XmlRootElement
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
class PkgId extends Serializable {
|
|
||||||
var name : String = null
|
|
||||||
|
|
||||||
var version : String = null
|
|
||||||
|
|
||||||
var arch : String = null
|
|
||||||
|
|
||||||
override def equals(obj: Any): Boolean = {
|
|
||||||
obj match {
|
|
||||||
case null => false
|
|
||||||
case pkgId : PkgId => name == pkgId.name && version == pkgId.version && arch == pkgId.arch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override def hashCode(): Int = {
|
|
||||||
var result : Int = 0
|
|
||||||
if(name != null) result ^= name.hashCode
|
|
||||||
if(version != null) result ^= version.hashCode
|
|
||||||
if(arch != null) result ^= arch.hashCode
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,19 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.persistence
|
|
||||||
|
|
||||||
sealed class InitialSchemaAction(val value : String) {
|
|
||||||
override def toString: String = value
|
|
||||||
}
|
|
||||||
|
|
||||||
object InitialSchemaAction {
|
|
||||||
private val map = LazyList(NONE, CREATE, DROP, DROP_AND_CREATE)
|
|
||||||
.map(v => v.value -> v).toMap
|
|
||||||
|
|
||||||
def apply(text : String) : InitialSchemaAction = map(text)
|
|
||||||
def values() = map.values
|
|
||||||
|
|
||||||
case object NONE extends InitialSchemaAction("none")
|
|
||||||
case object CREATE extends InitialSchemaAction("create")
|
|
||||||
case object DROP extends InitialSchemaAction("drop")
|
|
||||||
case object DROP_AND_CREATE extends InitialSchemaAction("drop-and-create")
|
|
||||||
}
|
|
||||||
|
|
@@ -1,174 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.service
|
|
||||||
|
|
||||||
import java.nio.file.{Files, Path}
|
|
||||||
import java.util
|
|
||||||
import java.util.{Calendar, Date}
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct
|
|
||||||
import javax.ejb._
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.persistence._
|
|
||||||
import net.woggioni.jpacrepo.config.AppConfig
|
|
||||||
import net.woggioni.jpacrepo.model.Parser
|
|
||||||
import net.woggioni.jpacrepo.pacbase.{CompressionFormat, PkgData}
|
|
||||||
import net.woggioni.jpacrepo.persistence.QueryEngine
|
|
||||||
import org.slf4j.Logger
|
|
||||||
|
|
||||||
import scala.jdk.CollectionConverters._
|
|
||||||
|
|
||||||
@Remote
|
|
||||||
trait PacmanServiceRemote {
|
|
||||||
|
|
||||||
def syncDB(): Unit
|
|
||||||
|
|
||||||
def deletePackage(filename: String): Unit
|
|
||||||
}
|
|
||||||
|
|
||||||
@Local
|
|
||||||
trait PacmanServiceView extends PacmanServiceRemote {
|
|
||||||
|
|
||||||
def countResults(name: String, version: String, arch: String): Long
|
|
||||||
|
|
||||||
def searchPackage(name: String, version: String, arch: String, page: Int, pageSize: Int, fileName: String): util.List[PkgData]
|
|
||||||
}
|
|
||||||
|
|
||||||
@Startup
|
|
||||||
@Singleton
|
|
||||||
@Lock(LockType.READ)
|
|
||||||
@TransactionManagement(TransactionManagementType.CONTAINER)
|
|
||||||
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
|
|
||||||
@Local(Array(classOf[PacmanServiceView]))
|
|
||||||
@Remote(Array(classOf[PacmanServiceRemote]))
|
|
||||||
class PacmanServiceEJB extends PacmanServiceView {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private var emf: EntityManagerFactory = _
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private var ctx: AppConfig = _
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private var logger: Logger = _
|
|
||||||
|
|
||||||
final private val nameQuery = "SELECT pname FROM PkgName pname WHERE id = :name"
|
|
||||||
|
|
||||||
final private val hashQuery = "SELECT pdata FROM PkgData pdata WHERE md5sum = :md5sum"
|
|
||||||
final private val hashQueryCount = "SELECT count(pdata) FROM PkgData pdata WHERE md5sum = :md5sum"
|
|
||||||
|
|
||||||
private def deletePkgData(em: EntityManager, pkgData: PkgData): Unit = {
|
|
||||||
if (pkgData.depend != null) pkgData.depend.asScala.foreach(em.remove)
|
|
||||||
if (pkgData.replaces != null) pkgData.replaces.asScala.foreach(em.remove)
|
|
||||||
if (pkgData.conflict != null) pkgData.conflict.asScala.foreach(em.remove)
|
|
||||||
if (pkgData.provides != null) pkgData.provides.asScala.foreach(em.remove)
|
|
||||||
if (pkgData.makedepend != null) pkgData.makedepend.asScala.foreach(em.remove)
|
|
||||||
if (pkgData.makeopkgopt != null) pkgData.makeopkgopt.asScala.foreach(em.remove)
|
|
||||||
if (pkgData.backup != null) pkgData.backup.asScala.foreach(em.remove)
|
|
||||||
em.remove(pkgData)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Asynchronous
|
|
||||||
@TransactionAttribute(TransactionAttributeType.REQUIRED)
|
|
||||||
@Schedule(hour = "4", minute = "00", persistent = false)
|
|
||||||
override def syncDB() = {
|
|
||||||
val em = emf.createEntityManager
|
|
||||||
logger.info("Starting repository cleanup")
|
|
||||||
//Elimina i pacchetti sul DB che non esistono più nel filesystem
|
|
||||||
logger.info("Searching for packages that are no more in the filesystem")
|
|
||||||
val listaDB = em.createQuery("SELECT p.fileName FROM PkgData p", classOf[String]).getResultList
|
|
||||||
logger.info("Got list of filenames from db")
|
|
||||||
val knownPkg = listaDB
|
|
||||||
.asScala
|
|
||||||
.filter(fileName => {
|
|
||||||
val file = ctx.getFile(fileName)
|
|
||||||
val result = Files.exists(file)
|
|
||||||
if (!result) {
|
|
||||||
logger.info(s"Removing package ${file.getFileName} which was not found in filesystem")
|
|
||||||
em.createQuery("SELECT p FROM PkgData p WHERE p.fileName = :fileName", classOf[PkgData])
|
|
||||||
.setParameter("fileName", file.getFileName)
|
|
||||||
.getResultList.asScala.foreach((pkgData: PkgData) => deletePkgData(em, pkgData))
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}).toSet
|
|
||||||
logger.info("Searching for new packages or packages that were modified after being added to the database")
|
|
||||||
Files.list(ctx.repoFolder).iterator().asScala.filter(file => {
|
|
||||||
val name = file.getFileName.toString
|
|
||||||
name.endsWith(".pkg.tar.xz") || name.endsWith(".pkg.tar.zst")
|
|
||||||
}).foreach(file => {
|
|
||||||
if (!knownPkg.contains(file.getFileName.toString) || {
|
|
||||||
val query = em.createQuery("SELECT p.updTimestamp FROM PkgData p WHERE filename = :filename", classOf[Date])
|
|
||||||
query.setParameter("filename", file.getFileName.toString)
|
|
||||||
val result = query.getSingleResult
|
|
||||||
Files.getLastModifiedTime(file).toMillis > result.getTime
|
|
||||||
}) {
|
|
||||||
try {
|
|
||||||
parseFile(em, file)
|
|
||||||
} catch {
|
|
||||||
case e: Exception =>
|
|
||||||
logger.error(s"Error parsing '${file.toAbsolutePath}'", e)
|
|
||||||
if (em.getTransaction.getRollbackOnly) throw e
|
|
||||||
else Files.delete(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
logger.info("Removing obsolete packages")
|
|
||||||
deleteOld(em)
|
|
||||||
logger.info("Repository cleanup completed successfully")
|
|
||||||
ctx.invalidateCache.set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def parseFile(em: EntityManager, file: Path) = {
|
|
||||||
val hquery = em.createQuery(hashQueryCount, classOf[java.lang.Long])
|
|
||||||
val data = Parser.parseFile(file, CompressionFormat.guess(file))
|
|
||||||
hquery.setParameter("md5sum", data.md5sum)
|
|
||||||
if (hquery.getSingleResult == 0) {
|
|
||||||
val fquery = em.createQuery("SELECT p FROM PkgData p WHERE p.fileName = :fileName", classOf[PkgData])
|
|
||||||
fquery.setParameter("fileName", file.getFileName.toString)
|
|
||||||
fquery.getResultList.forEach(deletePkgData(em, _))
|
|
||||||
em.persist(data)
|
|
||||||
logger.info(s"Persisting package ${file.getFileName}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TransactionAttribute(TransactionAttributeType.REQUIRED)
|
|
||||||
override def deletePackage(filename: String): Unit = {
|
|
||||||
val em = emf.createEntityManager
|
|
||||||
deletePackage(em, filename)
|
|
||||||
logger.info(s"Package $filename has been deleted")
|
|
||||||
}
|
|
||||||
|
|
||||||
private def deletePackage(em: EntityManager, filename: String): Unit = {
|
|
||||||
val fquery = em.createQuery("SELECT p FROM PkgData p WHERE fileName = :fileName", classOf[PkgData])
|
|
||||||
fquery.setParameter("fileName", filename)
|
|
||||||
val savedFiles = fquery.getResultList
|
|
||||||
if (savedFiles.size == 0) {
|
|
||||||
throw new RuntimeException(String.format("Package with name %s not found", filename))
|
|
||||||
}
|
|
||||||
val pkg = fquery.getResultList.get(0)
|
|
||||||
Files.delete(ctx.getFile(pkg))
|
|
||||||
em.remove(pkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
final private val deleteQuery = "SELECT p.fileName FROM PkgData p WHERE p.buildDate < :cutoff and p.id.name in \n" + "(SELECT p2.id.name FROM PkgData p2 GROUP BY p2.id.name HAVING count(p2.id.name) > :minVersions\n)"
|
|
||||||
|
|
||||||
private def deleteOld(em: EntityManager): Unit = {
|
|
||||||
val query = em.createQuery(deleteQuery, classOf[String])
|
|
||||||
val cutoff = Calendar.getInstance
|
|
||||||
cutoff.add(Calendar.YEAR, -2)
|
|
||||||
query.setParameter("cutoff", cutoff.getTime)
|
|
||||||
query.setParameter("minVersions", 2.toLong)
|
|
||||||
val list = query.getResultList
|
|
||||||
list.forEach(deletePackage(_))
|
|
||||||
}
|
|
||||||
|
|
||||||
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
|
|
||||||
override def countResults(name: String, version: String, arch: String): Long = {
|
|
||||||
val em = emf.createEntityManager
|
|
||||||
QueryEngine.countResults(em, name, version, arch)
|
|
||||||
}
|
|
||||||
|
|
||||||
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
|
|
||||||
override def searchPackage(name: String, version: String, arch: String, pageNumber: Int, pageSize: Int, fileName: String) = {
|
|
||||||
val em = emf.createEntityManager
|
|
||||||
QueryEngine.searchPackage(em, name, version, arch, pageNumber, pageSize, null)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,448 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.service
|
|
||||||
|
|
||||||
import java.io._
|
|
||||||
import java.net.URI
|
|
||||||
import java.nio.file.{Files, Paths}
|
|
||||||
import java.nio.file.StandardCopyOption.ATOMIC_MOVE
|
|
||||||
import java.util
|
|
||||||
|
|
||||||
import javax.ejb._
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.persistence._
|
|
||||||
import javax.ws.rs._
|
|
||||||
import javax.ws.rs.core._
|
|
||||||
import javax.xml.bind.annotation.{XmlElement, XmlRootElement}
|
|
||||||
import net.woggioni.jpacrepo.config.AppConfig
|
|
||||||
import net.woggioni.jpacrepo.model.Parser
|
|
||||||
import net.woggioni.jpacrepo.pacbase.{CompressionFormat, PkgData, PkgId, VersionOrdering}
|
|
||||||
import net.woggioni.jpacrepo.utils.Utils._
|
|
||||||
import net.woggioni.jpacrepo.version.{PkgIdComparator, VersionComparator}
|
|
||||||
import org.apache.commons.compress.archivers.tar.{TarArchiveEntry, TarArchiveOutputStream}
|
|
||||||
import org.slf4j.Logger
|
|
||||||
|
|
||||||
import scala.beans.BeanProperty
|
|
||||||
import scala.collection.SortedMap
|
|
||||||
import scala.collection.immutable.TreeMap
|
|
||||||
import scala.jdk.CollectionConverters._
|
|
||||||
|
|
||||||
@ApplicationPath("api")
|
|
||||||
class ApplicationConfig() extends Application {
|
|
||||||
|
|
||||||
val classes: Set[Class[_]] = Set(classOf[PacmanWebService])
|
|
||||||
|
|
||||||
override def getClasses = classes.asJava
|
|
||||||
}
|
|
||||||
|
|
||||||
object PacmanWebService {
|
|
||||||
val pkgIdOrdering: Ordering[PkgId] = Ordering.comparatorToOrdering(new PkgIdComparator)
|
|
||||||
val versionOrdering: Ordering[String] = Ordering.comparatorToOrdering(new VersionComparator)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@XmlRootElement
|
|
||||||
class PkgDataList extends util.ArrayList[PkgData] {
|
|
||||||
|
|
||||||
def this(l: util.List[PkgData]) {
|
|
||||||
this()
|
|
||||||
l.forEach(el => add(el))
|
|
||||||
}
|
|
||||||
|
|
||||||
def this(elements: PkgData*) {
|
|
||||||
this()
|
|
||||||
elements.foreach(el => add(el))
|
|
||||||
}
|
|
||||||
|
|
||||||
@XmlElement(name = "pkgData")
|
|
||||||
def getItems: util.List[PkgData] = this
|
|
||||||
|
|
||||||
def setItems(pkgs: util.List[PkgData]): Unit = {
|
|
||||||
this.clear()
|
|
||||||
this.addAll(pkgs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@XmlRootElement
|
|
||||||
class StringList extends util.ArrayList[String] {
|
|
||||||
|
|
||||||
def this(l: util.List[String]) {
|
|
||||||
this()
|
|
||||||
l.forEach(el => add(el))
|
|
||||||
}
|
|
||||||
|
|
||||||
def this(elements: String*) {
|
|
||||||
this()
|
|
||||||
elements.foreach(el => add(el))
|
|
||||||
}
|
|
||||||
|
|
||||||
def this(elements: Iterable[String]) {
|
|
||||||
this()
|
|
||||||
elements.foreach(el => add(el))
|
|
||||||
}
|
|
||||||
|
|
||||||
@XmlElement(name = "string")
|
|
||||||
def getItems: util.List[String] = this
|
|
||||||
|
|
||||||
def setItems(pkgs: util.List[String]): Unit = {
|
|
||||||
this.clear()
|
|
||||||
this.addAll(pkgs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@XmlRootElement
|
|
||||||
class PkgTuple {
|
|
||||||
@BeanProperty
|
|
||||||
var md5sum: String = _
|
|
||||||
|
|
||||||
@BeanProperty
|
|
||||||
var filename: String = _
|
|
||||||
|
|
||||||
@BeanProperty
|
|
||||||
var size: Long = _
|
|
||||||
}
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Path("/pkg")
|
|
||||||
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
|
|
||||||
@Produces(Array(MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON))
|
|
||||||
@Consumes(Array(MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON))
|
|
||||||
@TransactionManagement(TransactionManagementType.CONTAINER)
|
|
||||||
class PacmanWebService {
|
|
||||||
|
|
||||||
private var cachedMap: SortedMap[PkgId, PkgTuple] = _
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private var emf: EntityManagerFactory = _
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private var log: Logger = _
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private var service: PacmanServiceView = _
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private var ctx: AppConfig = _
|
|
||||||
|
|
||||||
private def getCachedMap: SortedMap[PkgId, PkgTuple] = {
|
|
||||||
var result: SortedMap[PkgId, PkgTuple] = null
|
|
||||||
if (!ctx.invalidateCache.get()) {
|
|
||||||
result = cachedMap
|
|
||||||
}
|
|
||||||
if (result == null) {
|
|
||||||
synchronized {
|
|
||||||
result = cachedMap
|
|
||||||
if (result == null) {
|
|
||||||
val em = emf.createEntityManager()
|
|
||||||
val query = em.createQuery(
|
|
||||||
"SELECT pkg.id.name, pkg.id.version, pkg.id.arch, pkg.fileName, pkg.size, pkg.md5sum " +
|
|
||||||
"FROM PkgData pkg ORDER BY pkg.id.name, pkg.id.version, pkg.id.arch",
|
|
||||||
classOf[Array[AnyRef]])
|
|
||||||
val stream = query.getResultList
|
|
||||||
.asScala
|
|
||||||
.to(LazyList)
|
|
||||||
.map(pkg => {
|
|
||||||
val name: String = pkg(0).asInstanceOf[String]
|
|
||||||
val version: String = pkg(1).asInstanceOf[String]
|
|
||||||
val arch: String = pkg(2).asInstanceOf[String]
|
|
||||||
val filename: String = pkg(3).asInstanceOf[String]
|
|
||||||
val size: Long = pkg(4).asInstanceOf[Long]
|
|
||||||
val md5sum: String = pkg(5).asInstanceOf[String]
|
|
||||||
val tuple: PkgTuple = new PkgTuple
|
|
||||||
tuple.filename = filename
|
|
||||||
tuple.size = size
|
|
||||||
tuple.md5sum = md5sum
|
|
||||||
val id = new PkgId()
|
|
||||||
id.name = name
|
|
||||||
id.version = version
|
|
||||||
id.arch = arch
|
|
||||||
id -> tuple
|
|
||||||
})
|
|
||||||
cachedMap = TreeMap.from(stream)(PacmanWebService.pkgIdOrdering)
|
|
||||||
ctx.invalidateCache.set(false)
|
|
||||||
result = cachedMap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("searchByName/{name}")
|
|
||||||
def searchByName(@PathParam("name") name: String): Response = {
|
|
||||||
val em = emf.createEntityManager()
|
|
||||||
if (name == null) throw new WebApplicationException(Response.Status.BAD_REQUEST)
|
|
||||||
val query: String = String.format("SELECT pkgId.name FROM PkgId pkgId WHERE LOWER(pkgId.name) LIKE '%%%s%%' ORDER BY pkgId.name", name)
|
|
||||||
Response.ok(em.createQuery(query, classOf[String]).getResultList).build
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("searchByHash/{md5sum}")
|
|
||||||
def searchByHash(@PathParam("md5sum") md5sum: String): Response = getPackageByHash(md5sum)
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("list/{name}")
|
|
||||||
def getPackage(@PathParam("name") name: String): Response = {
|
|
||||||
val em = emf.createEntityManager()
|
|
||||||
val query = em.createQuery("SELECT pkg.id.version FROM PkgData pkg WHERE pkg.id.name = :name ORDER BY pkg.id.version", classOf[String])
|
|
||||||
query.setParameter("name", name)
|
|
||||||
Response.ok(new StringList(query.getResultList)).build
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("list/{name}/{version}")
|
|
||||||
def getPackage(@PathParam("name") name: String, @PathParam("version") version: String): Response = {
|
|
||||||
val em = emf.createEntityManager()
|
|
||||||
val query = em.createQuery("SELECT pkg.arch FROM PkgData pkg WHERE pkg.id.name = :name AND pkg.id.version = :version ORDER BY pkg.id.arch", classOf[String])
|
|
||||||
query.setParameter("name", name)
|
|
||||||
query.setParameter("version", version)
|
|
||||||
Response.ok(new StringList(query.getResultList)).build
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("list/{name}/{version}/{arch}")
|
|
||||||
def getPackage(@PathParam("name") name: String, @PathParam("version") version: String, @PathParam("arch") arch: String): Response = {
|
|
||||||
val em = emf.createEntityManager()
|
|
||||||
val query: TypedQuery[PkgData] = em.createQuery("SELECT pkg FROM PkgData pkg WHERE " + "pkg.id.name = :name AND " + "pkg.id.version = :version AND " + "pkg.id.arch = :arch " + "ORDER BY pkg.arch", classOf[PkgData])
|
|
||||||
query.setParameter("name", name)
|
|
||||||
query.setParameter("version", version)
|
|
||||||
query.setParameter("arch", arch)
|
|
||||||
Response.ok(query.getSingleResult).build
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
|
||||||
@Path("map")
|
|
||||||
def getPackageMap(@Context request: Request): Response = {
|
|
||||||
val cc: CacheControl = new CacheControl
|
|
||||||
cc.setMaxAge(86400)
|
|
||||||
cc.setMustRevalidate(true)
|
|
||||||
cc.setNoCache(true)
|
|
||||||
|
|
||||||
val cachedMap = getCachedMap
|
|
||||||
val etag: EntityTag = new EntityTag(Integer.toString(cachedMap.hashCode))
|
|
||||||
var builder: Response.ResponseBuilder = request.evaluatePreconditions(etag)
|
|
||||||
if (builder == null) {
|
|
||||||
val result: util.Map[String, util.Map[String, util.Map[String, PkgTuple]]] = cachedMap.to(LazyList)
|
|
||||||
.groupBy(_._1.name).view.mapValues(t =>
|
|
||||||
SortedMap.from(t.groupBy(_._1.version).view.mapValues(
|
|
||||||
_.map(pair => pair._1.arch -> pair._2).to(SortedMap).asJava
|
|
||||||
))(PacmanWebService.versionOrdering.reverse).asJava
|
|
||||||
).toMap.asJava
|
|
||||||
builder = Response.ok(result)
|
|
||||||
builder.tag(etag)
|
|
||||||
}
|
|
||||||
builder.cacheControl(cc)
|
|
||||||
builder.build
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("hashes")
|
|
||||||
def getHashes: Response = {
|
|
||||||
val em = emf.createEntityManager()
|
|
||||||
val query = em.createQuery("SELECT p.md5sum FROM PkgData p", classOf[String])
|
|
||||||
Response.ok(new StringList(query.getResultList)).build
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("files")
|
|
||||||
def getFiles: Response = {
|
|
||||||
val em = emf.createEntityManager()
|
|
||||||
val query = em.createQuery("SELECT p.fileName FROM PkgData p", classOf[String])
|
|
||||||
Response.ok(new StringList(query.getResultList)).build
|
|
||||||
}
|
|
||||||
|
|
||||||
private def getPackageByHash(md5sum: String): Response = {
|
|
||||||
val em = emf.createEntityManager()
|
|
||||||
val hquery = em.createNamedQuery("searchByHash", classOf[PkgData])
|
|
||||||
if (md5sum != null) hquery.setParameter("md5sum", md5sum)
|
|
||||||
manageQueryResult(hquery.getResultList, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def getPackageByFileName(file: String): Response = {
|
|
||||||
val em = emf.createEntityManager()
|
|
||||||
val fnquery: TypedQuery[PkgData] = em.createNamedQuery("searchByFileName", classOf[PkgData])
|
|
||||||
fnquery.setParameter("fileName", file)
|
|
||||||
manageQueryResult(fnquery.getResultList, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("filesize/{filename}")
|
|
||||||
def getFileSize(@PathParam("filename") fileName: String, @Context request: Request): Response = {
|
|
||||||
val cc: CacheControl = new CacheControl
|
|
||||||
cc.setMaxAge(86400)
|
|
||||||
cc.setMustRevalidate(true)
|
|
||||||
cc.setNoCache(true)
|
|
||||||
val etag: EntityTag = new EntityTag(Integer.toString(getCachedMap.hashCode))
|
|
||||||
var builder: Response.ResponseBuilder = request.evaluatePreconditions(etag)
|
|
||||||
if (builder == null) {
|
|
||||||
val res = ctx.getFile(fileName)
|
|
||||||
if (!Files.exists(res)) throw new NotFoundException(String.format("File '%s' was not found", fileName))
|
|
||||||
builder = Response.ok(Files.size(res))
|
|
||||||
builder.tag(etag)
|
|
||||||
}
|
|
||||||
builder.cacheControl(cc)
|
|
||||||
builder.build
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("download/{filename}")
|
|
||||||
@Produces(Array(MediaType.APPLICATION_OCTET_STREAM))
|
|
||||||
def downloadPackage(@PathParam("filename") fileName: String): Response = {
|
|
||||||
val em = emf.createEntityManager()
|
|
||||||
val fnquery: TypedQuery[PkgData] = em.createNamedQuery("searchByFileName", classOf[PkgData])
|
|
||||||
fnquery.setParameter("fileName", fileName)
|
|
||||||
try {
|
|
||||||
val pkg: PkgData = fnquery.getSingleResult
|
|
||||||
val stream: StreamingOutput = (output: OutputStream) => {
|
|
||||||
Files.newInputStream(ctx.getFile(pkg)).use { is =>
|
|
||||||
val bytes: Array[Byte] = new Array[Byte](1024)
|
|
||||||
var read = 0
|
|
||||||
while ( {
|
|
||||||
read = is.read(bytes)
|
|
||||||
read >= 0
|
|
||||||
}) {
|
|
||||||
output.write(bytes, 0, read)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
()
|
|
||||||
}
|
|
||||||
Response.ok(stream).header("Content-Length", Files.size(ctx.getFile(pkg))).build
|
|
||||||
} catch {
|
|
||||||
case _: NoResultException =>
|
|
||||||
throw new NotFoundException
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Path("/doYouWantAny")
|
|
||||||
@Produces(Array(MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON))
|
|
||||||
def doYouWantAny(filenames: util.List[String]): Response = {
|
|
||||||
val em = emf.createEntityManager()
|
|
||||||
val result = Set.from(filenames.asScala)
|
|
||||||
if (result.nonEmpty) {
|
|
||||||
val query = em.createQuery("SELECT pkg.fileName from PkgData pkg WHERE pkg.fileName in :filenames", classOf[String])
|
|
||||||
query.setParameter("filenames", filenames)
|
|
||||||
val toBeRemoved = Set.from(query.getResultList.asScala)
|
|
||||||
Response.ok(new StringList(result -- toBeRemoved)).build
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Response.ok(result.toArray).build
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Path("/upload")
|
|
||||||
@TransactionAttribute(TransactionAttributeType.REQUIRED)
|
|
||||||
@Consumes(Array("application/x-xz", "application/gzip", "application/x-tar", MediaType.APPLICATION_OCTET_STREAM))
|
|
||||||
def createPackage(input: InputStream,
|
|
||||||
@MatrixParam("filename") filename: String,
|
|
||||||
@Context uriInfo: UriInfo): Response = {
|
|
||||||
val em = emf.createEntityManager()
|
|
||||||
if (filename == null) throw new BadRequestException
|
|
||||||
val file = Files.createTempFile(ctx.repoFolder, filename, null)
|
|
||||||
val fquery: TypedQuery[PkgData] = em.createNamedQuery("searchByFileName", classOf[PkgData])
|
|
||||||
fquery.setParameter("fileName", filename)
|
|
||||||
val savedFiles: util.List[PkgData] = fquery.getResultList
|
|
||||||
if (savedFiles.size > 0) Response.notModified.build
|
|
||||||
else {
|
|
||||||
Files.newOutputStream(file).useCatch { os =>
|
|
||||||
val buffer: Array[Byte] = new Array[Byte](0x1000)
|
|
||||||
var read = 0
|
|
||||||
while ( {
|
|
||||||
read = input.read(buffer)
|
|
||||||
read >= 0
|
|
||||||
}) {
|
|
||||||
os.write(buffer, 0, read)
|
|
||||||
}
|
|
||||||
val pkg = Parser.parseFile(file, CompressionFormat.guess(Paths.get(filename)))
|
|
||||||
pkg.fileName = filename
|
|
||||||
Option(em.find(classOf[PkgData], pkg.id)) match {
|
|
||||||
case Some(pkgData) => {
|
|
||||||
em.remove(pkgData)
|
|
||||||
Files.delete(ctx.repoFolder.resolve(pkgData.fileName))
|
|
||||||
}
|
|
||||||
case None =>
|
|
||||||
|
|
||||||
}
|
|
||||||
log.info(s"Persisting package ${pkg.fileName}")
|
|
||||||
em.persist(pkg)
|
|
||||||
val pkgUri: URI = uriInfo.getAbsolutePathBuilder.path(pkg.fileName).build()
|
|
||||||
Files.move(file, ctx.repoFolder.resolve(filename), ATOMIC_MOVE)
|
|
||||||
ctx.invalidateCache.set(true)
|
|
||||||
cachedMap = null
|
|
||||||
Response.created(pkgUri).build
|
|
||||||
} { t: Throwable =>
|
|
||||||
Files.delete(file)
|
|
||||||
throw t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/search")
|
|
||||||
def searchPackage(
|
|
||||||
@QueryParam("name") name: String,
|
|
||||||
@QueryParam("version") version: String,
|
|
||||||
@QueryParam("arch") arch: String,
|
|
||||||
@QueryParam("page") pageNumber: Int,
|
|
||||||
@QueryParam("pageSize") pageSize: Int,
|
|
||||||
@QueryParam("fileName") fileName: String): util.List[PkgData] = {
|
|
||||||
service.searchPackage(name, version, arch, pageNumber, pageSize, fileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
@OPTIONS
|
|
||||||
@Path("/downloadTar")
|
|
||||||
@Produces(Array("text/plain; charset=UTF-8"))
|
|
||||||
def options: Response = Response.ok("POST, OPTIONS").build
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Path("/downloadTar")
|
|
||||||
@Produces(Array("application/x-tar"))
|
|
||||||
@Consumes(Array(MediaType.APPLICATION_FORM_URLENCODED))
|
|
||||||
def downloadTar(@FormParam("pkgs") formData: String): Response = {
|
|
||||||
val files: Array[String] = formData.split(" ")
|
|
||||||
files.find(fileName => !Files.exists(ctx.getFile(fileName))) match {
|
|
||||||
case Some(fileName) => throw new NotFoundException(s"Package file '$fileName' does not exist")
|
|
||||||
case None =>
|
|
||||||
}
|
|
||||||
val stream = new StreamingOutput() {
|
|
||||||
override def write(output: OutputStream): Unit = {
|
|
||||||
val taos: TarArchiveOutputStream = new TarArchiveOutputStream(output)
|
|
||||||
try {
|
|
||||||
for (fname <- files) {
|
|
||||||
val file = ctx.getFile(fname)
|
|
||||||
Files.newInputStream(file).use { input =>
|
|
||||||
val entry: TarArchiveEntry = new TarArchiveEntry(fname)
|
|
||||||
entry.setSize(Files.size(file))
|
|
||||||
taos.putArchiveEntry(entry)
|
|
||||||
val bytes: Array[Byte] = new Array[Byte](1024)
|
|
||||||
var read = 0
|
|
||||||
while ( {
|
|
||||||
read = input.read(bytes)
|
|
||||||
read >= 0
|
|
||||||
}) {
|
|
||||||
taos.write(bytes, 0, read)
|
|
||||||
}
|
|
||||||
taos.closeArchiveEntry()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
taos.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Response.ok(stream).header("Content-Disposition", "attachment; filename=pkgs.tar").build
|
|
||||||
}
|
|
||||||
|
|
||||||
private def manageQueryResult(list: util.List[PkgData]): Response = manageQueryResult(list, false)
|
|
||||||
|
|
||||||
private def manageQueryResult(list: util.List[PkgData], singleResult: Boolean): Response = {
|
|
||||||
|
|
||||||
if (list.isEmpty) throw new NotFoundException
|
|
||||||
else if (singleResult) {
|
|
||||||
if (list.size == 1) Response.ok(list.get(0)).build
|
|
||||||
else throw new NonUniqueResultException("The returned list does not contain a single element")
|
|
||||||
} else {
|
|
||||||
Response.ok(new PkgDataList(list)).build
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,32 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.utils
|
|
||||||
|
|
||||||
object Utils {
|
|
||||||
|
|
||||||
implicit class Use[CLOSEABLE <: AutoCloseable, RESULT](closeable: CLOSEABLE) {
|
|
||||||
def useCatch(cb: CLOSEABLE => RESULT) (exceptionally : Throwable => RESULT): RESULT = {
|
|
||||||
try {
|
|
||||||
cb(closeable)
|
|
||||||
} catch {
|
|
||||||
case t : Throwable => exceptionally(t)
|
|
||||||
} finally {
|
|
||||||
closeable.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def use(cb: CLOSEABLE => RESULT): RESULT = {
|
|
||||||
try {
|
|
||||||
cb(closeable)
|
|
||||||
} finally {
|
|
||||||
closeable.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
implicit class ContextFunctions[IN, OUT](in: IN) {
|
|
||||||
def let(cb: (IN) => OUT): OUT = cb(in)
|
|
||||||
def also(cb: (IN) => Unit) : IN = {
|
|
||||||
cb(in)
|
|
||||||
in
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,30 +1,39 @@
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;
|
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import net.woggioni.jpacrepo.model.Hasher;
|
import net.woggioni.jpacrepo.api.model.PkgData;
|
||||||
import net.woggioni.jpacrepo.model.MD5InputStream;
|
import net.woggioni.jpacrepo.api.service.PacmanServiceRemote;
|
||||||
import net.woggioni.jpacrepo.model.Parser;
|
import net.woggioni.jpacrepo.impl.model.CompressionFormatImpl;
|
||||||
import net.woggioni.jpacrepo.pacbase.CompressionFormat;
|
import net.woggioni.jpacrepo.impl.model.PkgDataImpl;
|
||||||
import net.woggioni.jpacrepo.pacbase.PkgData;
|
import net.woggioni.jwo.Con;
|
||||||
import net.woggioni.jpacrepo.service.PacmanServiceRemote;
|
import net.woggioni.jwo.Fun;
|
||||||
|
import net.woggioni.jwo.Hash;
|
||||||
import net.woggioni.jwo.JWO;
|
import net.woggioni.jwo.JWO;
|
||||||
import org.jboss.resteasy.plugins.providers.RegisterBuiltin;
|
import org.jboss.resteasy.plugins.providers.RegisterBuiltin;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
|
|
||||||
import javax.naming.*;
|
import javax.naming.*;
|
||||||
import javax.ws.rs.client.*;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
import jakarta.ws.rs.client.*;
|
||||||
import javax.ws.rs.core.Response;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import jakarta.ws.rs.core.Response;
|
||||||
|
import jakarta.ws.rs.core.UriBuilder;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.InputStream;
|
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.Paths;
|
||||||
import java.security.DigestInputStream;
|
import java.security.DigestInputStream;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class ClientTest {
|
public class ClientTest {
|
||||||
|
|
||||||
@@ -59,8 +68,7 @@ public class ClientTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testPUT() throws Exception
|
public void testPUT() throws Exception {
|
||||||
{
|
|
||||||
ResteasyProviderFactory instance = ResteasyProviderFactory.getInstance();
|
ResteasyProviderFactory instance = ResteasyProviderFactory.getInstance();
|
||||||
RegisterBuiltin.register(instance);
|
RegisterBuiltin.register(instance);
|
||||||
Client client = ClientBuilder.newClient();
|
Client client = ClientBuilder.newClient();
|
||||||
@@ -77,60 +85,39 @@ public class ClientTest {
|
|||||||
assert Response.Status.CREATED.getStatusCode() == response.getStatus();
|
assert Response.Status.CREATED.getStatusCode() == response.getStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void hashTest() throws Exception
|
@Test
|
||||||
{
|
public void hashTest(@TempDir Path testDir) {
|
||||||
String[] files = new String[]{"/var/cache/pacman/pkg/mesa-10.4.5-1-x86_64.pkg.tar.xz", "/var/cache/pacman/pkg/mesa-10.5.3-1-x86_64.pkg.tar.xz"};
|
ClassLoader cl = getClass().getClassLoader();
|
||||||
|
Stream.of("gvfs-nfs-1.50.2-1-x86_64.pkg.tar.zst")
|
||||||
for (String file : files)
|
.forEach((Con<String>) resourceName -> {
|
||||||
{
|
Path tmpFile = testDir.resolve(resourceName);
|
||||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
try(InputStream is = cl.getResourceAsStream(resourceName)) {
|
||||||
try (InputStream is = Files.newInputStream(Paths.get(file)))
|
Files.copy(is, tmpFile);
|
||||||
{
|
|
||||||
DigestInputStream dis = new DigestInputStream(is, md);
|
|
||||||
dis.on(true);
|
|
||||||
// is.read();
|
|
||||||
long a = new File(file).length();
|
|
||||||
byte[] out = new byte[(int)a];
|
|
||||||
dis.read(out, 0, (int) a);
|
|
||||||
}
|
}
|
||||||
System.out.println(Hasher.bytesToHex(md.digest()));
|
Hash hash;
|
||||||
|
try(InputStream is = Files.newInputStream(tmpFile)) {
|
||||||
System.out.println(Hasher.computeMD5(new FileInputStream(file)));
|
hash = Hash.md5(is);
|
||||||
|
}
|
||||||
InputStream fis = new FileInputStream(file);
|
PkgData p = PkgDataImpl.parseFile(tmpFile, CompressionFormatImpl.guess(tmpFile));
|
||||||
MD5InputStream h = new MD5InputStream(fis);
|
Assertions.assertEquals(JWO.bytesToHex(hash.getBytes()), p.getMd5sum());
|
||||||
long a = new File(file).length();
|
});
|
||||||
byte[] out = new byte[(int)a];
|
|
||||||
h.read(out, 0, (int) a);
|
|
||||||
System.out.println(h.digest());
|
|
||||||
|
|
||||||
Path path = Paths.get(file);
|
|
||||||
PkgData p = Parser.parseFile(path, CompressionFormat.guess(path));
|
|
||||||
System.out.println(p.md5sum());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void traverseJndiNode(String nodeName, Context context)
|
private static void traverseJndiNode(String nodeName, Context context) {
|
||||||
{
|
try {
|
||||||
try
|
|
||||||
{
|
|
||||||
NamingEnumeration<NameClassPair> list = context.list(nodeName);
|
NamingEnumeration<NameClassPair> list = context.list(nodeName);
|
||||||
while (list.hasMore())
|
while (list.hasMore()) {
|
||||||
{
|
|
||||||
String childName = nodeName + "" + list.next().getName();
|
String childName = nodeName + "" + list.next().getName();
|
||||||
System.out.println(childName);
|
System.out.println(childName);
|
||||||
traverseJndiNode(childName, context);
|
traverseJndiNode(childName, context);
|
||||||
}
|
}
|
||||||
} catch (NamingException ex)
|
} catch (NamingException ex) {
|
||||||
{
|
|
||||||
// We reached a leaf
|
// We reached a leaf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void invokeStatelessBean() throws Exception
|
@Test
|
||||||
{
|
public void invokeStatelessBean() throws Exception {
|
||||||
Properties prop = new Properties();
|
Properties prop = new Properties();
|
||||||
InputStream in = getClass().getClassLoader().getResourceAsStream("jboss-ejb-client.properties");
|
InputStream in = getClass().getClassLoader().getResourceAsStream("jboss-ejb-client.properties");
|
||||||
prop.load(in);
|
prop.load(in);
|
||||||
@@ -140,10 +127,10 @@ public class ClientTest {
|
|||||||
prop.put(Context.PROVIDER_URL, "http-remoting://localhost:8080");
|
prop.put(Context.PROVIDER_URL, "http-remoting://localhost:8080");
|
||||||
// prop.put(Context.PROVIDER_URL, "http-remoting://nuc:8080");
|
// prop.put(Context.PROVIDER_URL, "http-remoting://nuc:8080");
|
||||||
// prop.put(Context.PROVIDER_URL, "remote://odroid-u3:4447");
|
// prop.put(Context.PROVIDER_URL, "remote://odroid-u3:4447");
|
||||||
prop.put(Context.SECURITY_PRINCIPAL, "walter");
|
// prop.put(Context.SECURITY_PRINCIPAL, "walter");
|
||||||
prop.put(Context.SECURITY_CREDENTIALS, "27ff5990757d1d");
|
// prop.put(Context.SECURITY_CREDENTIALS, "27ff5990757d1d");
|
||||||
// prop.put(Context.SECURITY_PRINCIPAL, "admin");
|
prop.put(Context.SECURITY_PRINCIPAL, "luser");
|
||||||
// prop.put(Context.SECURITY_CREDENTIALS, "123456");
|
prop.put(Context.SECURITY_CREDENTIALS, "123456");
|
||||||
|
|
||||||
prop.put("jboss.naming.client.ejb.context", true);
|
prop.put("jboss.naming.client.ejb.context", true);
|
||||||
Context context = new InitialContext(prop);
|
Context context = new InitialContext(prop);
|
||||||
@@ -151,7 +138,7 @@ public class ClientTest {
|
|||||||
traverseJndiNode("/", context);
|
traverseJndiNode("/", context);
|
||||||
// final PacmanService stateService = (PacmanService) ctx.lookup("/jpacrepo-1.0/remote/PacmanServiceEJB!service.PacmanService");
|
// final PacmanService stateService = (PacmanService) ctx.lookup("/jpacrepo-1.0/remote/PacmanServiceEJB!service.PacmanService");
|
||||||
final PacmanServiceRemote service = (PacmanServiceRemote) ctx.lookup(
|
final PacmanServiceRemote service = (PacmanServiceRemote) ctx.lookup(
|
||||||
"/jpacrepo_2.12-2.0/PacmanServiceEJB!net.woggioni.jpacrepo.service.PacmanServiceRemote"
|
"/jpacrepo-2.0-SNAPSHOT/PacmanServiceEJB!net.woggioni.jpacrepo.service.PacmanServiceRemote"
|
||||||
);
|
);
|
||||||
// List<PkgData> pkgs = service.searchPackage("google-earth", null, null, 1, 10);
|
// List<PkgData> pkgs = service.searchPackage("google-earth", null, null, 1, 10);
|
||||||
// System.out.println(new XStream().toXML(pkgs));
|
// System.out.println(new XStream().toXML(pkgs));
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;
|
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import net.woggioni.jpacrepo.model.Parser;
|
import net.woggioni.jpacrepo.api.model.PkgData;
|
||||||
|
import net.woggioni.jpacrepo.impl.model.PkgDataImpl;
|
||||||
import net.woggioni.jpacrepo.pacbase.CompressionFormat;
|
import net.woggioni.jpacrepo.pacbase.CompressionFormat;
|
||||||
import net.woggioni.jpacrepo.pacbase.PkgData;
|
|
||||||
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
@@ -26,7 +26,7 @@ public class ParseTest {
|
|||||||
Files.list(Paths.get("/var/cache/pacman/pkg"))
|
Files.list(Paths.get("/var/cache/pacman/pkg"))
|
||||||
.filter(Files::isRegularFile)
|
.filter(Files::isRegularFile)
|
||||||
.filter(p -> pattern.matcher(p.getFileName().toString()).matches())
|
.filter(p -> pattern.matcher(p.getFileName().toString()).matches())
|
||||||
.map(path -> Parser.parseFile(path, CompressionFormat.guess(path)))
|
.map(path -> PkgDataImpl.parseFile(path, CompressionFormat.guess(path)))
|
||||||
.limit(10)
|
.limit(10)
|
||||||
.map(new Function<PkgData, String>() {
|
.map(new Function<PkgData, String>() {
|
||||||
@Override
|
@Override
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package net.woggioni.jpacrepo.annotation;
|
package net.woggioni.jpacrepo.annotation;
|
||||||
|
|
||||||
import javax.enterprise.inject.Alternative;
|
import jakarta.enterprise.inject.Alternative;
|
||||||
import javax.enterprise.inject.Stereotype;
|
import jakarta.enterprise.inject.Stereotype;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
BIN
src/test/resources/gvfs-nfs-1.50.2-1-x86_64.pkg.tar.zst
Normal file
BIN
src/test/resources/gvfs-nfs-1.50.2-1-x86_64.pkg.tar.zst
Normal file
Binary file not shown.
@@ -1,8 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Configuration status="INFO">
|
<Configuration status="INFO">
|
||||||
<Properties>
|
|
||||||
<Property name="log-path">$${env:APOLLO_ENVIRONMENT_ROOT:-build}/var/logs</Property>
|
|
||||||
</Properties>
|
|
||||||
<Appenders>
|
<Appenders>
|
||||||
<Console name="Console" target="SYSTEM_OUT">
|
<Console name="Console" target="SYSTEM_OUT">
|
||||||
<PatternLayout pattern="%d{HH:mm:ss,SSS} %highlight{[%p]} (%t) %c: %m%n"/>
|
<PatternLayout pattern="%d{HH:mm:ss,SSS} %highlight{[%p]} (%t) %c: %m%n"/>
|
||||||
|
@@ -1,49 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.client
|
|
||||||
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.util.Properties
|
|
||||||
|
|
||||||
import javax.naming.{Context, InitialContext, NameClassPair, NamingEnumeration, NamingException}
|
|
||||||
import net.woggioni.jpacrepo.service.PacmanServiceRemote
|
|
||||||
import org.scalatest.flatspec.AnyFlatSpec
|
|
||||||
import org.scalatest.matchers.should.Matchers
|
|
||||||
|
|
||||||
class SyncDbTest extends AnyFlatSpec {
|
|
||||||
|
|
||||||
private def traverseJndiNode(nodeName: String, context: Context) {
|
|
||||||
try {
|
|
||||||
val list = context.list(nodeName)
|
|
||||||
while (list.hasMore) {
|
|
||||||
val childName = nodeName + list.next.getName
|
|
||||||
System.out.println(childName)
|
|
||||||
traverseJndiNode(childName, context)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
case _ : NamingException =>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"it" should "be possible to invoke syncDB remotely" in {
|
|
||||||
val prop = new Properties
|
|
||||||
val in = getClass.getClassLoader.getResourceAsStream("jboss-ejb-client.properties")
|
|
||||||
prop.load(in)
|
|
||||||
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:5080")
|
|
||||||
// prop.put(Context.PROVIDER_URL, "http-remoting://nuc:8080");
|
|
||||||
// prop.put(Context.PROVIDER_URL, "remote://odroid-u3:4447");
|
|
||||||
prop.put(Context.SECURITY_PRINCIPAL, "walter")
|
|
||||||
prop.put(Context.SECURITY_CREDENTIALS, "27ff5990757d1d")
|
|
||||||
// prop.put(Context.SECURITY_PRINCIPAL, "admin");
|
|
||||||
// prop.put(Context.SECURITY_CREDENTIALS, "123456");
|
|
||||||
prop.put("jboss.naming.client.ejb.context", true)
|
|
||||||
val context = new InitialContext(prop)
|
|
||||||
val ctx = new InitialContext(prop)
|
|
||||||
traverseJndiNode("/", context)
|
|
||||||
// final PacmanService stateService = (PacmanService) ctx.lookup("/jpacrepo-1.0/remote/PacmanServiceEJB!service.PacmanService");
|
|
||||||
val service = ctx.lookup("/jpacrepo_2.13-2.0/PacmanServiceEJB!net.woggioni.jpacrepo.service.PacmanServiceRemote").asInstanceOf[PacmanServiceRemote]
|
|
||||||
// List<PkgData> pkgs = service.searchPackage("google-earth", null, null, 1, 10);
|
|
||||||
// System.out.println(new XStream().toXML(pkgs));
|
|
||||||
service.syncDB()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,26 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.config
|
|
||||||
|
|
||||||
import net.woggioni.jpacrepo.factory.BeanFactory
|
|
||||||
import org.jboss.weld.environment.se.Weld
|
|
||||||
import org.scalatest.flatspec.AnyFlatSpec
|
|
||||||
import org.scalatest.matchers.must.Matchers
|
|
||||||
|
|
||||||
class AppConfigTest extends AnyFlatSpec with Matchers {
|
|
||||||
|
|
||||||
private val weld = new Weld
|
|
||||||
weld.disableDiscovery()
|
|
||||||
// weld.alternatives(classOf[TestPersistenceProducer])
|
|
||||||
weld.beanClasses(
|
|
||||||
// classOf[PacmanServiceEJB],
|
|
||||||
// classOf[PacmanWebService],
|
|
||||||
// classOf[AppConfig],
|
|
||||||
classOf[BeanFactory],
|
|
||||||
)
|
|
||||||
val container = weld.initialize()
|
|
||||||
"test" should "pass" in {
|
|
||||||
val appConfig = container.select(classOf[AppConfig]).get()
|
|
||||||
println(appConfig.repoFolder)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,49 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.pacbase
|
|
||||||
import java.nio.file.{Files, Path, Paths}
|
|
||||||
import java.util
|
|
||||||
import java.util.regex.Pattern
|
|
||||||
import java.util.stream.Collectors
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.{XmlAccessType, XmlAccessorType, XmlElement, XmlRootElement}
|
|
||||||
import javax.xml.bind.{JAXBContext, Marshaller}
|
|
||||||
import net.woggioni.jpacrepo.model.Parser
|
|
||||||
import net.woggioni.jpacrepo.service.PkgDataList
|
|
||||||
import org.scalatest.flatspec.AnyFlatSpec
|
|
||||||
|
|
||||||
import scala.jdk.CollectionConverters._
|
|
||||||
import scala.annotation.meta.field
|
|
||||||
|
|
||||||
@XmlRootElement(name = "List")
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
class JaxbList2[T] private (@(XmlElement @field)(name = "Item") private val list : util.List[T]) {
|
|
||||||
def this(s : Seq[T]) {
|
|
||||||
this(s.toList.asJava)
|
|
||||||
}
|
|
||||||
|
|
||||||
def this() {
|
|
||||||
this(new util.ArrayList[T]())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MarshalTest extends AnyFlatSpec {
|
|
||||||
|
|
||||||
"asdfs" should "sdfsd" in {
|
|
||||||
val context = JAXBContext.newInstance(classOf[PkgId], classOf[PkgDataList])
|
|
||||||
val mar = context.createMarshaller
|
|
||||||
mar.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true)
|
|
||||||
val pkgId2 = new PkgId
|
|
||||||
pkgId2.name = "linux"
|
|
||||||
pkgId2.version = "4.6.1"
|
|
||||||
pkgId2.arch = "x86_64"
|
|
||||||
val pattern = Pattern.compile(".*\\.pkg\\.tar\\.(zst)")
|
|
||||||
val pkgDatas = Files.list(Paths.get("/var/cache/pacman/pkg"))
|
|
||||||
.filter(Files.isRegularFile(_))
|
|
||||||
.filter((p: Path) => pattern.matcher(p.getFileName.toString).matches)
|
|
||||||
.limit(10)
|
|
||||||
.map(Parser.parseFile(_, CompressionFormat.Z_STANDARD))
|
|
||||||
.collect(Collectors.toList[PkgData])
|
|
||||||
val list = new PkgDataList(pkgDatas)
|
|
||||||
mar.marshal(list, System.out)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,11 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.persistence
|
|
||||||
|
|
||||||
import javax.enterprise.inject.Produces
|
|
||||||
import javax.persistence.Persistence
|
|
||||||
import net.woggioni.jpacrepo.annotation.UnitTesting
|
|
||||||
|
|
||||||
class TestPersistenceProducer {
|
|
||||||
@Produces
|
|
||||||
@UnitTesting
|
|
||||||
def createEntityManagerFactory = Persistence.createEntityManagerFactory("test")
|
|
||||||
}
|
|
@@ -1,38 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.sample
|
|
||||||
|
|
||||||
import net.woggioni.jpacrepo.persistence.InitialSchemaAction
|
|
||||||
import net.woggioni.jpacrepo.version.VersionComparator
|
|
||||||
import org.scalatest.flatspec.AnyFlatSpec
|
|
||||||
import org.scalatest.matchers.should.Matchers
|
|
||||||
|
|
||||||
import scala.collection.mutable
|
|
||||||
|
|
||||||
class ExampleSpec extends AnyFlatSpec with Matchers {
|
|
||||||
|
|
||||||
def foo(a : Int)(b : String = "walter"): Unit = {
|
|
||||||
println(s"a: $a, b: $b")
|
|
||||||
}
|
|
||||||
|
|
||||||
def foo(c : Int): Unit = {
|
|
||||||
foo(a=c)("adfsda")
|
|
||||||
}
|
|
||||||
|
|
||||||
// "A Stack" should "pop values in last-in-first-out order" in {
|
|
||||||
// val stack = new mutable.Stack[Int]
|
|
||||||
// stack.push(1)
|
|
||||||
// stack.push(2)
|
|
||||||
// stack.pop() should be (2)
|
|
||||||
// stack.pop() should be (1)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// it should "throw NoSuchElementException if an empty stack is popped" in {
|
|
||||||
// val emptyStack = new mutable.Stack[Int]
|
|
||||||
// a [NoSuchElementException] should be thrownBy {
|
|
||||||
// emptyStack.pop()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
"sdfgf" should "dfgfd" in {
|
|
||||||
foo(a=5)()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,58 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.service
|
|
||||||
|
|
||||||
import javax.enterprise.util.TypeLiteral
|
|
||||||
import net.woggioni.jpacrepo.factory.BeanFactory
|
|
||||||
import net.woggioni.jpacrepo.persistence.TestPersistenceProducer
|
|
||||||
import org.jboss.weld.environment.se.Weld
|
|
||||||
|
|
||||||
//object WeldContainer {
|
|
||||||
// private val weld = new Weld
|
|
||||||
// weld.disableDiscovery()
|
|
||||||
//
|
|
||||||
// weld.beanClasses(
|
|
||||||
// classOf[TestPersistenceProducer],
|
|
||||||
// classOf[PacmanServiceEJB],
|
|
||||||
// classOf[ApplicationContext],
|
|
||||||
// classOf[BeanFactory],
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// private val count = new AtomicInteger(0)
|
|
||||||
//}
|
|
||||||
|
|
||||||
//class WeldContainer extends AutoCloseable {
|
|
||||||
//
|
|
||||||
// val container = WeldContainer.weld.initialize()
|
|
||||||
// WeldContainer.count.incrementAndGet()
|
|
||||||
//
|
|
||||||
// override def close(): Unit = {
|
|
||||||
// container.close()
|
|
||||||
// if(WeldContainer.count.decrementAndGet() == 0) {
|
|
||||||
//// WeldContainer.weld.shutdown()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
class PacmanServiceEJBTest {
|
|
||||||
|
|
||||||
private val weld = new Weld
|
|
||||||
// weld.disableDiscovery()
|
|
||||||
weld.alternatives(classOf[TestPersistenceProducer])
|
|
||||||
////
|
|
||||||
weld.beanClasses(
|
|
||||||
classOf[PacmanServiceEJB],
|
|
||||||
classOf[PacmanWebService],
|
|
||||||
// classOf[ApplicationContext],
|
|
||||||
classOf[BeanFactory],
|
|
||||||
)
|
|
||||||
|
|
||||||
def test = {
|
|
||||||
val container = weld.initialize()
|
|
||||||
try {
|
|
||||||
val s = getClass.getResourceAsStream("/log4j2.xml")
|
|
||||||
val service = container.select(new TypeLiteral[PacmanServiceView] {}).get()
|
|
||||||
service.syncDB()
|
|
||||||
} finally {
|
|
||||||
container.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,33 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.service
|
|
||||||
|
|
||||||
class PacmanWebServiceTest {
|
|
||||||
|
|
||||||
// val server = {
|
|
||||||
// val server = new UndertowJaxrsServer()
|
|
||||||
// server.start()
|
|
||||||
// import org.jboss.resteasy.spi.ResteasyDeployment
|
|
||||||
// val deployment = new ResteasyDeployment
|
|
||||||
// deployment.setInjectorFactoryClass("org.jboss.resteasy.cdi.CdiInjectorFactory")
|
|
||||||
// deployment.setApplicationClass(classOf[ApplicationConfig].getName)
|
|
||||||
// val di: DeploymentInfo = server.undertowDeployment(deployment)
|
|
||||||
// di.setClassLoader(classOf[ApplicationConfig].getClassLoader)
|
|
||||||
// di.setContextPath("/jpacrepo")
|
|
||||||
// di.setDeploymentName("jpacrepo")
|
|
||||||
// di.addListeners(Servlets.listener(classOf[Listener]))
|
|
||||||
// server.deploy(di)
|
|
||||||
// server
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Test
|
|
||||||
// def foo {
|
|
||||||
// val client = ClientBuilder.newClient()
|
|
||||||
// val webTarget = client.target(TestPortProvider.generateURL("/jpacrepo/rest/pkg/map"))
|
|
||||||
// val response = webTarget.request().get()
|
|
||||||
// val res = response.getEntity
|
|
||||||
// response.getStatus
|
|
||||||
// }
|
|
||||||
|
|
||||||
def boh = {
|
|
||||||
println(System.getProperty("net.woggioni.jpacrepo.configuration.file"))
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,16 +0,0 @@
|
|||||||
package net.woggioni.jpacrepo.version
|
|
||||||
|
|
||||||
import org.scalatest.flatspec.AnyFlatSpec
|
|
||||||
import org.scalatest.matchers.should.Matchers
|
|
||||||
|
|
||||||
import scala.util.Random
|
|
||||||
|
|
||||||
class VersionComparatorSpec extends AnyFlatSpec with Matchers {
|
|
||||||
"Version sorting" should "work as expected" in {
|
|
||||||
val vc = new VersionComparator
|
|
||||||
val originalList = List("1.0", "2.0", "3.0", "5.6.7.arch1-1", "5.6.7.arch3-1", "5.6.7.arch3-2", "20200421.78c0348-1")
|
|
||||||
val shuffledList = new Random(101325).shuffle(originalList)
|
|
||||||
val sortedList = shuffledList.sorted(Ordering.comparatorToOrdering(vc))
|
|
||||||
sortedList should be (originalList)
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user