SEBSERV-73 made changed to Exam end Exam Config handling
This commit is contained in:
		
							parent
							
								
									6eaef577d2
								
							
						
					
					
						commit
						83985cdbf7
					
				
					 30 changed files with 844 additions and 259 deletions
				
			
		| 
						 | 
					@ -91,7 +91,7 @@ public final class API {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static final String INSTITUTION_ENDPOINT = "/institution";
 | 
					    public static final String INSTITUTION_ENDPOINT = "/institution";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static final String LMS_SETUP_ENDPOINT = "/lms_setup";
 | 
					    public static final String LMS_SETUP_ENDPOINT = "/lms-setup";
 | 
				
			||||||
    public static final String LMS_SETUP_TEST_PATH_SEGMENT = "/test";
 | 
					    public static final String LMS_SETUP_TEST_PATH_SEGMENT = "/test";
 | 
				
			||||||
    public static final String LMS_SETUP_TEST_ENDPOINT = LMS_SETUP_ENDPOINT
 | 
					    public static final String LMS_SETUP_TEST_ENDPOINT = LMS_SETUP_ENDPOINT
 | 
				
			||||||
            + LMS_SETUP_TEST_PATH_SEGMENT
 | 
					            + LMS_SETUP_TEST_PATH_SEGMENT
 | 
				
			||||||
| 
						 | 
					@ -106,18 +106,19 @@ public final class API {
 | 
				
			||||||
    public static final String QUIZ_DISCOVERY_ENDPOINT = "/quiz";
 | 
					    public static final String QUIZ_DISCOVERY_ENDPOINT = "/quiz";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static final String EXAM_ADMINISTRATION_ENDPOINT = "/exam";
 | 
					    public static final String EXAM_ADMINISTRATION_ENDPOINT = "/exam";
 | 
				
			||||||
    public static final String EXAM_ADMINISTRATION_DOWNLOAD_CONFIG_PATH_SEGMENT = "/downloadConfig";
 | 
					    public static final String EXAM_ADMINISTRATION_DOWNLOAD_CONFIG_PATH_SEGMENT = "/download-config";
 | 
				
			||||||
 | 
					    public static final String EXAM_ADMINISTRATION_CONSISTENCY_CHECK_PATH_SEGMENT = "/check-consistency";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static final String EXAM_INDICATOR_ENDPOINT = "/indicator";
 | 
					    public static final String EXAM_INDICATOR_ENDPOINT = "/indicator";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static final String SEB_CLIENT_CONFIG_ENDPOINT = "/client_configuration";
 | 
					    public static final String SEB_CLIENT_CONFIG_ENDPOINT = "/client_configuration";
 | 
				
			||||||
    public static final String SEB_CLIENT_CONFIG_DOWNLOAD_PATH_SEGMENT = "/download";
 | 
					    public static final String SEB_CLIENT_CONFIG_DOWNLOAD_PATH_SEGMENT = "/download";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static final String CONFIGURATION_NODE_ENDPOINT = "/configuration_node";
 | 
					    public static final String CONFIGURATION_NODE_ENDPOINT = "/configuration-node";
 | 
				
			||||||
    public static final String CONFIGURATION_FOLLOWUP_PATH_SEGMENT = "/followup";
 | 
					    public static final String CONFIGURATION_FOLLOWUP_PATH_SEGMENT = "/followup";
 | 
				
			||||||
    public static final String CONFIGURATION_CONFIG_KEY_PATH_SEGMENT = "/configkey";
 | 
					    public static final String CONFIGURATION_CONFIG_KEY_PATH_SEGMENT = "/configkey";
 | 
				
			||||||
    public static final String CONFIGURATION_ENDPOINT = "/configuration";
 | 
					    public static final String CONFIGURATION_ENDPOINT = "/configuration";
 | 
				
			||||||
    public static final String CONFIGURATION_SAVE_TO_HISTORY_PATH_SEGMENT = "/save_to_history";
 | 
					    public static final String CONFIGURATION_SAVE_TO_HISTORY_PATH_SEGMENT = "/save-to-history";
 | 
				
			||||||
    public static final String CONFIGURATION_UNDO_PATH_SEGMENT = "/undo";
 | 
					    public static final String CONFIGURATION_UNDO_PATH_SEGMENT = "/undo";
 | 
				
			||||||
    public static final String CONFIGURATION_COPY_PATH_SEGMENT = "/copy";
 | 
					    public static final String CONFIGURATION_COPY_PATH_SEGMENT = "/copy";
 | 
				
			||||||
    public static final String CONFIGURATION_RESTORE_FROM_HISTORY_PATH_SEGMENT = "/restore";
 | 
					    public static final String CONFIGURATION_RESTORE_FROM_HISTORY_PATH_SEGMENT = "/restore";
 | 
				
			||||||
| 
						 | 
					@ -137,7 +138,7 @@ public final class API {
 | 
				
			||||||
    public static final String ORIENTATION_ENDPOINT = "/orientation";
 | 
					    public static final String ORIENTATION_ENDPOINT = "/orientation";
 | 
				
			||||||
    public static final String VIEW_ENDPOINT = ORIENTATION_ENDPOINT + "/view";
 | 
					    public static final String VIEW_ENDPOINT = ORIENTATION_ENDPOINT + "/view";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static final String EXAM_CONFIGURATION_MAP_ENDPOINT = "/exam_configuration_map";
 | 
					    public static final String EXAM_CONFIGURATION_MAP_ENDPOINT = "/exam-configuration-map";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static final String USER_ACTIVITY_LOG_ENDPOINT = "/useractivity";
 | 
					    public static final String USER_ACTIVITY_LOG_ENDPOINT = "/useractivity";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,9 +43,12 @@ public class APIMessage implements Serializable {
 | 
				
			||||||
        ILLEGAL_API_ARGUMENT("1010", HttpStatus.BAD_REQUEST, "Illegal API request argument"),
 | 
					        ILLEGAL_API_ARGUMENT("1010", HttpStatus.BAD_REQUEST, "Illegal API request argument"),
 | 
				
			||||||
        UNEXPECTED("1100", HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected intenral server-side error"),
 | 
					        UNEXPECTED("1100", HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected intenral server-side error"),
 | 
				
			||||||
        FIELD_VALIDATION("1200", HttpStatus.BAD_REQUEST, "Field validation error"),
 | 
					        FIELD_VALIDATION("1200", HttpStatus.BAD_REQUEST, "Field validation error"),
 | 
				
			||||||
        PASSWORD_MISMATCH("1300", HttpStatus.BAD_REQUEST, "new password do not match confirmed password")
 | 
					        INTEGRITY_VALIDATION("1201", HttpStatus.BAD_REQUEST, "Action would lied to an integrity violation"),
 | 
				
			||||||
 | 
					        PASSWORD_MISMATCH("1300", HttpStatus.BAD_REQUEST, "new password do not match confirmed password"),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ;
 | 
					        EXAM_CONSISTANCY_VALIDATION_SUPPORTER("1400", HttpStatus.OK, "No Exam Supporter defined for the Exam"),
 | 
				
			||||||
 | 
					        EXAM_CONSISTANCY_VALIDATION_CONFIG("1401", HttpStatus.OK, "No SEB Exam Configuration defined for the Exam"),
 | 
				
			||||||
 | 
					        EXAM_CONSISTANCY_VALIDATION_INDICATOR("1402", HttpStatus.OK, "No Indicator defined for the Exam");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public final String messageCode;
 | 
					        public final String messageCode;
 | 
				
			||||||
        public final HttpStatus httpStatus;
 | 
					        public final HttpStatus httpStatus;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,7 @@ import java.util.ArrayList;
 | 
				
			||||||
import java.util.Collection;
 | 
					import java.util.Collection;
 | 
				
			||||||
import java.util.Collections;
 | 
					import java.util.Collections;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.validation.constraints.NotEmpty;
 | 
				
			||||||
import javax.validation.constraints.NotNull;
 | 
					import javax.validation.constraints.NotNull;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.apache.commons.lang3.StringUtils;
 | 
					import org.apache.commons.lang3.StringUtils;
 | 
				
			||||||
| 
						 | 
					@ -115,6 +116,7 @@ public final class Exam implements GrantEntity, Activatable {
 | 
				
			||||||
    public final String owner;
 | 
					    public final String owner;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @JsonProperty(EXAM.ATTR_SUPPORTER)
 | 
					    @JsonProperty(EXAM.ATTR_SUPPORTER)
 | 
				
			||||||
 | 
					    @NotEmpty(message = "exam:supporter:notNull")
 | 
				
			||||||
    public final Collection<String> supporter;
 | 
					    public final Collection<String> supporter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** Indicates whether this Exam is active or not */
 | 
					    /** Indicates whether this Exam is active or not */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,96 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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.gbl.model.sebconfig;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.validation.constraints.NotNull;
 | 
				
			||||||
 | 
					import javax.validation.constraints.Size;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JsonProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.api.EntityType;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.Domain.CONFIGURATION_NODE;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.Entity;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public final class ConfigCopyInfo implements Entity {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static final String ATTR_COPY_WITH_HISTORY = "with-history";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NotNull
 | 
				
			||||||
 | 
					    @JsonProperty(CONFIGURATION_NODE.ATTR_ID)
 | 
				
			||||||
 | 
					    public final Long configurationNodeId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NotNull(message = "configurationNode:name:notNull")
 | 
				
			||||||
 | 
					    @Size(min = 3, max = 255, message = "configurationNode:name:size:{min}:{max}:${validatedValue}")
 | 
				
			||||||
 | 
					    @JsonProperty(CONFIGURATION_NODE.ATTR_NAME)
 | 
				
			||||||
 | 
					    public final String name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Size(max = 4000, message = "configurationNode:description:size:{min}:{max}:${validatedValue}")
 | 
				
			||||||
 | 
					    @JsonProperty(CONFIGURATION_NODE.ATTR_DESCRIPTION)
 | 
				
			||||||
 | 
					    public final String description;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @JsonProperty(ATTR_COPY_WITH_HISTORY)
 | 
				
			||||||
 | 
					    public final Boolean withHistory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected ConfigCopyInfo(
 | 
				
			||||||
 | 
					            @JsonProperty(CONFIGURATION_NODE.ATTR_ID) final Long configurationNodeId,
 | 
				
			||||||
 | 
					            @JsonProperty(CONFIGURATION_NODE.ATTR_NAME) final String name,
 | 
				
			||||||
 | 
					            @JsonProperty(CONFIGURATION_NODE.ATTR_DESCRIPTION) final String description,
 | 
				
			||||||
 | 
					            @JsonProperty(ATTR_COPY_WITH_HISTORY) final Boolean withHistory) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.configurationNodeId = configurationNodeId;
 | 
				
			||||||
 | 
					        this.name = name;
 | 
				
			||||||
 | 
					        this.description = description;
 | 
				
			||||||
 | 
					        this.withHistory = withHistory;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public Long getConfigurationNodeId() {
 | 
				
			||||||
 | 
					        return this.configurationNodeId;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public String getName() {
 | 
				
			||||||
 | 
					        return this.name;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public String getDescription() {
 | 
				
			||||||
 | 
					        return this.description;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public Boolean getWithHistory() {
 | 
				
			||||||
 | 
					        return this.withHistory;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public String getModelId() {
 | 
				
			||||||
 | 
					        return (this.configurationNodeId != null)
 | 
				
			||||||
 | 
					                ? String.valueOf(this.configurationNodeId)
 | 
				
			||||||
 | 
					                : null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public EntityType entityType() {
 | 
				
			||||||
 | 
					        return EntityType.CONFIGURATION_NODE;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public String toString() {
 | 
				
			||||||
 | 
					        final StringBuilder builder = new StringBuilder();
 | 
				
			||||||
 | 
					        builder.append("ConfigCopyInfo [configurationNodeId=");
 | 
				
			||||||
 | 
					        builder.append(this.configurationNodeId);
 | 
				
			||||||
 | 
					        builder.append(", name=");
 | 
				
			||||||
 | 
					        builder.append(this.name);
 | 
				
			||||||
 | 
					        builder.append(", description=");
 | 
				
			||||||
 | 
					        builder.append(this.description);
 | 
				
			||||||
 | 
					        builder.append(", withHistory=");
 | 
				
			||||||
 | 
					        builder.append(this.withHistory);
 | 
				
			||||||
 | 
					        builder.append("]");
 | 
				
			||||||
 | 
					        return builder.toString();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -25,7 +25,6 @@ import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
 | 
				
			||||||
public final class ConfigurationNode implements GrantEntity {
 | 
					public final class ConfigurationNode implements GrantEntity {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static final Long DEFAULT_TEMPLATE_ID = 0L;
 | 
					    public static final Long DEFAULT_TEMPLATE_ID = 0L;
 | 
				
			||||||
    public static final String ATTR_COPY_WITH_HISTORY = "with-history";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static final String FILTER_ATTR_TEMPLATE_ID = "templateId";
 | 
					    public static final String FILTER_ATTR_TEMPLATE_ID = "templateId";
 | 
				
			||||||
    public static final String FILTER_ATTR_DESCRIPTION = "description";
 | 
					    public static final String FILTER_ATTR_DESCRIPTION = "description";
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,10 +9,12 @@
 | 
				
			||||||
package ch.ethz.seb.sebserver.gui.content;
 | 
					package ch.ethz.seb.sebserver.gui.content;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.Arrays;
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.Collection;
 | 
				
			||||||
import java.util.Collections;
 | 
					import java.util.Collections;
 | 
				
			||||||
import java.util.HashSet;
 | 
					import java.util.HashSet;
 | 
				
			||||||
import java.util.Set;
 | 
					import java.util.Set;
 | 
				
			||||||
import java.util.function.BooleanSupplier;
 | 
					import java.util.function.BooleanSupplier;
 | 
				
			||||||
 | 
					import java.util.function.Function;
 | 
				
			||||||
import java.util.function.Supplier;
 | 
					import java.util.function.Supplier;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.apache.commons.lang3.BooleanUtils;
 | 
					import org.apache.commons.lang3.BooleanUtils;
 | 
				
			||||||
| 
						 | 
					@ -28,6 +30,8 @@ import org.springframework.stereotype.Component;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.Constants;
 | 
					import ch.ethz.seb.sebserver.gbl.Constants;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.API;
 | 
					import ch.ethz.seb.sebserver.gbl.api.API;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.api.APIMessage;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
 | 
					import ch.ethz.seb.sebserver.gbl.api.EntityType;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
 | 
					import ch.ethz.seb.sebserver.gbl.model.Domain;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
 | 
					import ch.ethz.seb.sebserver.gbl.model.EntityKey;
 | 
				
			||||||
| 
						 | 
					@ -53,10 +57,10 @@ import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
 | 
					import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
 | 
					import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
 | 
					import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageState;
 | 
					 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.download.DownloadService;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.download.DownloadService;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.download.SebExamConfigDownload;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.download.SebExamConfigDownload;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckExamConsistency;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteExamConfigMapping;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteExamConfigMapping;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteIndicator;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteIndicator;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
 | 
				
			||||||
| 
						 | 
					@ -126,10 +130,21 @@ public class ExamForm implements TemplateComposer {
 | 
				
			||||||
    private final static LocTextKey INDICATOR_EMPTY_SELECTION_TEXT_KEY =
 | 
					    private final static LocTextKey INDICATOR_EMPTY_SELECTION_TEXT_KEY =
 | 
				
			||||||
            new LocTextKey("sebserver.exam.indicator.list.pleaseSelect");
 | 
					            new LocTextKey("sebserver.exam.indicator.list.pleaseSelect");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final static LocTextKey CONSISTENCY_MESSAGE_TITLE =
 | 
				
			||||||
 | 
					            new LocTextKey("sebserver.exam.consistency.title");
 | 
				
			||||||
 | 
					    private final static LocTextKey CONSISTENCY_MESSAGE_MISSING_SUPPORTER =
 | 
				
			||||||
 | 
					            new LocTextKey("sebserver.exam.consistency.missing-supporter");
 | 
				
			||||||
 | 
					    private final static LocTextKey CONSISTENCY_MESSAGE_MISSING_CONFIG =
 | 
				
			||||||
 | 
					            new LocTextKey("sebserver.exam.consistency.missing-config");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final static LocTextKey CONFIRM_MESSAGE_REMOVE_CONFIG =
 | 
				
			||||||
 | 
					            new LocTextKey("sebserver.exam.confirm.remove-config");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final PageService pageService;
 | 
					    private final PageService pageService;
 | 
				
			||||||
    private final ResourceService resourceService;
 | 
					    private final ResourceService resourceService;
 | 
				
			||||||
    private final DownloadService downloadService;
 | 
					    private final DownloadService downloadService;
 | 
				
			||||||
    private final String downloadFileName;
 | 
					    private final String downloadFileName;
 | 
				
			||||||
 | 
					    private final WidgetFactory widgetFactory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected ExamForm(
 | 
					    protected ExamForm(
 | 
				
			||||||
            final PageService pageService,
 | 
					            final PageService pageService,
 | 
				
			||||||
| 
						 | 
					@ -141,15 +156,15 @@ public class ExamForm implements TemplateComposer {
 | 
				
			||||||
        this.resourceService = resourceService;
 | 
					        this.resourceService = resourceService;
 | 
				
			||||||
        this.downloadService = downloadService;
 | 
					        this.downloadService = downloadService;
 | 
				
			||||||
        this.downloadFileName = downloadFileName;
 | 
					        this.downloadFileName = downloadFileName;
 | 
				
			||||||
 | 
					        this.widgetFactory = pageService.getWidgetFactory();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void compose(final PageContext pageContext) {
 | 
					    public void compose(final PageContext pageContext) {
 | 
				
			||||||
        final CurrentUser currentUser = this.resourceService.getCurrentUser();
 | 
					        final CurrentUser currentUser = this.resourceService.getCurrentUser();
 | 
				
			||||||
        final RestService restService = this.resourceService.getRestService();
 | 
					        final RestService restService = this.resourceService.getRestService();
 | 
				
			||||||
        final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
 | 
					 | 
				
			||||||
        final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
 | 
				
			||||||
        final EntityKey entityKey = pageContext.getEntityKey();
 | 
					        final EntityKey entityKey = pageContext.getEntityKey();
 | 
				
			||||||
        final EntityKey parentEntityKey = pageContext.getParentEntityKey();
 | 
					        final EntityKey parentEntityKey = pageContext.getParentEntityKey();
 | 
				
			||||||
        final boolean readonly = pageContext.isReadonly();
 | 
					        final boolean readonly = pageContext.isReadonly();
 | 
				
			||||||
| 
						 | 
					@ -173,12 +188,20 @@ public class ExamForm implements TemplateComposer {
 | 
				
			||||||
        // new PageContext with actual EntityKey
 | 
					        // new PageContext with actual EntityKey
 | 
				
			||||||
        final PageContext formContext = pageContext.withEntityKey(exam.getEntityKey());
 | 
					        final PageContext formContext = pageContext.withEntityKey(exam.getEntityKey());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // check exam consistency and inform the user if needed
 | 
				
			||||||
 | 
					        if (readonly) {
 | 
				
			||||||
 | 
					            restService.getBuilder(CheckExamConsistency.class)
 | 
				
			||||||
 | 
					                    .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
 | 
				
			||||||
 | 
					                    .call()
 | 
				
			||||||
 | 
					                    .ifPresent(result -> showConsistencyChecks(result, formContext.getParent()));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // the default page layout with title
 | 
					        // the default page layout with title
 | 
				
			||||||
        final LocTextKey titleKey = new LocTextKey(
 | 
					        final LocTextKey titleKey = new LocTextKey(
 | 
				
			||||||
                importFromQuizData
 | 
					                importFromQuizData
 | 
				
			||||||
                        ? "sebserver.exam.form.title.import"
 | 
					                        ? "sebserver.exam.form.title.import"
 | 
				
			||||||
                        : "sebserver.exam.form.title");
 | 
					                        : "sebserver.exam.form.title");
 | 
				
			||||||
        final Composite content = widgetFactory.defaultPageLayout(
 | 
					        final Composite content = this.widgetFactory.defaultPageLayout(
 | 
				
			||||||
                formContext.getParent(),
 | 
					                formContext.getParent(),
 | 
				
			||||||
                titleKey);
 | 
					                titleKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -187,9 +210,10 @@ public class ExamForm implements TemplateComposer {
 | 
				
			||||||
        final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(exam);
 | 
					        final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(exam);
 | 
				
			||||||
        final boolean modifyGrant = userGrantCheck.m();
 | 
					        final boolean modifyGrant = userGrantCheck.m();
 | 
				
			||||||
        final ExamStatus examStatus = exam.getStatus();
 | 
					        final ExamStatus examStatus = exam.getStatus();
 | 
				
			||||||
        final boolean editable = examStatus == ExamStatus.UP_COMING ||
 | 
					        final boolean isExamRunning = examStatus == ExamStatus.RUNNING;
 | 
				
			||||||
                examStatus == ExamStatus.RUNNING &&
 | 
					        final boolean editable = examStatus == ExamStatus.UP_COMING
 | 
				
			||||||
                        currentUser.get().hasRole(UserRole.EXAM_ADMIN);
 | 
					                || examStatus == ExamStatus.RUNNING
 | 
				
			||||||
 | 
					                        && currentUser.get().hasRole(UserRole.EXAM_ADMIN);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // The Exam form
 | 
					        // The Exam form
 | 
				
			||||||
        final FormHandle<Exam> formHandle = this.pageService.formBuilder(
 | 
					        final FormHandle<Exam> formHandle = this.pageService.formBuilder(
 | 
				
			||||||
| 
						 | 
					@ -283,14 +307,14 @@ public class ExamForm implements TemplateComposer {
 | 
				
			||||||
                .newAction(ActionDefinition.EXAM_CANCEL_MODIFY)
 | 
					                .newAction(ActionDefinition.EXAM_CANCEL_MODIFY)
 | 
				
			||||||
                .withEntityKey(entityKey)
 | 
					                .withEntityKey(entityKey)
 | 
				
			||||||
                .withAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA, String.valueOf(importFromQuizData))
 | 
					                .withAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA, String.valueOf(importFromQuizData))
 | 
				
			||||||
                .withExec(this::cancelModify)
 | 
					                .withExec(this.cancelModifyFunction())
 | 
				
			||||||
                .publishIf(() -> !readonly);
 | 
					                .publishIf(() -> !readonly);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // additional data in read-only view
 | 
					        // additional data in read-only view
 | 
				
			||||||
        if (readonly && !importFromQuizData) {
 | 
					        if (readonly && !importFromQuizData) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // List of SEB Configuration
 | 
					            // List of SEB Configuration
 | 
				
			||||||
            widgetFactory.labelLocalized(
 | 
					            this.widgetFactory.labelLocalized(
 | 
				
			||||||
                    content,
 | 
					                    content,
 | 
				
			||||||
                    CustomVariant.TEXT_H3,
 | 
					                    CustomVariant.TEXT_H3,
 | 
				
			||||||
                    CONFIG_LIST_TITLE_KEY);
 | 
					                    CONFIG_LIST_TITLE_KEY);
 | 
				
			||||||
| 
						 | 
					@ -354,6 +378,12 @@ public class ExamForm implements TemplateComposer {
 | 
				
			||||||
                            getConfigMappingSelection(configurationTable),
 | 
					                            getConfigMappingSelection(configurationTable),
 | 
				
			||||||
                            this::deleteExamConfigMapping,
 | 
					                            this::deleteExamConfigMapping,
 | 
				
			||||||
                            CONFIG_EMPTY_SELECTION_TEXT_KEY)
 | 
					                            CONFIG_EMPTY_SELECTION_TEXT_KEY)
 | 
				
			||||||
 | 
					                    .withConfirm(() -> {
 | 
				
			||||||
 | 
					                        if (isExamRunning) {
 | 
				
			||||||
 | 
					                            return CONFIRM_MESSAGE_REMOVE_CONFIG;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        return null;
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
                    .publishIf(() -> modifyGrant && configurationTable.hasAnyContent() && editable)
 | 
					                    .publishIf(() -> modifyGrant && configurationTable.hasAnyContent() && editable)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    .newAction(ActionDefinition.EXAM_CONFIGURATION_EXPORT)
 | 
					                    .newAction(ActionDefinition.EXAM_CONFIGURATION_EXPORT)
 | 
				
			||||||
| 
						 | 
					@ -374,7 +404,7 @@ public class ExamForm implements TemplateComposer {
 | 
				
			||||||
                    .publishIf(() -> userGrantCheck.r() && configurationTable.hasAnyContent());
 | 
					                    .publishIf(() -> userGrantCheck.r() && configurationTable.hasAnyContent());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // List of Indicators
 | 
					            // List of Indicators
 | 
				
			||||||
            widgetFactory.labelLocalized(
 | 
					            this.widgetFactory.labelLocalized(
 | 
				
			||||||
                    content,
 | 
					                    content,
 | 
				
			||||||
                    CustomVariant.TEXT_H3,
 | 
					                    CustomVariant.TEXT_H3,
 | 
				
			||||||
                    INDICATOR_LIST_TITLE_KEY);
 | 
					                    INDICATOR_LIST_TITLE_KEY);
 | 
				
			||||||
| 
						 | 
					@ -432,6 +462,35 @@ public class ExamForm implements TemplateComposer {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void showConsistencyChecks(final Collection<APIMessage> result, final Composite parent) {
 | 
				
			||||||
 | 
					        if (result == null || result.isEmpty()) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final Composite warningPanel = this.widgetFactory.createWarningPanel(parent);
 | 
				
			||||||
 | 
					        this.widgetFactory.labelLocalized(
 | 
				
			||||||
 | 
					                warningPanel,
 | 
				
			||||||
 | 
					                CustomVariant.TITLE_LABEL,
 | 
				
			||||||
 | 
					                CONSISTENCY_MESSAGE_TITLE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result
 | 
				
			||||||
 | 
					                .stream()
 | 
				
			||||||
 | 
					                .map(message -> {
 | 
				
			||||||
 | 
					                    if (message.messageCode.equals(ErrorMessage.EXAM_CONSISTANCY_VALIDATION_SUPPORTER.messageCode)) {
 | 
				
			||||||
 | 
					                        return CONSISTENCY_MESSAGE_MISSING_SUPPORTER;
 | 
				
			||||||
 | 
					                    } else if (message.messageCode
 | 
				
			||||||
 | 
					                            .equals(ErrorMessage.EXAM_CONSISTANCY_VALIDATION_CONFIG.messageCode)) {
 | 
				
			||||||
 | 
					                        return CONSISTENCY_MESSAGE_MISSING_CONFIG;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    return null;
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .filter(message -> message != null)
 | 
				
			||||||
 | 
					                .forEach(message -> this.widgetFactory.labelLocalized(
 | 
				
			||||||
 | 
					                        warningPanel,
 | 
				
			||||||
 | 
					                        CustomVariant.MESSAGE,
 | 
				
			||||||
 | 
					                        message));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private PageAction viewExamConfigPageAction(final EntityTable<ExamConfigurationMap> table) {
 | 
					    private PageAction viewExamConfigPageAction(final EntityTable<ExamConfigurationMap> table) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(table.getPageContext()
 | 
					        final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(table.getPageContext()
 | 
				
			||||||
| 
						 | 
					@ -549,6 +608,7 @@ public class ExamForm implements TemplateComposer {
 | 
				
			||||||
                .getText(ResourceService.EXAM_INDICATOR_TYPE_PREFIX + indicator.type.name());
 | 
					                .getText(ResourceService.EXAM_INDICATOR_TYPE_PREFIX + indicator.type.name());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO find a better way to show a threshold value as text
 | 
				
			||||||
    private static String thresholdsValue(final Indicator indicator) {
 | 
					    private static String thresholdsValue(final Indicator indicator) {
 | 
				
			||||||
        if (indicator.thresholds.isEmpty()) {
 | 
					        if (indicator.thresholds.isEmpty()) {
 | 
				
			||||||
            return Constants.EMPTY_NOTE;
 | 
					            return Constants.EMPTY_NOTE;
 | 
				
			||||||
| 
						 | 
					@ -558,13 +618,19 @@ public class ExamForm implements TemplateComposer {
 | 
				
			||||||
                .stream()
 | 
					                .stream()
 | 
				
			||||||
                .reduce(
 | 
					                .reduce(
 | 
				
			||||||
                        new StringBuilder(),
 | 
					                        new StringBuilder(),
 | 
				
			||||||
                        (sb, threshold) -> sb.append(threshold.value).append(":").append(threshold.color).append("|"),
 | 
					                        (sb, threshold) -> sb.append(threshold.value)
 | 
				
			||||||
 | 
					                                .append(":")
 | 
				
			||||||
 | 
					                                .append(threshold.color)
 | 
				
			||||||
 | 
					                                .append("|"),
 | 
				
			||||||
                        (sb1, sb2) -> sb1.append(sb2))
 | 
					                        (sb1, sb2) -> sb1.append(sb2))
 | 
				
			||||||
                .toString();
 | 
					                .toString();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private PageAction cancelModify(final PageAction action) {
 | 
					    private Function<PageAction, PageAction> cancelModifyFunction() {
 | 
				
			||||||
 | 
					        final Function<PageAction, PageAction> backToCurrentFunction = this.pageService.backToCurrentFunction();
 | 
				
			||||||
 | 
					        return action -> {
 | 
				
			||||||
            final boolean importFromQuizData = BooleanUtils.toBoolean(
 | 
					            final boolean importFromQuizData = BooleanUtils.toBoolean(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    action.pageContext().getAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA));
 | 
					                    action.pageContext().getAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA));
 | 
				
			||||||
            if (importFromQuizData) {
 | 
					            if (importFromQuizData) {
 | 
				
			||||||
                final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(action.pageContext());
 | 
					                final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(action.pageContext());
 | 
				
			||||||
| 
						 | 
					@ -575,8 +641,8 @@ public class ExamForm implements TemplateComposer {
 | 
				
			||||||
                return activityHomeAction;
 | 
					                return activityHomeAction;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        final PageState lastState = this.pageService.getCurrentState();
 | 
					            return backToCurrentFunction.apply(action);
 | 
				
			||||||
        return lastState.gotoAction;
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,18 +11,22 @@ package ch.ethz.seb.sebserver.gui.content;
 | 
				
			||||||
import java.util.function.BooleanSupplier;
 | 
					import java.util.function.BooleanSupplier;
 | 
				
			||||||
import java.util.function.Function;
 | 
					import java.util.function.Function;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.eclipse.rap.rwt.RWT;
 | 
				
			||||||
import org.eclipse.swt.widgets.Composite;
 | 
					import org.eclipse.swt.widgets.Composite;
 | 
				
			||||||
 | 
					import org.eclipse.swt.widgets.TableItem;
 | 
				
			||||||
import org.joda.time.DateTime;
 | 
					import org.joda.time.DateTime;
 | 
				
			||||||
import org.joda.time.DateTimeZone;
 | 
					import org.joda.time.DateTimeZone;
 | 
				
			||||||
import org.springframework.beans.factory.annotation.Value;
 | 
					import org.springframework.beans.factory.annotation.Value;
 | 
				
			||||||
import org.springframework.context.annotation.Lazy;
 | 
					import org.springframework.context.annotation.Lazy;
 | 
				
			||||||
import org.springframework.stereotype.Component;
 | 
					import org.springframework.stereotype.Component;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.api.API;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
 | 
					import ch.ethz.seb.sebserver.gbl.api.EntityType;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
 | 
					import ch.ethz.seb.sebserver.gbl.model.Domain;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
 | 
					import ch.ethz.seb.sebserver.gbl.model.Entity;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
 | 
					import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
 | 
					import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
 | 
					import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
 | 
					import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
 | 
					import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
 | 
				
			||||||
| 
						 | 
					@ -38,6 +42,7 @@ import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
 | 
					import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
 | 
					import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckExamConsistency;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamPage;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamPage;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
 | 
				
			||||||
| 
						 | 
					@ -46,6 +51,7 @@ import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
 | 
					import ch.ethz.seb.sebserver.gui.table.EntityTable;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
 | 
					import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
 | 
					import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Lazy
 | 
					@Lazy
 | 
				
			||||||
@Component
 | 
					@Component
 | 
				
			||||||
| 
						 | 
					@ -133,6 +139,7 @@ public class ExamList implements TemplateComposer {
 | 
				
			||||||
                this.pageService.entityTableBuilder(restService.getRestCall(GetExamPage.class))
 | 
					                this.pageService.entityTableBuilder(restService.getRestCall(GetExamPage.class))
 | 
				
			||||||
                        .withEmptyMessage(EMPTY_LIST_TEXT_KEY)
 | 
					                        .withEmptyMessage(EMPTY_LIST_TEXT_KEY)
 | 
				
			||||||
                        .withPaging(this.pageSize)
 | 
					                        .withPaging(this.pageSize)
 | 
				
			||||||
 | 
					                        .withRowDecorator(this::decorateOnExamConsistency)
 | 
				
			||||||
                        
 | 
					                        
 | 
				
			||||||
                        .withColumnIf(
 | 
					                        .withColumnIf(
 | 
				
			||||||
                                isSebAdmin,
 | 
					                                isSebAdmin,
 | 
				
			||||||
| 
						 | 
					@ -221,9 +228,26 @@ public class ExamList implements TemplateComposer {
 | 
				
			||||||
        return action.withEntityKey(action.getSingleSelection());
 | 
					        return action.withEntityKey(action.getSingleSelection());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    private void decorateOnExamConsistency(TableItem item, Exam exam) {
 | 
				
			||||||
 | 
					        if (exam.getStatus() != ExamStatus.RUNNING) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        this.pageService.getRestService().getBuilder(CheckExamConsistency.class)
 | 
				
			||||||
 | 
					            .withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
 | 
				
			||||||
 | 
					            .call()
 | 
				
			||||||
 | 
					            .ifPresent(warnings -> {
 | 
				
			||||||
 | 
					                if (warnings != null && !warnings.isEmpty()) {
 | 
				
			||||||
 | 
					                    item.setData(RWT.CUSTOM_VARIANT, CustomVariant.WARNING.key);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static Function<Exam, String> examLmsSetupNameFunction(final ResourceService resourceService) {
 | 
					    private static Function<Exam, String> examLmsSetupNameFunction(final ResourceService resourceService) {
 | 
				
			||||||
        return exam -> resourceService.getLmsSetupNameFunction()
 | 
					        return exam -> resourceService.getLmsSetupNameFunction()
 | 
				
			||||||
                .apply(String.valueOf(exam.lmsSetupId));
 | 
					                .apply(String.valueOf(exam.lmsSetupId));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,113 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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.content;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.function.Function;
 | 
				
			||||||
 | 
					import java.util.function.Supplier;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.eclipse.swt.widgets.Composite;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.api.EntityType;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.Domain;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.EntityKey;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigCopyInfo;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.util.Utils;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.form.FormBuilder;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.form.FormHandle;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.service.page.PageContext;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.service.page.PageService;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.CopyConfiguration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public final class SebExamConfigCopy {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static Function<PageAction, PageAction> importConfigFunction(
 | 
				
			||||||
 | 
					            final PageService pageService,
 | 
				
			||||||
 | 
					            final PageContext pageContext) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return action -> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            final ModalInputDialog<FormHandle<ConfigCopyInfo>> dialog =
 | 
				
			||||||
 | 
					                    new ModalInputDialog<FormHandle<ConfigCopyInfo>>(
 | 
				
			||||||
 | 
					                            action.pageContext().getParent().getShell(),
 | 
				
			||||||
 | 
					                            pageService.getWidgetFactory())
 | 
				
			||||||
 | 
					                                    .setDialogWidth(600);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            final CopyFormContext formContext = new CopyFormContext(
 | 
				
			||||||
 | 
					                    pageService,
 | 
				
			||||||
 | 
					                    action.pageContext());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            dialog.open(
 | 
				
			||||||
 | 
					                    SebExamConfigPropForm.FORM_COPY_TEXT_KEY,
 | 
				
			||||||
 | 
					                    formHandle -> doCopy(
 | 
				
			||||||
 | 
					                            pageService,
 | 
				
			||||||
 | 
					                            pageContext,
 | 
				
			||||||
 | 
					                            formHandle),
 | 
				
			||||||
 | 
					                    Utils.EMPTY_EXECUTION,
 | 
				
			||||||
 | 
					                    formContext);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return action;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final void doCopy(
 | 
				
			||||||
 | 
					            final PageService pageService,
 | 
				
			||||||
 | 
					            final PageContext pageContext,
 | 
				
			||||||
 | 
					            final FormHandle<ConfigCopyInfo> formHandle) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final ConfigurationNode newConfig = pageService.getRestService().getBuilder(CopyConfiguration.class)
 | 
				
			||||||
 | 
					                .withFormBinding(formHandle.getFormBinding())
 | 
				
			||||||
 | 
					                .call()
 | 
				
			||||||
 | 
					                .getOrThrow();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final PageAction viewNewConfig = pageService.pageActionBuilder(pageContext.copy().clearAttributes())
 | 
				
			||||||
 | 
					                .withEntityKey(new EntityKey(newConfig.id, EntityType.CONFIGURATION_NODE))
 | 
				
			||||||
 | 
					                .create();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pageService.executePageAction(viewNewConfig);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final class CopyFormContext implements ModalInputDialogComposer<FormHandle<ConfigCopyInfo>> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private final PageService pageService;
 | 
				
			||||||
 | 
					        private final PageContext pageContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected CopyFormContext(final PageService pageService, final PageContext pageContext) {
 | 
				
			||||||
 | 
					            this.pageService = pageService;
 | 
				
			||||||
 | 
					            this.pageContext = pageContext;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public Supplier<FormHandle<ConfigCopyInfo>> compose(final Composite parent) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            final EntityKey entityKey = this.pageContext.getEntityKey();
 | 
				
			||||||
 | 
					            final FormHandle<ConfigCopyInfo> formHandle = this.pageService.formBuilder(
 | 
				
			||||||
 | 
					                    this.pageContext.copyOf(parent), 4)
 | 
				
			||||||
 | 
					                    .readonly(false)
 | 
				
			||||||
 | 
					                    .putStaticValue(
 | 
				
			||||||
 | 
					                            Domain.CONFIGURATION_NODE.ATTR_ID,
 | 
				
			||||||
 | 
					                            entityKey.getModelId())
 | 
				
			||||||
 | 
					                    .addField(FormBuilder.text(
 | 
				
			||||||
 | 
					                            Domain.CONFIGURATION_NODE.ATTR_NAME,
 | 
				
			||||||
 | 
					                            SebExamConfigPropForm.FORM_NAME_TEXT_KEY))
 | 
				
			||||||
 | 
					                    .addField(FormBuilder.text(
 | 
				
			||||||
 | 
					                            Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
 | 
				
			||||||
 | 
					                            SebExamConfigPropForm.FORM_DESCRIPTION_TEXT_KEY)
 | 
				
			||||||
 | 
					                            .asArea())
 | 
				
			||||||
 | 
					                    .build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return () -> formHandle;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,140 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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.content;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.InputStream;
 | 
				
			||||||
 | 
					import java.util.function.Function;
 | 
				
			||||||
 | 
					import java.util.function.Supplier;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.eclipse.swt.widgets.Composite;
 | 
				
			||||||
 | 
					import org.eclipse.swt.widgets.Control;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.api.API;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.EntityKey;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.form.Form;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.form.FormBuilder;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.form.FormHandle;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.service.page.PageContext;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.service.page.PageService;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ImportExamConfig;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.widget.FileUploadSelection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public final class SebExamConfigImport {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static Function<PageAction, PageAction> importConfigFunction(final PageService pageService) {
 | 
				
			||||||
 | 
					        return action -> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            final ModalInputDialog<FormHandle<ConfigurationNode>> dialog =
 | 
				
			||||||
 | 
					                    new ModalInputDialog<FormHandle<ConfigurationNode>>(
 | 
				
			||||||
 | 
					                            action.pageContext().getParent().getShell(),
 | 
				
			||||||
 | 
					                            pageService.getWidgetFactory())
 | 
				
			||||||
 | 
					                                    .setDialogWidth(600);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            final ImportFormContext importFormContext = new ImportFormContext(
 | 
				
			||||||
 | 
					                    pageService,
 | 
				
			||||||
 | 
					                    action.pageContext());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            dialog.open(
 | 
				
			||||||
 | 
					                    SebExamConfigPropForm.FORM_IMPORT_TEXT_KEY,
 | 
				
			||||||
 | 
					                    formHandle -> doImport(
 | 
				
			||||||
 | 
					                            pageService,
 | 
				
			||||||
 | 
					                            formHandle),
 | 
				
			||||||
 | 
					                    importFormContext::cancelUpload,
 | 
				
			||||||
 | 
					                    importFormContext);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return action;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final void doImport(
 | 
				
			||||||
 | 
					            final PageService pageService,
 | 
				
			||||||
 | 
					            final FormHandle<ConfigurationNode> formHandle) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final Form form = formHandle.getForm();
 | 
				
			||||||
 | 
					        final EntityKey entityKey = formHandle.getContext().getEntityKey();
 | 
				
			||||||
 | 
					        final Control fieldControl = form.getFieldControl(API.IMPORT_FILE_ATTR_NAME);
 | 
				
			||||||
 | 
					        final PageContext context = formHandle.getContext();
 | 
				
			||||||
 | 
					        if (fieldControl != null && fieldControl instanceof FileUploadSelection) {
 | 
				
			||||||
 | 
					            final FileUploadSelection fileUpload = (FileUploadSelection) fieldControl;
 | 
				
			||||||
 | 
					            final InputStream inputStream = fileUpload.getInputStream();
 | 
				
			||||||
 | 
					            if (inputStream != null) {
 | 
				
			||||||
 | 
					                final Configuration configuration = pageService.getRestService()
 | 
				
			||||||
 | 
					                        .getBuilder(ImportExamConfig.class)
 | 
				
			||||||
 | 
					                        .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
 | 
				
			||||||
 | 
					                        .withHeader(
 | 
				
			||||||
 | 
					                                API.IMPORT_PASSWORD_ATTR_NAME,
 | 
				
			||||||
 | 
					                                form.getFieldValue(API.IMPORT_PASSWORD_ATTR_NAME))
 | 
				
			||||||
 | 
					                        .withBody(inputStream)
 | 
				
			||||||
 | 
					                        .call()
 | 
				
			||||||
 | 
					                        .get(e -> {
 | 
				
			||||||
 | 
					                            fileUpload.close();
 | 
				
			||||||
 | 
					                            return context.notifyError(e);
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (configuration != null) {
 | 
				
			||||||
 | 
					                    context.publishInfo(SebExamConfigPropForm.FORM_IMPORT_CONFIRM_TEXT_KEY);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                formHandle.getContext().publishPageMessage(
 | 
				
			||||||
 | 
					                        new LocTextKey("sebserver.error.unexpected"),
 | 
				
			||||||
 | 
					                        new LocTextKey("Please selecte a valid SEB Exam Configuration File"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final class ImportFormContext implements ModalInputDialogComposer<FormHandle<ConfigurationNode>> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private final PageService pageService;
 | 
				
			||||||
 | 
					        private final PageContext pageContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private Form form = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected ImportFormContext(final PageService pageService, final PageContext pageContext) {
 | 
				
			||||||
 | 
					            this.pageService = pageService;
 | 
				
			||||||
 | 
					            this.pageContext = pageContext;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public Supplier<FormHandle<ConfigurationNode>> compose(final Composite parent) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            final FormHandle<ConfigurationNode> formHandle = this.pageService.formBuilder(
 | 
				
			||||||
 | 
					                    this.pageContext.copyOf(parent), 4)
 | 
				
			||||||
 | 
					                    .readonly(false)
 | 
				
			||||||
 | 
					                    .addField(FormBuilder.fileUpload(
 | 
				
			||||||
 | 
					                            API.IMPORT_FILE_ATTR_NAME,
 | 
				
			||||||
 | 
					                            SebExamConfigPropForm.FORM_IMPORT_SELECT_TEXT_KEY,
 | 
				
			||||||
 | 
					                            null,
 | 
				
			||||||
 | 
					                            API.SEB_FILE_EXTENSION))
 | 
				
			||||||
 | 
					                    .addField(FormBuilder.text(
 | 
				
			||||||
 | 
					                            API.IMPORT_PASSWORD_ATTR_NAME,
 | 
				
			||||||
 | 
					                            SebExamConfigPropForm.FORM_IMPORT_PASSWORD_TEXT_KEY,
 | 
				
			||||||
 | 
					                            "").asPasswordField())
 | 
				
			||||||
 | 
					                    .build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.form = formHandle.getForm();
 | 
				
			||||||
 | 
					            return () -> formHandle;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        void cancelUpload() {
 | 
				
			||||||
 | 
					            if (this.form != null) {
 | 
				
			||||||
 | 
					                final Control fieldControl = this.form.getFieldControl(API.IMPORT_FILE_ATTR_NAME);
 | 
				
			||||||
 | 
					                if (fieldControl != null && fieldControl instanceof FileUploadSelection) {
 | 
				
			||||||
 | 
					                    ((FileUploadSelection) fieldControl).close();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -8,15 +8,12 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package ch.ethz.seb.sebserver.gui.content;
 | 
					package ch.ethz.seb.sebserver.gui.content;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.InputStream;
 | 
					 | 
				
			||||||
import java.util.function.Function;
 | 
					import java.util.function.Function;
 | 
				
			||||||
import java.util.function.Supplier;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.eclipse.rap.rwt.RWT;
 | 
					import org.eclipse.rap.rwt.RWT;
 | 
				
			||||||
import org.eclipse.rap.rwt.client.service.UrlLauncher;
 | 
					import org.eclipse.rap.rwt.client.service.UrlLauncher;
 | 
				
			||||||
import org.eclipse.swt.SWT;
 | 
					import org.eclipse.swt.SWT;
 | 
				
			||||||
import org.eclipse.swt.widgets.Composite;
 | 
					import org.eclipse.swt.widgets.Composite;
 | 
				
			||||||
import org.eclipse.swt.widgets.Control;
 | 
					 | 
				
			||||||
import org.eclipse.swt.widgets.Text;
 | 
					import org.eclipse.swt.widgets.Text;
 | 
				
			||||||
import org.slf4j.Logger;
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
import org.slf4j.LoggerFactory;
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
| 
						 | 
					@ -27,20 +24,18 @@ import org.springframework.stereotype.Component;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.API;
 | 
					import ch.ethz.seb.sebserver.gbl.api.API;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
 | 
					import ch.ethz.seb.sebserver.gbl.model.Domain;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
 | 
					import ch.ethz.seb.sebserver.gbl.model.EntityKey;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigKey;
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigKey;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
 | 
					 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
 | 
					import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
 | 
					import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
 | 
					import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.form.Form;
 | 
					 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
 | 
					import ch.ethz.seb.sebserver.gui.form.FormBuilder;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
 | 
					import ch.ethz.seb.sebserver.gui.form.FormHandle;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
 | 
					import ch.ethz.seb.sebserver.gui.service.ResourceService;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
 | 
					import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
 | 
					 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
 | 
					import ch.ethz.seb.sebserver.gui.service.page.PageContext;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
 | 
					import ch.ethz.seb.sebserver.gui.service.page.PageService;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
 | 
					import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
 | 
				
			||||||
| 
						 | 
					@ -49,14 +44,13 @@ import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.download.DownloadService;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.download.DownloadService;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.download.SebExamConfigPlaintextDownload;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.download.SebExamConfigPlaintextDownload;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingNames;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ExportConfigKey;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ExportConfigKey;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNode;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNode;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ImportExamConfig;
 | 
					 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.NewExamConfig;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.NewExamConfig;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.SaveExamConfig;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.SaveExamConfig;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.EntityGrantCheck;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.EntityGrantCheck;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.widget.FileUploadSelection;
 | 
					 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
 | 
					import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
 | 
					import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -67,29 +61,35 @@ public class SebExamConfigPropForm implements TemplateComposer {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static final Logger log = LoggerFactory.getLogger(SebExamConfigPropForm.class);
 | 
					    private static final Logger log = LoggerFactory.getLogger(SebExamConfigPropForm.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static final LocTextKey FORM_TITLE_NEW =
 | 
					    static final LocTextKey FORM_TITLE_NEW =
 | 
				
			||||||
            new LocTextKey("sebserver.examconfig.form.title.new");
 | 
					            new LocTextKey("sebserver.examconfig.form.title.new");
 | 
				
			||||||
    private static final LocTextKey FORM_TITLE =
 | 
					    static final LocTextKey FORM_TITLE =
 | 
				
			||||||
            new LocTextKey("sebserver.examconfig.form.title");
 | 
					            new LocTextKey("sebserver.examconfig.form.title");
 | 
				
			||||||
    private static final LocTextKey FORM_NAME_TEXT_KEY =
 | 
					    static final LocTextKey FORM_NAME_TEXT_KEY =
 | 
				
			||||||
            new LocTextKey("sebserver.examconfig.form.name");
 | 
					            new LocTextKey("sebserver.examconfig.form.name");
 | 
				
			||||||
    private static final LocTextKey FORM_DESCRIPTION_TEXT_KEY =
 | 
					    static final LocTextKey FORM_DESCRIPTION_TEXT_KEY =
 | 
				
			||||||
            new LocTextKey("sebserver.examconfig.form.description");
 | 
					            new LocTextKey("sebserver.examconfig.form.description");
 | 
				
			||||||
    private static final LocTextKey FORM_TEMPLATE_TEXT_KEY =
 | 
					    static final LocTextKey FORM_TEMPLATE_TEXT_KEY =
 | 
				
			||||||
            new LocTextKey("sebserver.examconfig.form.template");
 | 
					            new LocTextKey("sebserver.examconfig.form.template");
 | 
				
			||||||
    private static final LocTextKey FORM_STATUS_TEXT_KEY =
 | 
					    static final LocTextKey FORM_STATUS_TEXT_KEY =
 | 
				
			||||||
            new LocTextKey("sebserver.examconfig.form.status");
 | 
					            new LocTextKey("sebserver.examconfig.form.status");
 | 
				
			||||||
    private static final LocTextKey FORM_IMPORT_TEXT_KEY =
 | 
					    static final LocTextKey FORM_IMPORT_TEXT_KEY =
 | 
				
			||||||
            new LocTextKey("sebserver.examconfig.action.import-config");
 | 
					            new LocTextKey("sebserver.examconfig.action.import-config");
 | 
				
			||||||
    private static final LocTextKey FORM_IMPORT_SELECT_TEXT_KEY =
 | 
					    static final LocTextKey FORM_IMPORT_SELECT_TEXT_KEY =
 | 
				
			||||||
            new LocTextKey("sebserver.examconfig.action.import-file-select");
 | 
					            new LocTextKey("sebserver.examconfig.action.import-file-select");
 | 
				
			||||||
    private static final LocTextKey FORM_IMPORT_PASSWORD_TEXT_KEY =
 | 
					    static final LocTextKey FORM_IMPORT_PASSWORD_TEXT_KEY =
 | 
				
			||||||
            new LocTextKey("sebserver.examconfig.action.import-file-password");
 | 
					            new LocTextKey("sebserver.examconfig.action.import-file-password");
 | 
				
			||||||
    private static final LocTextKey CONFIG_KEY_TITLE_TEXT_KEY =
 | 
					    static final LocTextKey CONFIG_KEY_TITLE_TEXT_KEY =
 | 
				
			||||||
            new LocTextKey("sebserver.examconfig.form.config-key.title");
 | 
					            new LocTextKey("sebserver.examconfig.form.config-key.title");
 | 
				
			||||||
    private static final LocTextKey FORM_IMPORT_CONFIRM_TEXT_KEY =
 | 
					    static final LocTextKey FORM_IMPORT_CONFIRM_TEXT_KEY =
 | 
				
			||||||
            new LocTextKey("sebserver.examconfig.action.import-config.confirm");
 | 
					            new LocTextKey("sebserver.examconfig.action.import-config.confirm");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static final LocTextKey FORM_COPY_TEXT_KEY =
 | 
				
			||||||
 | 
					            new LocTextKey("sebserver.examconfig.action.copy");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static final LocTextKey SAVE_CONFIRM_STATE_CHANGE_WHILE_ATTACHED =
 | 
				
			||||||
 | 
					            new LocTextKey("sebserver.examconfig.action.state-change.confirm");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final PageService pageService;
 | 
					    private final PageService pageService;
 | 
				
			||||||
    private final RestService restService;
 | 
					    private final RestService restService;
 | 
				
			||||||
    private final CurrentUser currentUser;
 | 
					    private final CurrentUser currentUser;
 | 
				
			||||||
| 
						 | 
					@ -127,7 +127,6 @@ public class SebExamConfigPropForm implements TemplateComposer {
 | 
				
			||||||
                        .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
 | 
					                        .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
 | 
				
			||||||
                        .call()
 | 
					                        .call()
 | 
				
			||||||
                        .get(pageContext::notifyError);
 | 
					                        .get(pageContext::notifyError);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (examConfig == null) {
 | 
					        if (examConfig == null) {
 | 
				
			||||||
            log.error("Failed to get ConfigurationNode. "
 | 
					            log.error("Failed to get ConfigurationNode. "
 | 
				
			||||||
                    + "Error was notified to the User. "
 | 
					                    + "Error was notified to the User. "
 | 
				
			||||||
| 
						 | 
					@ -139,6 +138,12 @@ public class SebExamConfigPropForm implements TemplateComposer {
 | 
				
			||||||
        final boolean writeGrant = entityGrant.w();
 | 
					        final boolean writeGrant = entityGrant.w();
 | 
				
			||||||
        final boolean modifyGrant = entityGrant.m();
 | 
					        final boolean modifyGrant = entityGrant.m();
 | 
				
			||||||
        final boolean isReadonly = pageContext.isReadonly();
 | 
					        final boolean isReadonly = pageContext.isReadonly();
 | 
				
			||||||
 | 
					        final boolean isAttachedToExam = this.restService
 | 
				
			||||||
 | 
					                .getBuilder(GetExamConfigMappingNames.class)
 | 
				
			||||||
 | 
					                .withQueryParam(ExamConfigurationMap.FILTER_ATTR_CONFIG_ID, examConfig.getModelId())
 | 
				
			||||||
 | 
					                .call()
 | 
				
			||||||
 | 
					                .map(names -> names != null && !names.isEmpty())
 | 
				
			||||||
 | 
					                .getOr(Boolean.FALSE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // new PageContext with actual EntityKey
 | 
					        // new PageContext with actual EntityKey
 | 
				
			||||||
        final PageContext formContext = pageContext.withEntityKey(examConfig.getEntityKey());
 | 
					        final PageContext formContext = pageContext.withEntityKey(examConfig.getEntityKey());
 | 
				
			||||||
| 
						 | 
					@ -151,7 +156,6 @@ public class SebExamConfigPropForm implements TemplateComposer {
 | 
				
			||||||
                formContext.getParent(),
 | 
					                formContext.getParent(),
 | 
				
			||||||
                titleKey);
 | 
					                titleKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // The SebClientConfig form
 | 
					 | 
				
			||||||
        final FormHandle<ConfigurationNode> formHandle = this.pageService.formBuilder(
 | 
					        final FormHandle<ConfigurationNode> formHandle = this.pageService.formBuilder(
 | 
				
			||||||
                formContext.copyOf(content), 4)
 | 
					                formContext.copyOf(content), 4)
 | 
				
			||||||
                .readonly(isReadonly)
 | 
					                .readonly(isReadonly)
 | 
				
			||||||
| 
						 | 
					@ -185,7 +189,7 @@ public class SebExamConfigPropForm implements TemplateComposer {
 | 
				
			||||||
                        Domain.CONFIGURATION_NODE.ATTR_STATUS,
 | 
					                        Domain.CONFIGURATION_NODE.ATTR_STATUS,
 | 
				
			||||||
                        FORM_STATUS_TEXT_KEY,
 | 
					                        FORM_STATUS_TEXT_KEY,
 | 
				
			||||||
                        examConfig.status.name(),
 | 
					                        examConfig.status.name(),
 | 
				
			||||||
                        resourceService::examConfigStatusResources))
 | 
					                        () -> resourceService.examConfigStatusResources(isAttachedToExam)))
 | 
				
			||||||
                .buildFor((isNew)
 | 
					                .buildFor((isNew)
 | 
				
			||||||
                        ? this.restService.getRestCall(NewExamConfig.class)
 | 
					                        ? this.restService.getRestCall(NewExamConfig.class)
 | 
				
			||||||
                        : this.restService.getRestCall(SaveExamConfig.class));
 | 
					                        : this.restService.getRestCall(SaveExamConfig.class));
 | 
				
			||||||
| 
						 | 
					@ -229,7 +233,7 @@ public class SebExamConfigPropForm implements TemplateComposer {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                .newAction(ActionDefinition.SEB_EXAM_CONFIG_IMPORT_CONFIG)
 | 
					                .newAction(ActionDefinition.SEB_EXAM_CONFIG_IMPORT_CONFIG)
 | 
				
			||||||
                .withEntityKey(entityKey)
 | 
					                .withEntityKey(entityKey)
 | 
				
			||||||
                .withExec(SebExamConfigPropForm.importConfigFunction(this.pageService))
 | 
					                .withExec(SebExamConfigImport.importConfigFunction(this.pageService))
 | 
				
			||||||
                .noEventPropagation()
 | 
					                .noEventPropagation()
 | 
				
			||||||
                .publishIf(() -> modifyGrant && isReadonly)
 | 
					                .publishIf(() -> modifyGrant && isReadonly)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -237,6 +241,7 @@ public class SebExamConfigPropForm implements TemplateComposer {
 | 
				
			||||||
                .withEntityKey(entityKey)
 | 
					                .withEntityKey(entityKey)
 | 
				
			||||||
                .withExec(formHandle::processFormSave)
 | 
					                .withExec(formHandle::processFormSave)
 | 
				
			||||||
                .ignoreMoveAwayFromEdit()
 | 
					                .ignoreMoveAwayFromEdit()
 | 
				
			||||||
 | 
					                .withConfirm(() -> stateChangeConfirm(isAttachedToExam, formHandle))
 | 
				
			||||||
                .publishIf(() -> !isReadonly)
 | 
					                .publishIf(() -> !isReadonly)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                .newAction(ActionDefinition.SEB_EXAM_CONFIG_PROP_CANCEL_MODIFY)
 | 
					                .newAction(ActionDefinition.SEB_EXAM_CONFIG_PROP_CANCEL_MODIFY)
 | 
				
			||||||
| 
						 | 
					@ -246,6 +251,26 @@ public class SebExamConfigPropForm implements TemplateComposer {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private LocTextKey stateChangeConfirm(
 | 
				
			||||||
 | 
					            final boolean isAttachedToExam,
 | 
				
			||||||
 | 
					            final FormHandle<ConfigurationNode> formHandle) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isAttachedToExam) {
 | 
				
			||||||
 | 
					            final String fieldValue = formHandle
 | 
				
			||||||
 | 
					                    .getForm()
 | 
				
			||||||
 | 
					                    .getFieldValue(Domain.CONFIGURATION_NODE.ATTR_STATUS);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (fieldValue != null) {
 | 
				
			||||||
 | 
					                final ConfigurationStatus state = ConfigurationStatus.valueOf(fieldValue);
 | 
				
			||||||
 | 
					                if (state != ConfigurationStatus.IN_USE) {
 | 
				
			||||||
 | 
					                    return SAVE_CONFIRM_STATE_CHANGE_WHILE_ATTACHED;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static Function<PageAction, PageAction> getConfigKeyFunction(final PageService pageService) {
 | 
					    public static Function<PageAction, PageAction> getConfigKeyFunction(final PageService pageService) {
 | 
				
			||||||
        final RestService restService = pageService.getResourceService().getRestService();
 | 
					        final RestService restService = pageService.getResourceService().getRestService();
 | 
				
			||||||
        return action -> {
 | 
					        return action -> {
 | 
				
			||||||
| 
						 | 
					@ -281,108 +306,4 @@ public class SebExamConfigPropForm implements TemplateComposer {
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static Function<PageAction, PageAction> importConfigFunction(final PageService pageService) {
 | 
					 | 
				
			||||||
        return action -> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            final ModalInputDialog<FormHandle<ConfigurationNode>> dialog =
 | 
					 | 
				
			||||||
                    new ModalInputDialog<FormHandle<ConfigurationNode>>(
 | 
					 | 
				
			||||||
                            action.pageContext().getParent().getShell(),
 | 
					 | 
				
			||||||
                            pageService.getWidgetFactory())
 | 
					 | 
				
			||||||
                                    .setDialogWidth(600);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            final ImportFormContext importFormContext = new ImportFormContext(
 | 
					 | 
				
			||||||
                    pageService,
 | 
					 | 
				
			||||||
                    action.pageContext());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            dialog.open(
 | 
					 | 
				
			||||||
                    FORM_IMPORT_TEXT_KEY,
 | 
					 | 
				
			||||||
                    formHandle -> SebExamConfigPropForm.doImport(
 | 
					 | 
				
			||||||
                            pageService,
 | 
					 | 
				
			||||||
                            formHandle),
 | 
					 | 
				
			||||||
                    importFormContext::cancelUpload,
 | 
					 | 
				
			||||||
                    importFormContext);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return action;
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static final void doImport(
 | 
					 | 
				
			||||||
            final PageService pageService,
 | 
					 | 
				
			||||||
            final FormHandle<ConfigurationNode> formHandle) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final Form form = formHandle.getForm();
 | 
					 | 
				
			||||||
        final EntityKey entityKey = formHandle.getContext().getEntityKey();
 | 
					 | 
				
			||||||
        final Control fieldControl = form.getFieldControl(API.IMPORT_FILE_ATTR_NAME);
 | 
					 | 
				
			||||||
        final PageContext context = formHandle.getContext();
 | 
					 | 
				
			||||||
        if (fieldControl != null && fieldControl instanceof FileUploadSelection) {
 | 
					 | 
				
			||||||
            final FileUploadSelection fileUpload = (FileUploadSelection) fieldControl;
 | 
					 | 
				
			||||||
            final InputStream inputStream = fileUpload.getInputStream();
 | 
					 | 
				
			||||||
            if (inputStream != null) {
 | 
					 | 
				
			||||||
                final Configuration configuration = pageService.getRestService()
 | 
					 | 
				
			||||||
                        .getBuilder(ImportExamConfig.class)
 | 
					 | 
				
			||||||
                        .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
 | 
					 | 
				
			||||||
                        .withHeader(
 | 
					 | 
				
			||||||
                                API.IMPORT_PASSWORD_ATTR_NAME,
 | 
					 | 
				
			||||||
                                form.getFieldValue(API.IMPORT_PASSWORD_ATTR_NAME))
 | 
					 | 
				
			||||||
                        .withBody(inputStream)
 | 
					 | 
				
			||||||
                        .call()
 | 
					 | 
				
			||||||
                        .get(e -> {
 | 
					 | 
				
			||||||
                            fileUpload.close();
 | 
					 | 
				
			||||||
                            return context.notifyError(e);
 | 
					 | 
				
			||||||
                        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (configuration != null) {
 | 
					 | 
				
			||||||
                    context.publishInfo(FORM_IMPORT_CONFIRM_TEXT_KEY);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                formHandle.getContext().publishPageMessage(
 | 
					 | 
				
			||||||
                        new LocTextKey("sebserver.error.unexpected"),
 | 
					 | 
				
			||||||
                        new LocTextKey("Please selecte a valid SEB Exam Configuration File"));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static final class ImportFormContext implements ModalInputDialogComposer<FormHandle<ConfigurationNode>> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private final PageService pageService;
 | 
					 | 
				
			||||||
        private final PageContext pageContext;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private Form form = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        protected ImportFormContext(final PageService pageService, final PageContext pageContext) {
 | 
					 | 
				
			||||||
            this.pageService = pageService;
 | 
					 | 
				
			||||||
            this.pageContext = pageContext;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @Override
 | 
					 | 
				
			||||||
        public Supplier<FormHandle<ConfigurationNode>> compose(final Composite parent) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            final FormHandle<ConfigurationNode> formHandle = this.pageService.formBuilder(
 | 
					 | 
				
			||||||
                    this.pageContext.copyOf(parent), 4)
 | 
					 | 
				
			||||||
                    .readonly(false)
 | 
					 | 
				
			||||||
                    .addField(FormBuilder.fileUpload(
 | 
					 | 
				
			||||||
                            API.IMPORT_FILE_ATTR_NAME,
 | 
					 | 
				
			||||||
                            FORM_IMPORT_SELECT_TEXT_KEY,
 | 
					 | 
				
			||||||
                            null,
 | 
					 | 
				
			||||||
                            API.SEB_FILE_EXTENSION))
 | 
					 | 
				
			||||||
                    .addField(FormBuilder.text(
 | 
					 | 
				
			||||||
                            API.IMPORT_PASSWORD_ATTR_NAME,
 | 
					 | 
				
			||||||
                            FORM_IMPORT_PASSWORD_TEXT_KEY,
 | 
					 | 
				
			||||||
                            "").asPasswordField())
 | 
					 | 
				
			||||||
                    .build();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            this.form = formHandle.getForm();
 | 
					 | 
				
			||||||
            return () -> formHandle;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        void cancelUpload() {
 | 
					 | 
				
			||||||
            if (this.form != null) {
 | 
					 | 
				
			||||||
                final Control fieldControl = this.form.getFieldControl(API.IMPORT_FILE_ATTR_NAME);
 | 
					 | 
				
			||||||
                if (fieldControl != null && fieldControl instanceof FileUploadSelection) {
 | 
					 | 
				
			||||||
                    ((FileUploadSelection) fieldControl).close();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,6 +27,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
 | 
					import ch.ethz.seb.sebserver.gbl.model.EntityKey;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.View;
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.View;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
 | 
					import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
 | 
					import ch.ethz.seb.sebserver.gbl.util.Utils;
 | 
				
			||||||
| 
						 | 
					@ -112,6 +113,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
 | 
				
			||||||
                    .onError(pageContext::notifyError)
 | 
					                    .onError(pageContext::notifyError)
 | 
				
			||||||
                    .getOrThrow();
 | 
					                    .getOrThrow();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            final boolean readonly = pageContext.isReadonly() || configNode.status == ConfigurationStatus.IN_USE;
 | 
				
			||||||
            final List<View> views = this.examConfigurationService.getViews(attributes);
 | 
					            final List<View> views = this.examConfigurationService.getViews(attributes);
 | 
				
			||||||
            final TabFolder tabFolder = widgetFactory.tabFolderLocalized(content);
 | 
					            final TabFolder tabFolder = widgetFactory.tabFolderLocalized(content);
 | 
				
			||||||
            tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
 | 
					            tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
 | 
				
			||||||
| 
						 | 
					@ -124,7 +126,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
 | 
				
			||||||
                        view,
 | 
					                        view,
 | 
				
			||||||
                        attributes,
 | 
					                        attributes,
 | 
				
			||||||
                        20,
 | 
					                        20,
 | 
				
			||||||
                        pageContext.isReadonly());
 | 
					                        readonly);
 | 
				
			||||||
                viewContexts.add(viewContext);
 | 
					                viewContexts.add(viewContext);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                final Composite viewGrid = this.examConfigurationService.createViewGrid(
 | 
					                final Composite viewGrid = this.examConfigurationService.createViewGrid(
 | 
				
			||||||
| 
						 | 
					@ -153,7 +155,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
 | 
				
			||||||
                        return action;
 | 
					                        return action;
 | 
				
			||||||
                    })
 | 
					                    })
 | 
				
			||||||
                    .withSuccess(KEY_SAVE_TO_HISTORY_SUCCESS)
 | 
					                    .withSuccess(KEY_SAVE_TO_HISTORY_SUCCESS)
 | 
				
			||||||
                    .publishIf(() -> examConfigGrant.iw())
 | 
					                    .publishIf(() -> examConfigGrant.iw() && !readonly)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    .newAction(ActionDefinition.SEB_EXAM_CONFIG_UNDO)
 | 
					                    .newAction(ActionDefinition.SEB_EXAM_CONFIG_UNDO)
 | 
				
			||||||
                    .withEntityKey(entityKey)
 | 
					                    .withEntityKey(entityKey)
 | 
				
			||||||
| 
						 | 
					@ -166,7 +168,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
 | 
				
			||||||
                        return action;
 | 
					                        return action;
 | 
				
			||||||
                    })
 | 
					                    })
 | 
				
			||||||
                    .withSuccess(KEY_UNDO_SUCCESS)
 | 
					                    .withSuccess(KEY_UNDO_SUCCESS)
 | 
				
			||||||
                    .publishIf(() -> examConfigGrant.iw())
 | 
					                    .publishIf(() -> examConfigGrant.iw() && !readonly)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    .newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP)
 | 
					                    .newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP)
 | 
				
			||||||
                    .withEntityKey(entityKey)
 | 
					                    .withEntityKey(entityKey)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -403,6 +403,13 @@ public enum ActionDefinition {
 | 
				
			||||||
            ImageIcon.IMPORT,
 | 
					            ImageIcon.IMPORT,
 | 
				
			||||||
            ActionCategory.FORM),
 | 
					            ActionCategory.FORM),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO copy config action
 | 
				
			||||||
 | 
					    // TODO
 | 
				
			||||||
 | 
					    SEB_EXAM_CONFIG_COPY_CONFIG(
 | 
				
			||||||
 | 
					            new LocTextKey("sebserver.examconfig.action.copy-config"),
 | 
				
			||||||
 | 
					            ImageIcon.IMPORT,
 | 
				
			||||||
 | 
					            ActionCategory.FORM),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SEB_EXAM_CONFIG_MODIFY_FROM_LIST(
 | 
					    SEB_EXAM_CONFIG_MODIFY_FROM_LIST(
 | 
				
			||||||
            new LocTextKey("sebserver.examconfig.action.list.modify"),
 | 
					            new LocTextKey("sebserver.examconfig.action.list.modify"),
 | 
				
			||||||
            ImageIcon.EDIT_SETTINGS,
 | 
					            ImageIcon.EDIT_SETTINGS,
 | 
				
			||||||
| 
						 | 
					@ -421,7 +428,7 @@ public enum ActionDefinition {
 | 
				
			||||||
            ActionCategory.FORM),
 | 
					            ActionCategory.FORM),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SEB_EXAM_CONFIG_TEMPLATE_NEW(
 | 
					    SEB_EXAM_CONFIG_TEMPLATE_NEW(
 | 
				
			||||||
            new LocTextKey("sebserver.exam.configtemplate.action.list.new"),
 | 
					            new LocTextKey("sebserver.configtemplate.action.list.new"),
 | 
				
			||||||
            ImageIcon.NEW,
 | 
					            ImageIcon.NEW,
 | 
				
			||||||
            PageStateDefinitionImpl.SEB_EXAM_CONFIG_TEMPLATE_EDIT,
 | 
					            PageStateDefinitionImpl.SEB_EXAM_CONFIG_TEMPLATE_EDIT,
 | 
				
			||||||
            ActionCategory.VARIA),
 | 
					            ActionCategory.VARIA),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -365,8 +365,13 @@ public class ResourceService {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public List<Tuple<String>> examConfigStatusResources() {
 | 
					    public List<Tuple<String>> examConfigStatusResources() {
 | 
				
			||||||
 | 
					        return examConfigStatusResources(false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public List<Tuple<String>> examConfigStatusResources(final boolean isAttachedToExam) {
 | 
				
			||||||
        return Arrays.asList(ConfigurationStatus.values())
 | 
					        return Arrays.asList(ConfigurationStatus.values())
 | 
				
			||||||
                .stream()
 | 
					                .stream()
 | 
				
			||||||
 | 
					                .filter(status -> !isAttachedToExam || status != ConfigurationStatus.READY_TO_USE)
 | 
				
			||||||
                .map(type -> new Tuple<>(
 | 
					                .map(type -> new Tuple<>(
 | 
				
			||||||
                        type.name(),
 | 
					                        type.name(),
 | 
				
			||||||
                        this.i18nSupport.getText(EXAMCONFIG_STATUS_PREFIX + type.name())))
 | 
					                        this.i18nSupport.getText(EXAMCONFIG_STATUS_PREFIX + type.name())))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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.service.remote.webservice.api.exam;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.Collection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.springframework.context.annotation.Lazy;
 | 
				
			||||||
 | 
					import org.springframework.http.HttpMethod;
 | 
				
			||||||
 | 
					import org.springframework.http.MediaType;
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Component;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.core.type.TypeReference;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.api.API;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.api.APIMessage;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.api.EntityType;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Lazy
 | 
				
			||||||
 | 
					@Component
 | 
				
			||||||
 | 
					@GuiProfile
 | 
				
			||||||
 | 
					public class CheckExamConsistency extends RestCall<Collection<APIMessage>> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public CheckExamConsistency() {
 | 
				
			||||||
 | 
					        super(new TypeKey<>(
 | 
				
			||||||
 | 
					                CallType.UNDEFINED,
 | 
				
			||||||
 | 
					                EntityType.EXAM,
 | 
				
			||||||
 | 
					                new TypeReference<Collection<APIMessage>>() {
 | 
				
			||||||
 | 
					                }),
 | 
				
			||||||
 | 
					                HttpMethod.GET,
 | 
				
			||||||
 | 
					                MediaType.APPLICATION_FORM_URLENCODED,
 | 
				
			||||||
 | 
					                API.EXAM_ADMINISTRATION_ENDPOINT
 | 
				
			||||||
 | 
					                        + API.MODEL_ID_VAR_PATH_SEGMENT
 | 
				
			||||||
 | 
					                        + API.EXAM_ADMINISTRATION_CONSISTENCY_CHECK_PATH_SEGMENT);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -32,10 +32,9 @@ public class CopyConfiguration extends RestCall<ConfigurationNode> {
 | 
				
			||||||
                EntityType.CONFIGURATION_NODE,
 | 
					                EntityType.CONFIGURATION_NODE,
 | 
				
			||||||
                new TypeReference<ConfigurationNode>() {
 | 
					                new TypeReference<ConfigurationNode>() {
 | 
				
			||||||
                }),
 | 
					                }),
 | 
				
			||||||
                HttpMethod.POST,
 | 
					                HttpMethod.PUT,
 | 
				
			||||||
                MediaType.APPLICATION_FORM_URLENCODED,
 | 
					                MediaType.APPLICATION_JSON_UTF8,
 | 
				
			||||||
                API.CONFIGURATION_NODE_ENDPOINT
 | 
					                API.CONFIGURATION_NODE_ENDPOINT
 | 
				
			||||||
                        + API.MODEL_ID_VAR_PATH_SEGMENT
 | 
					 | 
				
			||||||
                        + API.CONFIGURATION_COPY_PATH_SEGMENT);
 | 
					                        + API.CONFIGURATION_COPY_PATH_SEGMENT);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,7 @@ import java.util.Collections;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Objects;
 | 
					import java.util.Objects;
 | 
				
			||||||
import java.util.Set;
 | 
					import java.util.Set;
 | 
				
			||||||
 | 
					import java.util.function.BiConsumer;
 | 
				
			||||||
import java.util.function.Consumer;
 | 
					import java.util.function.Consumer;
 | 
				
			||||||
import java.util.function.Function;
 | 
					import java.util.function.Function;
 | 
				
			||||||
import java.util.function.Predicate;
 | 
					import java.util.function.Predicate;
 | 
				
			||||||
| 
						 | 
					@ -54,6 +55,7 @@ import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
 | 
					import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon;
 | 
					import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class EntityTable<ROW extends Entity> {
 | 
					public class EntityTable<ROW extends Entity> {
 | 
				
			||||||
| 
						 | 
					@ -80,6 +82,7 @@ public class EntityTable<ROW extends Entity> {
 | 
				
			||||||
    private final Table table;
 | 
					    private final Table table;
 | 
				
			||||||
    private final TableNavigator navigator;
 | 
					    private final TableNavigator navigator;
 | 
				
			||||||
    private final MultiValueMap<String, String> staticQueryParams;
 | 
					    private final MultiValueMap<String, String> staticQueryParams;
 | 
				
			||||||
 | 
					    private final BiConsumer<TableItem, ROW> rowDecorator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    int pageNumber = 1;
 | 
					    int pageNumber = 1;
 | 
				
			||||||
    int pageSize;
 | 
					    int pageSize;
 | 
				
			||||||
| 
						 | 
					@ -99,7 +102,8 @@ public class EntityTable<ROW extends Entity> {
 | 
				
			||||||
            final LocTextKey emptyMessage,
 | 
					            final LocTextKey emptyMessage,
 | 
				
			||||||
            final Function<EntityTable<ROW>, PageAction> defaultActionFunction,
 | 
					            final Function<EntityTable<ROW>, PageAction> defaultActionFunction,
 | 
				
			||||||
            final boolean hideNavigation,
 | 
					            final boolean hideNavigation,
 | 
				
			||||||
            final MultiValueMap<String, String> staticQueryParams) {
 | 
					            final MultiValueMap<String, String> staticQueryParams,
 | 
				
			||||||
 | 
					            final BiConsumer<TableItem, ROW> rowDecorator) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.composite = new Composite(pageContext.getParent(), type);
 | 
					        this.composite = new Composite(pageContext.getParent(), type);
 | 
				
			||||||
        this.pageService = pageService;
 | 
					        this.pageService = pageService;
 | 
				
			||||||
| 
						 | 
					@ -120,6 +124,7 @@ public class EntityTable<ROW extends Entity> {
 | 
				
			||||||
        GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
 | 
					        GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
 | 
				
			||||||
        this.composite.setLayoutData(gridData);
 | 
					        this.composite.setLayoutData(gridData);
 | 
				
			||||||
        this.staticQueryParams = staticQueryParams;
 | 
					        this.staticQueryParams = staticQueryParams;
 | 
				
			||||||
 | 
					        this.rowDecorator = rowDecorator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO just for debugging, remove when tested
 | 
					// TODO just for debugging, remove when tested
 | 
				
			||||||
//        this.composite.setBackground(new Color(parent.getDisplay(), new RGB(0, 200, 0)));
 | 
					//        this.composite.setBackground(new Color(parent.getDisplay(), new RGB(0, 200, 0)));
 | 
				
			||||||
| 
						 | 
					@ -404,6 +409,9 @@ public class EntityTable<ROW extends Entity> {
 | 
				
			||||||
            final TableItem item = new TableItem(this.table, SWT.NONE);
 | 
					            final TableItem item = new TableItem(this.table, SWT.NONE);
 | 
				
			||||||
            item.setData(RWT.MARKUP_ENABLED, Boolean.TRUE);
 | 
					            item.setData(RWT.MARKUP_ENABLED, Boolean.TRUE);
 | 
				
			||||||
            item.setData(TABLE_ROW_DATA, row);
 | 
					            item.setData(TABLE_ROW_DATA, row);
 | 
				
			||||||
 | 
					            if (this.rowDecorator != null) {
 | 
				
			||||||
 | 
					                this.rowDecorator.accept(item, row);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            int index = 0;
 | 
					            int index = 0;
 | 
				
			||||||
            for (final ColumnDefinition<ROW> column : this.columns) {
 | 
					            for (final ColumnDefinition<ROW> column : this.columns) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,11 +10,13 @@ package ch.ethz.seb.sebserver.gui.table;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.function.BiConsumer;
 | 
				
			||||||
import java.util.function.BooleanSupplier;
 | 
					import java.util.function.BooleanSupplier;
 | 
				
			||||||
import java.util.function.Function;
 | 
					import java.util.function.Function;
 | 
				
			||||||
import java.util.function.Supplier;
 | 
					import java.util.function.Supplier;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.eclipse.swt.SWT;
 | 
					import org.eclipse.swt.SWT;
 | 
				
			||||||
 | 
					import org.eclipse.swt.widgets.TableItem;
 | 
				
			||||||
import org.springframework.util.LinkedMultiValueMap;
 | 
					import org.springframework.util.LinkedMultiValueMap;
 | 
				
			||||||
import org.springframework.util.MultiValueMap;
 | 
					import org.springframework.util.MultiValueMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,6 +40,7 @@ public class TableBuilder<ROW extends Entity> {
 | 
				
			||||||
    private int type = SWT.NONE;
 | 
					    private int type = SWT.NONE;
 | 
				
			||||||
    private boolean hideNavigation = false;
 | 
					    private boolean hideNavigation = false;
 | 
				
			||||||
    private Function<RestCall<Page<ROW>>.RestCallBuilder, RestCall<Page<ROW>>.RestCallBuilder> restCallAdapter;
 | 
					    private Function<RestCall<Page<ROW>>.RestCallBuilder, RestCall<Page<ROW>>.RestCallBuilder> restCallAdapter;
 | 
				
			||||||
 | 
					    private BiConsumer<TableItem, ROW> rowDecorator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public TableBuilder(
 | 
					    public TableBuilder(
 | 
				
			||||||
            final PageService pageService,
 | 
					            final PageService pageService,
 | 
				
			||||||
| 
						 | 
					@ -126,6 +129,11 @@ public class TableBuilder<ROW extends Entity> {
 | 
				
			||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public TableBuilder<ROW> withRowDecorator(final BiConsumer<TableItem, ROW> rowDecorator) {
 | 
				
			||||||
 | 
					        this.rowDecorator = rowDecorator;
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public EntityTable<ROW> compose(final PageContext pageContext) {
 | 
					    public EntityTable<ROW> compose(final PageContext pageContext) {
 | 
				
			||||||
        return new EntityTable<>(
 | 
					        return new EntityTable<>(
 | 
				
			||||||
                this.type,
 | 
					                this.type,
 | 
				
			||||||
| 
						 | 
					@ -138,7 +146,8 @@ public class TableBuilder<ROW extends Entity> {
 | 
				
			||||||
                this.emptyMessage,
 | 
					                this.emptyMessage,
 | 
				
			||||||
                this.defaultActionFunction,
 | 
					                this.defaultActionFunction,
 | 
				
			||||||
                this.hideNavigation,
 | 
					                this.hideNavigation,
 | 
				
			||||||
                this.staticQueryParams);
 | 
					                this.staticQueryParams,
 | 
				
			||||||
 | 
					                this.rowDecorator);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -131,6 +131,7 @@ public class WidgetFactory {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        MESSAGE("message"),
 | 
					        MESSAGE("message"),
 | 
				
			||||||
        ERROR("error"),
 | 
					        ERROR("error"),
 | 
				
			||||||
 | 
					        WARNING("warning"),
 | 
				
			||||||
        CONFIG_INPUT_READONLY("inputreadonly")
 | 
					        CONFIG_INPUT_READONLY("inputreadonly")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ;
 | 
					        ;
 | 
				
			||||||
| 
						 | 
					@ -218,6 +219,17 @@ public class WidgetFactory {
 | 
				
			||||||
        return grid;
 | 
					        return grid;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public Composite createWarningPanel(final Composite parent) {
 | 
				
			||||||
 | 
					        final Composite composite = new Composite(parent, SWT.NONE);
 | 
				
			||||||
 | 
					        composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
 | 
				
			||||||
 | 
					        final GridLayout gridLayout = new GridLayout(1, true);
 | 
				
			||||||
 | 
					        gridLayout.marginWidth = 20;
 | 
				
			||||||
 | 
					        gridLayout.marginHeight = 20;
 | 
				
			||||||
 | 
					        composite.setLayout(gridLayout);
 | 
				
			||||||
 | 
					        composite.setData(RWT.CUSTOM_VARIANT, CustomVariant.WARNING.key);
 | 
				
			||||||
 | 
					        return composite;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public Button buttonLocalized(final Composite parent, final String locTextKey) {
 | 
					    public Button buttonLocalized(final Composite parent, final String locTextKey) {
 | 
				
			||||||
        final Button button = new Button(parent, SWT.NONE);
 | 
					        final Button button = new Button(parent, SWT.NONE);
 | 
				
			||||||
        this.polyglotPageService.injectI18n(button, new LocTextKey(locTextKey));
 | 
					        this.polyglotPageService.injectI18n(button, new LocTextKey(locTextKey));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,6 +8,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
 | 
					package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigCopyInfo;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
 | 
					import ch.ethz.seb.sebserver.gbl.util.Result;
 | 
				
			||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO;
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO;
 | 
				
			||||||
| 
						 | 
					@ -19,7 +20,6 @@ public interface ConfigurationNodeDAO extends
 | 
				
			||||||
    Result<ConfigurationNode> createCopy(
 | 
					    Result<ConfigurationNode> createCopy(
 | 
				
			||||||
            Long institutionId,
 | 
					            Long institutionId,
 | 
				
			||||||
            String newOwner,
 | 
					            String newOwner,
 | 
				
			||||||
            Long configurationNodeId,
 | 
					            ConfigCopyInfo copyInfo);
 | 
				
			||||||
            boolean withHistory);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,6 +32,7 @@ import org.springframework.transaction.annotation.Transactional;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException;
 | 
					import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
 | 
					import ch.ethz.seb.sebserver.gbl.api.EntityType;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
 | 
					import ch.ethz.seb.sebserver.gbl.model.EntityKey;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigCopyInfo;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
 | 
				
			||||||
| 
						 | 
					@ -208,79 +209,16 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO {
 | 
				
			||||||
    public Result<ConfigurationNode> createCopy(
 | 
					    public Result<ConfigurationNode> createCopy(
 | 
				
			||||||
            final Long institutionId,
 | 
					            final Long institutionId,
 | 
				
			||||||
            final String newOwner,
 | 
					            final String newOwner,
 | 
				
			||||||
            final Long configurationNodeId,
 | 
					            final ConfigCopyInfo copyInfo) {
 | 
				
			||||||
            final boolean withHistory) {
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return this.recordById(configurationNodeId)
 | 
					        return this.recordById(copyInfo.configurationNodeId)
 | 
				
			||||||
                .flatMap(nodeRec -> (nodeRec.getInstitutionId().equals(institutionId)
 | 
					                .flatMap(nodeRec -> (nodeRec.getInstitutionId().equals(institutionId)
 | 
				
			||||||
                        ? Result.of(nodeRec)
 | 
					                        ? Result.of(nodeRec)
 | 
				
			||||||
                        : Result.ofError(new IllegalArgumentException("Institution integrity violation"))))
 | 
					                        : Result.ofError(new IllegalArgumentException("Institution integrity violation"))))
 | 
				
			||||||
                .map(nodeRec -> this.copyNodeRecord(nodeRec, newOwner, withHistory))
 | 
					                .map(nodeRec -> this.copyNodeRecord(nodeRec, newOwner, copyInfo))
 | 
				
			||||||
                .flatMap(ConfigurationNodeDAOImpl::toDomainModel);
 | 
					                .flatMap(ConfigurationNodeDAOImpl::toDomainModel);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private ConfigurationNodeRecord copyNodeRecord(
 | 
					 | 
				
			||||||
            final ConfigurationNodeRecord nodeRec,
 | 
					 | 
				
			||||||
            final String newOwner,
 | 
					 | 
				
			||||||
            final boolean withHistory) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final ConfigurationNodeRecord newNodeRec = new ConfigurationNodeRecord(
 | 
					 | 
				
			||||||
                null,
 | 
					 | 
				
			||||||
                nodeRec.getInstitutionId(),
 | 
					 | 
				
			||||||
                nodeRec.getTemplateId(),
 | 
					 | 
				
			||||||
                StringUtils.isNotBlank(newOwner) ? newOwner : nodeRec.getOwner(),
 | 
					 | 
				
			||||||
                this.copyNamePrefix + nodeRec.getName() + this.copyNameSuffix,
 | 
					 | 
				
			||||||
                nodeRec.getDescription(),
 | 
					 | 
				
			||||||
                nodeRec.getType(),
 | 
					 | 
				
			||||||
                ConfigurationStatus.CONSTRUCTION.name());
 | 
					 | 
				
			||||||
        this.configurationNodeRecordMapper.insert(newNodeRec);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final List<ConfigurationRecord> configs = this.configurationRecordMapper
 | 
					 | 
				
			||||||
                .selectByExample()
 | 
					 | 
				
			||||||
                .where(
 | 
					 | 
				
			||||||
                        ConfigurationRecordDynamicSqlSupport.configurationNodeId,
 | 
					 | 
				
			||||||
                        isEqualTo(nodeRec.getId()))
 | 
					 | 
				
			||||||
                .build()
 | 
					 | 
				
			||||||
                .execute();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (withHistory) {
 | 
					 | 
				
			||||||
            configs
 | 
					 | 
				
			||||||
                    .stream()
 | 
					 | 
				
			||||||
                    .forEach(configRec -> this.configurationDAOBatchService.copyConfiguration(
 | 
					 | 
				
			||||||
                            configRec.getInstitutionId(),
 | 
					 | 
				
			||||||
                            configRec.getId(),
 | 
					 | 
				
			||||||
                            newNodeRec.getId()));
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            configs
 | 
					 | 
				
			||||||
                    .stream()
 | 
					 | 
				
			||||||
                    .filter(configRec -> configRec.getVersionDate() == null)
 | 
					 | 
				
			||||||
                    .findFirst()
 | 
					 | 
				
			||||||
                    .map(configRec -> {
 | 
					 | 
				
			||||||
                        // No history means to create a first version and a follow-up with the copied values
 | 
					 | 
				
			||||||
                        final ConfigurationRecord newFirstVersion = new ConfigurationRecord(
 | 
					 | 
				
			||||||
                                null,
 | 
					 | 
				
			||||||
                                configRec.getInstitutionId(),
 | 
					 | 
				
			||||||
                                configRec.getConfigurationNodeId(),
 | 
					 | 
				
			||||||
                                ConfigurationDAOBatchService.INITIAL_VERSION_NAME,
 | 
					 | 
				
			||||||
                                DateTime.now(DateTimeZone.UTC),
 | 
					 | 
				
			||||||
                                BooleanUtils.toInteger(false));
 | 
					 | 
				
			||||||
                        this.configurationRecordMapper.insert(newFirstVersion);
 | 
					 | 
				
			||||||
                        this.configurationDAOBatchService.copyValues(
 | 
					 | 
				
			||||||
                                configRec.getInstitutionId(),
 | 
					 | 
				
			||||||
                                configRec.getId(),
 | 
					 | 
				
			||||||
                                newFirstVersion.getId());
 | 
					 | 
				
			||||||
                        // and copy the follow-up
 | 
					 | 
				
			||||||
                        this.configurationDAOBatchService.copyConfiguration(
 | 
					 | 
				
			||||||
                                configRec.getInstitutionId(),
 | 
					 | 
				
			||||||
                                configRec.getId(),
 | 
					 | 
				
			||||||
                                newNodeRec.getId());
 | 
					 | 
				
			||||||
                        return configRec;
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return newNodeRec;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    @Transactional
 | 
					    @Transactional
 | 
				
			||||||
    public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
 | 
					    public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
 | 
				
			||||||
| 
						 | 
					@ -345,6 +283,68 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO {
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private ConfigurationNodeRecord copyNodeRecord(
 | 
				
			||||||
 | 
					            final ConfigurationNodeRecord nodeRec,
 | 
				
			||||||
 | 
					            final String newOwner,
 | 
				
			||||||
 | 
					            final ConfigCopyInfo copyInfo) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final ConfigurationNodeRecord newNodeRec = new ConfigurationNodeRecord(
 | 
				
			||||||
 | 
					                null,
 | 
				
			||||||
 | 
					                nodeRec.getInstitutionId(),
 | 
				
			||||||
 | 
					                nodeRec.getTemplateId(),
 | 
				
			||||||
 | 
					                StringUtils.isNotBlank(newOwner) ? newOwner : nodeRec.getOwner(),
 | 
				
			||||||
 | 
					                this.copyNamePrefix + nodeRec.getName() + this.copyNameSuffix,
 | 
				
			||||||
 | 
					                nodeRec.getDescription(),
 | 
				
			||||||
 | 
					                nodeRec.getType(),
 | 
				
			||||||
 | 
					                ConfigurationStatus.CONSTRUCTION.name());
 | 
				
			||||||
 | 
					        this.configurationNodeRecordMapper.insert(newNodeRec);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final List<ConfigurationRecord> configs = this.configurationRecordMapper
 | 
				
			||||||
 | 
					                .selectByExample()
 | 
				
			||||||
 | 
					                .where(
 | 
				
			||||||
 | 
					                        ConfigurationRecordDynamicSqlSupport.configurationNodeId,
 | 
				
			||||||
 | 
					                        isEqualTo(nodeRec.getId()))
 | 
				
			||||||
 | 
					                .build()
 | 
				
			||||||
 | 
					                .execute();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (BooleanUtils.toBoolean(copyInfo.withHistory)) {
 | 
				
			||||||
 | 
					            configs
 | 
				
			||||||
 | 
					                    .stream()
 | 
				
			||||||
 | 
					                    .forEach(configRec -> this.configurationDAOBatchService.copyConfiguration(
 | 
				
			||||||
 | 
					                            configRec.getInstitutionId(),
 | 
				
			||||||
 | 
					                            configRec.getId(),
 | 
				
			||||||
 | 
					                            newNodeRec.getId()));
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            configs
 | 
				
			||||||
 | 
					                    .stream()
 | 
				
			||||||
 | 
					                    .filter(configRec -> configRec.getVersionDate() == null)
 | 
				
			||||||
 | 
					                    .findFirst()
 | 
				
			||||||
 | 
					                    .map(configRec -> {
 | 
				
			||||||
 | 
					                        // No history means to create a first version and a follow-up with the copied values
 | 
				
			||||||
 | 
					                        final ConfigurationRecord newFirstVersion = new ConfigurationRecord(
 | 
				
			||||||
 | 
					                                null,
 | 
				
			||||||
 | 
					                                configRec.getInstitutionId(),
 | 
				
			||||||
 | 
					                                configRec.getConfigurationNodeId(),
 | 
				
			||||||
 | 
					                                ConfigurationDAOBatchService.INITIAL_VERSION_NAME,
 | 
				
			||||||
 | 
					                                DateTime.now(DateTimeZone.UTC),
 | 
				
			||||||
 | 
					                                BooleanUtils.toInteger(false));
 | 
				
			||||||
 | 
					                        this.configurationRecordMapper.insert(newFirstVersion);
 | 
				
			||||||
 | 
					                        this.configurationDAOBatchService.copyValues(
 | 
				
			||||||
 | 
					                                configRec.getInstitutionId(),
 | 
				
			||||||
 | 
					                                configRec.getId(),
 | 
				
			||||||
 | 
					                                newFirstVersion.getId());
 | 
				
			||||||
 | 
					                        // and copy the follow-up
 | 
				
			||||||
 | 
					                        this.configurationDAOBatchService.copyConfiguration(
 | 
				
			||||||
 | 
					                                configRec.getInstitutionId(),
 | 
				
			||||||
 | 
					                                configRec.getId(),
 | 
				
			||||||
 | 
					                                newNodeRec.getId());
 | 
				
			||||||
 | 
					                        return configRec;
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return newNodeRec;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static Result<ConfigurationNode> toDomainModel(final ConfigurationNodeRecord record) {
 | 
					    static Result<ConfigurationNode> toDomainModel(final ConfigurationNodeRecord record) {
 | 
				
			||||||
        return Result.tryCatch(() -> new ConfigurationNode(
 | 
					        return Result.tryCatch(() -> new ConfigurationNode(
 | 
				
			||||||
                record.getId(),
 | 
					                record.getId(),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,7 @@ import java.util.function.Predicate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.context.event.EventListener;
 | 
					import org.springframework.context.event.EventListener;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.api.APIMessage;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
 | 
					import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
 | 
					import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
 | 
					import ch.ethz.seb.sebserver.gbl.util.Result;
 | 
				
			||||||
| 
						 | 
					@ -29,6 +30,17 @@ public interface ExamSessionService {
 | 
				
			||||||
     * @return the underling ExamDAO service. */
 | 
					     * @return the underling ExamDAO service. */
 | 
				
			||||||
    ExamDAO getExamDAO();
 | 
					    ExamDAO getExamDAO();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Use this to check the consistency of a running Exam.
 | 
				
			||||||
 | 
					     * Current consistency checks are:
 | 
				
			||||||
 | 
					     * - Check if there is at least one Exam supporter attached to the Exam
 | 
				
			||||||
 | 
					     * - Check if there is one default SEB Exam Configuration attached to the Exam
 | 
				
			||||||
 | 
					     * - Check if there is at least one Indicator defined for the monitoring of the Exam
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param examId the identifier of the Exam to check
 | 
				
			||||||
 | 
					     * @return Result of one APIMessage per consistency check if the check failed. An empty Collection of everything is
 | 
				
			||||||
 | 
					     *         okay. */
 | 
				
			||||||
 | 
					    Result<Collection<APIMessage>> checkRunningExamConsystency(Long examId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** Indicates whether an Exam is currently running or not.
 | 
					    /** Indicates whether an Exam is currently running or not.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param examId the PK of the Exam to test
 | 
					     * @param examId the PK of the Exam to test
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.io.OutputStream;
 | 
					import java.io.OutputStream;
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
import java.util.Collection;
 | 
					import java.util.Collection;
 | 
				
			||||||
import java.util.Collections;
 | 
					import java.util.Collections;
 | 
				
			||||||
import java.util.NoSuchElementException;
 | 
					import java.util.NoSuchElementException;
 | 
				
			||||||
| 
						 | 
					@ -25,6 +26,8 @@ import org.springframework.context.event.EventListener;
 | 
				
			||||||
import org.springframework.security.access.AccessDeniedException;
 | 
					import org.springframework.security.access.AccessDeniedException;
 | 
				
			||||||
import org.springframework.stereotype.Service;
 | 
					import org.springframework.stereotype.Service;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.api.APIMessage;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
 | 
					import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
 | 
					import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
 | 
					import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
 | 
				
			||||||
| 
						 | 
					@ -34,6 +37,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
 | 
				
			||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO;
 | 
					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.ExamDAO;
 | 
				
			||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO;
 | 
				
			||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationChangedEvent;
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationChangedEvent;
 | 
				
			||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,6 +49,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
 | 
				
			||||||
    private static final Logger log = LoggerFactory.getLogger(ExamSessionServiceImpl.class);
 | 
					    private static final Logger log = LoggerFactory.getLogger(ExamSessionServiceImpl.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final ClientConnectionDAO clientConnectionDAO;
 | 
					    private final ClientConnectionDAO clientConnectionDAO;
 | 
				
			||||||
 | 
					    private final IndicatorDAO indicatorDAO;
 | 
				
			||||||
    private final ExamSessionCacheService examSessionCacheService;
 | 
					    private final ExamSessionCacheService examSessionCacheService;
 | 
				
			||||||
    private final ExamDAO examDAO;
 | 
					    private final ExamDAO examDAO;
 | 
				
			||||||
    private final ExamConfigurationMapDAO examConfigurationMapDAO;
 | 
					    private final ExamConfigurationMapDAO examConfigurationMapDAO;
 | 
				
			||||||
| 
						 | 
					@ -55,6 +60,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
 | 
				
			||||||
            final ExamDAO examDAO,
 | 
					            final ExamDAO examDAO,
 | 
				
			||||||
            final ExamConfigurationMapDAO examConfigurationMapDAO,
 | 
					            final ExamConfigurationMapDAO examConfigurationMapDAO,
 | 
				
			||||||
            final ClientConnectionDAO clientConnectionDAO,
 | 
					            final ClientConnectionDAO clientConnectionDAO,
 | 
				
			||||||
 | 
					            final IndicatorDAO indicatorDAO,
 | 
				
			||||||
            final CacheManager cacheManager) {
 | 
					            final CacheManager cacheManager) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.examSessionCacheService = examSessionCacheService;
 | 
					        this.examSessionCacheService = examSessionCacheService;
 | 
				
			||||||
| 
						 | 
					@ -62,6 +68,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
 | 
				
			||||||
        this.examConfigurationMapDAO = examConfigurationMapDAO;
 | 
					        this.examConfigurationMapDAO = examConfigurationMapDAO;
 | 
				
			||||||
        this.clientConnectionDAO = clientConnectionDAO;
 | 
					        this.clientConnectionDAO = clientConnectionDAO;
 | 
				
			||||||
        this.cacheManager = cacheManager;
 | 
					        this.cacheManager = cacheManager;
 | 
				
			||||||
 | 
					        this.indicatorDAO = indicatorDAO;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
| 
						 | 
					@ -69,6 +76,40 @@ public class ExamSessionServiceImpl implements ExamSessionService {
 | 
				
			||||||
        return this.examDAO;
 | 
					        return this.examDAO;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public Result<Collection<APIMessage>> checkRunningExamConsystency(final Long examId) {
 | 
				
			||||||
 | 
					        return Result.tryCatch(() -> {
 | 
				
			||||||
 | 
					            final Collection<APIMessage> result = new ArrayList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (isExamRunning(examId)) {
 | 
				
			||||||
 | 
					                final Exam exam = getRunningExam(examId)
 | 
				
			||||||
 | 
					                        .getOrThrow();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // check exam supporter
 | 
				
			||||||
 | 
					                if (exam.getSupporter().isEmpty()) {
 | 
				
			||||||
 | 
					                    result.add(ErrorMessage.EXAM_CONSISTANCY_VALIDATION_SUPPORTER.of(exam.getModelId()));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // check SEB configuration
 | 
				
			||||||
 | 
					                this.examConfigurationMapDAO.getDefaultConfigurationForExam(examId)
 | 
				
			||||||
 | 
					                        .get(t -> {
 | 
				
			||||||
 | 
					                            result.add(ErrorMessage.EXAM_CONSISTANCY_VALIDATION_CONFIG.of(exam.getModelId()));
 | 
				
			||||||
 | 
					                            return null;
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // check indicator exists
 | 
				
			||||||
 | 
					                if (this.indicatorDAO.allForExam(examId)
 | 
				
			||||||
 | 
					                        .getOrThrow()
 | 
				
			||||||
 | 
					                        .isEmpty()) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    result.add(ErrorMessage.EXAM_CONSISTANCY_VALIDATION_INDICATOR.of(exam.getModelId()));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return result;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public boolean isExamRunning(final Long examId) {
 | 
					    public boolean isExamRunning(final Long examId) {
 | 
				
			||||||
        return !getRunningExam(examId).hasError();
 | 
					        return !getRunningExam(examId).hasError();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -62,7 +62,7 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
 | 
				
			||||||
            path = API.ACTIVE_PATH_SEGMENT,
 | 
					            path = API.ACTIVE_PATH_SEGMENT,
 | 
				
			||||||
            method = RequestMethod.GET,
 | 
					            method = RequestMethod.GET,
 | 
				
			||||||
            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
 | 
					            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
 | 
				
			||||||
            produces = MediaType.APPLICATION_JSON_VALUE)
 | 
					            produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
 | 
				
			||||||
    public Page<T> allActive(
 | 
					    public Page<T> allActive(
 | 
				
			||||||
            @RequestParam(
 | 
					            @RequestParam(
 | 
				
			||||||
                    name = Entity.FILTER_ATTR_INSTITUTION,
 | 
					                    name = Entity.FILTER_ATTR_INSTITUTION,
 | 
				
			||||||
| 
						 | 
					@ -90,7 +90,7 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
 | 
				
			||||||
            path = API.INACTIVE_PATH_SEGMENT,
 | 
					            path = API.INACTIVE_PATH_SEGMENT,
 | 
				
			||||||
            method = RequestMethod.GET,
 | 
					            method = RequestMethod.GET,
 | 
				
			||||||
            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
 | 
					            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
 | 
				
			||||||
            produces = MediaType.APPLICATION_JSON_VALUE)
 | 
					            produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
 | 
				
			||||||
    public Page<T> allInactive(
 | 
					    public Page<T> allInactive(
 | 
				
			||||||
            @RequestParam(
 | 
					            @RequestParam(
 | 
				
			||||||
                    name = Entity.FILTER_ATTR_INSTITUTION,
 | 
					                    name = Entity.FILTER_ATTR_INSTITUTION,
 | 
				
			||||||
| 
						 | 
					@ -118,7 +118,7 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
 | 
				
			||||||
            path = API.PATH_VAR_ACTIVE,
 | 
					            path = API.PATH_VAR_ACTIVE,
 | 
				
			||||||
            method = RequestMethod.POST,
 | 
					            method = RequestMethod.POST,
 | 
				
			||||||
            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
 | 
					            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
 | 
				
			||||||
            produces = MediaType.APPLICATION_JSON_VALUE)
 | 
					            produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
 | 
				
			||||||
    public EntityProcessingReport activate(@PathVariable final String modelId) {
 | 
					    public EntityProcessingReport activate(@PathVariable final String modelId) {
 | 
				
			||||||
        return setActive(modelId, true)
 | 
					        return setActive(modelId, true)
 | 
				
			||||||
                .getOrThrow();
 | 
					                .getOrThrow();
 | 
				
			||||||
| 
						 | 
					@ -128,7 +128,7 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
 | 
				
			||||||
            value = API.PATH_VAR_INACTIVE,
 | 
					            value = API.PATH_VAR_INACTIVE,
 | 
				
			||||||
            method = RequestMethod.POST,
 | 
					            method = RequestMethod.POST,
 | 
				
			||||||
            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
 | 
					            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
 | 
				
			||||||
            produces = MediaType.APPLICATION_JSON_VALUE)
 | 
					            produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
 | 
				
			||||||
    public EntityProcessingReport deactivate(@PathVariable final String modelId) {
 | 
					    public EntityProcessingReport deactivate(@PathVariable final String modelId) {
 | 
				
			||||||
        return setActive(modelId, false)
 | 
					        return setActive(modelId, false)
 | 
				
			||||||
                .getOrThrow();
 | 
					                .getOrThrow();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,6 +22,8 @@ import org.springframework.web.bind.annotation.RestController;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.Constants;
 | 
					import ch.ethz.seb.sebserver.gbl.Constants;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.API;
 | 
					import ch.ethz.seb.sebserver.gbl.api.API;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
 | 
					import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.api.APIMessage;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
 | 
					import ch.ethz.seb.sebserver.gbl.model.EntityKey;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
 | 
					import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
 | 
				
			||||||
| 
						 | 
					@ -157,7 +159,9 @@ public class ConfigurationController extends ReadonlyEntityController<Configurat
 | 
				
			||||||
                .count();
 | 
					                .count();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (activeConnections > 0) {
 | 
					        if (activeConnections > 0) {
 | 
				
			||||||
            throw new IllegalStateException("Integrity violation: There are currently active SEB Client connection.");
 | 
					            throw new APIMessage.APIMessageException(
 | 
				
			||||||
 | 
					                    ErrorMessage.INTEGRITY_VALIDATION,
 | 
				
			||||||
 | 
					                    "Integrity violation: There are currently active SEB Client connection.");
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            return Result.of(config);
 | 
					            return Result.of(config);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,9 +17,9 @@ import java.util.List;
 | 
				
			||||||
import javax.servlet.ServletOutputStream;
 | 
					import javax.servlet.ServletOutputStream;
 | 
				
			||||||
import javax.servlet.http.HttpServletRequest;
 | 
					import javax.servlet.http.HttpServletRequest;
 | 
				
			||||||
import javax.servlet.http.HttpServletResponse;
 | 
					import javax.servlet.http.HttpServletResponse;
 | 
				
			||||||
 | 
					import javax.validation.Valid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.apache.commons.io.IOUtils;
 | 
					import org.apache.commons.io.IOUtils;
 | 
				
			||||||
import org.apache.commons.lang3.BooleanUtils;
 | 
					 | 
				
			||||||
import org.mybatis.dynamic.sql.SqlTable;
 | 
					import org.mybatis.dynamic.sql.SqlTable;
 | 
				
			||||||
import org.slf4j.Logger;
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
import org.slf4j.LoggerFactory;
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
| 
						 | 
					@ -28,6 +28,7 @@ import org.springframework.http.MediaType;
 | 
				
			||||||
import org.springframework.http.ResponseEntity;
 | 
					import org.springframework.http.ResponseEntity;
 | 
				
			||||||
import org.springframework.util.MultiValueMap;
 | 
					import org.springframework.util.MultiValueMap;
 | 
				
			||||||
import org.springframework.web.bind.annotation.PathVariable;
 | 
					import org.springframework.web.bind.annotation.PathVariable;
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RequestBody;
 | 
				
			||||||
import org.springframework.web.bind.annotation.RequestHeader;
 | 
					import org.springframework.web.bind.annotation.RequestHeader;
 | 
				
			||||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
					import org.springframework.web.bind.annotation.RequestMapping;
 | 
				
			||||||
import org.springframework.web.bind.annotation.RequestMethod;
 | 
					import org.springframework.web.bind.annotation.RequestMethod;
 | 
				
			||||||
| 
						 | 
					@ -40,6 +41,7 @@ import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
 | 
					import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM;
 | 
					import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.Page;
 | 
					import ch.ethz.seb.sebserver.gbl.model.Page;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigCopyInfo;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigKey;
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigKey;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
 | 
				
			||||||
| 
						 | 
					@ -138,20 +140,18 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @RequestMapping(
 | 
					    @RequestMapping(
 | 
				
			||||||
            path = API.MODEL_ID_VAR_PATH_SEGMENT + API.CONFIGURATION_COPY_PATH_SEGMENT,
 | 
					            path = API.CONFIGURATION_COPY_PATH_SEGMENT,
 | 
				
			||||||
            method = RequestMethod.POST,
 | 
					            method = RequestMethod.PUT,
 | 
				
			||||||
            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
 | 
					            consumes = MediaType.APPLICATION_JSON_UTF8_VALUE,
 | 
				
			||||||
            produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
 | 
					            produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
 | 
				
			||||||
    public ConfigurationNode copyConfiguration(
 | 
					    public ConfigurationNode copyConfiguration(
 | 
				
			||||||
            @PathVariable final Long modelId,
 | 
					 | 
				
			||||||
            @RequestParam(
 | 
					            @RequestParam(
 | 
				
			||||||
                    name = API.PARAM_INSTITUTION_ID,
 | 
					                    name = API.PARAM_INSTITUTION_ID,
 | 
				
			||||||
                    required = true,
 | 
					                    required = true,
 | 
				
			||||||
                    defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
 | 
					                    defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
 | 
				
			||||||
            @RequestParam(name = ConfigurationNode.ATTR_COPY_WITH_HISTORY,
 | 
					            @Valid @RequestBody final ConfigCopyInfo copyInfo) {
 | 
				
			||||||
                    required = false) final Boolean withHistory) {
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.entityDAO.byPK(modelId)
 | 
					        this.entityDAO.byPK(copyInfo.configurationNodeId)
 | 
				
			||||||
                .flatMap(this.authorization::checkWrite);
 | 
					                .flatMap(this.authorization::checkWrite);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        final SEBServerUser currentUser = this.authorization
 | 
					        final SEBServerUser currentUser = this.authorization
 | 
				
			||||||
| 
						 | 
					@ -161,8 +161,7 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
 | 
				
			||||||
        return this.configurationNodeDAO.createCopy(
 | 
					        return this.configurationNodeDAO.createCopy(
 | 
				
			||||||
                institutionId,
 | 
					                institutionId,
 | 
				
			||||||
                currentUser.getUserInfo().uuid,
 | 
					                currentUser.getUserInfo().uuid,
 | 
				
			||||||
                modelId,
 | 
					                copyInfo)
 | 
				
			||||||
                BooleanUtils.toBoolean(withHistory))
 | 
					 | 
				
			||||||
                .getOrThrow();
 | 
					                .getOrThrow();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.Collection;
 | 
				
			||||||
import java.util.Collections;
 | 
					import java.util.Collections;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Set;
 | 
					import java.util.Set;
 | 
				
			||||||
| 
						 | 
					@ -59,6 +60,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
 | 
				
			||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
 | 
				
			||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
 | 
				
			||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebExamConfigService;
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebExamConfigService;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
 | 
				
			||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@WebServiceProfile
 | 
					@WebServiceProfile
 | 
				
			||||||
| 
						 | 
					@ -72,6 +74,7 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
 | 
				
			||||||
    private final UserDAO userDAO;
 | 
					    private final UserDAO userDAO;
 | 
				
			||||||
    private final LmsAPIService lmsAPIService;
 | 
					    private final LmsAPIService lmsAPIService;
 | 
				
			||||||
    private final SebExamConfigService sebExamConfigService;
 | 
					    private final SebExamConfigService sebExamConfigService;
 | 
				
			||||||
 | 
					    private final ExamSessionService examSessionService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public ExamAdministrationController(
 | 
					    public ExamAdministrationController(
 | 
				
			||||||
            final AuthorizationService authorization,
 | 
					            final AuthorizationService authorization,
 | 
				
			||||||
| 
						 | 
					@ -82,7 +85,8 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
 | 
				
			||||||
            final BeanValidationService beanValidationService,
 | 
					            final BeanValidationService beanValidationService,
 | 
				
			||||||
            final LmsAPIService lmsAPIService,
 | 
					            final LmsAPIService lmsAPIService,
 | 
				
			||||||
            final UserDAO userDAO,
 | 
					            final UserDAO userDAO,
 | 
				
			||||||
            final SebExamConfigService sebExamConfigService) {
 | 
					            final SebExamConfigService sebExamConfigService,
 | 
				
			||||||
 | 
					            final ExamSessionService examSessionService) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        super(authorization,
 | 
					        super(authorization,
 | 
				
			||||||
                bulkActionService,
 | 
					                bulkActionService,
 | 
				
			||||||
| 
						 | 
					@ -95,6 +99,7 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
 | 
				
			||||||
        this.userDAO = userDAO;
 | 
					        this.userDAO = userDAO;
 | 
				
			||||||
        this.lmsAPIService = lmsAPIService;
 | 
					        this.lmsAPIService = lmsAPIService;
 | 
				
			||||||
        this.sebExamConfigService = sebExamConfigService;
 | 
					        this.sebExamConfigService = sebExamConfigService;
 | 
				
			||||||
 | 
					        this.examSessionService = examSessionService;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
| 
						 | 
					@ -105,7 +110,7 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
 | 
				
			||||||
    @RequestMapping(
 | 
					    @RequestMapping(
 | 
				
			||||||
            method = RequestMethod.GET,
 | 
					            method = RequestMethod.GET,
 | 
				
			||||||
            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
 | 
					            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
 | 
				
			||||||
            produces = MediaType.APPLICATION_JSON_VALUE)
 | 
					            produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public Page<Exam> getPage(
 | 
					    public Page<Exam> getPage(
 | 
				
			||||||
            @RequestParam(
 | 
					            @RequestParam(
 | 
				
			||||||
| 
						 | 
					@ -187,6 +192,17 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @RequestMapping(
 | 
				
			||||||
 | 
					            path = API.MODEL_ID_VAR_PATH_SEGMENT
 | 
				
			||||||
 | 
					                    + API.EXAM_ADMINISTRATION_CONSISTENCY_CHECK_PATH_SEGMENT,
 | 
				
			||||||
 | 
					            method = RequestMethod.GET,
 | 
				
			||||||
 | 
					            produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
 | 
				
			||||||
 | 
					    public Collection<APIMessage> checkExamConsistency(@PathVariable final Long modelId) {
 | 
				
			||||||
 | 
					        return this.examSessionService
 | 
				
			||||||
 | 
					                .checkRunningExamConsystency(modelId)
 | 
				
			||||||
 | 
					                .getOrThrow();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static Page<Exam> buildSortedExamPage(
 | 
					    public static Page<Exam> buildSortedExamPage(
 | 
				
			||||||
            final Integer pageNumber,
 | 
					            final Integer pageNumber,
 | 
				
			||||||
            final Integer pageSize,
 | 
					            final Integer pageSize,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,11 +16,14 @@ import org.springframework.web.bind.annotation.RestController;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.API;
 | 
					import ch.ethz.seb.sebserver.gbl.api.API;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
 | 
					import ch.ethz.seb.sebserver.gbl.api.APIMessage;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
 | 
					import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
 | 
					import ch.ethz.seb.sebserver.gbl.api.EntityType;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
 | 
					import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
 | 
					import ch.ethz.seb.sebserver.gbl.model.Domain;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
 | 
					import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
 | 
					import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange;
 | 
					import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
 | 
					import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
 | 
					import ch.ethz.seb.sebserver.gbl.util.Result;
 | 
				
			||||||
| 
						 | 
					@ -28,6 +31,7 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamConfiguration
 | 
				
			||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
 | 
				
			||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
 | 
				
			||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO;
 | 
				
			||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.EntityDAO;
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.dao.EntityDAO;
 | 
				
			||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
 | 
				
			||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
 | 
				
			||||||
| 
						 | 
					@ -39,6 +43,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
 | 
				
			||||||
public class ExamConfigurationMappingController extends EntityController<ExamConfigurationMap, ExamConfigurationMap> {
 | 
					public class ExamConfigurationMappingController extends EntityController<ExamConfigurationMap, ExamConfigurationMap> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final ExamDAO examDao;
 | 
					    private final ExamDAO examDao;
 | 
				
			||||||
 | 
					    private final ConfigurationNodeDAO configurationNodeDAO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected ExamConfigurationMappingController(
 | 
					    protected ExamConfigurationMappingController(
 | 
				
			||||||
            final AuthorizationService authorization,
 | 
					            final AuthorizationService authorization,
 | 
				
			||||||
| 
						 | 
					@ -47,7 +52,8 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
 | 
				
			||||||
            final UserActivityLogDAO userActivityLogDAO,
 | 
					            final UserActivityLogDAO userActivityLogDAO,
 | 
				
			||||||
            final PaginationService paginationService,
 | 
					            final PaginationService paginationService,
 | 
				
			||||||
            final BeanValidationService beanValidationService,
 | 
					            final BeanValidationService beanValidationService,
 | 
				
			||||||
            final ExamDAO examDao) {
 | 
					            final ExamDAO examDao,
 | 
				
			||||||
 | 
					            final ConfigurationNodeDAO configurationNodeDAO) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        super(
 | 
					        super(
 | 
				
			||||||
                authorization,
 | 
					                authorization,
 | 
				
			||||||
| 
						 | 
					@ -58,6 +64,7 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
 | 
				
			||||||
                beanValidationService);
 | 
					                beanValidationService);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.examDao = examDao;
 | 
					        this.examDao = examDao;
 | 
				
			||||||
 | 
					        this.configurationNodeDAO = configurationNodeDAO;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
| 
						 | 
					@ -97,6 +104,7 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    protected Result<ExamConfigurationMap> validForCreate(final ExamConfigurationMap entity) {
 | 
					    protected Result<ExamConfigurationMap> validForCreate(final ExamConfigurationMap entity) {
 | 
				
			||||||
        return super.validForCreate(entity)
 | 
					        return super.validForCreate(entity)
 | 
				
			||||||
 | 
					                .map(this::checkConfigurationState)
 | 
				
			||||||
                .map(this::checkPasswordMatch);
 | 
					                .map(this::checkPasswordMatch);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -106,6 +114,21 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
 | 
				
			||||||
                .map(this::checkPasswordMatch);
 | 
					                .map(this::checkPasswordMatch);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    protected Result<ExamConfigurationMap> notifyCreated(final ExamConfigurationMap entity) {
 | 
				
			||||||
 | 
					        // update the attached configurations state to "In Use"
 | 
				
			||||||
 | 
					        return this.configurationNodeDAO.save(new ConfigurationNode(
 | 
				
			||||||
 | 
					                entity.configurationNodeId,
 | 
				
			||||||
 | 
					                entity.institutionId,
 | 
				
			||||||
 | 
					                null,
 | 
				
			||||||
 | 
					                null,
 | 
				
			||||||
 | 
					                null,
 | 
				
			||||||
 | 
					                null,
 | 
				
			||||||
 | 
					                null,
 | 
				
			||||||
 | 
					                ConfigurationStatus.IN_USE))
 | 
				
			||||||
 | 
					                .map(config -> entity);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private ExamConfigurationMap checkPasswordMatch(final ExamConfigurationMap entity) {
 | 
					    private ExamConfigurationMap checkPasswordMatch(final ExamConfigurationMap entity) {
 | 
				
			||||||
        if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.confirmEncryptSecret)) {
 | 
					        if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.confirmEncryptSecret)) {
 | 
				
			||||||
            throw new APIMessageException(APIMessage.fieldValidationError(
 | 
					            throw new APIMessageException(APIMessage.fieldValidationError(
 | 
				
			||||||
| 
						 | 
					@ -118,4 +141,22 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
 | 
				
			||||||
        return entity;
 | 
					        return entity;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private ExamConfigurationMap checkConfigurationState(final ExamConfigurationMap entity) {
 | 
				
			||||||
 | 
					        final ConfigurationStatus status;
 | 
				
			||||||
 | 
					        if (entity.getConfigStatus() != null) {
 | 
				
			||||||
 | 
					            status = entity.getConfigStatus();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            status = this.configurationNodeDAO.byPK(entity.configurationNodeId)
 | 
				
			||||||
 | 
					                    .getOrThrow()
 | 
				
			||||||
 | 
					                    .getStatus();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (status != ConfigurationStatus.READY_TO_USE) {
 | 
				
			||||||
 | 
					            throw new APIMessageException(ErrorMessage.INTEGRITY_VALIDATION.of(
 | 
				
			||||||
 | 
					                    "Illegal SEB Exam Configuration state"));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return entity;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -130,8 +130,8 @@ public class UserAccountController extends ActivatableEntityController<UserInfo,
 | 
				
			||||||
    @RequestMapping(
 | 
					    @RequestMapping(
 | 
				
			||||||
            path = API.PASSWORD_PATH_SEGMENT,
 | 
					            path = API.PASSWORD_PATH_SEGMENT,
 | 
				
			||||||
            method = RequestMethod.PUT,
 | 
					            method = RequestMethod.PUT,
 | 
				
			||||||
            consumes = MediaType.APPLICATION_JSON_VALUE,
 | 
					            consumes = MediaType.APPLICATION_JSON_UTF8_VALUE,
 | 
				
			||||||
            produces = MediaType.APPLICATION_JSON_VALUE)
 | 
					            produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
 | 
				
			||||||
    public UserInfo changePassword(@Valid @RequestBody final PasswordChange passwordChange) {
 | 
					    public UserInfo changePassword(@Valid @RequestBody final PasswordChange passwordChange) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        final String modelId = passwordChange.getModelId();
 | 
					        final String modelId = passwordChange.getModelId();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -288,6 +288,11 @@ sebserver.exam.list.empty=No Exam has been found. Please adapt the filter or imp
 | 
				
			||||||
sebserver.exam.list.modify.out.dated=Finished exams cannot be modified.
 | 
					sebserver.exam.list.modify.out.dated=Finished exams cannot be modified.
 | 
				
			||||||
sebserver.exam.list.action.no.modify.privilege=No Access: An Exam from other institution cannot be modified.
 | 
					sebserver.exam.list.action.no.modify.privilege=No Access: An Exam from other institution cannot be modified.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					sebserver.exam.consistency.title=Note: This exam is already running but has some missing configurations
 | 
				
			||||||
 | 
					sebserver.exam.consistency.missing-supporter= - There currently are no Exam-Supporter defined for this exam. Edit the exam to add an Exam-Supporter
 | 
				
			||||||
 | 
					sebserver.exam.consistency.missing-config= - There is currently no exam-configuration defined for this exam. Use 'Add Configuration' to attach one
 | 
				
			||||||
 | 
					sebserver.exam.confirm.remove-config=This exam is current running. The remove of the attached configuration will led to an invalid state<br/>where connecting SEB clients cannot download the configuration for the exam.<br/><br/>Are you sure to remove the Configuration?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sebserver.exam.action.list=Exam
 | 
					sebserver.exam.action.list=Exam
 | 
				
			||||||
sebserver.exam.action.list.view=View Exam
 | 
					sebserver.exam.action.list.view=View Exam
 | 
				
			||||||
sebserver.exam.action.list.modify=Edit Exam
 | 
					sebserver.exam.action.list.modify=Edit Exam
 | 
				
			||||||
| 
						 | 
					@ -451,6 +456,7 @@ sebserver.examconfig.action.import-config=Import Configuration
 | 
				
			||||||
sebserver.examconfig.action.import-file-select=Import From File
 | 
					sebserver.examconfig.action.import-file-select=Import From File
 | 
				
			||||||
sebserver.examconfig.action.import-file-password=Password
 | 
					sebserver.examconfig.action.import-file-password=Password
 | 
				
			||||||
sebserver.examconfig.action.import-config.confirm=Configuration successfully imported
 | 
					sebserver.examconfig.action.import-config.confirm=Configuration successfully imported
 | 
				
			||||||
 | 
					sebserver.examconfig.action.state-change.confirm=This configuration is already attached to an exam.<br/>Please note that changing an attached configuration will take effect on the exam when the configuration changes are saved<br/><br/>Are you sure to change this configuration to an editable state?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sebserver.examconfig.form.title.new=Add Exam Configuration
 | 
					sebserver.examconfig.form.title.new=Add Exam Configuration
 | 
				
			||||||
sebserver.examconfig.form.title=Exam Configuration
 | 
					sebserver.examconfig.form.title=Exam Configuration
 | 
				
			||||||
| 
						 | 
					@ -937,7 +943,7 @@ sebserver.configtemplate.list.actions=Selected Template
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sebserver.configtemplate.info.pleaseSelect=Please select an exam configuration template first
 | 
					sebserver.configtemplate.info.pleaseSelect=Please select an exam configuration template first
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sebserver.exam.configtemplate.action.list.new=Add Template
 | 
					sebserver.configtemplate.action.list.new=Add Template
 | 
				
			||||||
sebserver.configtemplate.action.list.view=View Template
 | 
					sebserver.configtemplate.action.list.view=View Template
 | 
				
			||||||
sebserver.configtemplate.action.view=View Template
 | 
					sebserver.configtemplate.action.view=View Template
 | 
				
			||||||
sebserver.configtemplate.action.list.modify=Edit Template
 | 
					sebserver.configtemplate.action.list.modify=Edit Template
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -170,6 +170,14 @@ Composite.error {
 | 
				
			||||||
    border-radius: 1px;
 | 
					    border-radius: 1px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Composite.warning {
 | 
				
			||||||
 | 
					    background-gradient-color: rgba( 168, 50, 45, 0.5 );
 | 
				
			||||||
 | 
					    background-image: gradient( linear, left top, left bottom, from(rgba( 168, 50, 45, 0.5 ) ), to( rgba( 168, 50, 45, 0.5 ) ) );
 | 
				
			||||||
 | 
					    background-repeat: repeat;
 | 
				
			||||||
 | 
					    background-position: left top;
 | 
				
			||||||
 | 
					    opacity: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
*.header {
 | 
					*.header {
 | 
				
			||||||
    font: bold 12px Arial, Helvetica, sans-serif;
 | 
					    font: bold 12px Arial, Helvetica, sans-serif;
 | 
				
			||||||
    color: #FFFFFF;
 | 
					    color: #FFFFFF;
 | 
				
			||||||
| 
						 | 
					@ -740,7 +748,7 @@ TableColumn:hover {
 | 
				
			||||||
    background-image: gradient( linear, left top, left bottom, from( #595959 ), to( #595959 ) );
 | 
					    background-image: gradient( linear, left top, left bottom, from( #595959 ), to( #595959 ) );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TableItem, TableItem:linesvisible:even:rowtemplate {
 | 
					TableItem {
 | 
				
			||||||
    background-color: transparent;
 | 
					    background-color: transparent;
 | 
				
			||||||
    color: inherit;
 | 
					    color: inherit;
 | 
				
			||||||
    text-decoration: none;
 | 
					    text-decoration: none;
 | 
				
			||||||
| 
						 | 
					@ -748,6 +756,12 @@ TableItem, TableItem:linesvisible:even:rowtemplate {
 | 
				
			||||||
    background-image: none;
 | 
					    background-image: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Table-RowOverlay.warning {
 | 
				
			||||||
 | 
					    background-color: rgba( 168, 50, 45, 0.5 );
 | 
				
			||||||
 | 
					    background-gradient-color: rgba( 168, 50, 45, 0.5 );
 | 
				
			||||||
 | 
					    background-image: gradient( linear, left top, left bottom, from(rgba( 168, 50, 45, 0.5 ) ), to( rgba( 168, 50, 45, 0.5 ) ) );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TableItem:linesvisible:even {
 | 
					TableItem:linesvisible:even {
 | 
				
			||||||
    background-color: #ffffff;
 | 
					    background-color: #ffffff;
 | 
				
			||||||
    color: inherit;
 | 
					    color: inherit;
 | 
				
			||||||
| 
						 | 
					@ -759,6 +773,7 @@ Table-RowOverlay {
 | 
				
			||||||
    background-image: none;
 | 
					    background-image: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Table-RowOverlay:hover {
 | 
					Table-RowOverlay:hover {
 | 
				
			||||||
    color: #4a4a4a;
 | 
					    color: #4a4a4a;
 | 
				
			||||||
    background-color: #b5b5b5;
 | 
					    background-color: #b5b5b5;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue