diff --git a/pom.xml b/pom.xml
index 6e6caff0..4e25504e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -322,6 +322,18 @@
org.eclipse.rap.fileupload
3.7.0
+
+ org.xeustechnologies
+ jcl-core
+ 2.8
+
+
+
+
+
+
+
+
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/RAPConfiguration.java b/src/main/java/ch/ethz/seb/sebserver/gui/RAPConfiguration.java
index c77ee71c..e35ecd27 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/RAPConfiguration.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/RAPConfiguration.java
@@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.gui;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
@@ -17,6 +18,7 @@ import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
+import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.application.AbstractEntryPoint;
import org.eclipse.rap.rwt.application.Application;
@@ -33,6 +35,9 @@ import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
+import org.xeustechnologies.jcl.JarClassLoader;
+
+//import com.eclipsesource.rap.aria.Aria;
import ch.ethz.seb.sebserver.gui.service.remote.download.DownloadService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
@@ -72,6 +77,7 @@ public class RAPConfiguration implements ApplicationConfiguration {
application.addEntryPoint(guiEntrypoint, new RAPSpringEntryPointFactory(), properties);
application.addEntryPoint(proctoringEntrypoint, new RAPRemoteProcotringEntryPointFactory(), properties);
+
} catch (final RuntimeException re) {
throw re;
} catch (final Exception e) {
@@ -99,6 +105,7 @@ public class RAPConfiguration implements ApplicationConfiguration {
@Override
protected void createContents(final Composite parent) {
+
final HttpSession httpSession = RWT
.getUISession(parent.getDisplay())
.getHttpSession();
@@ -120,6 +127,7 @@ public class RAPConfiguration implements ApplicationConfiguration {
public static final class RAPSpringEntryPointFactory implements EntryPointFactory {
+ private final JarClassLoader jcl = new JarClassLoader();
private boolean initialized = false;
@Override
@@ -154,6 +162,28 @@ public class RAPConfiguration implements ApplicationConfiguration {
final WebApplicationContext webApplicationContext = getWebApplicationContext(httpSession);
initSpringBasedRAPServices(webApplicationContext);
+ final String ariaPluginPath = ariaPluginPath(webApplicationContext);
+ if (StringUtils.isNotBlank(ariaPluginPath)) {
+
+ log.debug("Try to initialize com.eclipsesource.rap.aria.Aria plugin...");
+
+ try {
+
+ final Class> forName = Class.forName(
+ "com.eclipsesource.rap.aria.Aria",
+ false,
+ RAPSpringEntryPointFactory.this.jcl);
+
+ final Method method = forName.getMethod("activate");
+ method.invoke(null);
+
+ log.info("Initialization of com.eclipsesource.rap.aria.Aria plugin was successful");
+
+ } catch (final Exception e) {
+ log.error("Failed to initialize com.eclipsesource.rap.aria.Aria plugin: ", e);
+ }
+ }
+
final EntryPointService entryPointService = webApplicationContext
.getBean(EntryPointService.class);
@@ -172,12 +202,25 @@ public class RAPConfiguration implements ApplicationConfiguration {
final ServiceManager manager = RWT.getServiceManager();
final DownloadService downloadService = webApplicationContext.getBean(DownloadService.class);
manager.registerServiceHandler(DownloadService.DOWNLOAD_SERVICE_NAME, downloadService);
+
+ final String ariaPluginPath = ariaPluginPath(webApplicationContext);
+ if (StringUtils.isNotBlank(ariaPluginPath)) {
+ this.jcl.add(ariaPluginPath);
+ }
+
this.initialized = true;
} catch (final IllegalArgumentException iae) {
log.warn("Failed to register DownloadService on ServiceManager. Already registered: ", iae);
}
}
}
+
+ private String ariaPluginPath(final WebApplicationContext webApplicationContext) {
+ return webApplicationContext
+ .getEnvironment()
+ .getProperty("sebserver.gui.external.lib.aria.plugin.path", "");
+
+ }
}
private static boolean isAuthenticated(
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/DBIntegrityCheck.java b/src/main/java/ch/ethz/seb/sebserver/webservice/DBIntegrityCheck.java
new file mode 100644
index 00000000..1e96b296
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/DBIntegrityCheck.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+package ch.ethz.seb.sebserver.webservice;
+
+import ch.ethz.seb.sebserver.gbl.util.Result;
+
+public interface DBIntegrityCheck {
+
+ String name();
+
+ String description();
+
+ Result applyCheck(boolean tryFix);
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/DBIntegrityChecker.java b/src/main/java/ch/ethz/seb/sebserver/webservice/DBIntegrityChecker.java
new file mode 100644
index 00000000..2e3d0768
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/DBIntegrityChecker.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+package ch.ethz.seb.sebserver.webservice;
+
+import java.util.Collection;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.seb.sebserver.SEBServerInit;
+import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
+import ch.ethz.seb.sebserver.gbl.util.Result;
+
+@Lazy
+@Component
+@WebServiceProfile
+public class DBIntegrityChecker {
+
+ private static final Logger log = LoggerFactory.getLogger(DBIntegrityChecker.class);
+
+ private final Collection checkers;
+ private final boolean runIntegrityChecks;
+ private final boolean tryFix;
+
+ public DBIntegrityChecker(
+ final Collection checkers,
+ @Value("${sebserver.init.database.integrity.checks:true}") final boolean runIntegrityChecks,
+ @Value("${sebserver.init.database.integrity.try-fix:true}") final boolean tryFix) {
+
+ this.checkers = checkers;
+ this.runIntegrityChecks = runIntegrityChecks;
+ this.tryFix = tryFix;
+ }
+
+ public void checkIntegrity() {
+ if (this.runIntegrityChecks && !this.checkers.isEmpty()) {
+
+ SEBServerInit.INIT_LOGGER.info("---->");
+ SEBServerInit.INIT_LOGGER.info("----> **** Run data-base integrity checks ****");
+ SEBServerInit.INIT_LOGGER.info("---->");
+
+ this.checkers.stream().forEach(this::doCheck);
+ }
+ }
+
+ private void doCheck(final DBIntegrityCheck dbIntegrityCheck) {
+ try {
+
+ SEBServerInit.INIT_LOGGER.info("------> Apply check: {} / {}", dbIntegrityCheck.name(),
+ dbIntegrityCheck.description());
+
+ final Result applyCheck = dbIntegrityCheck.applyCheck(this.tryFix);
+ if (applyCheck.hasError()) {
+ SEBServerInit.INIT_LOGGER.info("--------> Unexpected Error: {}", applyCheck.getError().getMessage());
+ } else {
+ SEBServerInit.INIT_LOGGER.info("--------> Result: {}", applyCheck.get());
+ }
+
+ } catch (final Exception e) {
+ log.error("Unexpected error while trying to apply data base integrity check: {}", dbIntegrityCheck);
+ }
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java
index cf00233f..d87df930 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java
@@ -37,6 +37,7 @@ public class WebserviceInit implements ApplicationListener");
SEBServerInit.INIT_LOGGER.info("----> Property Override Test: {}", this.webserviceInfo.getTestProperty());
+ // Run the data base integrity checks and fixes if configured
+ this.dbIntegrityChecker.checkIntegrity();
+
// Create an initial admin account if requested and not already in the data-base
this.adminUserInitializer.initAdminAccount();
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/checks/OrientationTableDuplicatesCheck.java b/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/checks/OrientationTableDuplicatesCheck.java
new file mode 100644
index 00000000..0a391e44
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/checks/OrientationTableDuplicatesCheck.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+package ch.ethz.seb.sebserver.webservice.datalayer.checks;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
+import ch.ethz.seb.sebserver.gbl.util.Result;
+import ch.ethz.seb.sebserver.webservice.DBIntegrityCheck;
+import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.OrientationRecordMapper;
+import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.OrientationRecord;
+
+@Lazy
+@Component
+@WebServiceProfile
+public class OrientationTableDuplicatesCheck implements DBIntegrityCheck {
+
+ private final OrientationRecordMapper orientationRecordMapper;
+
+ public OrientationTableDuplicatesCheck(final OrientationRecordMapper orientationRecordMapper) {
+ this.orientationRecordMapper = orientationRecordMapper;
+ }
+
+ @Override
+ public String name() {
+ return "OrientationTableDuplicatesCheck";
+ }
+
+ @Override
+ public String description() {
+ return "Checks if there are duplicate entries in the orientation table by using the config_attribute_id and template_id to identify duplicates.";
+ }
+
+ @Override
+ @Transactional
+ public Result applyCheck(final boolean tryFix) {
+ return Result.tryCatch(() -> {
+ final List records = this.orientationRecordMapper
+ .selectByExample()
+ .build()
+ .execute();
+
+ final Set once = new HashSet<>();
+ final Set toDelete = new HashSet<>();
+ for (final OrientationRecord record : records) {
+ final String id = record.getConfigAttributeId().toString() + record.getTemplateId().toString();
+ if (once.contains(id)) {
+ toDelete.add(record.getId());
+ } else {
+ once.add(id);
+ }
+ }
+
+ if (toDelete.isEmpty()) {
+ return "OK";
+ }
+
+ if (tryFix) {
+ toDelete
+ .stream()
+ .forEach(this.orientationRecordMapper::deleteByPrimaryKey);
+ return "Fixed duplicates by deletion: " + toDelete;
+ } else {
+ return "Found duplicates: " + toDelete;
+ }
+
+ });
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("OrientationTableDuplicatesCheck [name()=");
+ builder.append(name());
+ builder.append(", description()=");
+ builder.append(description());
+ builder.append("]");
+ return builder.toString();
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/checks/ViewTableDuplicatesCheck.java b/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/checks/ViewTableDuplicatesCheck.java
new file mode 100644
index 00000000..970ced2a
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/checks/ViewTableDuplicatesCheck.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+package ch.ethz.seb.sebserver.webservice.datalayer.checks;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
+import ch.ethz.seb.sebserver.gbl.util.Result;
+import ch.ethz.seb.sebserver.webservice.DBIntegrityCheck;
+import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ViewRecordMapper;
+import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ViewRecord;
+
+@Lazy
+@Component
+@WebServiceProfile
+public class ViewTableDuplicatesCheck implements DBIntegrityCheck {
+
+ private final ViewRecordMapper viewRecordMapper;
+
+ public ViewTableDuplicatesCheck(final ViewRecordMapper viewRecordMapper) {
+ this.viewRecordMapper = viewRecordMapper;
+ }
+
+ @Override
+ public String name() {
+ return "ViewTableDuplicatesCheck";
+ }
+
+ @Override
+ public String description() {
+ return "Checks if there are duplicate entries in the view table by using the name and template_id to identify duplicates.";
+ }
+
+ @Override
+ @Transactional
+ public Result applyCheck(final boolean tryFix) {
+ return Result.tryCatch(() -> {
+ final List records = this.viewRecordMapper
+ .selectByExample()
+ .build()
+ .execute();
+
+ final Set once = new HashSet<>();
+ final Set toDelete = new HashSet<>();
+ for (final ViewRecord record : records) {
+ final String id = record.getName() + record.getTemplateId().toString();
+ if (once.contains(id)) {
+ toDelete.add(record.getId());
+ } else {
+ once.add(id);
+ }
+ }
+
+ if (toDelete.isEmpty()) {
+ return "OK";
+ }
+
+ if (tryFix) {
+ toDelete
+ .stream()
+ .forEach(this.viewRecordMapper::deleteByPrimaryKey);
+ return "Fixed duplicates by deletion: " + toDelete;
+ } else {
+ return "Found duplicates: " + toDelete;
+ }
+
+ });
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("ViewTableDuplicatesCheck [name()=");
+ builder.append(name());
+ builder.append(", description()=");
+ builder.append(description());
+ builder.append("]");
+ return builder.toString();
+ }
+
+}
diff --git a/src/main/resources/config/application-dev.properties b/src/main/resources/config/application-dev.properties
index 7a2939a8..34a799ba 100644
--- a/src/main/resources/config/application-dev.properties
+++ b/src/main/resources/config/application-dev.properties
@@ -1,5 +1,7 @@
spring.profiles.include=dev-ws,dev-gui
+sebserver.test.property=This is the development Setup
+
server.address=localhost
server.port=8080
server.servlet.context-path=/
diff --git a/src/main/resources/config/application-gui.properties b/src/main/resources/config/application-gui.properties
index be7a8d45..fdb9eaf6 100644
--- a/src/main/resources/config/application-gui.properties
+++ b/src/main/resources/config/application-gui.properties
@@ -38,6 +38,9 @@ sebserver.gui.filter.date.from.years=2
sebserver.gui.remote.proctoring.entrypoint=/remote-proctoring
sebserver.gui.remote.proctoring.api-servler.endpoint=/remote-view-servlet
+# external libs / plugins
+sebserver.gui.external.lib.aria.plugin.path=
+
# Webservice connection details
sebserver.webservice.api.exam.endpoint=/exam-api
sebserver.webservice.api.exam.endpoint.discovery=${sebserver.webservice.api.exam.endpoint}/discovery
diff --git a/src/main/resources/config/application-ws.properties b/src/main/resources/config/application-ws.properties
index 8f6d94c2..511ce231 100644
--- a/src/main/resources/config/application-ws.properties
+++ b/src/main/resources/config/application-ws.properties
@@ -7,6 +7,8 @@ sebserver.test.property=This is the default/root configuration
sebserver.init.adminaccount.gen-on-init=true
sebserver.init.organisation.name=SEB Server
sebserver.init.adminaccount.username=sebserver-admin
+sebserver.init.database.integrity.checks=true
+sebserver.init.database.integrity.try-fix=true
### webservice caching
spring.cache.jcache.provider=org.ehcache.jsr107.EhcacheCachingProvider