Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9bf63d9

Browse files
committedNov 25, 2024
Initial commit
0 parents  commit 9bf63d9

25 files changed

+1498
-0
lines changed
 

‎.dockerignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
*
2+
!target/*-runner
3+
!target/*-runner.jar
4+
!target/lib/*
5+
!target/quarkus-app/*

‎.gitignore

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#Maven
2+
target/
3+
pom.xml.tag
4+
pom.xml.releaseBackup
5+
pom.xml.versionsBackup
6+
release.properties
7+
.flattened-pom.xml
8+
9+
# Eclipse
10+
.project
11+
.classpath
12+
.settings/
13+
bin/
14+
15+
# IntelliJ
16+
.idea
17+
*.ipr
18+
*.iml
19+
*.iws
20+
21+
# NetBeans
22+
nb-configuration.xml
23+
24+
# Visual Studio Code
25+
.vscode
26+
.factorypath
27+
28+
# OSX
29+
.DS_Store
30+
31+
# Vim
32+
*.swp
33+
*.swo
34+
35+
# patch
36+
*.orig
37+
*.rej
38+
39+
# Local environment
40+
.env
41+
42+
# Plugin directory
43+
/.quarkus/cli/plugins/
44+
# TLS Certificates
45+
.certs/

‎.mvn/wrapper/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
maven-wrapper.jar
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import java.io.IOException;
21+
import java.io.InputStream;
22+
import java.net.Authenticator;
23+
import java.net.PasswordAuthentication;
24+
import java.net.URI;
25+
import java.net.URL;
26+
import java.nio.file.Files;
27+
import java.nio.file.Path;
28+
import java.nio.file.Paths;
29+
import java.nio.file.StandardCopyOption;
30+
import java.util.concurrent.ThreadLocalRandom;
31+
32+
public final class MavenWrapperDownloader {
33+
private static final String WRAPPER_VERSION = "3.3.2";
34+
35+
private static final boolean VERBOSE = Boolean.parseBoolean(System.getenv("MVNW_VERBOSE"));
36+
37+
public static void main(String[] args) {
38+
log("Apache Maven Wrapper Downloader " + WRAPPER_VERSION);
39+
40+
if (args.length != 2) {
41+
System.err.println(" - ERROR wrapperUrl or wrapperJarPath parameter missing");
42+
System.exit(1);
43+
}
44+
45+
try {
46+
log(" - Downloader started");
47+
final URL wrapperUrl = URI.create(args[0]).toURL();
48+
final String jarPath = args[1].replace("..", ""); // Sanitize path
49+
final Path wrapperJarPath = Paths.get(jarPath).toAbsolutePath().normalize();
50+
downloadFileFromURL(wrapperUrl, wrapperJarPath);
51+
log("Done");
52+
} catch (IOException e) {
53+
System.err.println("- Error downloading: " + e.getMessage());
54+
if (VERBOSE) {
55+
e.printStackTrace();
56+
}
57+
System.exit(1);
58+
}
59+
}
60+
61+
private static void downloadFileFromURL(URL wrapperUrl, Path wrapperJarPath)
62+
throws IOException {
63+
log(" - Downloading to: " + wrapperJarPath);
64+
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
65+
final String username = System.getenv("MVNW_USERNAME");
66+
final char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
67+
Authenticator.setDefault(new Authenticator() {
68+
@Override
69+
protected PasswordAuthentication getPasswordAuthentication() {
70+
return new PasswordAuthentication(username, password);
71+
}
72+
});
73+
}
74+
Path temp = wrapperJarPath
75+
.getParent()
76+
.resolve(wrapperJarPath.getFileName() + "."
77+
+ Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp");
78+
try (InputStream inStream = wrapperUrl.openStream()) {
79+
Files.copy(inStream, temp, StandardCopyOption.REPLACE_EXISTING);
80+
Files.move(temp, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING);
81+
} finally {
82+
Files.deleteIfExists(temp);
83+
}
84+
log(" - Downloader complete");
85+
}
86+
87+
private static void log(String msg) {
88+
if (VERBOSE) {
89+
System.out.println(msg);
90+
}
91+
}
92+
93+
}

‎.mvn/wrapper/maven-wrapper.properties

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
wrapperVersion=3.3.2
18+
distributionType=source
19+
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
20+
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar

‎README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Optimizing Santa's Travels (Java Advent)
2+
3+
This repository contains the example code for my blog post at [Java Advent](https://www.javaadvent.com/).
4+
It is purposefully kept simplistic to show how to use *Timefold Solver* in a minimal example.
5+
6+
For more advanced examples, check out the [Timefold Quickstarts Repository](https://github.com/TimefoldAI/timefold-quickstarts).
7+
8+
## Technologies Used
9+
10+
- [Timefold Solver](https://docs.timefold.ai/timefold-solver/latest/introduction), an Open Source AI Solver.
11+
- [Quarkus](https://quarkus.io/), the Supersonic Subatomic Java Framework.
12+
- [LeafletJS](https://leafletjs.com/), Javascript library for interactive maps.
13+
14+
## Running the application
15+
You can run your application in dev mode that enables live coding using:
16+
17+
```shell script
18+
./mvnw compile quarkus:dev
19+
```
20+
21+
After visiting localhost:8080, you will be presented with a simple UI.
22+
Click the map a couple of times to add some visits for Santa, then click the _solve_ button.

‎SantaClassDiagram.puml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
@startuml
2+
'https://plantuml.com/class-diagram
3+
4+
class Location {
5+
double lat
6+
double lng
7+
}
8+
9+
class Santa {
10+
Location home
11+
List<Visit> visits
12+
}
13+
14+
class Visit {
15+
String id
16+
String name
17+
Location location
18+
}
19+
20+
21+
Santa -> Visit
22+
Santa --> Location
23+
Visit --> Location
24+
25+
@enduml

‎mvnw

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
#!/bin/sh
2+
# ----------------------------------------------------------------------------
3+
# Licensed to the Apache Software Foundation (ASF) under one
4+
# or more contributor license agreements. See the NOTICE file
5+
# distributed with this work for additional information
6+
# regarding copyright ownership. The ASF licenses this file
7+
# to you under the Apache License, Version 2.0 (the
8+
# "License"); you may not use this file except in compliance
9+
# with the License. You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing,
14+
# software distributed under the License is distributed on an
15+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
# KIND, either express or implied. See the License for the
17+
# specific language governing permissions and limitations
18+
# under the License.
19+
# ----------------------------------------------------------------------------
20+
21+
# ----------------------------------------------------------------------------
22+
# Apache Maven Wrapper startup batch script, version 3.3.2
23+
#
24+
# Required ENV vars:
25+
# ------------------
26+
# JAVA_HOME - location of a JDK home dir
27+
#
28+
# Optional ENV vars
29+
# -----------------
30+
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
31+
# e.g. to debug Maven itself, use
32+
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33+
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34+
# ----------------------------------------------------------------------------
35+
36+
if [ -z "$MAVEN_SKIP_RC" ]; then
37+
38+
if [ -f /usr/local/etc/mavenrc ]; then
39+
. /usr/local/etc/mavenrc
40+
fi
41+
42+
if [ -f /etc/mavenrc ]; then
43+
. /etc/mavenrc
44+
fi
45+
46+
if [ -f "$HOME/.mavenrc" ]; then
47+
. "$HOME/.mavenrc"
48+
fi
49+
50+
fi
51+
52+
# OS specific support. $var _must_ be set to either true or false.
53+
cygwin=false
54+
darwin=false
55+
mingw=false
56+
case "$(uname)" in
57+
CYGWIN*) cygwin=true ;;
58+
MINGW*) mingw=true ;;
59+
Darwin*)
60+
darwin=true
61+
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
62+
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
63+
if [ -z "$JAVA_HOME" ]; then
64+
if [ -x "/usr/libexec/java_home" ]; then
65+
JAVA_HOME="$(/usr/libexec/java_home)"
66+
export JAVA_HOME
67+
else
68+
JAVA_HOME="/Library/Java/Home"
69+
export JAVA_HOME
70+
fi
71+
fi
72+
;;
73+
esac
74+
75+
if [ -z "$JAVA_HOME" ]; then
76+
if [ -r /etc/gentoo-release ]; then
77+
JAVA_HOME=$(java-config --jre-home)
78+
fi
79+
fi
80+
81+
# For Cygwin, ensure paths are in UNIX format before anything is touched
82+
if $cygwin; then
83+
[ -n "$JAVA_HOME" ] \
84+
&& JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
85+
[ -n "$CLASSPATH" ] \
86+
&& CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
87+
fi
88+
89+
# For Mingw, ensure paths are in UNIX format before anything is touched
90+
if $mingw; then
91+
[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \
92+
&& JAVA_HOME="$(
93+
cd "$JAVA_HOME" || (
94+
echo "cannot cd into $JAVA_HOME." >&2
95+
exit 1
96+
)
97+
pwd
98+
)"
99+
fi
100+
101+
if [ -z "$JAVA_HOME" ]; then
102+
javaExecutable="$(which javac)"
103+
if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then
104+
# readlink(1) is not available as standard on Solaris 10.
105+
readLink=$(which readlink)
106+
if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
107+
if $darwin; then
108+
javaHome="$(dirname "$javaExecutable")"
109+
javaExecutable="$(cd "$javaHome" && pwd -P)/javac"
110+
else
111+
javaExecutable="$(readlink -f "$javaExecutable")"
112+
fi
113+
javaHome="$(dirname "$javaExecutable")"
114+
javaHome=$(expr "$javaHome" : '\(.*\)/bin')
115+
JAVA_HOME="$javaHome"
116+
export JAVA_HOME
117+
fi
118+
fi
119+
fi
120+
121+
if [ -z "$JAVACMD" ]; then
122+
if [ -n "$JAVA_HOME" ]; then
123+
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
124+
# IBM's JDK on AIX uses strange locations for the executables
125+
JAVACMD="$JAVA_HOME/jre/sh/java"
126+
else
127+
JAVACMD="$JAVA_HOME/bin/java"
128+
fi
129+
else
130+
JAVACMD="$(
131+
\unset -f command 2>/dev/null
132+
\command -v java
133+
)"
134+
fi
135+
fi
136+
137+
if [ ! -x "$JAVACMD" ]; then
138+
echo "Error: JAVA_HOME is not defined correctly." >&2
139+
echo " We cannot execute $JAVACMD" >&2
140+
exit 1
141+
fi
142+
143+
if [ -z "$JAVA_HOME" ]; then
144+
echo "Warning: JAVA_HOME environment variable is not set." >&2
145+
fi
146+
147+
# traverses directory structure from process work directory to filesystem root
148+
# first directory with .mvn subdirectory is considered project base directory
149+
find_maven_basedir() {
150+
if [ -z "$1" ]; then
151+
echo "Path not specified to find_maven_basedir" >&2
152+
return 1
153+
fi
154+
155+
basedir="$1"
156+
wdir="$1"
157+
while [ "$wdir" != '/' ]; do
158+
if [ -d "$wdir"/.mvn ]; then
159+
basedir=$wdir
160+
break
161+
fi
162+
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
163+
if [ -d "${wdir}" ]; then
164+
wdir=$(
165+
cd "$wdir/.." || exit 1
166+
pwd
167+
)
168+
fi
169+
# end of workaround
170+
done
171+
printf '%s' "$(
172+
cd "$basedir" || exit 1
173+
pwd
174+
)"
175+
}
176+
177+
# concatenates all lines of a file
178+
concat_lines() {
179+
if [ -f "$1" ]; then
180+
# Remove \r in case we run on Windows within Git Bash
181+
# and check out the repository with auto CRLF management
182+
# enabled. Otherwise, we may read lines that are delimited with
183+
# \r\n and produce $'-Xarg\r' rather than -Xarg due to word
184+
# splitting rules.
185+
tr -s '\r\n' ' ' <"$1"
186+
fi
187+
}
188+
189+
log() {
190+
if [ "$MVNW_VERBOSE" = true ]; then
191+
printf '%s\n' "$1"
192+
fi
193+
}
194+
195+
BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
196+
if [ -z "$BASE_DIR" ]; then
197+
exit 1
198+
fi
199+
200+
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
201+
export MAVEN_PROJECTBASEDIR
202+
log "$MAVEN_PROJECTBASEDIR"
203+
204+
##########################################################################################
205+
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
206+
# This allows using the maven wrapper in projects that prohibit checking in binary data.
207+
##########################################################################################
208+
wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
209+
if [ -r "$wrapperJarPath" ]; then
210+
log "Found $wrapperJarPath"
211+
else
212+
log "Couldn't find $wrapperJarPath, downloading it ..."
213+
214+
if [ -n "$MVNW_REPOURL" ]; then
215+
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
216+
else
217+
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
218+
fi
219+
while IFS="=" read -r key value; do
220+
# Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
221+
safeValue=$(echo "$value" | tr -d '\r')
222+
case "$key" in wrapperUrl)
223+
wrapperUrl="$safeValue"
224+
break
225+
;;
226+
esac
227+
done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
228+
log "Downloading from: $wrapperUrl"
229+
230+
if $cygwin; then
231+
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
232+
fi
233+
234+
if command -v wget >/dev/null; then
235+
log "Found wget ... using wget"
236+
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
237+
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
238+
wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
239+
else
240+
wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
241+
fi
242+
elif command -v curl >/dev/null; then
243+
log "Found curl ... using curl"
244+
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
245+
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
246+
curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
247+
else
248+
curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
249+
fi
250+
else
251+
log "Falling back to using Java to download"
252+
javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
253+
javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
254+
# For Cygwin, switch paths to Windows format before running javac
255+
if $cygwin; then
256+
javaSource=$(cygpath --path --windows "$javaSource")
257+
javaClass=$(cygpath --path --windows "$javaClass")
258+
fi
259+
if [ -e "$javaSource" ]; then
260+
if [ ! -e "$javaClass" ]; then
261+
log " - Compiling MavenWrapperDownloader.java ..."
262+
("$JAVA_HOME/bin/javac" "$javaSource")
263+
fi
264+
if [ -e "$javaClass" ]; then
265+
log " - Running MavenWrapperDownloader.java ..."
266+
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
267+
fi
268+
fi
269+
fi
270+
fi
271+
##########################################################################################
272+
# End of extension
273+
##########################################################################################
274+
275+
# If specified, validate the SHA-256 sum of the Maven wrapper jar file
276+
wrapperSha256Sum=""
277+
while IFS="=" read -r key value; do
278+
case "$key" in wrapperSha256Sum)
279+
wrapperSha256Sum=$value
280+
break
281+
;;
282+
esac
283+
done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
284+
if [ -n "$wrapperSha256Sum" ]; then
285+
wrapperSha256Result=false
286+
if command -v sha256sum >/dev/null; then
287+
if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c >/dev/null 2>&1; then
288+
wrapperSha256Result=true
289+
fi
290+
elif command -v shasum >/dev/null; then
291+
if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then
292+
wrapperSha256Result=true
293+
fi
294+
else
295+
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
296+
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2
297+
exit 1
298+
fi
299+
if [ $wrapperSha256Result = false ]; then
300+
echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
301+
echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
302+
echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
303+
exit 1
304+
fi
305+
fi
306+
307+
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
308+
309+
# For Cygwin, switch paths to Windows format before running java
310+
if $cygwin; then
311+
[ -n "$JAVA_HOME" ] \
312+
&& JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
313+
[ -n "$CLASSPATH" ] \
314+
&& CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
315+
[ -n "$MAVEN_PROJECTBASEDIR" ] \
316+
&& MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
317+
fi
318+
319+
# Provide a "standardized" way to retrieve the CLI args that will
320+
# work with both Windows and non-Windows executions.
321+
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
322+
export MAVEN_CMD_LINE_ARGS
323+
324+
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
325+
326+
# shellcheck disable=SC2086 # safe args
327+
exec "$JAVACMD" \
328+
$MAVEN_OPTS \
329+
$MAVEN_DEBUG_OPTS \
330+
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
331+
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
332+
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

‎mvnw.cmd

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
@REM ----------------------------------------------------------------------------
2+
@REM Licensed to the Apache Software Foundation (ASF) under one
3+
@REM or more contributor license agreements. See the NOTICE file
4+
@REM distributed with this work for additional information
5+
@REM regarding copyright ownership. The ASF licenses this file
6+
@REM to you under the Apache License, Version 2.0 (the
7+
@REM "License"); you may not use this file except in compliance
8+
@REM with the License. You may obtain a copy of the License at
9+
@REM
10+
@REM http://www.apache.org/licenses/LICENSE-2.0
11+
@REM
12+
@REM Unless required by applicable law or agreed to in writing,
13+
@REM software distributed under the License is distributed on an
14+
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
@REM KIND, either express or implied. See the License for the
16+
@REM specific language governing permissions and limitations
17+
@REM under the License.
18+
@REM ----------------------------------------------------------------------------
19+
20+
@REM ----------------------------------------------------------------------------
21+
@REM Apache Maven Wrapper startup batch script, version 3.3.2
22+
@REM
23+
@REM Required ENV vars:
24+
@REM JAVA_HOME - location of a JDK home dir
25+
@REM
26+
@REM Optional ENV vars
27+
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
28+
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
29+
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
30+
@REM e.g. to debug Maven itself, use
31+
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
32+
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
33+
@REM ----------------------------------------------------------------------------
34+
35+
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
36+
@echo off
37+
@REM set title of command window
38+
title %0
39+
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
40+
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
41+
42+
@REM set %HOME% to equivalent of $HOME
43+
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
44+
45+
@REM Execute a user defined script before this one
46+
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
47+
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
48+
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
49+
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
50+
:skipRcPre
51+
52+
@setlocal
53+
54+
set ERROR_CODE=0
55+
56+
@REM To isolate internal variables from possible post scripts, we use another setlocal
57+
@setlocal
58+
59+
@REM ==== START VALIDATION ====
60+
if not "%JAVA_HOME%" == "" goto OkJHome
61+
62+
echo. >&2
63+
echo Error: JAVA_HOME not found in your environment. >&2
64+
echo Please set the JAVA_HOME variable in your environment to match the >&2
65+
echo location of your Java installation. >&2
66+
echo. >&2
67+
goto error
68+
69+
:OkJHome
70+
if exist "%JAVA_HOME%\bin\java.exe" goto init
71+
72+
echo. >&2
73+
echo Error: JAVA_HOME is set to an invalid directory. >&2
74+
echo JAVA_HOME = "%JAVA_HOME%" >&2
75+
echo Please set the JAVA_HOME variable in your environment to match the >&2
76+
echo location of your Java installation. >&2
77+
echo. >&2
78+
goto error
79+
80+
@REM ==== END VALIDATION ====
81+
82+
:init
83+
84+
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
85+
@REM Fallback to current working directory if not found.
86+
87+
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
88+
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
89+
90+
set EXEC_DIR=%CD%
91+
set WDIR=%EXEC_DIR%
92+
:findBaseDir
93+
IF EXIST "%WDIR%"\.mvn goto baseDirFound
94+
cd ..
95+
IF "%WDIR%"=="%CD%" goto baseDirNotFound
96+
set WDIR=%CD%
97+
goto findBaseDir
98+
99+
:baseDirFound
100+
set MAVEN_PROJECTBASEDIR=%WDIR%
101+
cd "%EXEC_DIR%"
102+
goto endDetectBaseDir
103+
104+
:baseDirNotFound
105+
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
106+
cd "%EXEC_DIR%"
107+
108+
:endDetectBaseDir
109+
110+
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
111+
112+
@setlocal EnableExtensions EnableDelayedExpansion
113+
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
114+
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
115+
116+
:endReadAdditionalConfig
117+
118+
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
119+
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
120+
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
121+
122+
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
123+
124+
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
125+
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
126+
)
127+
128+
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
129+
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
130+
if exist %WRAPPER_JAR% (
131+
if "%MVNW_VERBOSE%" == "true" (
132+
echo Found %WRAPPER_JAR%
133+
)
134+
) else (
135+
if not "%MVNW_REPOURL%" == "" (
136+
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
137+
)
138+
if "%MVNW_VERBOSE%" == "true" (
139+
echo Couldn't find %WRAPPER_JAR%, downloading it ...
140+
echo Downloading from: %WRAPPER_URL%
141+
)
142+
143+
powershell -Command "&{"^
144+
"$webclient = new-object System.Net.WebClient;"^
145+
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
146+
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
147+
"}"^
148+
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
149+
"}"
150+
if "%MVNW_VERBOSE%" == "true" (
151+
echo Finished downloading %WRAPPER_JAR%
152+
)
153+
)
154+
@REM End of extension
155+
156+
@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
157+
SET WRAPPER_SHA_256_SUM=""
158+
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
159+
IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
160+
)
161+
IF NOT %WRAPPER_SHA_256_SUM%=="" (
162+
powershell -Command "&{"^
163+
"Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^
164+
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
165+
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
166+
" Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
167+
" Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
168+
" Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
169+
" exit 1;"^
170+
"}"^
171+
"}"
172+
if ERRORLEVEL 1 goto error
173+
)
174+
175+
@REM Provide a "standardized" way to retrieve the CLI args that will
176+
@REM work with both Windows and non-Windows executions.
177+
set MAVEN_CMD_LINE_ARGS=%*
178+
179+
%MAVEN_JAVA_EXE% ^
180+
%JVM_CONFIG_MAVEN_PROPS% ^
181+
%MAVEN_OPTS% ^
182+
%MAVEN_DEBUG_OPTS% ^
183+
-classpath %WRAPPER_JAR% ^
184+
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
185+
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
186+
if ERRORLEVEL 1 goto error
187+
goto end
188+
189+
:error
190+
set ERROR_CODE=1
191+
192+
:end
193+
@endlocal & set ERROR_CODE=%ERROR_CODE%
194+
195+
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
196+
@REM check for post script, once with legacy .bat ending and once with .cmd ending
197+
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
198+
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
199+
:skipRcPost
200+
201+
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
202+
if "%MAVEN_BATCH_PAUSE%"=="on" pause
203+
204+
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
205+
206+
cmd /C exit /B %ERROR_CODE%

‎pom.xml

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<groupId>be.tomcools</groupId>
6+
<artifactId>santa-java-advent</artifactId>
7+
<version>1.0-SNAPSHOT</version>
8+
9+
<properties>
10+
<compiler-plugin.version>3.13.0</compiler-plugin.version>
11+
<maven.compiler.release>21</maven.compiler.release>
12+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
13+
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
14+
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
15+
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
16+
<quarkus.platform.version>3.16.3</quarkus.platform.version>
17+
<skipITs>true</skipITs>
18+
<surefire-plugin.version>3.5.0</surefire-plugin.version>
19+
20+
<version.ai.timefold.solver>1.16.0</version.ai.timefold.solver>
21+
</properties>
22+
23+
<dependencyManagement>
24+
<dependencies>
25+
<dependency>
26+
<groupId>${quarkus.platform.group-id}</groupId>
27+
<artifactId>${quarkus.platform.artifact-id}</artifactId>
28+
<version>${quarkus.platform.version}</version>
29+
<type>pom</type>
30+
<scope>import</scope>
31+
</dependency>
32+
<dependency>
33+
<groupId>ai.timefold.solver</groupId>
34+
<artifactId>timefold-solver-bom</artifactId>
35+
<version>${version.ai.timefold.solver}</version>
36+
<type>pom</type>
37+
<scope>import</scope>
38+
</dependency>
39+
</dependencies>
40+
</dependencyManagement>
41+
42+
<dependencies>
43+
<dependency>
44+
<groupId>io.quarkus</groupId>
45+
<artifactId>quarkus-resteasy</artifactId>
46+
</dependency>
47+
<dependency>
48+
<groupId>io.quarkus</groupId>
49+
<artifactId>quarkus-resteasy-jackson</artifactId>
50+
</dependency>
51+
<dependency>
52+
<groupId>ai.timefold.solver</groupId>
53+
<artifactId>timefold-solver-quarkus</artifactId>
54+
</dependency>
55+
<dependency>
56+
<groupId>io.quarkus</groupId>
57+
<artifactId>quarkus-websockets</artifactId>
58+
</dependency>
59+
<dependency>
60+
<groupId>ai.timefold.solver</groupId>
61+
<artifactId>timefold-solver-quarkus-jackson</artifactId>
62+
</dependency>
63+
<dependency>
64+
<groupId>io.quarkus</groupId>
65+
<artifactId>quarkus-junit5</artifactId>
66+
<scope>test</scope>
67+
</dependency>
68+
<dependency>
69+
<groupId>io.rest-assured</groupId>
70+
<artifactId>rest-assured</artifactId>
71+
<scope>test</scope>
72+
</dependency>
73+
</dependencies>
74+
75+
<build>
76+
<plugins>
77+
<plugin>
78+
<groupId>${quarkus.platform.group-id}</groupId>
79+
<artifactId>quarkus-maven-plugin</artifactId>
80+
<version>${quarkus.platform.version}</version>
81+
<extensions>true</extensions>
82+
<executions>
83+
<execution>
84+
<goals>
85+
<goal>build</goal>
86+
<goal>generate-code</goal>
87+
<goal>generate-code-tests</goal>
88+
<goal>native-image-agent</goal>
89+
</goals>
90+
</execution>
91+
</executions>
92+
</plugin>
93+
<plugin>
94+
<artifactId>maven-compiler-plugin</artifactId>
95+
<version>${compiler-plugin.version}</version>
96+
<configuration>
97+
<parameters>true</parameters>
98+
</configuration>
99+
</plugin>
100+
<plugin>
101+
<artifactId>maven-surefire-plugin</artifactId>
102+
<version>${surefire-plugin.version}</version>
103+
<configuration>
104+
<systemPropertyVariables>
105+
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
106+
<maven.home>${maven.home}</maven.home>
107+
</systemPropertyVariables>
108+
</configuration>
109+
</plugin>
110+
<plugin>
111+
<artifactId>maven-failsafe-plugin</artifactId>
112+
<version>${surefire-plugin.version}</version>
113+
<executions>
114+
<execution>
115+
<goals>
116+
<goal>integration-test</goal>
117+
<goal>verify</goal>
118+
</goals>
119+
</execution>
120+
</executions>
121+
<configuration>
122+
<systemPropertyVariables>
123+
<native.image.path>${project.build.directory}/${project.build.finalName}-runner
124+
</native.image.path>
125+
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
126+
<maven.home>${maven.home}</maven.home>
127+
</systemPropertyVariables>
128+
</configuration>
129+
</plugin>
130+
</plugins>
131+
</build>
132+
133+
<profiles>
134+
<profile>
135+
<id>native</id>
136+
<activation>
137+
<property>
138+
<name>native</name>
139+
</property>
140+
</activation>
141+
<properties>
142+
<skipITs>false</skipITs>
143+
<quarkus.native.enabled>true</quarkus.native.enabled>
144+
</properties>
145+
</profile>
146+
</profiles>
147+
</project>

‎src/main/docker/Dockerfile.jvm

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
####
2+
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
3+
#
4+
# Before building the container image run:
5+
#
6+
# ./mvnw package
7+
#
8+
# Then, build the image with:
9+
#
10+
# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/santa-java-advent-jvm .
11+
#
12+
# Then run the container using:
13+
#
14+
# docker run -i --rm -p 8080:8080 quarkus/santa-java-advent-jvm
15+
#
16+
# If you want to include the debug port into your docker image
17+
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
18+
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
19+
# when running the container
20+
#
21+
# Then run the container using :
22+
#
23+
# docker run -i --rm -p 8080:8080 quarkus/santa-java-advent-jvm
24+
#
25+
# This image uses the `run-java.sh` script to run the application.
26+
# This scripts computes the command line to execute your Java application, and
27+
# includes memory/GC tuning.
28+
# You can configure the behavior using the following environment properties:
29+
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
30+
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
31+
# in JAVA_OPTS (example: "-Dsome.property=foo")
32+
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
33+
# used to calculate a default maximal heap memory based on a containers restriction.
34+
# If used in a container without any memory constraints for the container then this
35+
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
36+
# of the container available memory as set here. The default is `50` which means 50%
37+
# of the available memory is used as an upper boundary. You can skip this mechanism by
38+
# setting this value to `0` in which case no `-Xmx` option is added.
39+
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
40+
# is used to calculate a default initial heap memory based on the maximum heap memory.
41+
# If used in a container without any memory constraints for the container then this
42+
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
43+
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
44+
# is used as the initial heap size. You can skip this mechanism by setting this value
45+
# to `0` in which case no `-Xms` option is added (example: "25")
46+
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
47+
# This is used to calculate the maximum value of the initial heap memory. If used in
48+
# a container without any memory constraints for the container then this option has
49+
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
50+
# here. The default is 4096MB which means the calculated value of `-Xms` never will
51+
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
52+
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
53+
# when things are happening. This option, if set to true, will set
54+
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
55+
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
56+
# true").
57+
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
58+
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
59+
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
60+
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
61+
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
62+
# (example: "20")
63+
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
64+
# (example: "40")
65+
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
66+
# (example: "4")
67+
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
68+
# previous GC times. (example: "90")
69+
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
70+
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
71+
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
72+
# contain the necessary JRE command-line options to specify the required GC, which
73+
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
74+
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
75+
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
76+
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
77+
# accessed directly. (example: "foo.example.com,bar.example.com")
78+
#
79+
###
80+
FROM registry.access.redhat.com/ubi8/openjdk-21:1.20
81+
82+
ENV LANGUAGE='en_US:en'
83+
84+
85+
# We make four distinct layers so if there are application changes the library layers can be re-used
86+
COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
87+
COPY --chown=185 target/quarkus-app/*.jar /deployments/
88+
COPY --chown=185 target/quarkus-app/app/ /deployments/app/
89+
COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
90+
91+
EXPOSE 8080
92+
USER 185
93+
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
94+
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
95+
96+
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
97+

‎src/main/docker/Dockerfile.legacy-jar

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
####
2+
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
3+
#
4+
# Before building the container image run:
5+
#
6+
# ./mvnw package -Dquarkus.package.jar.type=legacy-jar
7+
#
8+
# Then, build the image with:
9+
#
10+
# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/santa-java-advent-legacy-jar .
11+
#
12+
# Then run the container using:
13+
#
14+
# docker run -i --rm -p 8080:8080 quarkus/santa-java-advent-legacy-jar
15+
#
16+
# If you want to include the debug port into your docker image
17+
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
18+
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
19+
# when running the container
20+
#
21+
# Then run the container using :
22+
#
23+
# docker run -i --rm -p 8080:8080 quarkus/santa-java-advent-legacy-jar
24+
#
25+
# This image uses the `run-java.sh` script to run the application.
26+
# This scripts computes the command line to execute your Java application, and
27+
# includes memory/GC tuning.
28+
# You can configure the behavior using the following environment properties:
29+
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
30+
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
31+
# in JAVA_OPTS (example: "-Dsome.property=foo")
32+
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
33+
# used to calculate a default maximal heap memory based on a containers restriction.
34+
# If used in a container without any memory constraints for the container then this
35+
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
36+
# of the container available memory as set here. The default is `50` which means 50%
37+
# of the available memory is used as an upper boundary. You can skip this mechanism by
38+
# setting this value to `0` in which case no `-Xmx` option is added.
39+
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
40+
# is used to calculate a default initial heap memory based on the maximum heap memory.
41+
# If used in a container without any memory constraints for the container then this
42+
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
43+
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
44+
# is used as the initial heap size. You can skip this mechanism by setting this value
45+
# to `0` in which case no `-Xms` option is added (example: "25")
46+
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
47+
# This is used to calculate the maximum value of the initial heap memory. If used in
48+
# a container without any memory constraints for the container then this option has
49+
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
50+
# here. The default is 4096MB which means the calculated value of `-Xms` never will
51+
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
52+
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
53+
# when things are happening. This option, if set to true, will set
54+
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
55+
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
56+
# true").
57+
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
58+
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
59+
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
60+
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
61+
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
62+
# (example: "20")
63+
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
64+
# (example: "40")
65+
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
66+
# (example: "4")
67+
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
68+
# previous GC times. (example: "90")
69+
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
70+
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
71+
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
72+
# contain the necessary JRE command-line options to specify the required GC, which
73+
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
74+
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
75+
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
76+
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
77+
# accessed directly. (example: "foo.example.com,bar.example.com")
78+
#
79+
###
80+
FROM registry.access.redhat.com/ubi8/openjdk-21:1.20
81+
82+
ENV LANGUAGE='en_US:en'
83+
84+
85+
COPY target/lib/* /deployments/lib/
86+
COPY target/*-runner.jar /deployments/quarkus-run.jar
87+
88+
EXPOSE 8080
89+
USER 185
90+
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
91+
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
92+
93+
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]

‎src/main/docker/Dockerfile.native

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
####
2+
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
3+
#
4+
# Before building the container image run:
5+
#
6+
# ./mvnw package -Dnative
7+
#
8+
# Then, build the image with:
9+
#
10+
# docker build -f src/main/docker/Dockerfile.native -t quarkus/santa-java-advent .
11+
#
12+
# Then run the container using:
13+
#
14+
# docker run -i --rm -p 8080:8080 quarkus/santa-java-advent
15+
#
16+
###
17+
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.10
18+
WORKDIR /work/
19+
RUN chown 1001 /work \
20+
&& chmod "g+rwX" /work \
21+
&& chown 1001:root /work
22+
COPY --chown=1001:root target/*-runner /work/application
23+
24+
EXPOSE 8080
25+
USER 1001
26+
27+
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
####
2+
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
3+
# It uses a micro base image, tuned for Quarkus native executables.
4+
# It reduces the size of the resulting container image.
5+
# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
6+
#
7+
# Before building the container image run:
8+
#
9+
# ./mvnw package -Dnative
10+
#
11+
# Then, build the image with:
12+
#
13+
# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/santa-java-advent .
14+
#
15+
# Then run the container using:
16+
#
17+
# docker run -i --rm -p 8080:8080 quarkus/santa-java-advent
18+
#
19+
###
20+
FROM quay.io/quarkus/quarkus-micro-image:2.0
21+
WORKDIR /work/
22+
RUN chown 1001 /work \
23+
&& chmod "g+rwX" /work \
24+
&& chown 1001:root /work
25+
COPY --chown=1001:root target/*-runner /work/application
26+
27+
EXPOSE 8080
28+
USER 1001
29+
30+
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package be.tomcools;
2+
3+
import ai.timefold.solver.core.api.solver.*;
4+
import be.tomcools.domain.Location;
5+
import be.tomcools.domain.Santa;
6+
import be.tomcools.domain.SantaPlan;
7+
import be.tomcools.domain.Visit;
8+
import jakarta.ws.rs.Consumes;
9+
import jakarta.ws.rs.POST;
10+
import jakarta.ws.rs.Path;
11+
import jakarta.ws.rs.Produces;
12+
import jakarta.ws.rs.core.MediaType;
13+
14+
import java.util.List;
15+
16+
@Path("santa")
17+
public class SantaResource {
18+
19+
private final SolverFactory<SantaPlan> solverFactory;
20+
21+
public SantaResource(SolverFactory<SantaPlan> solverFactory) {
22+
this.solverFactory = solverFactory;
23+
}
24+
25+
/**
26+
* Uses a solver to optimally sort a list of visits
27+
* @param visits the locations santa needs to visit
28+
* @return an optimized list of visits ordered
29+
*/
30+
@POST
31+
@Path("plan")
32+
@Consumes(MediaType.APPLICATION_JSON)
33+
@Produces(MediaType.APPLICATION_JSON)
34+
public SantaPlan solve(List<Visit> visits) {
35+
Santa santa = new Santa(new Location(66.5039, 25.7294));
36+
SantaPlan santaPlan = new SantaPlan(santa, visits);
37+
38+
Solver<SantaPlan> solver = solverFactory.buildSolver();
39+
SantaPlan solution = solver.solve(santaPlan);
40+
return solution;
41+
}
42+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package be.tomcools.domain;
2+
3+
public record Location(double lat, double lng) {
4+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package be.tomcools.domain;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
7+
import ai.timefold.solver.core.api.domain.variable.PlanningListVariable;
8+
9+
@PlanningEntity
10+
public class Santa {
11+
12+
private Location home;
13+
14+
@PlanningListVariable
15+
private List<Visit> visits;
16+
17+
public Santa() {
18+
}
19+
20+
public Santa(Location home) {
21+
this.home = home;
22+
this.visits = new ArrayList<>();}
23+
24+
public Location getHome() {
25+
return home;
26+
}
27+
28+
public void setHome(Location home) {
29+
this.home = home;
30+
}
31+
32+
public List<Visit> getVisits() {
33+
return visits;
34+
}
35+
36+
public void setVisits(List<Visit> visits) {
37+
this.visits = visits;
38+
}
39+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package be.tomcools.domain;
2+
3+
import ai.timefold.solver.core.api.domain.solution.*;
4+
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider;
5+
import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore;
6+
import ai.timefold.solver.core.api.score.buildin.hardsoftlong.HardSoftLongScore;
7+
8+
import java.util.List;
9+
10+
@PlanningSolution
11+
public class SantaPlan {
12+
@PlanningEntityProperty
13+
private Santa santa;
14+
15+
@ProblemFactCollectionProperty
16+
@ValueRangeProvider
17+
private List<Visit> visits;
18+
19+
@PlanningScore
20+
private HardSoftLongScore score;
21+
22+
public SantaPlan() {
23+
}
24+
25+
public SantaPlan(Santa santa, List<Visit> visits) {
26+
this.santa = santa;
27+
this.visits = visits;
28+
}
29+
30+
public Santa getSanta() {
31+
return santa;
32+
}
33+
34+
public void setSanta(Santa santa) {
35+
this.santa = santa;
36+
}
37+
38+
public List<Visit> getVisits() {
39+
return visits;
40+
}
41+
42+
public HardSoftLongScore getScore() {
43+
return score;
44+
}
45+
46+
public void setScore(HardSoftLongScore score) {
47+
this.score = score;
48+
}
49+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package be.tomcools.domain;
2+
3+
public record Visit(String id,
4+
String name,
5+
Location location) {
6+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package be.tomcools.geo;
2+
3+
import be.tomcools.domain.Location;
4+
import be.tomcools.domain.Santa;
5+
import be.tomcools.domain.Visit;
6+
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
10+
/**
11+
* Calculates the driving time (in seconds) between two locations by calculating their Haversine distance in meters
12+
* assuming average speed {@link #AVERAGE_SPEED_KMPH}.
13+
*/
14+
public final class HaversineDrivingTimeCalculator {
15+
private static final HaversineDrivingTimeCalculator INSTANCE = new HaversineDrivingTimeCalculator();
16+
17+
public static final int SPEED_OF_LIGHT_KMPH = 300_000;
18+
public static final int AVERAGE_SPEED_KMPH = SPEED_OF_LIGHT_KMPH / 2; //santa moves at half of the speed of light
19+
20+
private static final int EARTH_RADIUS_IN_M = 6371000;
21+
private static final int TWICE_EARTH_RADIUS_IN_M = 2 * EARTH_RADIUS_IN_M;
22+
23+
private static final Map<String, Long> distanceMap = new HashMap<>();
24+
25+
static long metersToDrivingSeconds(long meters) {
26+
return Math.round((double) meters / AVERAGE_SPEED_KMPH * 3.6);
27+
}
28+
29+
public static synchronized HaversineDrivingTimeCalculator getInstance() {
30+
return INSTANCE;
31+
}
32+
33+
private HaversineDrivingTimeCalculator() {
34+
}
35+
36+
public long getTotalDrivingTimeSeconds(Santa santa) {
37+
var visits = santa.getVisits();
38+
if (visits.isEmpty()) {
39+
return 0;
40+
}
41+
42+
long totalDrivingTime = 0;
43+
Location previousLocation = santa.getHome();
44+
45+
for (Visit visit : visits) {
46+
totalDrivingTime += calculateDrivingTime(previousLocation,visit.location());
47+
previousLocation = visit.location();
48+
}
49+
totalDrivingTime += calculateDrivingTime(previousLocation, santa.getHome());
50+
51+
return totalDrivingTime;
52+
}
53+
54+
public static long calculateDrivingTime(Location from, Location to) {
55+
String key = "" + from.hashCode() + to.hashCode();
56+
if (distanceMap.containsKey(key)) {
57+
return distanceMap.get(key);
58+
} else {
59+
if (from.equals(to)) {
60+
return 0L;
61+
}
62+
CartesianCoordinate fromCartesian = locationToCartesian(from);
63+
CartesianCoordinate toCartesian = locationToCartesian(to);
64+
long drivingSeconds = metersToDrivingSeconds(calculateDistance(fromCartesian, toCartesian));
65+
distanceMap.put(key, drivingSeconds);
66+
return drivingSeconds;
67+
}
68+
}
69+
70+
private static long calculateDistance(CartesianCoordinate from, CartesianCoordinate to) {
71+
if (from.equals(to)) {
72+
return 0L;
73+
}
74+
75+
double dX = from.x - to.x;
76+
double dY = from.y - to.y;
77+
double dZ = from.z - to.z;
78+
double r = Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ));
79+
return Math.round(TWICE_EARTH_RADIUS_IN_M * Math.asin(r));
80+
}
81+
82+
private static CartesianCoordinate locationToCartesian(Location location) {
83+
double latitudeInRads = Math.toRadians(location.lat());
84+
double longitudeInRads = Math.toRadians(location.lng());
85+
// Cartesian coordinates, normalized for a sphere of diameter 1.0
86+
double cartesianX = 0.5 * Math.cos(latitudeInRads) * Math.sin(longitudeInRads);
87+
double cartesianY = 0.5 * Math.cos(latitudeInRads) * Math.cos(longitudeInRads);
88+
double cartesianZ = 0.5 * Math.sin(latitudeInRads);
89+
return new CartesianCoordinate(cartesianX, cartesianY, cartesianZ);
90+
}
91+
92+
private record CartesianCoordinate(double x, double y, double z) {
93+
94+
}
95+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package be.tomcools.solver;
2+
3+
import ai.timefold.solver.core.api.score.buildin.hardsoftlong.HardSoftLongScore;
4+
import ai.timefold.solver.core.api.score.stream.Constraint;
5+
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
6+
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
7+
import be.tomcools.domain.Santa;
8+
import be.tomcools.geo.HaversineDrivingTimeCalculator;
9+
import org.jspecify.annotations.NonNull;
10+
11+
public class SantaConstraintProvider implements ConstraintProvider {
12+
HaversineDrivingTimeCalculator drivingTimeCalculator = HaversineDrivingTimeCalculator.getInstance();
13+
public static final String MINIMIZE_TRAVEL_TIME = "minimizeTravelTime";
14+
15+
@Override
16+
public Constraint @NonNull [] defineConstraints(@NonNull ConstraintFactory factory) {
17+
return new Constraint[]{
18+
minimizeTravelTime(factory),
19+
};
20+
}
21+
22+
Constraint minimizeTravelTime(ConstraintFactory factory) {
23+
return factory.forEach(Santa.class)
24+
.penalizeLong(HardSoftLongScore.ONE_SOFT,
25+
santa -> drivingTimeCalculator.getTotalDrivingTimeSeconds(santa))
26+
.asConstraint(MINIMIZE_TRAVEL_TIME);
27+
}
28+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Santas travels - Timefold Solver on Quarkus</title>
6+
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
7+
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
8+
<style>
9+
<!-- These CSS lines are just to make the map look a bit darker -->
10+
11+
.leaflet-tile {
12+
filter: grayscale(1) !important;
13+
}
14+
15+
.leaflet-layer,
16+
.leaflet-control-zoom-in,
17+
.leaflet-control-zoom-out {
18+
filter: invert(100%) hue-rotate(180deg) brightness(95%) contrast(50%);
19+
}
20+
</style>
21+
</head>
22+
<body style="margin: 0; padding: 0">
23+
<nav>
24+
<button id="solveButton" onclick="solve()">Solve</button>
25+
<span>Note: If the solution doesn't seem optimal, you might need to increase the spent-limit in the properties.</span>
26+
</nav>
27+
<div id="map" style="height: 95vh; width: 100vw;"></div>
28+
<footer>This demo was created for Java Advent and was purposefully kept simplistic. For more elaborate examples, see <a href="https://github.com/TimefoldAI/timefold-quickstarts">The official Timefold Quickstarts Repository</a></footer>
29+
<script src="/META-INF/resources/santa-app.js"></script>
30+
</body>
31+
</html>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Custom Christmas Icon
2+
const treeIcon = L.icon({iconUrl: 'tree-marker.png', iconSize: [20, 20]})
3+
4+
const map = L.map('map', {doubleClickZoom: false}).setView([66.5039, 25.7294], 5);
5+
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
6+
attribution: '&copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors',
7+
}).addTo(map);
8+
9+
10+
const linesGroup = L.layerGroup().addTo(map);
11+
12+
const visitsOnMap = [];
13+
const solveButton = document.getElementById("solveButton");
14+
15+
// Allows you to manually add visits
16+
map.on('click', function(e) {
17+
const { lat, lng } = e.latlng;
18+
addLocation(lat, lng);
19+
});
20+
21+
// Add a location to the map and Javascript array
22+
function addLocation(lat, lng) {
23+
visitsOnMap.push({
24+
id: visitsOnMap.length,
25+
location: {
26+
lat: lat,
27+
lng: lng
28+
}
29+
})
30+
// Add a marker at the clicked location
31+
L.marker([lat, lng], {icon: treeIcon}).addTo(map);
32+
}
33+
34+
function solve() {
35+
linesGroup.clearLayers();
36+
solveButton.setAttribute("disabled", "disabled")
37+
solveButton.innerText = "Solving";
38+
fetch('http://localhost:8080/santa/plan', {
39+
method: 'POST',
40+
headers: {
41+
'Content-Type': 'application/json', // Specify the content type
42+
},
43+
body: JSON.stringify(visitsOnMap) // Convert your data to JSON string
44+
})
45+
.then(response => response.json()) // Parse JSON
46+
.then(data => {
47+
const homeLocation = data.santa.home;
48+
const locations = data.santa.visits.map(visit => visit.location);
49+
L.polyline([homeLocation, ...locations, homeLocation]).addTo(linesGroup);
50+
51+
solveButton.removeAttribute("disabled")
52+
solveButton.innerText = "Solve";
53+
return data;
54+
})
55+
.catch(error => {
56+
console.error('Fetch error:', error);
57+
});
58+
}
Loading
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# The solver runs for 10 seconds. To run for 5 minutes use "5m" and for 2 hours use "2h".
2+
# Typically, for larger solutions, we suggest 5 minutes or more.
3+
quarkus.timefold.solver.termination.spent-limit=10s

0 commit comments

Comments
 (0)
Please sign in to comment.