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