diff --git a/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI_.xml b/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI_.xml
index 3cacc929df9a..bf04b8954c1b 100644
--- a/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI_.xml
+++ b/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI_.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/README.md b/README.md
index 3ad3b4f25acb..130935b23ba1 100644
--- a/README.md
+++ b/README.md
@@ -157,7 +157,7 @@ Refer to [Using JHipster in production](http://www.jhipster.tech/production) for
The following command can automate the deployment to a server. The example shows the deployment to the main Artemis test server (which runs a virtual machine):
```shell
-./artemis-server-cli deploy username@artemistest.ase.in.tum.de -w build/libs/Artemis-7.0.3.war
+./artemis-server-cli deploy username@artemistest.ase.in.tum.de -w build/libs/Artemis-7.0.4.war
```
## Architecture
diff --git a/build.gradle b/build.gradle
index d713f51dc37f..07ee7acd4b1e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -32,7 +32,7 @@ plugins {
}
group = "de.tum.in.www1.artemis"
-version = "7.0.3"
+version = "7.0.4"
description = "Interactive Learning with Individual Feedback"
java {
@@ -190,7 +190,7 @@ jacocoTestCoverageVerification {
counter = "CLASS"
value = "MISSEDCOUNT"
// TODO: in the future the following value should become less than 10
- maximum = 26
+ maximum = 27
}
}
}
diff --git a/docker/artemis/config/node1.env b/docker/artemis/config/node1.env
index 5db1cae1da99..65d16156ff29 100644
--- a/docker/artemis/config/node1.env
+++ b/docker/artemis/config/node1.env
@@ -1,4 +1,4 @@
-SPRING_PROFILES_ACTIVE='prod,localvc,localci,buildagent,core,scheduling,docker'
+SPRING_PROFILES_ACTIVE='prod,localvc,localci,core,scheduling,docker'
EUREKA_INSTANCE_INSTANCEID='Artemis:1'
EUREKA_INSTANCE_HOSTNAME='artemis-app-node-1'
SPRING_HAZELCAST_INTERFACE='artemis-app-node-1'
diff --git a/docker/artemis/config/node2.env b/docker/artemis/config/node2.env
index 8fe579d9d0d8..c8438fadc51f 100644
--- a/docker/artemis/config/node2.env
+++ b/docker/artemis/config/node2.env
@@ -2,3 +2,6 @@ SPRING_PROFILES_ACTIVE='prod,localvc,localci,buildagent,core,docker'
EUREKA_INSTANCE_INSTANCEID='Artemis:2'
EUREKA_INSTANCE_HOSTNAME='artemis-app-node-2'
SPRING_HAZELCAST_INTERFACE='artemis-app-node-2'
+
+ARTEMIS_VERSIONCONTROL_USER='artemis_admin'
+ARTEMIS_VERSIONCONTROL_PASSWORD='artemis_admin'
diff --git a/docker/artemis/config/node3.env b/docker/artemis/config/node3.env
index feeec81050dc..f0b2937cac32 100644
--- a/docker/artemis/config/node3.env
+++ b/docker/artemis/config/node3.env
@@ -1,4 +1,7 @@
-SPRING_PROFILES_ACTIVE='prod,localvc,localci,buildagent,core,docker'
+SPRING_PROFILES_ACTIVE='prod,buildagent'
EUREKA_INSTANCE_INSTANCEID='Artemis:3'
EUREKA_INSTANCE_HOSTNAME='artemis-app-node-3'
SPRING_HAZELCAST_INTERFACE='artemis-app-node-3'
+
+ARTEMIS_VERSIONCONTROL_USER='artemis_admin'
+ARTEMIS_VERSIONCONTROL_PASSWORD='artemis_admin'
diff --git a/docker/artemis/config/prod-multinode.env b/docker/artemis/config/prod-multinode.env
index 4ca433fa7fd8..5965e05a4e49 100755
--- a/docker/artemis/config/prod-multinode.env
+++ b/docker/artemis/config/prod-multinode.env
@@ -35,7 +35,7 @@ EUREKA_INSTANCE_APPNAME='Artemis'
JHIPSTER_REGISTRY_PASSWORD="admin"
JHIPSTER_SECURITY_AUTHENTICATION_JWT_BASE64SECRET="bXktc2VjcmV0LWtleS13aGljaC1zaG91bGQtYmUtY2hhbmdlZC1pbi1wcm9kdWN0aW9uLWFuZC1iZS1iYXNlNjQtZW5jb2RlZAo="
-ARTEMIS_VERSIONCONTROL_URL='https://localhost'
+ARTEMIS_VERSIONCONTROL_URL='http://artemis-app-node-2:8080'
ARTEMIS_VERSIONCONTROL_USER='demo'
ARTEMIS_VERSIONCONTROL_PASSWORD='demo'
ARTEMIS_CONTINUOUSINTEGRATION_ARTEMISAUTHENTICATIONTOKENVALUE='demo'
diff --git a/docker/nginx/artemis-upstream-multi-node.conf b/docker/nginx/artemis-upstream-multi-node.conf
index f8d94a933494..1ea41db8232e 100644
--- a/docker/nginx/artemis-upstream-multi-node.conf
+++ b/docker/nginx/artemis-upstream-multi-node.conf
@@ -1,3 +1,2 @@
server artemis-app-node-1:8080;
server artemis-app-node-2:8080;
-server artemis-app-node-3:8080;
diff --git a/docs/admin/setup/distributed.rst b/docs/admin/setup/distributed.rst
index 00b21980bc57..f1bd48e0e7ec 100644
--- a/docs/admin/setup/distributed.rst
+++ b/docs/admin/setup/distributed.rst
@@ -43,6 +43,8 @@ have to be synchronized:
Each of these three aspects is synchronized using a different solution
+.. _Database Cache:
+
Database cache
^^^^^^^^^^^^^^
Artemis uses a cache provider that supports distributed caching: Hazelcast_.
@@ -281,6 +283,8 @@ This enables the registry in nginx
This will apply the config changes and the registry will be reachable.
+.. _WebSockets:
+
WebSockets
^^^^^^^^^^
@@ -652,3 +656,116 @@ different ports and a unique instance ID for each instance.
#. Start the remaining instances.
You should now be able to see all instances in the registry interface at ``http://localhost:8761``.
+
+.. _Running multiple instances locally with Docker:
+
+Running multiple instances locally with Docker
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+You can also run multiple instances of Artemis locally using Docker. This will start 3 Artemis instances, each running
+on a its own container. A load balancer (nginx) will be used to distribute the requests to the different instances. The
+load balancer will be running in a separate container and will be accessible on ports 80/443 of the host system. The
+instances will be registered in the registry service running on a separate container. The instances will use the registry
+service to discover each other and form a Hazelcast cluster. Further details can be found in :ref:`Database Cache`. The
+instances will also use a ActiveMQ Artemis broker to synchronize WebSocket messages. Further details can be found in
+:ref:`WebSockets`. In summary, the setup will look like this:
+
+* 3 Artemis instances:
+
+ * artemis-app-node-1: using following spring profile: ``prod,localvc,localci,core,scheduling,docker``
+ * artemis-app-node-2: using following profile: ``prod,localvc,localci,buildagent,core,docker``
+ * artemis-app-node-3: using following profile: ``prod,buildagent``
+* A MySQL database addressable on port 3306 of the host system
+* A Load balancer (nginx) addressable on ports 80/443 of the host system: ``http(s)://localhost``
+* A Registry service addressable on port 8761 of the host system: ``http://localhost:8761``
+* An ActiveMQ broker
+
+
+ .. figure:: distributed/multi-node-setup.drawio.png
+ :align: center
+
+
+
+.. note::
+
+ - You don't have to start the client manually. The client files are served by the Artemis instances and can be
+ accessed through the load balancer on ``http(s)://localhost``.
+
+ - You may run into the following error when starting the containers
+ ``No member group is available to assign partition ownership...``. This issue should resolve itself after a few
+ minutes. Otherwise, you can first start the following containers:
+ ``docker compose -f docker/test-server-multi-node-mysql-localci.yml up mysql jhipster-registry activemq-broker artemis-app-node-1``.
+ After these containers are up and running, you can start the remaining containers:
+ ``docker compose -f docker/test-server-multi-node-mysql-localci.yml up artemis-app-node-2 artemis-app-node-3 nginx``.
+
+
+Linux setup
+"""""""""""
+
+#. When running the Artemis container on a Unix system, you will have to give the user running in the container
+ permission to access the Docker socket by adding them to the docker group. You can find the group ID of the docker
+ group by running ``getent group docker | cut -d: -f3``. Afterwards, create a new file ``docker/.env`` with the
+ following content:
+
+ .. code:: bash
+
+ DOCKER_GROUP_ID=
+
+#. The docker compose setup which we will use will mount some local directories
+ (namely the ones under docker/.docker-data) into the containers. To ensure that the user running in the container has
+ the necessary permissions to these directories, you will have to change the owner of these directories to the
+ user running in the container (User with ID 1337). You can do this by running the following command:
+
+ .. code:: bash
+
+ sudo chown -R 1337:1337 docker/.docker-data
+
+ .. note::
+
+ - If you don't want to change the owner of the directories, you can create other directories with the necessary
+ permissions and adjust the paths in the docker-compose file accordingly.
+ - You could also use docker volumes instead of mounting local directories. You will have to adjust the docker-compose
+ file accordingly (`Docker docs `_).
+ However, this would make it more difficult to access the files on the host system.
+
+#. Start the docker containers by running the following command:
+
+ .. code:: bash
+
+ docker compose -f docker/test-server-multi-node-mysql-localci.yml up
+
+#. You can now access artemis on ``http(s)://localhost`` and the registry on ``http://localhost:8761``.
+
+Windows setup
+"""""""""""""
+
+#. When running the Artemis container on a Windows system, you will have to change the value for the Docker connection
+ URI. You need to change the value of the environment variable ``ARTEMIS_CONTINUOUSINTEGRATION_DOCKERCONNECTIONURI``
+ in the file ``docker/artemis/config/prod-multinode.env`` to ``tcp://host.docker.internal:2375``.
+
+ .. note::
+
+ - Make sure that option "Expose daemon on tcp://localhost:2375 without TLS" is enabled. This can be found under
+ Settings > General in Docker Desktop.
+
+#. Start the docker containers by running the following command:
+
+ .. code:: bash
+
+ docker compose -f docker/test-server-multi-node-mysql-localci.yml up
+
+#. You can now access artemis on ``http(s)://localhost`` and the registry on ``http://localhost:8761``.
+
+MacOS setup
+"""""""""""
+
+#. Make sure to enable "Allow the default Docker socket to be used (requires password)" in the Docker Desktop settings.
+ This can be found under Settings > Advanced in Docker Desktop.
+
+#. Start the docker containers by running the following command:
+
+ .. code:: bash
+
+ docker compose -f docker/test-server-multi-node-mysql-localci.yml up
+
+#. You can now access artemis on ``http(s)://localhost`` and the registry on ``http://localhost:8761``.
diff --git a/docs/admin/setup/distributed/multi-node-setup.drawio.png b/docs/admin/setup/distributed/multi-node-setup.drawio.png
new file mode 100644
index 000000000000..2ed573edef8c
Binary files /dev/null and b/docs/admin/setup/distributed/multi-node-setup.drawio.png differ
diff --git a/docs/dev/guidelines/language-guidelines.rst b/docs/dev/guidelines/language-guidelines.rst
index 5daef380fb07..e7a2bec778ff 100644
--- a/docs/dev/guidelines/language-guidelines.rst
+++ b/docs/dev/guidelines/language-guidelines.rst
@@ -52,6 +52,7 @@ The German language is mainly used for user interaction.
**Best practices** (written in German for demonstration purposes)
* **Nutze neutrale Formulierungen**: Wenn möglich ist es empfehlenswert, neutrale Formulierungen zu finden. Neben passiven Formen, wie z.B. Studierende, können auch aktive mit „Mensch“ bzw. „Person“ etc. verwendet werden. Das spart „der/die“ und inkludiert alle Menschen unabhängig davon, welche Eigenschaften/Kategorien diese Personen sonst noch mitbringen.
+* **Verwende die Anrede „du“**:Um eine persönliche und weniger formelle Atmosphäre zu schaffen, verwende „du“ statt „Sie".
* **Beschreibe Handlungen (aktiv oder passiv)**:
* „Automatisch generierte Ergebnisse werden am Ende des Bearbeitungszeitraums erstellt“. Statt „Die Studierenden erhalten am Ende des Bearbeitungszeitraums ein automisch generiertes Ergebnis.“)
diff --git a/gradle.properties b/gradle.properties
index 139855db4992..97ce34ef74a4 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -39,4 +39,10 @@ org.gradle.jvmargs=-Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF
--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \
--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \
--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \
- --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+ --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \
+ --add-modules java.se \
+ --add-exports java.base/jdk.internal.ref=ALL-UNNAMED \
+ --add-opens java.base/java.lang=ALL-UNNAMED \
+ --add-opens java.base/sun.nio.ch=ALL-UNNAMED \
+ --add-opens java.management/sun.management=ALL-UNNAMED \
+ --add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED
diff --git a/package-lock.json b/package-lock.json
index fbbc7827b592..e31feab48511 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "artemis",
- "version": "7.0.3",
+ "version": "7.0.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "artemis",
- "version": "7.0.3",
+ "version": "7.0.4",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 1d63e654a779..3beddbdad865 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "artemis",
- "version": "7.0.3",
+ "version": "7.0.4",
"description": "Interactive Learning with Individual Feedback",
"private": true,
"license": "MIT",
diff --git a/src/main/java/de/tum/in/www1/artemis/config/CacheConfiguration.java b/src/main/java/de/tum/in/www1/artemis/config/CacheConfiguration.java
index 206d1518cd2a..829f994a1a50 100644
--- a/src/main/java/de/tum/in/www1/artemis/config/CacheConfiguration.java
+++ b/src/main/java/de/tum/in/www1/artemis/config/CacheConfiguration.java
@@ -188,7 +188,7 @@ public HazelcastInstance hazelcastInstance(JHipsterProperties jHipsterProperties
config.getSerializationConfig().addSerializerConfig(createPathSerializerConfig());
if (registration == null) {
- log.warn("No discovery service is set up, Hazelcast cannot create a cluster.");
+ log.info("No discovery service is set up, Hazelcast cannot create a cluster.");
hazelcastBindOnlyOnInterface("127.0.0.1", config);
}
else {
@@ -270,7 +270,7 @@ public HazelcastInstance hazelcastInstance(JHipsterProperties jHipsterProperties
private void configureQueueCluster(Config config, JHipsterProperties jHipsterProperties) {
// Queue specific configurations
- log.info("Configure Build Job Queue synchronization in Hazelcast for Local CI");
+ log.debug("Configure Build Job Queue synchronization in Hazelcast for Local CI");
QueueConfig queueConfig = new QueueConfig("buildJobQueue");
queueConfig.setBackupCount(jHipsterProperties.getCache().getHazelcast().getBackupCount());
queueConfig.setPriorityComparatorClassName("de.tum.in.www1.artemis.service.connectors.localci.LocalCIPriorityQueueComparator");
@@ -279,7 +279,7 @@ private void configureQueueCluster(Config config, JHipsterProperties jHipsterPro
private void hazelcastBindOnlyOnInterface(String hazelcastInterface, Config config) {
// Hazelcast should bind to the interface and use it as local and public address
- log.info("Binding Hazelcast to interface {}", hazelcastInterface);
+ log.debug("Binding Hazelcast to interface {}", hazelcastInterface);
System.setProperty("hazelcast.local.localAddress", hazelcastInterface);
System.setProperty("hazelcast.local.publicAddress", hazelcastInterface);
config.getNetworkConfig().getInterfaces().setEnabled(true).setInterfaces(Collections.singleton(hazelcastInterface));
diff --git a/src/main/java/de/tum/in/www1/artemis/config/EurekaClientRestTemplateConfiguration.java b/src/main/java/de/tum/in/www1/artemis/config/EurekaClientRestTemplateConfiguration.java
index 71ada032b59d..9c18d00f111d 100644
--- a/src/main/java/de/tum/in/www1/artemis/config/EurekaClientRestTemplateConfiguration.java
+++ b/src/main/java/de/tum/in/www1/artemis/config/EurekaClientRestTemplateConfiguration.java
@@ -49,7 +49,7 @@ public class EurekaClientRestTemplateConfiguration {
@Bean
public RestTemplateDiscoveryClientOptionalArgs restTemplateDiscoveryClientOptionalArgs(TlsProperties tlsProperties,
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier) throws GeneralSecurityException, IOException {
- log.info("Using RestTemplate for the Eureka client.");
+ log.debug("Using RestTemplate for the Eureka client.");
// The Eureka DiscoveryClientOptionalArgsConfiguration invokes a private method setupTLS.
// This code is taken from that method.
var args = new RestTemplateDiscoveryClientOptionalArgs(eurekaClientHttpRequestFactorySupplier);
diff --git a/src/main/java/de/tum/in/www1/artemis/config/MetricsBean.java b/src/main/java/de/tum/in/www1/artemis/config/MetricsBean.java
index 19e98b4f562b..d22025c09a0e 100644
--- a/src/main/java/de/tum/in/www1/artemis/config/MetricsBean.java
+++ b/src/main/java/de/tum/in/www1/artemis/config/MetricsBean.java
@@ -357,7 +357,7 @@ public void calculateCachedActiveUserNames() {
cachedActiveUserNames = statisticsRepository.getActiveUserNames(ZonedDateTime.now().minusDays(14), ZonedDateTime.now());
- log.info("calculateCachedActiveUserLogins took {}ms", System.currentTimeMillis() - startDate);
+ log.debug("calculateCachedActiveUserLogins took {}ms", System.currentTimeMillis() - startDate);
}
/**
@@ -399,7 +399,7 @@ public void recalculateMetrics() {
updateMultiGaugeIntegerForMinuteRanges(releaseExamGauge, examRepository::countExamsWithStartDateBetween);
updateMultiGaugeIntegerForMinuteRanges(releaseExamStudentMultiplierGauge, examRepository::countExamUsersInExamsWithStartDateBetween);
- log.info("recalculateMetrics took {}ms", System.currentTimeMillis() - startDate);
+ log.debug("recalculateMetrics took {}ms", System.currentTimeMillis() - startDate);
}
@FunctionalInterface
@@ -546,7 +546,7 @@ public void updatePublicArtemisMetrics() {
activeExamsGauge.set(examRepository.countAllActiveExams(now));
examsGauge.set((int) examRepository.count());
- log.info("updatePublicArtemisMetrics took {}ms", System.currentTimeMillis() - startDate);
+ log.debug("updatePublicArtemisMetrics took {}ms", System.currentTimeMillis() - startDate);
}
private void updateActiveUserMultiGauge(ZonedDateTime now) {
diff --git a/src/main/java/de/tum/in/www1/artemis/config/ProgrammingLanguageConfiguration.java b/src/main/java/de/tum/in/www1/artemis/config/ProgrammingLanguageConfiguration.java
index 5cc5099a57a0..11150adf6762 100644
--- a/src/main/java/de/tum/in/www1/artemis/config/ProgrammingLanguageConfiguration.java
+++ b/src/main/java/de/tum/in/www1/artemis/config/ProgrammingLanguageConfiguration.java
@@ -49,7 +49,7 @@ public void setImages(final Map> buildImages) {
final var languageSpecificBuildImages = loadImages(buildImages);
checkImageForAllProgrammingLanguagesDefined(languageSpecificBuildImages);
images = languageSpecificBuildImages;
- log.info("Loaded Docker image configuration: {}", images);
+ log.debug("Loaded Docker image configuration: {}", images);
}
/**
@@ -77,7 +77,7 @@ private Stream buildDockerFlag(final DockerFlag dockerFlag) {
* @param dockerFlags key value pairs of run arguments
*/
public void setDefaultDockerFlags(final List dockerFlags) {
- log.info("Set Docker flags to {}", dockerFlags);
+ log.debug("Set Docker flags to {}", dockerFlags);
this.defaultDockerFlags = dockerFlags;
}
diff --git a/src/main/java/de/tum/in/www1/artemis/config/WebConfigurer.java b/src/main/java/de/tum/in/www1/artemis/config/WebConfigurer.java
index 18370c823e12..304ccd33b6be 100644
--- a/src/main/java/de/tum/in/www1/artemis/config/WebConfigurer.java
+++ b/src/main/java/de/tum/in/www1/artemis/config/WebConfigurer.java
@@ -52,10 +52,10 @@ public WebConfigurer(Environment env, JHipsterProperties jHipsterProperties) {
@Override
public void onStartup(ServletContext servletContext) {
if (env.getActiveProfiles().length != 0) {
- log.info("Web application configuration, using profiles: {}", (Object[]) env.getActiveProfiles());
+ log.debug("Web application configuration, using profiles: {}", (Object[]) env.getActiveProfiles());
}
setCachingHttpHeaders(servletContext);
- log.info("Web application fully configured");
+ log.debug("Web application fully configured");
}
/**
diff --git a/src/main/java/de/tum/in/www1/artemis/config/localvcci/JGitServletConfiguration.java b/src/main/java/de/tum/in/www1/artemis/config/localvcci/JGitServletConfiguration.java
index a07d099d2b40..679edeb377b5 100644
--- a/src/main/java/de/tum/in/www1/artemis/config/localvcci/JGitServletConfiguration.java
+++ b/src/main/java/de/tum/in/www1/artemis/config/localvcci/JGitServletConfiguration.java
@@ -31,7 +31,7 @@ public JGitServletConfiguration(LocalVCServletService localVCServletService) {
@Bean
public ServletRegistrationBean jgitServlet() {
ArtemisGitServlet gitServlet = new ArtemisGitServlet(localVCServletService);
- log.info("Registering ArtemisGitServlet for handling fetch and push requests to [Artemis URL]/git/[Project Key]/[Repository Slug].git");
+ log.debug("Registering ArtemisGitServlet for handling fetch and push requests to [Artemis URL]/git/[Project Key]/[Repository Slug].git");
return new ServletRegistrationBean<>(gitServlet, "/git/*");
}
}
diff --git a/src/main/java/de/tum/in/www1/artemis/config/localvcci/LocalCIConfiguration.java b/src/main/java/de/tum/in/www1/artemis/config/localvcci/LocalCIConfiguration.java
index 3a4c72700f0b..1b7fcdb9fe2e 100644
--- a/src/main/java/de/tum/in/www1/artemis/config/localvcci/LocalCIConfiguration.java
+++ b/src/main/java/de/tum/in/www1/artemis/config/localvcci/LocalCIConfiguration.java
@@ -86,12 +86,34 @@ public HostConfig hostConfig() {
}
}
- log.info("Using Build Job Container HostConfig with cpus {}, memory {}, memorySwap {}, pidsLimit {}.", cpuCount, memory, memorySwap, pidsLimit);
+ log.info("Using build job container docker host config with CPU(s): {}, memory: {}, memory swap: {}, pids limit: {}.", cpuCount, formatMemory(memory),
+ formatMemory(memorySwap), pidsLimit);
return HostConfig.newHostConfig().withCpuQuota(cpuCount * cpuPeriod).withCpuPeriod(cpuPeriod).withMemory(memory).withMemorySwap(memorySwap).withPidsLimit(pidsLimit)
.withAutoRemove(true);
}
+ /**
+ * Converts bytes into a human-readable format (KB, MB, or GB).
+ *
+ * @param bytes The number of bytes.
+ * @return A string representing the memory size in KB, MB, or GB.
+ */
+ public static String formatMemory(long bytes) {
+ if (bytes < 1024) {
+ return bytes + " Bytes";
+ }
+ else if (bytes < 1024 * 1024) {
+ return (bytes / 1024) + " KB";
+ }
+ else if (bytes < 1024 * 1024 * 1024) {
+ return (bytes / (1024 * 1024)) + " MB";
+ }
+ else {
+ return String.format("%.1f GB", bytes / (1024.0 * 1024.0 * 1024.0));
+ }
+ }
+
/**
* Creates an executor service that manages the queue of build jobs.
*
@@ -116,7 +138,7 @@ public ExecutorService localCIBuildExecutorService() {
throw new RejectedExecutionException("Task " + runnable.toString() + " rejected from " + executor.toString());
};
- log.info("Using ExecutorService with thread pool size {}.", threadPoolSize);
+ log.debug("Using ExecutorService with thread pool size {}.", threadPoolSize);
return new ThreadPoolExecutor(threadPoolSize, threadPoolSize, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1), customThreadFactory, customRejectedExecutionHandler);
}
@@ -143,7 +165,7 @@ public DockerClient dockerClient() {
DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder().dockerHost(config.getDockerHost()).sslConfig(config.getSSLConfig()).build();
DockerClient dockerClient = DockerClientImpl.getInstance(config, httpClient);
- log.info("Docker client created with connection URI: {}", dockerConnectionUri);
+ log.debug("Docker client created with connection URI: {}", dockerConnectionUri);
return dockerClient;
}
diff --git a/src/main/java/de/tum/in/www1/artemis/config/migration/MigrationService.java b/src/main/java/de/tum/in/www1/artemis/config/migration/MigrationService.java
index f3dfa02215ed..8f88c73e4e26 100644
--- a/src/main/java/de/tum/in/www1/artemis/config/migration/MigrationService.java
+++ b/src/main/java/de/tum/in/www1/artemis/config/migration/MigrationService.java
@@ -63,7 +63,7 @@ public void execute(ApplicationReadyEvent event, SortedMap entryMap = instantiateEntryMap(entryClassMap);
@@ -72,7 +72,7 @@ public void execute(ApplicationReadyEvent event, SortedMap executedChanges = migrationChangeRepository.findAll().stream().map(MigrationChangelog::getDateString).collect(Collectors.toCollection(HashSet::new));
@@ -100,9 +100,9 @@ public void execute(ApplicationReadyEvent event, SortedMap instantiateEntryMap(SortedMap> entryClassMap) {
@@ -121,7 +121,7 @@ public SortedMap instantiateEntryMap(SortedMap entryMap) {
- log.info("Starting migration integrity check");
+ log.debug("Starting migration integrity check");
boolean passed = true;
Map brokenInstances = entryMap.entrySet().stream()
.filter(entry -> !StringUtils.hasLength(entry.getValue().date()) || !StringUtils.hasLength(entry.getValue().author()))
@@ -152,7 +152,7 @@ public boolean checkIntegrity(SortedMap entryMap) {
baseEntry = entry;
}
}
- log.info("Ending migration integrity check.");
+ log.debug("Ending migration integrity check.");
return passed;
}
diff --git a/src/main/java/de/tum/in/www1/artemis/config/websocket/WebsocketConfiguration.java b/src/main/java/de/tum/in/www1/artemis/config/websocket/WebsocketConfiguration.java
index 9b819a29194d..e4bbf50c3b79 100644
--- a/src/main/java/de/tum/in/www1/artemis/config/websocket/WebsocketConfiguration.java
+++ b/src/main/java/de/tum/in/www1/artemis/config/websocket/WebsocketConfiguration.java
@@ -126,7 +126,7 @@ protected void configureMessageBroker(@NotNull MessageBrokerRegistry config) {
// If tcpClient is null, there is no valid address specified in the config. This could be due to a development setup or a mistake in the config.
TcpOperations tcpClient = createTcpClient();
if (tcpClient != null) {
- log.info("Enabling StompBrokerRelay for WebSocket messages using {}", String.join(", ", brokerAddresses));
+ log.debug("Enabling StompBrokerRelay for WebSocket messages using {}", String.join(", ", brokerAddresses));
config
// Enable the relay for "/topic"
.enableStompBrokerRelay("/topic")
@@ -142,7 +142,7 @@ protected void configureMessageBroker(@NotNull MessageBrokerRegistry config) {
.setTcpClient(tcpClient);
}
else {
- log.info("Did NOT enable StompBrokerRelay for WebSocket messages");
+ log.debug("Did NOT enable StompBrokerRelay for WebSocket messages");
config.enableSimpleBroker("/topic").setHeartbeatValue(new long[] { 10000, 20000 }).setTaskScheduler(messageBrokerTaskScheduler);
}
}
diff --git a/src/main/java/de/tum/in/www1/artemis/domain/BuildJob.java b/src/main/java/de/tum/in/www1/artemis/domain/BuildJob.java
index c367047ef0d0..fcf6ac5ba4a1 100644
--- a/src/main/java/de/tum/in/www1/artemis/domain/BuildJob.java
+++ b/src/main/java/de/tum/in/www1/artemis/domain/BuildJob.java
@@ -92,7 +92,7 @@ public BuildJob(LocalCIBuildJobQueueItem queueItem, BuildStatus buildStatus, Res
this.buildCompletionDate = queueItem.jobTimingInfo().buildCompletionDate();
this.repositoryType = queueItem.repositoryInfo().repositoryType();
this.repositoryName = queueItem.repositoryInfo().repositoryName();
- this.commitHash = queueItem.buildConfig().commitHash();
+ this.commitHash = queueItem.buildConfig().commitHashToBuild();
this.retryCount = queueItem.retryCount();
this.priority = queueItem.priority();
this.triggeredByPushTo = queueItem.repositoryInfo().triggeredByPushTo();
diff --git a/src/main/java/de/tum/in/www1/artemis/domain/lti/Claims.java b/src/main/java/de/tum/in/www1/artemis/domain/lti/Claims.java
index be99b242aed2..ca5d8494cc74 100644
--- a/src/main/java/de/tum/in/www1/artemis/domain/lti/Claims.java
+++ b/src/main/java/de/tum/in/www1/artemis/domain/lti/Claims.java
@@ -13,4 +13,10 @@ public class Claims extends uk.ac.ox.ctl.lti13.lti.Claims {
* Used to carry messages specific to LTI Deep Linking requests and responses.
*/
public static final String MSG = "https://purl.imsglobal.org/spec/lti-dl/claim/msg";
+
+ /**
+ * Constant for LTI Deep Linking return url claim.
+ * Used to carry url specific to LTI Deep Linking requests and responses.
+ */
+ public static final String DEEPLINK_RETURN_URL_CLAIM = "deep_link_return_url";
}
diff --git a/src/main/java/de/tum/in/www1/artemis/domain/lti/Lti13AgsClaim.java b/src/main/java/de/tum/in/www1/artemis/domain/lti/Lti13AgsClaim.java
index 4196ce423889..62282902ae69 100644
--- a/src/main/java/de/tum/in/www1/artemis/domain/lti/Lti13AgsClaim.java
+++ b/src/main/java/de/tum/in/www1/artemis/domain/lti/Lti13AgsClaim.java
@@ -6,20 +6,13 @@
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
-import com.google.gson.Gson;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonPrimitive;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
/**
- * A wrapper class for an LTI 1.3 Assignment and Grading Services Claim. We support the Score Publishing Service in order to transmit scores.
+ * A wrapper record for an LTI 1.3 Assignment and Grading Services Claim. We support the Score Publishing Service in order to transmit scores.
*/
-public class Lti13AgsClaim {
-
- private List scope;
-
- private String lineItem;
+public record Lti13AgsClaim(List scope, String lineItem) {
/**
* Returns an Ags-Claim representation if the provided idToken contains any.
@@ -34,53 +27,29 @@ public static Optional from(OidcIdToken idToken) {
}
try {
- JsonObject agsClaimJson = new Gson().toJsonTree(idToken.getClaim(Claims.AGS_CLAIM)).getAsJsonObject();
- Lti13AgsClaim agsClaim = new Lti13AgsClaim();
- JsonArray scopes = agsClaimJson.get("scope").getAsJsonArray();
-
- if (scopes == null) {
- return Optional.empty();
- }
+ ObjectMapper objectMapper = new ObjectMapper();
+ JsonNode agsClaimJson = objectMapper.convertValue(idToken.getClaim(Claims.AGS_CLAIM), JsonNode.class);
- if (scopes.contains(new JsonPrimitive(Scopes.AGS_SCORE))) {
- agsClaim.setScope(Collections.singletonList(Scopes.AGS_SCORE));
+ JsonNode scopes = agsClaimJson.get("scope");
+ List scopeList = null;
+ if (scopes != null && scopes.isArray() && scopes.has(Scopes.AGS_SCORE)) {
+ scopeList = Collections.singletonList(Scopes.AGS_SCORE);
}
// For moodle lineItem is stored in lineitem claim, for edX it is in lineitems
- JsonElement lineItem;
+ JsonNode lineItemNode;
if (agsClaimJson.get("lineitem") == null) {
- lineItem = agsClaimJson.get("lineitems");
+ lineItemNode = agsClaimJson.get("lineitems");
}
else {
- lineItem = agsClaimJson.get("lineitem");
+ lineItemNode = agsClaimJson.get("lineitem");
}
- if (lineItem != null) {
- agsClaim.setLineItem(lineItem.getAsString());
- }
- else {
- agsClaim.setLineItem(null);
- }
- return Optional.of(agsClaim);
+ String lineItem = lineItemNode != null ? lineItemNode.asText() : null;
+ return Optional.of(new Lti13AgsClaim(scopeList, lineItem));
}
catch (IllegalStateException | ClassCastException ex) {
throw new IllegalStateException("Failed to parse LTI 1.3 ags claim.", ex);
}
}
-
- public List getScope() {
- return scope;
- }
-
- private void setScope(List scope) {
- this.scope = scope;
- }
-
- public String getLineItem() {
- return lineItem;
- }
-
- private void setLineItem(String lineItem) {
- this.lineItem = lineItem;
- }
}
diff --git a/src/main/java/de/tum/in/www1/artemis/domain/lti/Lti13DeepLinkingResponse.java b/src/main/java/de/tum/in/www1/artemis/domain/lti/Lti13DeepLinkingResponse.java
index ff9a101f27a2..9e89152aec91 100644
--- a/src/main/java/de/tum/in/www1/artemis/domain/lti/Lti13DeepLinkingResponse.java
+++ b/src/main/java/de/tum/in/www1/artemis/domain/lti/Lti13DeepLinkingResponse.java
@@ -1,67 +1,46 @@
package de.tum.in.www1.artemis.domain.lti;
-import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import com.fasterxml.jackson.annotation.JsonProperty;
-import com.google.gson.Gson;
-import com.google.gson.JsonObject;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Represents the LTI 1.3 Deep Linking Response.
* It encapsulates the necessary information to construct a valid deep linking response according to the LTI 1.3 specification.
* For more details, refer to LTI 1.3 Deep Linking Response Specification
+ *
+ * @param aud The 'aud' (Audience) field is used to specify the intended recipient of the response
+ * In the context of LTI Deep Linking, this field is set to the 'iss' (Issuer) value from the original request.
+ * This indicates that the response is specifically intended for the issuer of the original request.
+ * @param iss * The 'iss' (Issuer) field identifies the principal that issued the response.
+ * For LTI Deep Linking, this field is set to the 'aud' (Audience) value from the original request.
+ * This reversal signifies that the tool (originally the audience) is now the issuer of the response.
+ * @param exp Expiration time of the response.
+ * @param iat Issued at time of the response.
+ * @param nonce A string value used to associate a Client session with an ID Token.
+ * @param message A message included in the deep linking response.
+ * @param deploymentId The deployment ID from the LTI request.
+ * @param messageType The type of LTI message, for deep linking this is "LtiDeepLinkingResponse".
+ * @param ltiVersion The LTI version, for deep linking responses this is typically "1.3.0".
+ * @param contentItems The actual content items being linked.
+ * @param deepLinkingSettings A JSON object containing deep linking settings.
+ * @param clientRegistrationId The client registration ID.
+ * @param returnUrl The URL to return to after deep linking is completed.
*/
-public class Lti13DeepLinkingResponse {
+public record Lti13DeepLinkingResponse(@JsonProperty(IdTokenClaimNames.AUD) String aud, @JsonProperty(IdTokenClaimNames.ISS) String iss,
+ @JsonProperty(IdTokenClaimNames.EXP) String exp, @JsonProperty(IdTokenClaimNames.IAT) String iat, @JsonProperty(IdTokenClaimNames.NONCE) String nonce,
+ @JsonProperty(Claims.MSG) String message, @JsonProperty(Claims.LTI_DEPLOYMENT_ID) String deploymentId, @JsonProperty(Claims.MESSAGE_TYPE) String messageType,
+ @JsonProperty(Claims.LTI_VERSION) String ltiVersion, @JsonProperty(Claims.CONTENT_ITEMS) List