SEBSERV-27 User Account Form language and Time Zone selector

This commit is contained in:
anhefti 2019-02-21 17:00:42 +01:00
parent 88356ed821
commit 422d816093
28 changed files with 772 additions and 354 deletions

View file

@ -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

View file

@ -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);
}

View file

@ -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() {

View file

@ -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();
}

View file

@ -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;

View file

@ -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<String> 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

View file

@ -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);

View file

@ -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<String, String> staticValues = new LinkedHashMap<>();
private final Map<String, FormFieldAccessor> formFields = new LinkedHashMap<>();
private final Map<String, Form> subForms = new LinkedHashMap<>();
private final Map<String, List<Form>> subLists = new LinkedHashMap<>();
@ -69,16 +69,17 @@ public final class Form implements FormBinding {
}
@Override
public MultiValueMap<String, String> getFormAsQueryAttributes() {
final LinkedMultiValueMap<String, String> result = new LinkedMultiValueMap<>();
for (final Map.Entry<String, FormFieldAccessor> 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<String, String> entry : this.staticValues.entrySet()) {
appendFormUrlEncoded(buffer, entry.getKey(), entry.getValue());
}
return result;
for (final Map.Entry<String, FormFieldAccessor> 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<String, String> entry : this.staticValues.entrySet()) {
this.objectRoot.put(entry.getKey(), entry.getValue());
}
for (final Map.Entry<String, FormFieldAccessor> 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;

View file

@ -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<Tuple<String>> items,
final Consumer<Form> selectionListener) {
return addSingleSelection(name, label, value, items, selectionListener, 1, null);
}
public FormBuilder addSingleSelection(
final String name,
final String label,
final String value,
final List<Tuple<String>> items,
final Consumer<Form> 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<Tuple<String>> items,
final Consumer<Form> 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<List<Tuple<String>>> 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<List<Tuple<String>>> itemsSupplier;
boolean isLocalizationSupplied = false;
Consumer<Form> selectionListener = null;
SingleSelectionField(
final String name,
final String label,
final String value,
final Supplier<List<Tuple<String>>> itemsSupplier) {
super(name, label, value);
this.itemsSupplier = itemsSupplier;
}
public SingleSelectionField withLocalizationSupplied() {
this.isLocalizationSupplied = true;
return this;
}
public SingleSelectionField withSelectionListener(final Consumer<Form> 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;
}
}

View file

@ -68,23 +68,26 @@ public class FormHandle<T> {
.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) {

View file

@ -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<Tuple<String>> getLanguageResources() {
return getLanguageResources(this);
}
default List<Tuple<String>> 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<Tuple<String>> 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<Tuple<String>> 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());
}
}

View file

@ -154,6 +154,8 @@ public interface PageContext {
<T> T logoutOnError(Throwable error);
void publishPageMessage(LocTextKey title, LocTextKey message);
void publishPageMessage(PageMessageException pme);
}

View file

@ -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<LocTextKey> confirmDeactivation(
final Entity entity,
final RestService restService) {
return () -> {
try {
final Set<EntityKey> 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<Tuple<String>> 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());
}
}

View file

@ -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);

View file

@ -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<UserInfo, UserInfo> 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<String> 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) {

View file

@ -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")),

View file

@ -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<Institution> 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<LocTextKey> confirmDeactivation(final Institution institution) {
return () -> {
try {
final Set<EntityKey> 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", "");
}
};
}
}

View file

@ -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<UserInfo> 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();
}
}
}

View file

@ -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(

View file

@ -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<String, String> getFormAsQueryAttributes();
String getFormUrlEncoded();
}

View file

@ -113,6 +113,7 @@ public abstract class RestCall<T> {
}));
} 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<T> {
return this;
}
public RestCallBuilder withHeaders(final MultiValueMap<String, String> 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<T> {
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());
}
}

View file

@ -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 + "]";

View file

@ -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<ROW extends Entity> extends Composite {
@ -232,7 +232,7 @@ public class TableFilter<ROW extends Entity> 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<ROW extends Entity> 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<ROW extends Entity> extends Composite {
FilterComponent reset() {
if (this.selector != null) {
this.selector.clear();
this.selector.layout();
}
return this;
}

View file

@ -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<LanguageSelector> 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<LanguageSelector> updateFunction(final I18nSupport i18nSupport) {
return selection -> selection.applyNewMapping(getLanguages(i18nSupport));
}
public static final List<Tuple<String>> 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);
}
}

View file

@ -33,6 +33,7 @@ public class SingleSelection extends Combo {
}
protected void applyNewMapping(final List<Tuple<String>> 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()]));
}
}

View file

@ -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<Tuple<String>> items) {
return formSingleSelectionLocalized(parent, selection, items, 1, 1);
}
public Combo formSingleSelectionLocalized(
final Composite parent,
final String selection,
final List<Tuple<String>> 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<Tuple<String>> 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<List<Tuple<String>>> itemsSupplier) {
final Consumer<SingleSelection> 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<SingleSelection> 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,

View file

@ -333,7 +333,7 @@ public class UserDaoImpl implements UserDAO {
@Override
public List<Long> extractPKsFromKeys(final Collection<EntityKey> 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<String> uuids = keys.stream()

View file

@ -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.<br/>Those will also be deactivated by deactivating this entity.<br/><br/>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.<br/>Those will also be deactivated by deactivating this Institution.<br/><br/>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