diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java index a36e1277..23360ad3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java @@ -17,6 +17,8 @@ public final class Constants { public static final Character LIST_SEPARATOR_CHAR = ','; public static final String LIST_SEPARATOR = ","; public static final String EMPTY_NOTE = "--"; + public static final String FORM_URL_ENCODED_SEPARATOR = "&"; + public static final String FORM_URL_ENCODED_NAME_VALUE_SEPARATOR = "="; /** Date-Time formatter without milliseconds using UTC time-zone. Pattern is yyyy-MM-dd HH:mm:ss */ public static final DateTimeFormatter DATE_TIME_PATTERN_UTC_NO_MILLIS = DateTimeFormat diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/APIMessage.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/APIMessage.java index b3a91f61..5db80b43 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/APIMessage.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/APIMessage.java @@ -54,6 +54,10 @@ public class APIMessage implements Serializable { this.systemMessage = systemMessage; } + public boolean isOf(final APIMessage message) { + return message != null && this.messageCode.equals(message.messageCode); + } + public APIMessage of() { return new APIMessage(this.messageCode, this.systemMessage); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityKey.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityKey.java index 978c8085..7c3f0482 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityKey.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityKey.java @@ -11,7 +11,6 @@ package ch.ethz.seb.sebserver.gbl.model; import javax.validation.constraints.NotNull; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import ch.ethz.seb.sebserver.gbl.api.EntityType; @@ -24,20 +23,21 @@ public class EntityKey { @JsonProperty(value = "entityType", required = true) @NotNull public final EntityType entityType; - @JsonIgnore - public final boolean isIdPK; @JsonCreator public EntityKey( @JsonProperty(value = "modelId", required = true) final String modelId, @JsonProperty(value = "entityType", required = true) final EntityType entityType) { - assert (modelId != null) : "modelId has null reference"; - assert (entityType != null) : "entityType has null reference"; + if (modelId == null) { + throw new IllegalArgumentException("modelId has null reference"); + } + if (entityType == null) { + throw new IllegalArgumentException("entityType has null reference"); + } this.modelId = modelId; this.entityType = entityType; - this.isIdPK = entityType != EntityType.USER; } public EntityKey( @@ -46,7 +46,6 @@ public class EntityKey { this.modelId = String.valueOf(pk); this.entityType = entityType; - this.isIdPK = true; } public String getModelId() { diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserAccount.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserAccount.java index 2d6432ad..eab6ffcd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserAccount.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserAccount.java @@ -13,12 +13,18 @@ import java.util.Set; import org.joda.time.DateTimeZone; -public interface UserAccount { +import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.GrantEntity; +public interface UserAccount extends GrantEntity { + + @Override String getModelId(); + @Override Long getInstitutionId(); + @Override String getName(); String getUsername(); @@ -39,4 +45,7 @@ public interface UserAccount { String getRetypedNewPassword(); + @Override + EntityKey getEntityKey(); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserInfo.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserInfo.java index 8761e392..af9d124c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserInfo.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserInfo.java @@ -17,6 +17,7 @@ import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTimeZone; import com.fasterxml.jackson.annotation.JsonCreator; @@ -28,8 +29,8 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.model.Activatable; import ch.ethz.seb.sebserver.gbl.model.Domain.USER; import ch.ethz.seb.sebserver.gbl.model.Domain.USER_ROLE; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.util.Utils; -import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.GrantEntity; /** The user info domain model contains primary user information * @@ -37,7 +38,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.GrantEntity; * to and from JSON within the Jackson library. * * This domain model is immutable and thread-save */ -public final class UserInfo implements UserAccount, GrantEntity, Activatable, Serializable { +public final class UserInfo implements UserAccount, Activatable, Serializable { private static final long serialVersionUID = 2526446136264377808L; @@ -197,6 +198,15 @@ public final class UserInfo implements UserAccount, GrantEntity, Activatable, Se return null; } + @JsonIgnore + @Override + public EntityKey getEntityKey() { + if (StringUtils.isBlank(this.uuid)) { + return null; + } + return new EntityKey(this.uuid, entityType()); + } + @Override public int hashCode() { final int prime = 31; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserMod.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserMod.java index b977265c..4178766f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserMod.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserMod.java @@ -16,9 +16,11 @@ import javax.validation.constraints.Email; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTimeZone; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -26,9 +28,9 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.model.Domain.USER; import ch.ethz.seb.sebserver.gbl.model.Domain.USER_ROLE; -import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.GrantEntity; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; -public final class UserMod implements UserAccount, GrantEntity { +public final class UserMod implements UserAccount { public static final String ATTR_NAME_NEW_PASSWORD = "newPassword"; public static final String ATTR_NAME_RETYPED_NEW_PASSWORD = "retypedNewPassword"; @@ -71,10 +73,12 @@ public final class UserMod implements UserAccount, GrantEntity { @JsonProperty(USER_ROLE.REFERENCE_NAME) public final Set roles; + @NotNull(message = "user:newPassword:notNull") @Size(min = 8, max = 255, message = "user:password:size:{min}:{max}:${validatedValue}") @JsonProperty(ATTR_NAME_NEW_PASSWORD) private final String newPassword; + @NotNull(message = "user:retypedNewPassword:notNull") @JsonProperty(ATTR_NAME_RETYPED_NEW_PASSWORD) private final String retypedNewPassword; @@ -140,9 +144,9 @@ public final class UserMod implements UserAccount, GrantEntity { this.name = null; this.username = null; this.email = null; - this.locale = null; - this.timeZone = null; - this.roles = null; + this.locale = Locale.ENGLISH; + this.timeZone = DateTimeZone.UTC; + this.roles = Collections.emptySet(); } @Override @@ -223,6 +227,15 @@ public final class UserMod implements UserAccount, GrantEntity { return false; } + @JsonIgnore + @Override + public EntityKey getEntityKey() { + if (StringUtils.isBlank(this.uuid)) { + return null; + } + return new EntityKey(this.uuid, entityType()); + } + @Override public String toString() { return "UserMod [uuid=" + this.uuid + ", institutionId=" + this.institutionId + ", name=" + this.name diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/RAPConfiguration.java b/src/main/java/ch/ethz/seb/sebserver/gui/RAPConfiguration.java index 15a4edb4..03618e37 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/RAPConfiguration.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/RAPConfiguration.java @@ -110,15 +110,9 @@ public class RAPConfiguration implements ApplicationConfiguration { log.debug("Initialize Spring-Context on Servlet-Context: " + servletContext); - final WebApplicationContext cc = WebApplicationContextUtils.getRequiredWebApplicationContext( + return WebApplicationContextUtils.getRequiredWebApplicationContext( servletContext); - if (cc == null) { - log.error("Failed to initialize Spring-Context on Servlet-Context: " + servletContext); - throw new RuntimeException( - "Failed to initialize Spring-Context on Servlet-Context: " + servletContext); - } - return cc; } catch (final Exception e) { log.error("Failed to initialize Spring-Context on HttpSession: " + httpSession); throw new RuntimeException("Failed to initialize Spring-Context on HttpSession: " + httpSession); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/form/Form.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/form/Form.java index ad30fee4..8029d5c1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/form/Form.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/form/Form.java @@ -23,12 +23,11 @@ import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.FormBinding; @@ -40,6 +39,7 @@ public final class Form implements FormBinding { private final JSONMapper jsonMapper; private final ObjectNode objectRoot; + private final Map staticValues = new LinkedHashMap<>(); private final Map formFields = new LinkedHashMap<>(); private final Map subForms = new LinkedHashMap<>(); private final Map> subLists = new LinkedHashMap<>(); @@ -69,16 +69,17 @@ public final class Form implements FormBinding { } @Override - public MultiValueMap getFormAsQueryAttributes() { - final LinkedMultiValueMap result = new LinkedMultiValueMap<>(); - for (final Map.Entry entry : this.formFields.entrySet()) { - final String value = entry.getValue().getValue(); - if (StringUtils.isNoneBlank(value)) { - result.add(entry.getKey(), value); - } + public String getFormUrlEncoded() { + final StringBuffer buffer = new StringBuffer(); + for (final Map.Entry entry : this.staticValues.entrySet()) { + appendFormUrlEncoded(buffer, entry.getKey(), entry.getValue()); } - return result; + for (final Map.Entry entry : this.formFields.entrySet()) { + appendFormUrlEncoded(buffer, entry.getKey(), entry.getValue().getValue()); + } + + return buffer.toString(); } public String getValue(final String name) { @@ -91,7 +92,9 @@ public final class Form implements FormBinding { } public void putStatic(final String name, final String value) { - this.objectRoot.put(name, value); + if (StringUtils.isNoneBlank(value)) { + this.staticValues.put(name, value); + } } public void addToGroup(final String groupName, final String fieldName) { @@ -101,6 +104,10 @@ public final class Form implements FormBinding { } } + public boolean hasFields() { + return !this.formFields.isEmpty(); + } + public Form putField(final String name, final Label label, final Label field) { this.formFields.put(name, createAccessor(label, field)); return this; @@ -172,6 +179,10 @@ public final class Form implements FormBinding { } private void flush() { + for (final Map.Entry entry : this.staticValues.entrySet()) { + this.objectRoot.put(entry.getKey(), entry.getValue()); + } + for (final Map.Entry entry : this.formFields.entrySet()) { final FormFieldAccessor accessor = entry.getValue(); if (accessor.control.isVisible()) { @@ -229,6 +240,17 @@ public final class Form implements FormBinding { } //@formatter:on + private void appendFormUrlEncoded(final StringBuffer buffer, final String name, final String value) { + if (StringUtils.isNoneBlank(value)) { + if (buffer.length() > 0) { + buffer.append(Constants.FORM_URL_ENCODED_SEPARATOR); + } + buffer.append(name) + .append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR) + .append(value); + } + } + public static abstract class FormFieldAccessor { public final Label label; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/form/FormBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/form/FormBuilder.java index 396e36a7..d811f277 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/form/FormBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/form/FormBuilder.java @@ -12,18 +12,20 @@ import java.util.List; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; import org.apache.commons.lang3.StringUtils; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.TabItem; +import org.eclipse.swt.widgets.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.util.Tuple; @@ -32,6 +34,7 @@ import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService; import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; import ch.ethz.seb.sebserver.gui.service.widget.ImageUpload; +import ch.ethz.seb.sebserver.gui.service.widget.SingleSelection; import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory; public class FormBuilder { @@ -45,6 +48,10 @@ public class FormBuilder { public final Form form; private boolean readonly = false; + private int defaultSpanLabel = 1; + private int defaultSpanInput = 2; + private int defaultSpanEmptyCell = 1; + private boolean emptyCellSeparation = true; FormBuilder( final EntityKey entityKey, @@ -84,12 +91,47 @@ public class FormBuilder { return this; } + public FormBuilder withDefaultSpanLabel(final int span) { + this.defaultSpanLabel = span; + return this; + } + + public FormBuilder withDefaultSpanInput(final int span) { + this.defaultSpanInput = span; + return this; + } + + public FormBuilder withDefaultSpanEmptyCell(final int span) { + this.defaultSpanEmptyCell = span; + return this; + } + + public FormBuilder withEmptyCellSeparation(final boolean separation) { + this.emptyCellSeparation = separation; + return this; + } + + public FormBuilder addEmptyCellIf(final BooleanSupplier condition) { + if (condition != null && condition.getAsBoolean()) { + return addEmptyCell(); + } + return this; + } + public FormBuilder addEmptyCell() { return addEmptyCell(1); } public FormBuilder addEmptyCell(final int span) { - this.widgetFactory.formEmpty(this.formParent, span, 1); + empty(this.formParent, span, 1); + return this; + } + + public FormBuilder putStaticValueIf(final BooleanSupplier condition, final String name, final String value) { + if (condition != null && condition.getAsBoolean()) { + return putStaticValue(name, value); + } + return this; } @@ -102,128 +144,14 @@ public class FormBuilder { return this; } - public FormBuilder addTextField( - final String name, - final String label, - final String value) { - - return addTextField(name, label, value, 1, null); - } - - public FormBuilder addTextField( - final String name, - final String label, - final String value, - final int span) { - - return addTextField(name, label, value, span, null); - } - - public FormBuilder addTextField( - final String name, - final String label, - final String value, - final int span, - final String group) { - - final Label lab = this.widgetFactory.formLabelLocalized(this.formParent, label); - if (this.readonly) { - this.form.putField(name, lab, this.widgetFactory.formValueLabel(this.formParent, value, span)); - } else { - this.form.putField(name, lab, this.widgetFactory.formTextInput(this.formParent, value, span, 1)); + public FormBuilder addField(final FieldTemplate template) { + if (template.condition == null || template.condition.getAsBoolean()) { + template.spanLabel = (template.spanLabel < 0) ? this.defaultSpanLabel : template.spanLabel; + template.spanInput = (template.spanInput < 0) ? this.defaultSpanInput : template.spanInput; + template.spanEmptyCell = (template.spanEmptyCell < 0) ? this.defaultSpanEmptyCell : template.spanEmptyCell; + template.autoEmptyCellSeparation = template.autoEmptyCellSeparation || this.emptyCellSeparation; + template.build(this); } - if (StringUtils.isNoneBlank(group)) { - this.form.addToGroup(group, name); - } - return this; - } - - public FormBuilder addSingleSelection( - final String name, - final String label, - final String value, - final List> items, - final Consumer
selectionListener) { - - return addSingleSelection(name, label, value, items, selectionListener, 1, null); - } - - public FormBuilder addSingleSelection( - final String name, - final String label, - final String value, - final List> items, - final Consumer selectionListener, - final int span) { - - return addSingleSelection(name, label, value, items, selectionListener, span, null); - } - - public FormBuilder addSingleSelection( - final String name, - final String label, - final String value, - final List> items, - final Consumer selectionListener, - final int span, - final String group) { - - final Label lab = this.widgetFactory.formLabelLocalized(this.formParent, label); - if (this.readonly) { - this.form.putField(name, lab, this.widgetFactory.formValueLabel(this.formParent, value, 2)); - } else { - final Combo selection = - this.widgetFactory.formSingleSelectionLocalized(this.formParent, value, items, span, 1); - this.form.putField(name, lab, selection); - if (selectionListener != null) { - selection.addListener(SWT.Selection, e -> { - selectionListener.accept(this.form); - }); - } - } - if (StringUtils.isNoneBlank(group)) { - this.form.addToGroup(group, name); - } - return this; - } - - public FormBuilder addImageUploadIf( - final BooleanSupplier condition, - final String name, - final String label, - final String value, - final int span) { - - if (condition != null && condition.getAsBoolean()) { - return addImageUpload(name, label, value, span); - } - return this; - } - - public FormBuilder addImageUpload( - final String name, - final String label, - final String value, - final int span) { - - return addImageUpload(name, label, value, span, null); - } - - public FormBuilder addImageUpload( - final String name, - final String label, - final String value, - final int span, - final String group) { - - final Label lab = this.widgetFactory.formLabelLocalized(this.formParent, label); - final ImageUpload imageUpload = this.widgetFactory.formImageUpload( - this.formParent, - value, - new LocTextKey("sebserver.overall.upload"), - span, 1, this.readonly); - this.form.putField(name, lab, imageUpload); - return this; } @@ -239,4 +167,225 @@ public class FormBuilder { this.polyglotPageService.getI18nSupport()); } + private void empty(final Composite parent, final int hspan, final int vspan) { + final Label empty = new Label(parent, SWT.LEFT); + empty.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, hspan, vspan)); + empty.setText(""); + } + + public static TextField text(final String name, final String label, final String value) { + return new TextField(name, label, value); + } + + public static SingleSelectionField singleSelection( + final String name, + final String label, + final String value, + final Supplier>> itemsSupplier) { + return new SingleSelectionField(name, label, value, itemsSupplier); + } + + public static ImageUploadField imageUpload(final String name, final String label, final String value) { + return new ImageUploadField(name, label, value); + } + + abstract static class FieldTemplate { + int spanLabel = -1; + int spanInput = -1; + int spanEmptyCell = -1; + boolean autoEmptyCellSeparation = false; + String group = null; + BooleanSupplier condition = null; + + final String name; + final String label; + final String value; + + protected FieldTemplate(final String name, final String label, final String value) { + this.name = name; + this.label = label; + this.value = value; + } + + public FieldTemplate withLabelSpan(final int span) { + this.spanLabel = span; + return this; + } + + public FieldTemplate withInputSpan(final int span) { + this.spanInput = span; + return this; + } + + public FieldTemplate withEmptyCellSpan(final int span) { + this.spanEmptyCell = span; + return this; + } + + public FieldTemplate withEmptyCellSeparation(final boolean separation) { + this.autoEmptyCellSeparation = separation; + return this; + } + + public FieldTemplate withGroup(final String group) { + this.group = group; + return this; + } + + public FieldTemplate withCondition(final BooleanSupplier condition) { + this.condition = condition; + return this; + } + + abstract void build(FormBuilder builder); + + } + + public static final class TextField extends FieldTemplate { + + boolean isPassword = false; + + TextField(final String name, final String label, final String value) { + super(name, label, value); + } + + public TextField asPasswordField() { + this.isPassword = true; + return this; + } + + @Override + void build(final FormBuilder builder) { + if (this.isPassword && builder.readonly) { + return; + } + + if (this.autoEmptyCellSeparation && builder.form.hasFields()) { + builder.addEmptyCell(this.spanEmptyCell); + } + + final Label lab = builder.labelLocalized(builder.formParent, this.label, this.spanLabel); + if (builder.readonly) { + builder.form.putField(this.name, lab, + builder.valueLabel(builder.formParent, this.value, this.spanInput)); + } else { + final Text textInput = new Text(builder.formParent, (this.isPassword) + ? SWT.LEFT | SWT.BORDER | SWT.PASSWORD + : SWT.LEFT | SWT.BORDER); + final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false, this.spanInput, 1); + gridData.heightHint = 15; + textInput.setLayoutData(gridData); + if (this.value != null) { + textInput.setText(this.value); + } + builder.form.putField(this.name, lab, textInput); + } + if (StringUtils.isNoneBlank(this.group)) { + builder.form.addToGroup(this.group, this.name); + } + } + } + + public static final class SingleSelectionField extends FieldTemplate { + + final Supplier>> itemsSupplier; + boolean isLocalizationSupplied = false; + Consumer selectionListener = null; + + SingleSelectionField( + final String name, + final String label, + final String value, + final Supplier>> itemsSupplier) { + + super(name, label, value); + this.itemsSupplier = itemsSupplier; + } + + public SingleSelectionField withLocalizationSupplied() { + this.isLocalizationSupplied = true; + return this; + } + + public SingleSelectionField withSelectionListener(final Consumer selectionListener) { + this.selectionListener = selectionListener; + return this; + } + + @Override + void build(final FormBuilder builder) { + + if (this.autoEmptyCellSeparation && builder.form.hasFields()) { + builder.addEmptyCell(this.spanEmptyCell); + } + + final Label lab = builder.labelLocalized(builder.formParent, this.label, this.spanLabel); + if (builder.readonly) { + builder.form.putField( + this.name, lab, + builder.valueLabel(builder.formParent, this.value, this.spanInput)); + } else { + final SingleSelection selection = (this.isLocalizationSupplied) + ? builder.widgetFactory.singleSelectionLocalizedSupplier( + builder.formParent, + this.itemsSupplier) + : builder.widgetFactory.singleSelectionLocalized( + builder.formParent, + this.itemsSupplier.get()); + final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false, this.spanInput, 1); + gridData.heightHint = 25; + selection.setLayoutData(gridData); + selection.select(this.value); + builder.form.putField(this.name, lab, selection); + if (this.selectionListener != null) { + selection.addListener(SWT.Selection, e -> { + this.selectionListener.accept(builder.form); + }); + } + } + if (StringUtils.isNoneBlank(this.group)) { + builder.form.addToGroup(this.group, this.name); + } + } + } + + public static final class ImageUploadField extends FieldTemplate { + + ImageUploadField(final String name, final String label, final String value) { + super(name, label, value); + } + + @Override + void build(final FormBuilder builder) { + + if (this.autoEmptyCellSeparation && builder.form.hasFields()) { + builder.addEmptyCell(this.spanEmptyCell); + } + + final Label lab = builder.labelLocalized(builder.formParent, this.label, this.spanLabel); + final ImageUpload imageUpload = builder.widgetFactory.formImageUpload( + builder.formParent, + this.value, + new LocTextKey("sebserver.overall.upload"), + this.spanInput, 1, builder.readonly); + builder.form.putField(this.name, lab, imageUpload); + } + + } + + private Label labelLocalized(final Composite parent, final String locTextKey, final int hspan) { + final Label label = this.widgetFactory.labelLocalized(parent, locTextKey); + final GridData gridData = new GridData(SWT.RIGHT, SWT.CENTER, true, false, hspan, 1); + label.setLayoutData(gridData); + return label; + } + + private Label valueLabel(final Composite parent, final String value, final int hspan) { + final Label label = new Label(parent, SWT.NONE); + label.setText((StringUtils.isNoneBlank(value)) ? value : Constants.EMPTY_NOTE); + final GridData gridData = new GridData(SWT.LEFT, SWT.CENTER, true, false, hspan, 1); + label.setLayoutData(gridData); + return label; + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/form/FormHandle.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/form/FormHandle.java index cda4f2dd..a20a1317 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/form/FormHandle.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/form/FormHandle.java @@ -68,23 +68,26 @@ public class FormHandle { .map(result -> { this.pageContext.publishPageEvent(new ActionEvent(action, result)); return result; - }).onErrorDo(error -> { - if (error instanceof RestCallError) { - ((RestCallError) error) - .getErrorMessages() - .stream() - .map(FieldValidationError::new) - .forEach(fve -> this.form.process( - name -> name.equals(fve.fieldName), - fieldAccessor -> showValidationError(fieldAccessor, fve))); - } else { - log.error("Unexpected error while trying to post form: ", error); - this.pageContext.notifyError(error); - } }) + .onErrorDo(this::handleError) .map(this.postPostHandle); } + private void handleError(final Throwable error) { + if (error instanceof RestCallError) { + ((RestCallError) error) + .getErrorMessages() + .stream() + .map(FieldValidationError::new) + .forEach(fve -> this.form.process( + name -> name.equals(fve.fieldName), + fieldAccessor -> showValidationError(fieldAccessor, fve))); + } else { + log.error("Unexpected error while trying to post form: ", error); + this.pageContext.notifyError(error); + } + } + private final void showValidationError( final FormFieldAccessor fieldAccessor, final FieldValidationError valError) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/i18n/I18nSupport.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/i18n/I18nSupport.java index 940d7722..b2e3023d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/i18n/I18nSupport.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/i18n/I18nSupport.java @@ -9,9 +9,15 @@ package ch.ethz.seb.sebserver.gui.service.i18n; import java.util.Collection; +import java.util.List; import java.util.Locale; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; + +import ch.ethz.seb.sebserver.gbl.util.Tuple; public interface I18nSupport { @@ -72,4 +78,38 @@ public interface I18nSupport { * @return the text in current language parsed from localized text */ String getText(String key, Locale locale, String def, Object... args); + default List> getLanguageResources() { + return getLanguageResources(this); + } + + default List> getTimeZoneResources() { + return getTimeZoneResources(this); + } + + /** Get a list of language key/name tuples for all supported languages in the + * language of the current users locale. + * + * @param i18nSupport I18nSupport to get the actual current users locale + * @return list of language key/name tuples for all supported languages in the language of the current users + * locale */ + static List> getLanguageResources(final I18nSupport i18nSupport) { + final Locale currentLocale = i18nSupport.getCurrentLocale(); + return i18nSupport.supportedLanguages() + .stream() + .map(locale -> new Tuple<>(locale.toLanguageTag(), locale.getDisplayLanguage(currentLocale))) + .filter(tuple -> StringUtils.isNoneBlank(tuple._2)) + .sorted((t1, t2) -> t1._2.compareTo(t2._2)) + .collect(Collectors.toList()); + } + + static List> getTimeZoneResources(final I18nSupport i18nSupport) { + final Locale currentLocale = i18nSupport.getCurrentLocale(); + return DateTimeZone + .getAvailableIDs() + .stream() + .map(id -> new Tuple<>(id, DateTimeZone.forID(id).getName(0, currentLocale) + " (" + id + ")")) + .sorted((t1, t2) -> t1._2.compareTo(t2._2)) + .collect(Collectors.toList()); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java index 39b3b1a3..7f09b41f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java @@ -154,6 +154,8 @@ public interface PageContext { T logoutOnError(Throwable error); + void publishPageMessage(LocTextKey title, LocTextKey message); + void publishPageMessage(PageMessageException pme); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageUtils.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageUtils.java new file mode 100644 index 00000000..27ff9201 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageUtils.java @@ -0,0 +1,67 @@ +/* + * 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.page; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType; +import ch.ethz.seb.sebserver.gbl.model.Entity; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.util.Tuple; +import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitutionDependency; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitutionNames; + +public final class PageUtils { + + private static final Logger log = LoggerFactory.getLogger(PageUtils.class); + + public static final Supplier confirmDeactivation( + final Entity entity, + final RestService restService) { + + return () -> { + try { + final Set dependencies = restService.getBuilder(GetInstitutionDependency.class) + .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(entity.getModelId())) + .withQueryParam(API.PARAM_BULK_ACTION_TYPE, BulkActionType.DEACTIVATE.name()) + .call() + .getOrThrow(); + final int size = dependencies.size(); + if (size > 0) { + return new LocTextKey("sebserver.dialog.confirm.deactivation", String.valueOf(size)); + } else { + return new LocTextKey("sebserver.dialog.confirm.deactivation.noDependencies"); + } + } catch (final Exception e) { + log.error("Failed to get dependencyies for Entity: {}", entity, e); + return new LocTextKey("sebserver.dialog.confirm.deactivation", ""); + } + }; + } + + public static List> getInstitutionSelectionResource(final RestService restService) { + return restService.getBuilder(GetInstitutionNames.class) + .call() + .getOr(Collections.emptyList()) + .stream() + .map(entityName -> new Tuple<>(entityName.modelId, entityName.name)) + .collect(Collectors.toList()); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java index 3c0a51e3..63340c1e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java @@ -22,6 +22,7 @@ import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageMessageException; import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent; import ch.ethz.seb.sebserver.gui.service.page.event.ActionPublishEvent; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; public final class Action implements Runnable { @@ -73,6 +74,16 @@ public final class Action implements Runnable { } catch (final PageMessageException pme) { Action.this.pageContext.publishPageMessage(pme); + + } catch (final RestCallError restCallError) { + if (restCallError.isFieldValidationError()) { + Action.this.pageContext.publishPageMessage( + new LocTextKey("sebserver.form.validation.error.title"), + new LocTextKey("sebserver.form.validation.error.message")); + } else { + log.error("Failed to execute action: {}", Action.this, restCallError); + Action.this.pageContext.notifyError("action.error.unexpected.message", restCallError); + } } catch (final Throwable t) { log.error("Failed to execute action: {}", Action.this, t); Action.this.pageContext.notifyError("action.error.unexpected.message", t); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/UserAccountActions.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/UserAccountActions.java index 3f07b0ae..c87c8db8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/UserAccountActions.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/UserAccountActions.java @@ -9,9 +9,12 @@ package ch.ethz.seb.sebserver.gui.service.page.action; import java.util.Collection; +import java.util.function.Function; +import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys; @@ -19,9 +22,18 @@ import ch.ethz.seb.sebserver.gui.service.page.PageMessageException; import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection; import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection.Activity; import ch.ethz.seb.sebserver.gui.service.page.event.ActivitySelectionEvent; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.ActivateUserAccount; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.DeactivateUserAccount; public final class UserAccountActions { + public static Function postSaveAdapter(final PageContext pageContext) { + return userAccount -> { + goToUserAccount(pageContext, userAccount.getModelId(), false); + return userAccount; + }; + } + public static Result newUserAccount(final Action action) { return Result.of(goToUserAccount(action.pageContext, null, true)); } @@ -34,6 +46,46 @@ public final class UserAccountActions { return fromSelection(action, true); } + public static Result editUserAccount(final Action action) { + return Result.of(goToUserAccount( + action.pageContext, + action.pageContext.getAttribute(AttributeKeys.ENTITY_ID), + true)); + } + + public static Result cancelEditUserAccount(final Action action) { + if (action.pageContext.getEntityKey() == null) { + final ActivitySelection toList = Activity.USER_ACCOUNT_LIST.createSelection(); + action.pageContext.publishPageEvent(new ActivitySelectionEvent(toList)); + return Result.of(toList); + } else { + return Result.of(goToUserAccount( + action.pageContext, + action.pageContext.getAttribute(AttributeKeys.ENTITY_ID), + false)); + } + } + + public static Result activateUserAccount(final Action action) { + return action.restService + .getBuilder(ActivateUserAccount.class) + .withURIVariable( + API.PARAM_MODEL_ID, + action.pageContext.getAttribute(AttributeKeys.ENTITY_ID)) + .call() + .map(report -> goToUserAccount(action.pageContext, report.getSingleSource().modelId, false)); + } + + public static Result deactivateUserAccount(final Action action) { + return action.restService + .getBuilder(DeactivateUserAccount.class) + .withURIVariable( + API.PARAM_MODEL_ID, + action.pageContext.getAttribute(AttributeKeys.ENTITY_ID)) + .call() + .map(report -> goToUserAccount(action.pageContext, report.getSingleSource().modelId, false)); + } + private static Result fromSelection(final Action action, final boolean edit) { final Collection selection = action.selectionSupplier.get(); if (selection.isEmpty()) { @@ -50,7 +102,6 @@ public final class UserAccountActions { final ActivitySelection activitySelection = Activity.USER_ACCOUNT_FORM .createSelection() - .withEntity(new EntityKey(modelId, EntityType.USER)) .withAttribute(AttributeKeys.READ_ONLY, String.valueOf(!edit)); if (modelId != null) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitySelection.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitySelection.java index 10a10627..9b15a06f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitySelection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitySelection.java @@ -22,6 +22,7 @@ import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; import ch.ethz.seb.sebserver.gui.service.page.action.ActionPane; import ch.ethz.seb.sebserver.gui.service.page.content.InstitutionForm; import ch.ethz.seb.sebserver.gui.service.page.content.InstitutionList; +import ch.ethz.seb.sebserver.gui.service.page.content.UserAccountForm; import ch.ethz.seb.sebserver.gui.service.page.content.UserAccountList; import ch.ethz.seb.sebserver.gui.service.page.impl.TODOTemplate; @@ -51,7 +52,7 @@ public class ActivitySelection { new LocTextKey("sebserver.activities.useraccount")), USER_ACCOUNT_FORM( - UserAccountList.class, + UserAccountForm.class, ActionPane.class, new LocTextKey("sebserver.activities.useraccount")), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/InstitutionForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/InstitutionForm.java index 4484cf20..c42ef299 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/InstitutionForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/InstitutionForm.java @@ -8,9 +8,6 @@ package ch.ethz.seb.sebserver.gui.service.page.content; -import java.util.Set; -import java.util.function.Supplier; - import org.eclipse.swt.widgets.Composite; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,23 +15,22 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import ch.ethz.seb.sebserver.gbl.api.API; -import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType; import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType; import ch.ethz.seb.sebserver.gbl.model.Domain; -import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.institution.Institution; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.form.FormBuilder; import ch.ethz.seb.sebserver.gui.service.form.FormHandle; import ch.ethz.seb.sebserver.gui.service.form.PageFormService; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.page.PageContext; +import ch.ethz.seb.sebserver.gui.service.page.PageUtils; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition; import ch.ethz.seb.sebserver.gui.service.page.action.InstitutionActions; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitution; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitutionDependency; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.NewInstitution; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.SaveInstitution; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; @@ -71,7 +67,7 @@ public class InstitutionForm implements TemplateComposer { final WidgetFactory widgetFactory = this.pageFormService.getWidgetFactory(); final EntityKey entityKey = pageContext.getEntityKey(); - // get data or create new and handle error + // get data or create new. Handle error if happen final Institution institution = (entityKey == null) ? new Institution(null, null, null, null, false) : this.restService @@ -103,35 +99,28 @@ public class InstitutionForm implements TemplateComposer { institution.name); final Composite content = widgetFactory.defaultPageLayout( formContext.getParent(), - titleKey, - ActionDefinition.INSTITUTION_SAVE, - title -> event -> { - final Entity entity = (Entity) event.source; - widgetFactory.injectI18n(title, new LocTextKey( - "sebserver.institution.form.title", - entity.getName())); - title.getParent().layout(); - }); + titleKey); // The Institution form final FormHandle formHandle = this.pageFormService.getBuilder( formContext.copyOf(content), 4) .readonly(pageContext.isReadonly()) - .putStaticValue("id", institution.getModelId()) - .addTextField( + .putStaticValueIf(() -> entityKey != null, + Domain.INSTITUTION.ATTR_ID, + institution.getModelId()) + .addField(FormBuilder.text( Domain.INSTITUTION.ATTR_NAME, "sebserver.institution.form.name", - institution.name, 2) - .addEmptyCell() - .addTextField( + institution.name)) + .addField(FormBuilder.text( Domain.INSTITUTION.ATTR_URL_SUFFIX, "sebserver.institution.form.urlSuffix", - institution.urlSuffix, 2) - .addEmptyCell() - .addImageUploadIf(() -> entityKey != null, + institution.urlSuffix)) + .addField(FormBuilder.imageUpload( Domain.INSTITUTION.ATTR_LOGO_IMAGE, "sebserver.institution.form.logoImage", - institution.logoImage, 2) + institution.logoImage) + .withCondition(() -> entityKey != null)) .buildFor((entityKey == null) ? this.restService.getRestCall(NewInstitution.class) : this.restService.getRestCall(SaveInstitution.class), @@ -155,7 +144,7 @@ public class InstitutionForm implements TemplateComposer { } else { formContext.createAction(ActionDefinition.INSTITUTION_DEACTIVATE) .withExec(InstitutionActions::deactivateInstitution) - .withConfirm(confirmDeactivation(institution)) + .withConfirm(PageUtils.confirmDeactivation(institution, this.restService)) .publishIf(() -> modifyGrant); } @@ -170,25 +159,4 @@ public class InstitutionForm implements TemplateComposer { } } - private Supplier confirmDeactivation(final Institution institution) { - return () -> { - try { - final Set dependencies = this.restService.getBuilder(GetInstitutionDependency.class) - .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(institution.id)) - .withQueryParam(API.PARAM_BULK_ACTION_TYPE, BulkActionType.DEACTIVATE.name()) - .call() - .getOrThrow(); - final int size = dependencies.size(); - if (size > 0) { - return new LocTextKey("sebserver.institution.form.confirm.deactivation", String.valueOf(size)); - } else { - return new LocTextKey("sebserver.institution.form.confirm.deactivation.noDependencies"); - } - } catch (final Exception e) { - log.error("Failed to get dependencyies for Institution: {}", institution, e); - return new LocTextKey("sebserver.institution.form.confirm.deactivation", ""); - } - }; - } - } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/UserAccountForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/UserAccountForm.java index de0b59bc..83745550 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/UserAccountForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/UserAccountForm.java @@ -9,22 +9,36 @@ package ch.ethz.seb.sebserver.gui.service.page.content; import java.util.UUID; +import java.util.function.BooleanSupplier; +import org.eclipse.swt.widgets.Composite; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType; +import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.user.UserAccount; +import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; import ch.ethz.seb.sebserver.gbl.model.user.UserMod; +import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.form.FormBuilder; +import ch.ethz.seb.sebserver.gui.service.form.FormHandle; import ch.ethz.seb.sebserver.gui.service.form.PageFormService; +import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.page.PageContext; +import ch.ethz.seb.sebserver.gui.service.page.PageUtils; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; +import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition; +import ch.ethz.seb.sebserver.gui.service.page.action.UserAccountActions; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.GetUserAccount; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.NewUserAccount; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.SaveUserAccount; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory; @@ -58,9 +72,12 @@ public class UserAccountForm implements TemplateComposer { final WidgetFactory widgetFactory = this.pageFormService.getWidgetFactory(); final EntityKey entityKey = pageContext.getEntityKey(); + final BooleanSupplier isNew = () -> entityKey == null; + final BooleanSupplier isNotNew = () -> !isNew.getAsBoolean(); + final BooleanSupplier isSEBAdmin = () -> this.currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN); - // get data or create new and handle error - final UserAccount userAccount = (entityKey == null) + // get data or create new. handle error if happen + final UserAccount userAccount = isNew.getAsBoolean() ? new UserMod( UUID.randomUUID().toString(), this.currentUser.get().institutionId) @@ -72,10 +89,119 @@ public class UserAccountForm implements TemplateComposer { if (userAccount == null) { log.error( - "Failed to get UserAccount. Error was notified to the User. See previous logs for more infomation"); + "Failed to get UserAccount. " + + "Error was notified to the User. " + + "See previous logs for more infomation"); return; } + // new PageContext with actual EntityKey + final PageContext formContext = pageContext; + pageContext.withEntityKey(userAccount.getEntityKey()); + + if (log.isDebugEnabled()) { + log.debug("UserAccount Form for user {}", userAccount.getName()); + } + + // the default page layout with title + final LocTextKey titleKey = new LocTextKey( + isNotNew.getAsBoolean() + ? "sebserver.useraccount.form.title" + : "sebserver.useraccount.form.title.new", + userAccount.getUsername()); + final Composite content = widgetFactory.defaultPageLayout( + formContext.getParent(), + titleKey); + + // The UserAccount form + final FormHandle formHandle = this.pageFormService.getBuilder( + formContext.copyOf(content), 4) + .readonly(pageContext.isReadonly()) + .putStaticValueIf(isNotNew, + Domain.USER.ATTR_UUID, + userAccount.getModelId()) + .putStaticValueIf(isNew, + Domain.USER.ATTR_TIMEZONE, + userAccount.getTimeZone().getID()) + .addField(FormBuilder.singleSelection( + Domain.USER.ATTR_INSTITUTION_ID, + "sebserver.useraccount.form.institution", + String.valueOf(userAccount.getInstitutionId()), + () -> PageUtils.getInstitutionSelectionResource(this.restService)) + .withCondition(isSEBAdmin)) + .addField(FormBuilder.text( + Domain.USER.ATTR_NAME, + "sebserver.useraccount.form.name", + userAccount.getName())) + .addField(FormBuilder.text( + Domain.USER.ATTR_USERNAME, + "sebserver.useraccount.form.username", + userAccount.getUsername())) + .addField(FormBuilder.text( + Domain.USER.ATTR_EMAIL, + "sebserver.useraccount.form.mail", + userAccount.getEmail())) + .addField(FormBuilder.singleSelection( + Domain.USER.ATTR_LOCALE, + "sebserver.useraccount.form.language", + userAccount.getTimeZone().getID(), + () -> widgetFactory.getI18nSupport().getLanguageResources()) + .withLocalizationSupplied()) + .addField(FormBuilder.singleSelection( + Domain.USER.ATTR_TIMEZONE, + "sebserver.useraccount.form.timezone", + userAccount.getTimeZone().getID(), + () -> widgetFactory.getI18nSupport().getTimeZoneResources()) + .withLocalizationSupplied()) + // TODO add role selection (create multi selector) + .addField(FormBuilder.text( + UserMod.ATTR_NAME_NEW_PASSWORD, + "sebserver.useraccount.form.password", + null) + .asPasswordField() + .withCondition(isNew)) + .addField(FormBuilder.text( + UserMod.ATTR_NAME_RETYPED_NEW_PASSWORD, + "sebserver.useraccount.form.password.retyped", + null) + .asPasswordField() + .withCondition(isNew)) + .buildFor((entityKey == null) + ? this.restService.getRestCall(NewUserAccount.class) + : this.restService.getRestCall(SaveUserAccount.class), + UserAccountActions.postSaveAdapter(pageContext)); + + // propagate content actions to action-pane + final boolean writeGrant = this.currentUser.hasPrivilege(PrivilegeType.WRITE, userAccount); + final boolean modifyGrant = this.currentUser.hasPrivilege(PrivilegeType.MODIFY, userAccount); + if (pageContext.isReadonly()) { + formContext.createAction(ActionDefinition.USER_ACCOUNT_NEW) + .withExec(UserAccountActions::newUserAccount) + .publishIf(() -> writeGrant); + formContext.createAction(ActionDefinition.USER_ACCOUNT_MODIFY) + .withExec(UserAccountActions::editUserAccount) + .publishIf(() -> modifyGrant); + + if (!userAccount.isActive()) { + formContext.createAction(ActionDefinition.USER_ACCOUNT_ACTIVATE) + .withExec(UserAccountActions::activateUserAccount) + .publishIf(() -> modifyGrant); + } else { + formContext.createAction(ActionDefinition.USER_ACCOUNT_DEACTIVATE) + .withExec(UserAccountActions::deactivateUserAccount) + .withConfirm(PageUtils.confirmDeactivation(userAccount, this.restService)) + .publishIf(() -> modifyGrant); + } + + } else { + formContext.createAction(ActionDefinition.USER_ACCOUNT_SAVE) + .withExec(formHandle::postChanges) + .publish() + .createAction(ActionDefinition.USER_ACCOUNT_CANCEL_MODIFY) + .withExec(UserAccountActions::cancelEditUserAccount) + .withConfirm("sebserver.overall.action.modify.cancel.confirm") + .publish(); + } } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java index a7326505..36ab1216 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java @@ -311,6 +311,17 @@ public class PageContextImpl implements PageContext { forwardToPage(this.composerService.loginPage(), pageContext); } + @Override + public void publishPageMessage(final LocTextKey title, final LocTextKey message) { + final MessageBox messageBox = new Message( + getShell(), + this.i18nSupport.getText(title), + this.i18nSupport.getText(message), + SWT.NONE); + messageBox.setMarkupEnabled(true); + messageBox.open(null); + } + @Override public void publishPageMessage(final PageMessageException pme) { final MessageBox messageBox = new Message( diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/FormBinding.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/FormBinding.java index aad50bed..56d39b4f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/FormBinding.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/FormBinding.java @@ -8,8 +8,6 @@ package ch.ethz.seb.sebserver.gui.service.remote.webservice.api; -import org.springframework.util.MultiValueMap; - import ch.ethz.seb.sebserver.gbl.model.EntityKey; public interface FormBinding { @@ -18,6 +16,6 @@ public interface FormBinding { String getFormAsJson(); - MultiValueMap getFormAsQueryAttributes(); + String getFormUrlEncoded(); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java index a7649e84..e947f6ea 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java @@ -113,6 +113,7 @@ public abstract class RestCall { })); } catch (final Exception e) { log.error("Unexpected error-response while webservice API call for: {}", builder, e); + log.error("Unexpected error-response cause: ", t); restCallError.errors.add(APIMessage.ErrorMessage.UNEXPECTED.of(e)); } @@ -147,6 +148,11 @@ public abstract class RestCall { return this; } + public RestCallBuilder withHeaders(final MultiValueMap params) { + this.httpHeaders.addAll(params); + return this; + } + public RestCallBuilder withBody(final Object body) { if (body instanceof String) { this.body = String.valueOf(body); @@ -198,7 +204,9 @@ public abstract class RestCall { return withURIVariable(API.PARAM_MODEL_ID, formBinding.entityKey().modelId) .withBody(formBinding.getFormAsJson()); } else { - return withQueryParams(formBinding.getFormAsQueryAttributes()); + this.body = formBinding.getFormUrlEncoded(); + return this; + //return withHeaders(formBinding.getFormAsQueryAttributes()); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCallError.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCallError.java index aecc9e41..1ffc61b4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCallError.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCallError.java @@ -37,6 +37,14 @@ public class RestCallError extends RuntimeException implements APIMessageError { return !this.errors.isEmpty(); } + public boolean isFieldValidationError() { + return this.errors + .stream() + .filter(error -> APIMessage.ErrorMessage.FIELD_VALIDATION.isOf(error)) + .findFirst() + .isPresent(); + } + @Override public String toString() { return "RestCallError [errors=" + this.errors + "]"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/table/TableFilter.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/table/TableFilter.java index e36dae7f..673d67eb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/table/TableFilter.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/table/TableFilter.java @@ -26,7 +26,7 @@ import org.springframework.util.MultiValueMap; import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.table.ColumnDefinition.TableFilterAttribute; -import ch.ethz.seb.sebserver.gui.service.widget.LanguageSelector; +import ch.ethz.seb.sebserver.gui.service.widget.SingleSelection; import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory.ImageIcon; public class TableFilter extends Composite { @@ -232,7 +232,7 @@ public class TableFilter extends Composite { private class LanguageFilter extends FilterComponent { - private LanguageSelector selector; + private SingleSelection selector; LanguageFilter(final TableFilterAttribute attribute) { super(attribute); @@ -240,7 +240,12 @@ public class TableFilter extends Composite { @Override FilterComponent build(final Composite parent) { - this.selector = TableFilter.this.entityTable.widgetFactory.countrySelector(parent); + this.selector = TableFilter.this.entityTable.widgetFactory + .singleSelectionLocalizedSupplier( + parent, + () -> TableFilter.this.entityTable.widgetFactory + .getI18nSupport() + .getLanguageResources()); this.selector.setLayoutData(this.rowData); return this; } @@ -249,7 +254,6 @@ public class TableFilter extends Composite { FilterComponent reset() { if (this.selector != null) { this.selector.clear(); - this.selector.layout(); } return this; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/LanguageSelector.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/LanguageSelector.java deleted file mode 100644 index b67fc331..00000000 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/LanguageSelector.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.widget; - -import static ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService.POLYGLOT_WIDGET_FUNCTION_KEY; - -import java.util.List; -import java.util.Locale; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import org.apache.commons.lang3.StringUtils; -import org.eclipse.swt.widgets.Composite; - -import ch.ethz.seb.sebserver.gbl.util.Tuple; -import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; - -public class LanguageSelector extends SingleSelection { - - private static final long serialVersionUID = -8590909580787576722L; - - private final Consumer updateFunction; - - public LanguageSelector(final Composite parent, final I18nSupport i18nSupport) { - super(parent, getLanguages(i18nSupport)); - this.updateFunction = updateFunction(i18nSupport); - this.setData(POLYGLOT_WIDGET_FUNCTION_KEY, this.updateFunction); - } - - private static final Consumer updateFunction(final I18nSupport i18nSupport) { - return selection -> selection.applyNewMapping(getLanguages(i18nSupport)); - } - - public static final List> getLanguages(final I18nSupport i18nSupport) { - final Locale currentLocale = i18nSupport.getCurrentLocale(); - return i18nSupport.supportedLanguages() - .stream() - .map(locale -> new Tuple<>(locale.toLanguageTag(), locale.getDisplayLanguage(currentLocale))) - .filter(tuple -> StringUtils.isNoneBlank(tuple._2)) - .sorted((t1, t2) -> t1._2.compareTo(t2._2)) - .collect(Collectors.toList()); - } - - public void clear() { - super.clearSelection(); - this.updateFunction.accept(this); - } - -} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/SingleSelection.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/SingleSelection.java index 8404e8e6..b78e8fd3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/SingleSelection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/SingleSelection.java @@ -33,6 +33,7 @@ public class SingleSelection extends Combo { } protected void applyNewMapping(final List> mapping) { + final String selectionValue = getSelectionValue(); this.valueMapping.clear(); this.keyMapping.clear(); this.valueMapping.addAll(mapping.stream() @@ -42,6 +43,7 @@ public class SingleSelection extends Combo { .map(t -> t._1) .collect(Collectors.toList())); super.setItems(this.valueMapping.toArray(new String[mapping.size()])); + select(selectionValue); } public void select(final String key) { @@ -62,4 +64,9 @@ public class SingleSelection extends Combo { return this.keyMapping.get(selectionindex); } + public void clear() { + super.clearSelection(); + super.setItems(this.valueMapping.toArray(new String[this.valueMapping.size()])); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/WidgetFactory.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/WidgetFactory.java index 892c8486..4fff9887 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/WidgetFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/WidgetFactory.java @@ -16,8 +16,8 @@ import java.util.List; import java.util.Locale; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; -import org.apache.commons.lang3.StringUtils; import org.eclipse.rap.rwt.RWT; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Device; @@ -34,7 +34,6 @@ import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.TableItem; -import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import org.slf4j.Logger; @@ -42,7 +41,6 @@ import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; -import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.Page; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; @@ -311,68 +309,6 @@ public class WidgetFactory { return imageButton; } - public Label formLabelLocalized(final Composite parent, final String locTextKey) { - final Label label = labelLocalized(parent, locTextKey); - final GridData gridData = new GridData(SWT.RIGHT, SWT.CENTER, true, false); - label.setLayoutData(gridData); - return label; - } - - public Label formValueLabel(final Composite parent, final String value, final int span) { - final Label label = new Label(parent, SWT.NONE); - label.setText((StringUtils.isNoneBlank(value)) ? value : Constants.EMPTY_NOTE); - final GridData gridData = new GridData(SWT.LEFT, SWT.CENTER, true, false, span, 1); - label.setLayoutData(gridData); - return label; - } - - public Text formTextInput(final Composite parent, final String value) { - return formTextInput(parent, value, 1, 1); - } - - public Text formTextInput(final Composite parent, final String value, final int hspan, final int vspan) { - final Text textInput = new Text(parent, SWT.LEFT | SWT.BORDER); - final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false, hspan, vspan); - gridData.heightHint = 15; - textInput.setLayoutData(gridData); - if (value != null) { - textInput.setText(value); - } - return textInput; - } - - public Combo formSingleSelectionLocalized( - final Composite parent, - final String selection, - final List> items) { - - return formSingleSelectionLocalized(parent, selection, items, 1, 1); - } - - public Combo formSingleSelectionLocalized( - final Composite parent, - final String selection, - final List> items, - final int hspan, final int vspan) { - - final SingleSelection combo = singleSelectionLocalized(parent, items); - final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false, hspan, vspan); - gridData.heightHint = 25; - combo.setLayoutData(gridData); - combo.select(selection); - return combo; - } - - public void formEmpty(final Composite parent) { - formEmpty(parent, 1, 1); - } - - public void formEmpty(final Composite parent, final int hspan, final int vspan) { - final Label empty = new Label(parent, SWT.LEFT); - empty.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, hspan, vspan)); - empty.setText(""); - } - public SingleSelection singleSelectionLocalized( final Composite parent, final List> items) { @@ -382,10 +318,25 @@ public class WidgetFactory { return combo; } - public LanguageSelector countrySelector(final Composite parent) { - return new LanguageSelector(parent, this.i18nSupport); + public SingleSelection singleSelectionLocalizedSupplier( + final Composite parent, + final Supplier>> itemsSupplier) { + + final Consumer updateFunction = + selection -> selection.applyNewMapping(itemsSupplier.get()); + final SingleSelection selection = new SingleSelection(parent, itemsSupplier.get()); + selection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction); + return selection; } +// public SingleSelection languageSelector(final Composite parent) { +// final Consumer updateFunction = +// selection -> selection.applyNewMapping(this.i18nSupport.getLanguageResources()); +// final SingleSelection selection = new SingleSelection(parent, this.i18nSupport.getLanguageResources()); +// selection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction); +// return selection; +// } + public ImageUpload formImageUpload( final Composite parent, final String value, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserDaoImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserDaoImpl.java index 1e495d90..600e2844 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserDaoImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserDaoImpl.java @@ -333,7 +333,7 @@ public class UserDaoImpl implements UserDAO { @Override public List extractPKsFromKeys(final Collection keys) { - if (keys == null || keys.isEmpty() || keys.iterator().next().isIdPK) { + if (keys == null || keys.isEmpty() || keys.iterator().next().entityType != EntityType.USER) { return UserDAO.super.extractPKsFromKeys(keys); } else { final List uuids = keys.stream() diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index aaccfbfe..6a64a6a0 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -17,6 +17,8 @@ sebserver.overall.action.filter.clear=Clear Filter Criteria # Form validation and messages ################################ +sebserver.form.validation.error.title=Form Data Validation Failed +sebserver.form.validation.error.message=There is missing or incorrect form data. sebserver.form.validation.fieldError.size=The size must be between {3} and {4} sebserver.form.validation.fieldError.name=Name is mandatory and must have a size between 3 and 255 character sebserver.form.validation.fieldError.urlSuffix=URL Suffix must have a size between 3 and 255 character @@ -25,6 +27,9 @@ sebserver.error.unexpected=Unexpected Error sebserver.page.message=Information sebserver.dialog.confirm.title=Confirmation +sebserver.dialog.confirm.deactivation=Note that there are {0} other entities that belongs to this entity.
Those will also be deactivated by deactivating this entity.

Are You sure to deactivate this entity? +sebserver.dialog.confirm.deactivation.noDependencies=Are You sure to deactivate? + ################################ # Login Page ################################ @@ -78,8 +83,7 @@ sebserver.institution.form.title=Institution : {0} sebserver.institution.form.name=Name sebserver.institution.form.urlSuffix=URL Suffix sebserver.institution.form.logoImage=Logo Image -sebserver.institution.form.confirm.deactivation=Note that there are {0} other entities that belongs to this Institution.
Those will also be deactivated by deactivating this Institution.

Are You sure to deactivate this Institution? -sebserver.institution.form.confirm.deactivation.noDependencies=Are You sure to deactivate this Institution? + ################################ # User Account @@ -102,5 +106,16 @@ sebserver.useraccount.action.delete=Delete User Account sebserver.useraccount.info.pleaseSelect=Please Select an User Account first. +sebserver.useraccount.form.title=User Account : {0} +sebserver.useraccount.form.title.new=New User Account +sebserver.useraccount.form.institution=Institution +sebserver.useraccount.form.name=Name +sebserver.useraccount.form.username=Username +sebserver.useraccount.form.mail=E-Mail +sebserver.useraccount.form.language=Language +sebserver.useraccount.form.timezone=Time Zone +sebserver.useraccount.form.password=Password +sebserver.useraccount.form.password.retyped=Retyped Password +