SEBSERV-46 fixed some stuff in back-end, more logging

This commit is contained in:
anhefti 2019-10-08 11:08:33 +02:00
parent 526b97d47b
commit 4d9f4faf09
17 changed files with 146 additions and 57 deletions

View file

@ -67,6 +67,7 @@ public final class Constants {
public static final String XML_PLIST_BOOLEAN_TRUE = "true";
public static final String XML_PLIST_BOOLEAN_FALSE = "false";
public static final String XML_PLIST_STRING = "string";
public static final String XML_PLIST_DATA = "data";
public static final String XML_PLIST_INTEGER = "integer";
public static final String OAUTH2_GRANT_TYPE_PASSWORD = "password";

View file

@ -121,6 +121,8 @@ public final class API {
public static final String CONFIGURATION_ATTRIBUTE_ENDPOINT = "/configuration_attribute";
public static final String CONFIGURATION_PLAIN_XML_DOWNLOAD_PATH_SEGMENT = "/downloadxml";
public static final String CONFIGURATION_IMPORT_PATH_SEGMENT = "/import";
public static final String IMPORT_PASSWORD_ATTR_NAME = "importFilePassword";
public static final String IMPORT_FILE_ATTR_NAME = "importFile";
public static final String ORIENTATION_ENDPOINT = "/orientation";
public static final String VIEW_ENDPOINT = ORIENTATION_ENDPOINT + "/view";

View file

@ -18,6 +18,7 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.util.HtmlUtils;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
@ -189,18 +190,19 @@ public class APIMessage implements Serializable {
public static String toHTML(final Collection<APIMessage> messages) {
final StringBuilder builder = new StringBuilder();
builder.append("<b>Messages:</b><br/><br/>");
messages.stream().forEach(message -> {
builder
.append("&nbsp;&nbsp;code&nbsp;:&nbsp;")
.append(message.messageCode)
.append("<br/>")
.append("&nbsp;&nbsp;system message&nbsp;:&nbsp;")
.append(message.systemMessage)
.append("<br/>")
.append("&nbsp;&nbsp;details&nbsp;:&nbsp;")
.append(StringUtils.abbreviate(message.details, 100))
.append("<br/><br/>");
});
messages.stream()
.forEach(message -> {
builder
.append("&nbsp;&nbsp;code&nbsp;:&nbsp;")
.append(message.messageCode)
.append("<br/>")
.append("&nbsp;&nbsp;system message&nbsp;:&nbsp;")
.append(HtmlUtils.htmlEscape(message.systemMessage))
.append("<br/>")
.append("&nbsp;&nbsp;details&nbsp;:&nbsp;")
.append(HtmlUtils.htmlEscape(StringUtils.abbreviate(message.details, 100)))
.append("<br/><br/>");
});
return builder.toString();
}

View file

@ -18,10 +18,6 @@ public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(AsyncExceptionHandler.class);
public AsyncExceptionHandler() {
// TODO Auto-generated constructor stub
}
@Override
public void handleUncaughtException(final Throwable ex, final Method method, final Object... params) {
log.error("Unexpected error while async processing. method: {}", method, ex);

View file

@ -28,6 +28,7 @@ import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
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.ConfigurationType;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
@ -66,8 +67,6 @@ public class SebExamConfigPropForm implements TemplateComposer {
private static final Logger log = LoggerFactory.getLogger(SebExamConfigPropForm.class);
private static final String PASSWORD_ATTR_NAME = "importFilePassword";
private static final String IMPORT_FILE_ATTR_NAME = "importFile";
private static final LocTextKey FORM_TITLE_NEW =
new LocTextKey("sebserver.examconfig.form.title.new");
private static final LocTextKey FORM_TITLE =
@ -86,6 +85,8 @@ public class SebExamConfigPropForm implements TemplateComposer {
new LocTextKey("sebserver.examconfig.action.import-file-password");
private static final LocTextKey CONFIG_KEY_TITLE_TEXT_KEY =
new LocTextKey("sebserver.examconfig.form.config-key.title");
private static final LocTextKey FORM_IMPORT_CONFIRM_TEXT_KEY =
new LocTextKey("sebserver.examconfig.action.import-config.confirm");
private final PageService pageService;
private final RestService restService;
@ -298,16 +299,21 @@ public class SebExamConfigPropForm implements TemplateComposer {
final Form form = formHandle.getForm();
final EntityKey entityKey = formHandle.getContext().getEntityKey();
final Control fieldControl = form.getFieldControl(IMPORT_FILE_ATTR_NAME);
final Control fieldControl = form.getFieldControl(API.IMPORT_FILE_ATTR_NAME);
final PageContext context = formHandle.getContext();
if (fieldControl != null && fieldControl instanceof FileUploadSelection) {
final InputStream inputStream = ((FileUploadSelection) fieldControl).getInputStream();
if (inputStream != null) {
pageService.getRestService()
final Configuration configuration = pageService.getRestService()
.getBuilder(ImportExamConfig.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.withBody(inputStream)
.call()
.getOrThrow();
.get(context::notifyError);
if (configuration != null) {
context.publishInfo(FORM_IMPORT_CONFIRM_TEXT_KEY);
}
} else {
formHandle.getContext().publishPageMessage(
new LocTextKey("sebserver.error.unexpected"),
@ -333,12 +339,12 @@ public class SebExamConfigPropForm implements TemplateComposer {
this.pageContext.copyOf(parent), 4)
.readonly(false)
.addField(FormBuilder.fileUpload(
IMPORT_FILE_ATTR_NAME,
API.IMPORT_FILE_ATTR_NAME,
FORM_IMPORT_SELECT_TEXT_KEY,
null,
API.SEB_FILE_EXTENSION))
.addField(FormBuilder.text(
PASSWORD_ATTR_NAME,
API.IMPORT_PASSWORD_ATTR_NAME,
FORM_IMPORT_PASSWORD_TEXT_KEY,
"").asPasswordField())
.build();

View file

@ -19,6 +19,7 @@ import java.util.function.Function;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@ -334,7 +335,7 @@ public abstract class RestCall<T> {
public HttpEntity<?> buildRequestEntity() {
if (this.streamingBody != null) {
return new HttpEntity<>(this.streamingBody, this.httpHeaders);
return new HttpEntity<>(new InputStreamResource(this.streamingBody), this.httpHeaders);
} else if (this.body != null) {
return new HttpEntity<>(this.body, this.httpHeaders);
} else {

View file

@ -33,7 +33,7 @@ public class ImportExamConfig extends RestCall<Configuration> {
new TypeReference<Configuration>() {
}),
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
MediaType.APPLICATION_OCTET_STREAM,
API.CONFIGURATION_NODE_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.CONFIGURATION_IMPORT_PATH_SEGMENT);

View file

@ -10,11 +10,14 @@ package ch.ethz.seb.sebserver.gui.widget;
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;
import org.apache.commons.io.IOUtils;
import org.eclipse.rap.fileupload.FileDetails;
import org.eclipse.rap.fileupload.FileUploadHandler;
import org.eclipse.rap.fileupload.FileUploadReceiver;
@ -77,6 +80,11 @@ public class FileUploadSelection extends Composite {
this.fileUpload.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
this.fileUpload.setToolTipText(this.i18nSupport.getText(PLEASE_SELECT_TEXT));
final FileUploadHandler uploadHandler = new FileUploadHandler(new InputReceiver());
this.fileName = new Label(this, SWT.NONE);
this.fileName.setText(i18nSupport.getText(PLEASE_SELECT_TEXT));
this.fileName.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
this.fileUpload.addListener(SWT.Selection, event -> {
final String fileName = FileUploadSelection.this.fileUpload.getFileName();
if (fileName == null || !fileSupported(fileName)) {
@ -90,11 +98,10 @@ public class FileUploadSelection extends Composite {
return;
}
FileUploadSelection.this.fileUpload.submit(uploadHandler.getUploadUrl());
FileUploadSelection.this.fileName.setText(fileName);
FileUploadSelection.this.errorHandler.accept(null);
});
this.fileName = new Label(this, SWT.NONE);
this.fileName.setText(i18nSupport.getText(PLEASE_SELECT_TEXT));
this.fileName.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false));
}
}
@ -148,7 +155,18 @@ public class FileUploadSelection extends Composite {
private final class InputReceiver extends FileUploadReceiver {
@Override
public void receive(final InputStream stream, final FileDetails details) throws IOException {
FileUploadSelection.this.inputStream = stream;
final PipedInputStream pIn = new PipedInputStream();
final PipedOutputStream pOut = new PipedOutputStream(pIn);
FileUploadSelection.this.inputStream = pIn;
try {
IOUtils.copyLarge(stream, pOut);
} catch (final Exception e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(pOut);
}
}
}

View file

@ -19,7 +19,12 @@ public final class Message extends MessageBox {
private static final int NORMAL_WIDTH = 400;
private static final long serialVersionUID = 6973272221493264432L;
public Message(final Shell parent, final String title, final String message, final int type) {
public Message(
final Shell parent,
final String title,
final String message,
final int type) {
super(parent, type);
super.setText(title);
super.setMessage(message);

View file

@ -234,7 +234,7 @@ class ConfigurationDAOBatchService {
.forEach(newValRec -> this.batchConfigurationValueRecordMapper.insert(newValRec));
return this.batchConfigurationRecordMapper
.selectByPrimaryKey(configUpdate.getId());
.selectByPrimaryKey(newFollowup.getId());
})
.flatMap(ConfigurationDAOImpl::toDomainModel);

View file

@ -24,6 +24,8 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
import org.mybatis.dynamic.sql.SqlBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@ -53,6 +55,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
@WebServiceProfile
public class ConfigurationValueDAOImpl implements ConfigurationValueDAO {
private static final Logger log = LoggerFactory.getLogger(ConfigurationValueDAOImpl.class);
private final ConfigurationValueRecordMapper configurationValueRecordMapper;
private final ConfigurationAttributeRecordMapper configurationAttributeRecordMapper;
private final ConfigurationRecordMapper configurationRecordMapper;
@ -192,9 +196,9 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO {
id = getByProperties(data)
.orElseGet(() -> {
log.warn("Missing SEB exam configuration attrribute value for: {}", data);
log.info("Use self-healing strategy to recover from missing SEB exam configuration "
+ "attrribute value\n**** Create new AttributeValue for: {}",
log.debug("Missing SEB exam configuration attrribute value for: {}", data);
log.debug("Use self-healing strategy to recover from missing SEB exam "
+ "configuration attrribute value\n**** Create new AttributeValue for: {}",
data);
createNew(data);

View file

@ -162,7 +162,7 @@ public class ExamConfigIO {
/** This parses the XML from given InputStream with a SAX parser to avoid keeping the
* whole XML file in memory and keep up with the streaming approach of SEB Exam Configuration
* to avoid trouble with big SEB Exam Configuration in the future.
*
*
* @param in The InputString to constantly read the XML from
* @param institutionId the institionId of the import
* @param configurationId the identifier of the internal configuration to apply the imported values to */
@ -181,7 +181,9 @@ public class ExamConfigIO {
final ExamConfigImportHandler examConfigImportHandler = new ExamConfigImportHandler(
institutionId,
configurationId,
value -> this.configurationValueDAO.save(value),
value -> this.configurationValueDAO
.save(value)
.getOrThrow(),
attributeMap::get);
// SAX parsing

View file

@ -15,6 +15,8 @@ import java.util.Stack;
import java.util.function.Consumer;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
@ -25,10 +27,13 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.ExamConfigIm
public class ExamConfigImportHandler extends DefaultHandler {
private static final Logger log = LoggerFactory.getLogger(ExamConfigImportHandler.class);
private static final Set<String> VALUE_ELEMENTS = new HashSet<>(Arrays.asList(
Constants.XML_PLIST_BOOLEAN_FALSE,
Constants.XML_PLIST_BOOLEAN_TRUE,
Constants.XML_PLIST_STRING,
Constants.XML_PLIST_DATA,
Constants.XML_PLIST_INTEGER));
private final Consumer<ConfigurationValue> valueConsumer;
@ -51,6 +56,16 @@ public class ExamConfigImportHandler extends DefaultHandler {
this.configId = configId;
}
@Override
public void startDocument() throws SAXException {
log.debug("Start parsing document");
}
@Override
public void endDocument() throws SAXException {
log.debug("End parsing document");
}
@Override
public void startElement(
final String uri,
@ -58,6 +73,8 @@ public class ExamConfigImportHandler extends DefaultHandler {
final String qName,
final Attributes attributes) throws SAXException {
log.debug("start element: {}", qName);
final Type type = Type.getType(qName);
final PListNode top = (this.stack.isEmpty()) ? null : this.stack.peek();
@ -77,6 +94,7 @@ public class ExamConfigImportHandler extends DefaultHandler {
case VALUE_BOOLEAN_FALSE:
case VALUE_BOOLEAN_TRUE:
case VALUE_STRING:
case VALUE_DATA:
case VALUE_INTEGER:
startValueElement(type, top);
break;
@ -196,13 +214,30 @@ public class ExamConfigImportHandler extends DefaultHandler {
? parent.name + "." + top.name
: top.name;
this.valueConsumer.accept(new ConfigurationValue(
null,
this.institutionId,
this.configId,
this.attributeNameIdResolver.apply(attrName),
top.listIndex,
top.value));
final Long attributeId = this.attributeNameIdResolver.apply(attrName);
if (attributeId == null) {
if (log.isDebugEnabled()) {
log.debug("Skip unknown configuration attribute: {}", attrName);
}
} else {
// TODO use AttributeValueConverterService here. Extend the converters with fromXML functionality
final ConfigurationValue configurationValue = new ConfigurationValue(
null,
this.institutionId,
this.configId,
attributeId,
top.listIndex,
top.value);
if (log.isDebugEnabled()) {
log.debug("Save imported value: {} : {}", attrName, configurationValue);
}
this.valueConsumer.accept(configurationValue);
}
}
} else if (!Constants.XML_PLIST_KEY_NAME.equals(qName)) {
this.stack.pop();
@ -215,13 +250,16 @@ public class ExamConfigImportHandler extends DefaultHandler {
final int start,
final int length) throws SAXException {
final char[] valueChar = new char[length];
System.arraycopy(ch, start, valueChar, 0, length);
final String value = String.valueOf(valueChar);
final PListNode top = this.stack.peek();
if (top.type == Type.VALUE_STRING) {
top.value = String.valueOf(ch);
top.value = value;
} else if (top.type == Type.VALUE_INTEGER) {
top.value = String.valueOf(ch);
top.value = value;
} else if (top.type == Type.KEY) {
top.name = String.valueOf(ch);
top.name = value;
}
}
@ -235,6 +273,7 @@ public class ExamConfigImportHandler extends DefaultHandler {
VALUE_BOOLEAN_TRUE(true, Constants.XML_PLIST_BOOLEAN_TRUE),
VALUE_BOOLEAN_FALSE(true, Constants.XML_PLIST_BOOLEAN_FALSE),
VALUE_STRING(true, Constants.XML_PLIST_STRING),
VALUE_DATA(true, Constants.XML_PLIST_DATA),
VALUE_INTEGER(true, Constants.XML_PLIST_INTEGER);
private final boolean isValueType;

View file

@ -336,12 +336,19 @@ public class SebExamConfigServiceImpl implements SebExamConfigService {
final byte[] header = new byte[4];
input.read(header);
final Strategy strategy = SebConfigEncryptionService.Strategy.getStrategy(header);
if (strategy == null) {
Strategy strategy = null;
try {
strategy = SebConfigEncryptionService.Strategy.getStrategy(header);
} catch (final IllegalArgumentException iae) {
log.info("{} : Trying to import as unzipped plain text configuration", iae.getMessage());
importPlainOnly(input, newConfig, header);
} else {
return newConfig;
}
if (strategy != null) {
final InputStream cryptIn = this.unzip(input);
final PipedInputStream plainIn = new PipedInputStream();
final PipedOutputStream cryptOut = new PipedOutputStream(plainIn);
@ -369,9 +376,11 @@ public class SebExamConfigServiceImpl implements SebExamConfigService {
} catch (final Exception e) {
log.error("Unexpected error while trying to import SEB Exam Configuration: ", e);
log.debug("Make an undo on the ConfigurationNode to rollback the changes");
return this.configurationDAO
this.configurationDAO
.undo(configNodeId)
.getOrThrow();
throw new RuntimeException("Failed to import SEB configuration. Cause is: " + e.getMessage(), e);
}
});
}
@ -412,16 +421,18 @@ public class SebExamConfigServiceImpl implements SebExamConfigService {
plainIn = new PipedInputStream();
out = new PipedOutputStream(plainIn);
this.examConfigIO.importPlainXML(plainIn, newConfig.institutionId, newConfig.id);
this.examConfigIO.importPlainXML(
plainIn,
newConfig.institutionId,
newConfig.id);
out.write(header);
IOUtils.copyLarge(input, out);
IOUtils.closeQuietly(out);
} catch (final Exception e) {
log.error("Error while stream plain text SEB Configuration import data: ", e);
throw e;
} finally {
IOUtils.closeQuietly(out);
IOUtils.closeQuietly(plainIn);
}
}

View file

@ -164,11 +164,12 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.CONFIGURATION_IMPORT_PATH_SEGMENT,
method = RequestMethod.GET,
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Configuration importExamConfig(
@PathVariable final Long modelId,
@RequestHeader final String password,
@RequestHeader(name = API.IMPORT_PASSWORD_ATTR_NAME, required = false) final String password,
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,

View file

@ -449,6 +449,7 @@ sebserver.examconfig.action.get-config-key=Export Config-Key
sebserver.examconfig.action.import-config=Import Configuration
sebserver.examconfig.action.import-file-select=Import From File
sebserver.examconfig.action.import-file-password=Password
sebserver.examconfig.action.import-config.confirm=Configuration successfully imported
sebserver.examconfig.form.title.new=Add Exam Configuration
sebserver.examconfig.form.title=Exam Configuration

View file

@ -1080,7 +1080,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
assertNotNull(saveHistoryResponse);
assertFalse(saveHistoryResponse.hasError());
Configuration configuration = saveHistoryResponse.get();
assertFalse(configuration.followup);
assertTrue(configuration.followup);
configHistoryResponse = restService
.getBuilder(GetConfigurations.class)