diff --git a/README.rst b/README.rst index 401e1448..fda4782b 100644 --- a/README.rst +++ b/README.rst @@ -38,7 +38,7 @@ The SEB Server Setup repository contains predefined, docker-based installation d Install SEB Server ------------------ -For a complete guide to install SEB Server please go to `SEB Server Installation Guide `_ +For a complete guide to install SEB Server please go to `SEB Server Installation Guide `_ Getting started with SEB Server ------------------------------- diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/GuiInit.java b/src/main/java/ch/ethz/seb/sebserver/gui/GuiInit.java new file mode 100644 index 00000000..8d7dd7b6 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/GuiInit.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 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.gui; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; + +@Component +@GuiProfile +public class GuiInit implements ApplicationListener { + + static final Logger INIT_LOGGER = LoggerFactory.getLogger("SEB SERVER INIT"); + + @Override + public void onApplicationEvent(final ApplicationReadyEvent event) { + INIT_LOGGER.info("----> SEB Server GUI Component sucessfully initialized!"); + + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/AdminUserInitializer.java b/src/main/java/ch/ethz/seb/sebserver/webservice/AdminUserInitializer.java index d715e4f1..ed44739f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/AdminUserInitializer.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/AdminUserInitializer.java @@ -51,7 +51,7 @@ class AdminUserInitializer { @Qualifier(WebSecurityConfig.USER_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder passwordEncoder, @Value("${sebserver.init.adminaccount.gen-on-init:false}") final boolean initializeAdmin, @Value("${sebserver.init.adminaccount.username:seb-server-admin}") final String adminName, - @Value("${sebserver.init.organisation.name:ETHZ}") final String orgName) { + @Value("${sebserver.init.organisation.name:[SET_ORGANIZATION_NAME]}") final String orgName) { this.userDAO = userDAO; this.institutionDAO = institutionDAO; @@ -67,72 +67,78 @@ class AdminUserInitializer { return; } - log.debug("Create initial admin account is switched on. Check database if exists..."); - final Result byUsername = this.userDAO.sebServerUserByUsername(this.adminName); - if (byUsername.hasValue()) { + try { - log.debug("Initial admin account already exists. Check if the password must be reset..."); + log.debug("Create initial admin account is switched on. Check database if exists..."); + final Result byUsername = this.userDAO.sebServerUserByUsername(this.adminName); + if (byUsername.hasValue()) { - final SEBServerUser sebServerUser = byUsername.get(); - final String password = sebServerUser.getPassword(); - if (this.passwordEncoder.matches("admin", password)) { + log.debug("Initial admin account already exists. Check if the password must be reset..."); - log.debug("Setting new generated password for already existing admin account"); + final SEBServerUser sebServerUser = byUsername.get(); + final String password = sebServerUser.getPassword(); + if (this.passwordEncoder.matches("admin", password)) { + + log.debug("Setting new generated password for already existing admin account"); + final CharSequence generateAdminPassword = this.generateAdminPassword(); + if (generateAdminPassword != null) { + this.userDAO.changePassword( + sebServerUser.getUserInfo().getModelId(), + generateAdminPassword); + this.writeAdminCredentials(this.adminName, generateAdminPassword); + } + } + } else { final CharSequence generateAdminPassword = this.generateAdminPassword(); if (generateAdminPassword != null) { - this.userDAO.changePassword( - sebServerUser.getUserInfo().getModelId(), - generateAdminPassword); - this.writeAdminCredentials(this.adminName, generateAdminPassword); - } - } - } else { - final CharSequence generateAdminPassword = this.generateAdminPassword(); - if (generateAdminPassword != null) { - Long institutionId = this.institutionDAO.allMatching(new FilterMap()) - .getOrElse(() -> Collections.emptyList()) - .stream() - .findFirst() - .filter(Institution::isActive) - .map(Institution::getInstitutionId) - .orElseGet(() -> -1L); - - if (institutionId < 0) { - - log.debug("Create new initial institution"); - institutionId = this.institutionDAO.createNew(new Institution( - null, - this.orgName, - null, - null, - null, - true)) - .map(inst -> this.institutionDAO.setActive(inst, true).getOrThrow()) + Long institutionId = this.institutionDAO.allMatching(new FilterMap()) + .getOrElse(() -> Collections.emptyList()) + .stream() + .findFirst() + .filter(Institution::isActive) .map(Institution::getInstitutionId) + .orElseGet(() -> -1L); + + if (institutionId < 0) { + + log.debug("Create new initial institution"); + institutionId = this.institutionDAO.createNew(new Institution( + null, + this.orgName, + null, + null, + null, + true)) + .map(inst -> this.institutionDAO.setActive(inst, true).getOrThrow()) + .map(Institution::getInstitutionId) + .getOrThrow(); + } + + this.userDAO.createNew(new UserMod( + this.adminName, + institutionId, + this.adminName, + this.adminName, + generateAdminPassword, + generateAdminPassword, + null, + null, + null, + new HashSet<>(Arrays.asList(UserRole.SEB_SERVER_ADMIN.name())))) + .flatMap(account -> this.userDAO.setActive(account, true)) + .map(account -> { + writeAdminCredentials(this.adminName, generateAdminPassword); + return account; + }) .getOrThrow(); } - - this.userDAO.createNew(new UserMod( - this.adminName, - institutionId, - this.adminName, - this.adminName, - generateAdminPassword, - generateAdminPassword, - null, - null, - null, - new HashSet<>(Arrays.asList(UserRole.SEB_SERVER_ADMIN.name())))) - .flatMap(account -> this.userDAO.setActive(account, true)) - .map(account -> { - writeAdminCredentials(this.adminName, generateAdminPassword); - return account; - }) - .getOrThrow(); } + } catch (final Exception e) { + WebserviceInit.INIT_LOGGER.error("---->"); + WebserviceInit.INIT_LOGGER.error("----> SEB Server initial admin-account creation failed: ", e); + WebserviceInit.INIT_LOGGER.error("---->"); } - } private void writeAdminCredentials(final String name, final CharSequence pwd) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java index 1e5a1305..d1d5eec0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java @@ -33,6 +33,7 @@ public class WebserviceInfo { private static final Logger log = LoggerFactory.getLogger(WebserviceInfo.class); + private static final String VERSION_KEY = "sebserver.version"; private static final String WEB_SERVICE_TEST_PROPERTY = "sebserver.test.property"; private static final String WEB_SERVICE_SERVER_NAME_KEY = "sebserver.webservice.http.server.name"; private static final String WEB_SERVICE_HTTP_SCHEME_KEY = "sebserver.webservice.http.scheme"; @@ -43,6 +44,7 @@ public class WebserviceInfo { "sebserver.webservice.api.exam.endpoint.discovery"; private static final String WEB_SERVICE_EXTERNAL_ADDRESS_ALIAS = "sebserver.webservice.lms.address.alias"; + private final String sebServerVersion; private final String testProperty; private final String httpScheme; private final String hostAddress; // internal @@ -57,6 +59,7 @@ public class WebserviceInfo { private Map externalAddressAlias; public WebserviceInfo(final Environment environment) { + this.sebServerVersion = environment.getRequiredProperty(VERSION_KEY); this.testProperty = environment.getProperty(WEB_SERVICE_TEST_PROPERTY, "NOT_AVAILABLE"); this.httpScheme = environment.getRequiredProperty(WEB_SERVICE_HTTP_SCHEME_KEY); this.hostAddress = environment.getRequiredProperty(WEB_SERVICE_HOST_ADDRESS_KEY); @@ -100,6 +103,10 @@ public class WebserviceInfo { } } + public String getSebServerVersion() { + return this.sebServerVersion; + } + public String getTestProperty() { return this.testProperty; } 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 12735621..e0910770 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java @@ -58,21 +58,24 @@ public class WebserviceInit implements ApplicationListener"); INIT_LOGGER.info("----> SEB Server successfully started up!"); INIT_LOGGER.info("---->"); + INIT_LOGGER.info("----> Version: {}", this.webserviceInfo.getSebServerVersion()); try { - INIT_LOGGER.info("----> config server address: {}", this.environment.getProperty("server.address")); - INIT_LOGGER.info("----> config server port: {}", this.environment.getProperty("server.port")); - - INIT_LOGGER.info("----> local host address: {}", InetAddress.getLocalHost().getHostAddress()); - INIT_LOGGER.info("----> local host name: {}", InetAddress.getLocalHost().getHostName()); - - INIT_LOGGER.info("----> remote host address: {}", InetAddress.getLoopbackAddress().getHostAddress()); - INIT_LOGGER.info("----> remote host name: {}", InetAddress.getLoopbackAddress().getHostName()); + INIT_LOGGER.info("----> Server address: {}", this.environment.getProperty("server.address")); + INIT_LOGGER.info("----> Server port: {}", this.environment.getProperty("server.port")); + INIT_LOGGER.info("---->"); + INIT_LOGGER.info("----> Local-Host address: {}", InetAddress.getLocalHost().getHostAddress()); + INIT_LOGGER.info("----> Local-Host name: {}", InetAddress.getLocalHost().getHostName()); + INIT_LOGGER.info("---->"); + INIT_LOGGER.info("----> Remote-Host address: {}", InetAddress.getLoopbackAddress().getHostAddress()); + INIT_LOGGER.info("----> Remote-Host name: {}", InetAddress.getLoopbackAddress().getHostName()); } catch (final UnknownHostException e) { log.error("Unknown Host: ", e); } - INIT_LOGGER.info("----> {}", this.webserviceInfo); + INIT_LOGGER.info("---->"); + INIT_LOGGER.info("----> HTTP Scheme {}", this.webserviceInfo.getHttpScheme()); + INIT_LOGGER.info("----> Property Override Test: {}", this.webserviceInfo.getTestProperty()); // TODO integration of Flyway for database initialization and migration: https://flywaydb.org // see also https://flywaydb.org/getstarted/firststeps/api diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java index 641b83ed..72979963 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java @@ -96,6 +96,12 @@ public interface ExamDAO extends ActivatableEntityDAO, BulkActionSup * @return Result refer to the specified exam or to an error if happened */ Result forceUnlock(Long examId); + /** Used to force unlock all locked exams for a specified updateId + * + * @param updateId the update identifier + * @return list of identifiers of unlocked exams */ + Result> forceUnlockAll(String updateId); + /** Indicates if the exam with specified identifier has an internal write lock. * * @param examId the exam identifier diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java index 0c304ef7..819fc76f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java @@ -465,7 +465,9 @@ public class ExamDAOImpl implements ExamDAO { @Transactional(propagation = Propagation.REQUIRES_NEW) public Result forceUnlock(final Long examId) { - log.info("forceUnlock for exam: {}", examId); + if (log.isDebugEnabled()) { + log.debug("forceUnlock for exam: {}", examId); + } return Result.tryCatch(() -> { final ExamRecord examRecord = new ExamRecord( @@ -481,6 +483,30 @@ public class ExamDAOImpl implements ExamDAO { } + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public Result> forceUnlockAll(final String updateId) { + + if (log.isDebugEnabled()) { + log.debug("forceUnlock for updateId: {}", updateId); + } + + return Result.tryCatch(() -> { + final Collection result = this.examRecordMapper.selectIdsByExample() + .where(ExamRecordDynamicSqlSupport.lastupdate, isEqualTo(updateId)) + .build() + .execute() + .stream() + .map(this::forceUnlock) + .flatMap(Result::skipOnError) + .collect(Collectors.toList()); + + return result; + }) + .onError(TransactionHandler::rollback); + + } + @Override @Transactional(readOnly = true) public Result isLocked(final Long examId) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserDAOImpl.java index d06aa578..e4b900a6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserDAOImpl.java @@ -293,8 +293,12 @@ public class UserDAOImpl implements UserDAO { .build() .execute(); - return ids.stream() - .map(id -> new EntityKey(id, EntityType.USER)) + return this.userRecordMapper.selectByExample() + .where(UserRecordDynamicSqlSupport.id, isIn(ids)) + .build() + .execute() + .stream() + .map(record -> new EntityKey(record.getUuid(), EntityType.USER)) .collect(Collectors.toList()); }); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java index 72b79aec..7043bc77 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java @@ -33,7 +33,6 @@ import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; @@ -150,7 +149,7 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService { return examIdsFirstCheck; }) - .onError(TransactionHandler::rollback); + .onError(t -> this.examDAO.forceUnlockAll(updateId)); } @Override @@ -180,7 +179,8 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService { checkActiveClientConnections(exam); // lock the exam - this.examDAO.placeLock(exam.id, updateId) + this.examDAO + .placeLock(exam.id, updateId) .getOrThrow(); // check again if there are no new active client connections in the meantime @@ -188,7 +188,8 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService { // apply the referenced change action. On error the change is rolled back and // this processing returns immediately with the error - final T result = changeAction.apply(mapping) + final T result = changeAction + .apply(mapping) .onError(t -> log.error("Fauled to save exam configuration: {}", mapping.configurationNodeId)) .getOrThrow(); @@ -205,11 +206,13 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService { // flush the exam cache. If there was an error during flush, it is logged but this process goes on // and the saved changes are not rolled back - this.examSessionService.flushCache(exam) + this.examSessionService + .flushCache(exam) .onError(t -> log.error("Failed to flush cache for exam: {}", exam)); // release the exam lock - this.examDAO.releaseLock(exam.id, updateId) + this.examDAO + .releaseLock(exam.id, updateId) .onError(t -> log.error("Failed to release lock for exam: {}", exam)); return result; @@ -238,7 +241,8 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService { configurationId); try { - final Configuration config = this.configurationDAO.byPK(configurationId) + final Configuration config = this.configurationDAO + .byPK(configurationId) .getOrThrow(); final Collection involvedExams = this.examConfigurationMapDAO @@ -260,7 +264,8 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService { @Override public Collection> forceReleaseUpdateLocks(final Collection examIds) { - return examIds.stream() + return examIds + .stream() .map(this.examDAO::forceUnlock) .collect(Collectors.toList()); } diff --git a/src/main/resources/config/application-dev-ws.properties b/src/main/resources/config/application-dev-ws.properties index dcae67db..7f932e60 100644 --- a/src/main/resources/config/application-dev-ws.properties +++ b/src/main/resources/config/application-dev-ws.properties @@ -20,6 +20,7 @@ sebserver.http.client.connection-request-timeout=10000 sebserver.http.client.read-timeout=10000 # webservice configuration +sebserver.init.adminaccount.gen-on-init=false sebserver.webservice.distributed=false sebserver.webservice.http.scheme=http sebserver.webservice.http.server.name=${server.address} diff --git a/src/test/java/ch/ethz/seb/sebserver/HTTPClientBot.java b/src/test/java/ch/ethz/seb/sebserver/HTTPClientBot.java index 652e1888..45e7ba44 100644 --- a/src/test/java/ch/ethz/seb/sebserver/HTTPClientBot.java +++ b/src/test/java/ch/ethz/seb/sebserver/HTTPClientBot.java @@ -79,8 +79,8 @@ public class HTTPClientBot { public HTTPClientBot(final Map args) { - this.webserviceAddress = args.getOrDefault("webserviceAddress", "http://ralph.ethz.ch:8080"); - // this.webserviceAddress = args.getOrDefault("webserviceAddress", "http://localhost:8080"); + // this.webserviceAddress = args.getOrDefault("webserviceAddress", "http://ralph.ethz.ch:8080"); + this.webserviceAddress = args.getOrDefault("webserviceAddress", "http://localhost:8080"); //this.webserviceAddress = args.getOrDefault("webserviceAddress", "https://seb.test-swissmooc.ch"); this.accessTokenEndpoint = args.getOrDefault("accessTokenEndpoint", "/oauth/token");