SEBSERV-27 #Institution Form and actions, refactoring

This commit is contained in:
anhefti 2019-02-13 14:40:22 +01:00
parent 5a9b85ccde
commit 377df32f72
61 changed files with 1222 additions and 190 deletions

13
pom.xml
View file

@ -152,10 +152,10 @@
<includes>
<include>ch/ethz/seb/sebserver/*</include>
</includes>
<!-- <excludes> -->
<!-- <exclude>ch/ethz/seb/sebserver/webservice/datalayer/batis/mapper/*</exclude> -->
<!-- <exclude>ch/ethz/seb/sebserver/webservice/datalayer/batis/model/*</exclude> -->
<!-- </excludes> -->
<!-- <excludes> -->
<!-- <exclude>ch/ethz/seb/sebserver/webservice/datalayer/batis/mapper/*</exclude> -->
<!-- <exclude>ch/ethz/seb/sebserver/webservice/datalayer/batis/model/*</exclude> -->
<!-- </excludes> -->
</configuration>
<executions>
<execution>
@ -270,6 +270,11 @@
<artifactId>org.eclipse.rap.rwt</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.rap</groupId>
<artifactId>org.eclipse.rap.fileupload</artifactId>
<version>3.7.0</version>
</dependency>
<!-- Misc -->
<dependency>

View file

@ -12,6 +12,21 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
/** SEB-Server (Safe Exam Browser Server) is a server component to maintain and support
* Exams running with SEB (Safe Exam Browser). TODO add link(s)
*
* SEB-Server uses Spring Boot as main framework is divided into two main components,
* a webservice component that implements the business logic, persistence management
* and defines a REST API to expose the services over HTTP. The second component is a
* GUI component built on RAP RWT/SWT that also uses Spring components to connect and use
* the webservice over HTTP. The two components are (implementation-wise) completely separated
* from each other by the Rest API and the webservice can also be used by another client.
* SEB-Server uses Spring's profiles to consequently separate sub-components of the webservice
* and GUI and can be used to start the components on separate servers or within the same
* server instance. Additional to the usual profiles like dev, prod, test there are combining
* profiles like dev-ws, dev-gui and prod-ws, prod-gui
*
* TODO documentation for presets to start all-in-one server or separated gui- and webservice- server */
@SpringBootApplication(exclude = {
// OAuth2ResourceServerAutoConfiguration.class,
UserDetailsServiceAutoConfiguration.class,

View file

@ -34,4 +34,9 @@ public class API {
public static final String INACTIVE_SUFFIX = "/inactive";
public static final String PATH_VAR_MODEL_ID_NAME = "modelId";
public static final String PATH_VAR_MODEL_ID = "/{" + PATH_VAR_MODEL_ID_NAME + "}";
public static final String PATH_VAR_ACTIVE = PATH_VAR_MODEL_ID + ACTIVE_SUFFIX;
public static final String PATH_VAR_INACTIVE = PATH_VAR_MODEL_ID + INACTIVE_SUFFIX;
}

View file

@ -8,14 +8,20 @@
package ch.ethz.seb.sebserver.gbl.api;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import ch.ethz.seb.sebserver.gbl.util.Utils;
@ -26,7 +32,23 @@ public class POSTMapper {
public POSTMapper(final MultiValueMap<String, String> params) {
super();
this.params = params;
this.params = new LinkedMultiValueMap<>();
if (params != null) {
for (final Map.Entry<String, List<String>> entry : params.entrySet()) {
this.params.put(
entry.getKey(),
entry.getValue()
.stream()
.map(encoded -> {
try {
return URLDecoder.decode(encoded, "UTF-8");
} catch (final UnsupportedEncodingException e) {
return encoded;
}
})
.collect(Collectors.toList()));
}
}
}
public String getString(final String name) {
@ -117,4 +139,10 @@ public class POSTMapper {
return Utils.toDateTime(value);
}
@SuppressWarnings("unchecked")
public <T extends POSTMapper> T putIfAbsent(final String name, final String value) {
this.params.putIfAbsent(name, Arrays.asList(value));
return (T) this;
}
}

View file

@ -1,13 +1,13 @@
/*
* 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.content;
package ch.ethz.seb.sebserver.gbl.authorization;
public class Institution {
public class EntityPrivileges {
}

View file

@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
package ch.ethz.seb.sebserver.gbl.authorization;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
@ -15,6 +15,8 @@ import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
* institutional rights and ownershipRights. */
public final class Privilege {
/** The RoleTypeKey defining the UserRole and EntityType for this Privilege */
public final RoleTypeKey roleTypeKey;
/** Defines a base-privilege type that defines the overall access for an entity-type */
public final PrivilegeType privilegeType;
/** Defines an institutional privilege type that defines the institutional restricted access for a
@ -24,10 +26,12 @@ public final class Privilege {
public final PrivilegeType ownershipPrivilege;
public Privilege(
final RoleTypeKey roleTypeKey,
final PrivilegeType privilegeType,
final PrivilegeType institutionalPrivilege,
final PrivilegeType ownershipPrivilege) {
this.roleTypeKey = roleTypeKey;
this.privilegeType = privilegeType;
this.institutionalPrivilege = institutionalPrivilege;
this.ownershipPrivilege = ownershipPrivilege;
@ -68,7 +72,7 @@ public final class Privilege {
}
/** A key that combines UserRole EntityType identity */
static final class RoleTypeKey {
public static final class RoleTypeKey {
public final EntityType entityType;
public final UserRole userRole;

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gbl.authorization;
public enum PrivilegeLevel {
BASE,
INSTITUTIONAL,
OWNERSHIP
}

View file

@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
package ch.ethz.seb.sebserver.gbl.authorization;
/** Defines SEB-Server internal privilege types **/
public enum PrivilegeType {

View file

@ -22,6 +22,11 @@ public interface Entity extends ModelIdAware {
@JsonIgnore
String getName();
@JsonIgnore
default EntityKey getEntityKey() {
return new EntityKey(getModelId(), entityType());
}
public static EntityName toName(final Entity entity) {
return new EntityName(
entity.entityType(),

View file

@ -8,6 +8,8 @@
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;
@ -15,8 +17,10 @@ import com.fasterxml.jackson.annotation.JsonProperty;
public class EntityKey {
@JsonProperty(value = "modelId", required = true)
@NotNull
public final String modelId;
@JsonProperty(value = "entityType", required = true)
@NotNull
public final EntityType entityType;
@JsonIgnore
public final boolean isIdPK;
@ -26,6 +30,9 @@ public class 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";
this.modelId = modelId;
this.entityType = entityType;
this.isIdPK = entityType != EntityType.USER;

View file

@ -12,11 +12,14 @@ import java.util.Collection;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.util.Utils;
@JsonIgnoreProperties(ignoreUnknown = true)
public class EntityProcessingReport {
@JsonProperty(value = "source", required = true)
@ -37,6 +40,15 @@ public class EntityProcessingReport {
this.errors = Utils.immutableSetOf(errors);
}
@JsonIgnore
public EntityKey getSingleSource() {
if (!this.source.isEmpty()) {
return this.source.iterator().next();
}
return null;
}
public static final class ErrorEntry {
public final EntityKey entityKey;

View file

@ -9,9 +9,11 @@
package ch.ethz.seb.sebserver.gbl.model.institution;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
@ -21,6 +23,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain.INSTITUTION;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.GrantEntity;
@JsonIgnoreProperties(ignoreUnknown = true)
public final class Institution implements GrantEntity, Activatable {
@JsonProperty(Domain.ATTR_ID)
@ -32,7 +35,7 @@ public final class Institution implements GrantEntity, Activatable {
public final String name;
@JsonProperty(INSTITUTION.ATTR_URL_SUFFIX)
@Size(min = 3, max = 255, message = "institution:urlSuffix:size:{min}:{max}:${validatedValue}")
@Pattern(regexp = "(^$|.{3,255})", message = "institution:urlSuffix:size:{min}:{max}:${validatedValue}")
public final String urlSuffix;
@JsonProperty(INSTITUTION.ATTR_LOGO_IMAGE)

View file

@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.service.page;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Shell;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gui.service.page.action.Action;
import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection;
@ -33,7 +34,8 @@ public interface PageContext {
public static final String PAGE_TEMPLATE_COMPOSER_NAME = "ATTR_PAGE_TEMPLATE_COMPOSER_NAME";
public static final String INSTITUTION_ID = "INSTITUTION_ID";
public static final String READ_ONLY = "READ_ONLY";
public static final String CREATE_NEW = "CREATE_NEW";
public static final String ENTITY_ID = "ENTITY_ID";
public static final String PARENT_ENTITY_ID = "PARENT_ENTITY_ID";
@ -55,8 +57,7 @@ public interface PageContext {
//
// public static final String AUTHORIZATION_CONTEXT = "AUTHORIZATION_CONTEXT";
// public static final String AUTHORIZATION_HEADER = "AUTHORIZATION_HEADER";
public static final String AUTHORIZATION_FAILURE = "AUTHORIZATION_FAILURE";
public static final String LGOUT_SUCCESS = "LGOUT_SUCCESS";
// public static final String AUTHORIZATION_FAILURE = "AUTHORIZATION_FAILURE";
}
@ -92,16 +93,30 @@ public interface PageContext {
* @param key the key of the attribute
* @param value the value of the attribute
* @return this PageContext instance (builder pattern) */
PageContext withAttr(String key, String value);
PageContext withAttribute(String key, String value);
PageContext withSelection(ActivitySelection selection);
default PageContext withSelection(final ActivitySelection selection) {
return withSelection(selection, true);
}
PageContext withSelection(ActivitySelection selection, boolean clearAttributes);
String getAttribute(String name);
String getAttribute(String name, String def);
EntityKey getEntityKey();
EntityKey getParentEntityKey();
PageContext withEntityKey(EntityKey entityKey);
PageContext withParentEntityKey(EntityKey entityKey);
boolean hasAttribute(String name);
PageContext removeAttribute(String name);
/** Publishes a given PageEvent to the current page tree
* This goes through the page-tree and collects all listeners the are listen to
* the specified page event type.
@ -133,7 +148,7 @@ public interface PageContext {
* @param error the error as Throwable */
void notifyError(String errorMessage, Throwable error);
void notifyError(Throwable error);
<T> T notifyError(Throwable error);
<T> T logoutOnError(Throwable error);

View file

@ -9,6 +9,7 @@
package ch.ethz.seb.sebserver.gui.service.page.action;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.Supplier;
@ -23,12 +24,13 @@ 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.RestService;
public class Action implements Runnable {
public final class Action implements Runnable {
private static final Logger log = LoggerFactory.getLogger(Action.class);
public final ActionDefinition definition;
String confirmationMessage;
BooleanSupplier confirmComdition = () -> true;
String successMessage;
boolean updateOnSelection;
@ -49,7 +51,7 @@ public class Action implements Runnable {
@Override
public void run() {
if (StringUtils.isNotBlank(this.confirmationMessage)) {
if (StringUtils.isNotBlank(this.confirmationMessage) && this.confirmComdition.getAsBoolean()) {
this.pageContext.applyConfirmDialog(
this.confirmationMessage,
() -> exec());

View file

@ -16,16 +16,32 @@ public enum ActionDefinition {
"sebserver.institution.action.new",
IconButtonType.NEW_ACTION),
INSTITUTION_VIEW(
"sebserver.institution.action.view",
IconButtonType.VIEW_ACTION),
INSTITUTION_MODIFY(
"sebserver.institution.action.modify",
IconButtonType.MODIFY_ACTION),
INSTITUTION_CANCEL_MODIFY(
"sebserver.overall.action.modify.cancel",
IconButtonType.CANCEL_ACTION),
INSTITUTION_SAVE(
"actions.modify.institution",
"sebserver.institution.action.save",
IconButtonType.SAVE_ACTION),
INSTITUTION_ACTIVATE(
"sebserver.institution.action.activate",
IconButtonType.ACTIVATE_ACTION),
INSTITUTION_DEACTIVATE(
"sebserver.institution.action.deactivate",
IconButtonType.DEACTIVATE_ACTION),
INSTITUTION_DELETE(
"actions.delete.institution",
"sebserver.institution.action.modify",
IconButtonType.DELETE_ACTION),
LMS_SETUP_NEW(

View file

@ -13,67 +13,92 @@ import static ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection.
import java.util.Collection;
import java.util.function.Function;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
import ch.ethz.seb.sebserver.gbl.model.institution.Institution;
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;
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.event.ActivitySelectionEvent;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.NewInstitution;
import ch.ethz.seb.sebserver.gui.service.table.EntityTable;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.ActivateInstitution;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.DeactivateInstitution;
public final class InstitutionActions {
public static Result<?> newInstitution(final Action action) {
return action.restService
.getBuilder(NewInstitution.class)
.call();
}
public static Function<Action, Result<?>> editInstitution(final EntityTable<?> fromTable) {
return action -> {
final Collection<String> selection = fromTable.getSelection();
if (selection.isEmpty()) {
return Result.ofError(new PageMessageException("sebserver.institution.info.pleaseSelect"));
}
final EntityKey entityKey = new EntityKey(
selection.iterator().next(),
EntityType.INSTITUTION);
action.pageContext.publishPageEvent(new ActivitySelectionEvent(
INSTITUTION_NODE
.createSelection()
.withEntity(entityKey)));
return Result.of(entityKey);
public static Function<Institution, Institution> postSaveAdapter(final PageContext pageContext) {
return inst -> {
goToInstitution(pageContext, inst.getModelId(), false);
return inst;
};
}
// /** Use this higher-order function to create a new Institution action function.
// *
// * @return */
// static Runnable newInstitution(final PageContext composerCtx, final RestServices restServices) {
// return () -> {
// final IdAndName newInstitutionId = restServices
// .sebServerAPICall(NewInstitution.class)
// .doAPICall()
// .onErrorThrow("Unexpected Error");
// composerCtx.notify(new ActionEvent(ActionDefinition.INSTITUTION_NEW, newInstitutionId));
// };
// }
//
// /** Use this higher-order function to create a delete Institution action function.
// *
// * @return */
// static Runnable deleteInstitution(final PageContext composerCtx, final RestServices restServices,
// final String instId) {
// return () -> {
// restServices
// .sebServerAPICall(DeleteInstitution.class)
// .attribute(AttributeKeys.INSTITUTION_ID, instId)
// .doAPICall()
// .onErrorThrow("Unexpected Error");
// composerCtx.notify(new ActionEvent(ActionDefinition.INSTITUTION_DELETE, instId));
// };
// }
public static Result<?> newInstitution(final Action action) {
return Result.of(goToInstitution(action.pageContext, null, true));
}
public static Result<?> viewInstitution(final Action action) {
return fromInstitution(action, false);
}
public static Result<?> editInstitutionFromList(final Action action) {
return fromInstitution(action, true);
}
public static Result<?> editInstitution(final Action action) {
return Result.of(goToInstitution(
action.pageContext,
action.pageContext.getAttribute(AttributeKeys.ENTITY_ID),
true));
}
public static Result<?> cancelEditInstitution(final Action action) {
return Result.of(goToInstitution(
action.pageContext,
action.pageContext.getAttribute(AttributeKeys.ENTITY_ID),
false));
}
public static Result<?> activateInstitution(final Action action) {
return action.restService
.getBuilder(ActivateInstitution.class)
.withURIVariable(
API.PATH_VAR_MODEL_ID_NAME,
action.pageContext.getAttribute(AttributeKeys.ENTITY_ID))
.call()
.map(report -> goToInstitution(action.pageContext, report.getSingleSource().modelId, false));
}
public static Result<?> deactivateInstitution(final Action action) {
return action.restService
.getBuilder(DeactivateInstitution.class)
.withURIVariable(
API.PATH_VAR_MODEL_ID_NAME,
action.pageContext.getAttribute(AttributeKeys.ENTITY_ID))
.call()
.map(report -> goToInstitution(action.pageContext, report.getSingleSource().modelId, false));
}
private static Result<?> fromInstitution(final Action action, final boolean edit) {
final Collection<String> selection = action.selectionSupplier.get();
if (selection.isEmpty()) {
return Result.ofError(new PageMessageException("sebserver.institution.info.pleaseSelect"));
}
return Result.of(goToInstitution(action.pageContext, selection.iterator().next(), edit));
}
private static ActivitySelection goToInstitution(final PageContext pageContext, final String modelId,
final boolean edit) {
final ActivitySelection activitySelection = INSTITUTION_NODE
.createSelection()
.withEntity(new EntityKey(modelId, EntityType.INSTITUTION))
.withAttribute(AttributeKeys.READ_ONLY, String.valueOf(!edit))
.withAttribute(AttributeKeys.CREATE_NEW, (modelId != null) ? "false" : "true");
pageContext.publishPageEvent(new ActivitySelectionEvent(activitySelection));
return activitySelection;
}
}

View file

@ -35,7 +35,7 @@ import ch.ethz.seb.sebserver.gui.service.page.event.ActivitySelectionEvent;
import ch.ethz.seb.sebserver.gui.service.page.event.PageEventListener;
import ch.ethz.seb.sebserver.gui.service.page.impl.MainPageState;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory.CustomVariant;
@ -43,22 +43,25 @@ import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory.CustomVariant;
@Component
public class ActivitiesPane implements TemplateComposer {
private static final String ATTR_ACTIVITY_SELECTION = "ACTIVITY_SELECTION";
private final WidgetFactory widgetFactory;
private final RestService restService;
private final AuthorizationContextHolder authorizationContextHolder;
private final CurrentUser currentUser;
// TODO are those really needed?
private final Map<ActionDefinition, ActivityActionHandler> activityActionHandler =
new EnumMap<>(ActionDefinition.class);
public ActivitiesPane(
final WidgetFactory widgetFactory,
final RestService restService,
final AuthorizationContextHolder authorizationContextHolder,
final CurrentUser currentUser,
final Collection<ActivityActionHandler> activityActionHandler) {
this.widgetFactory = widgetFactory;
this.restService = restService;
this.authorizationContextHolder = authorizationContextHolder;
this.currentUser = currentUser;
for (final ActivityActionHandler aah : activityActionHandler) {
this.activityActionHandler.put(aah.handlesAction(), aah);
@ -67,10 +70,8 @@ public class ActivitiesPane implements TemplateComposer {
@Override
public void compose(final PageContext pageContext) {
final UserInfo userInfo = this.authorizationContextHolder
.getAuthorizationContext()
.getLoggedInUser()
.get(pageContext::logoutOnError);
final UserInfo userInfo = this.currentUser
.getOrHandleError(pageContext::logoutOnError);
final Label activities = this.widgetFactory.labelLocalized(
pageContext.getParent(),
@ -80,8 +81,9 @@ public class ActivitiesPane implements TemplateComposer {
activitiesGridData.horizontalIndent = 20;
activities.setLayoutData(activitiesGridData);
final Tree navigation =
this.widgetFactory.treeLocalized(pageContext.getParent(), SWT.SINGLE | SWT.FULL_SELECTION);
final Tree navigation = this.widgetFactory.treeLocalized(
pageContext.getParent(),
SWT.SINGLE | SWT.FULL_SELECTION);
final GridData navigationGridData = new GridData(SWT.FILL, SWT.FILL, true, true);
navigationGridData.horizontalIndent = 10;
navigation.setLayoutData(navigationGridData);
@ -96,7 +98,7 @@ public class ActivitiesPane implements TemplateComposer {
final TreeItem institutions = this.widgetFactory.treeItemLocalized(
navigation,
Activity.INSTITUTION_ROOT.title);
ActivitySelection.inject(institutions, Activity.INSTITUTION_ROOT.createSelection());
injectActivitySelection(institutions, Activity.INSTITUTION_ROOT.createSelection());
// for (final EntityName inst : insitutionNames) {
// createInstitutionItem(institutions, inst);
@ -106,7 +108,7 @@ public class ActivitiesPane implements TemplateComposer {
final TreeItem institutions = this.widgetFactory.treeItemLocalized(
navigation,
Activity.INSTITUTION_ROOT.title);
ActivitySelection.inject(institutions, Activity.INSTITUTION_NODE.createSelection());
injectActivitySelection(institutions, Activity.INSTITUTION_NODE.createSelection());
// final EntityName inst = insitutionNames.iterator().next();
// createInstitutionItem(navigation, inst);
}
@ -155,16 +157,20 @@ public class ActivitiesPane implements TemplateComposer {
navigation.addListener(SWT.Expand, this::handleExpand);
navigation.addListener(SWT.Selection, event -> handleSelection(pageContext, event));
navigation.setData(
PageEventListener.LISTENER_ATTRIBUTE_KEY,
new ActionEventListener() {
@Override
public void notify(final ActionEvent event) {
final ActivityActionHandler aah =
ActivitiesPane.this.activityActionHandler.get(event.actionDefinition);
if (aah != null) {
aah.notifyAction(event, navigation, pageContext);
// final ActivityActionHandler aah =
// ActivitiesPane.this.activityActionHandler.get(event.actionDefinition);
// if (aah != null) {
// aah.notifyAction(event, navigation, pageContext);
// }
// on case of an Action with ActivitySelection, reset the MainPageState
if (event.source instanceof ActivitySelection) {
final MainPageState mainPageState = MainPageState.get();
mainPageState.activitySelection = (ActivitySelection) event.source;
}
}
});
@ -172,11 +178,13 @@ public class ActivitiesPane implements TemplateComposer {
// page-selection on (re)load
final MainPageState mainPageState = MainPageState.get();
if (mainPageState.activitySelection == null) {
mainPageState.activitySelection = ActivitySelection.get(navigation.getItem(0));
if (mainPageState.activitySelection == null ||
mainPageState.activitySelection.activity == ActivitySelection.Activity.NONE) {
mainPageState.activitySelection = getActivitySelection(navigation.getItem(0));
}
pageContext.publishPageEvent(
new ActivitySelectionEvent(mainPageState.activitySelection));
navigation.select(navigation.getItem(0));
}
// private void runningExamExpand(final TreeItem item) {
@ -202,7 +210,7 @@ public class ActivitiesPane implements TemplateComposer {
System.out.println("opened: " + treeItem);
final ActivitySelection activity = ActivitySelection.get(treeItem);
final ActivitySelection activity = getActivitySelection(treeItem);
if (activity != null) {
activity.processExpand(treeItem);
}
@ -214,7 +222,7 @@ public class ActivitiesPane implements TemplateComposer {
System.out.println("selected: " + treeItem);
final MainPageState mainPageState = MainPageState.get();
final ActivitySelection activitySelection = ActivitySelection.get(treeItem);
final ActivitySelection activitySelection = getActivitySelection(treeItem);
if (mainPageState.activitySelection == null) {
mainPageState.activitySelection = Activity.NONE.createSelection();
}
@ -239,7 +247,7 @@ public class ActivitiesPane implements TemplateComposer {
static void createInstitutionItem(final EntityName entityName, final TreeItem institution) {
institution.setText(entityName.name);
ActivitySelection.inject(
injectActivitySelection(
institution,
Activity.INSTITUTION_NODE
.createSelection()
@ -256,7 +264,7 @@ public class ActivitiesPane implements TemplateComposer {
}
for (final TreeItem item : items) {
final ActivitySelection activitySelection = ActivitySelection.get(item);
final ActivitySelection activitySelection = getActivitySelection(item);
final String id = activitySelection.getEntityId();
if (activitySelection != null && activitySelection.activity == activity &&
(id == null || (objectId != null && objectId.equals(id)))) {
@ -285,4 +293,12 @@ public class ActivitiesPane implements TemplateComposer {
expand(item.getParentItem());
}
public static ActivitySelection getActivitySelection(final TreeItem item) {
return (ActivitySelection) item.getData(ATTR_ACTIVITY_SELECTION);
}
public static void injectActivitySelection(final TreeItem item, final ActivitySelection selection) {
item.setData(ATTR_ACTIVITY_SELECTION, selection);
}
}

View file

@ -20,6 +20,7 @@ import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
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.impl.TODOTemplate;
@ -39,7 +40,7 @@ public class ActivitySelection {
ActionPane.class,
new LocTextKey("sebserver.activities.inst")),
INSTITUTION_NODE(
TODOTemplate.class,
InstitutionForm.class,
ActionPane.class,
new LocTextKey("sebserver.activities.inst")),
//
@ -84,8 +85,6 @@ public class ActivitySelection {
}
}
private static final String ATTR_ACTIVITY_SELECTION = "ACTIVITY_SELECTION";
public final Activity activity;
final Map<String, String> attributes;
Consumer<TreeItem> expandFunction = EMPTY_FUNCTION;
@ -114,6 +113,11 @@ public class ActivitySelection {
return this;
}
public ActivitySelection withAttribute(final String name, final String value) {
this.attributes.put(name, value);
return this;
}
public Map<String, String> getAttributes() {
return Collections.unmodifiableMap(this.attributes);
}
@ -134,12 +138,4 @@ public class ActivitySelection {
return this.attributes.get(AttributeKeys.ENTITY_ID);
}
public static ActivitySelection get(final TreeItem item) {
return (ActivitySelection) item.getData(ATTR_ACTIVITY_SELECTION);
}
public static void inject(final TreeItem item, final ActivitySelection selection) {
item.setData(ATTR_ACTIVITY_SELECTION, selection);
}
}

View file

@ -0,0 +1,172 @@
/*
* 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.content;
import java.util.UUID;
import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
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.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.institution.Institution;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
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.PageContext.AttributeKeys;
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.page.event.ActionEventListener;
import ch.ethz.seb.sebserver.gui.service.page.form.FormHandle;
import ch.ethz.seb.sebserver.gui.service.page.form.PageFormService;
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.NewInstitution;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.SaveInstitution;
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
@Lazy
@Component
@GuiProfile
public class InstitutionForm implements TemplateComposer {
private final PageFormService pageFormService;
private final RestService restService;
protected InstitutionForm(final PageFormService pageFormService, final RestService restService) {
this.pageFormService = pageFormService;
this.restService = restService;
}
@Override
public void compose(final PageContext pageContext) {
final WidgetFactory widgetFactory = this.pageFormService.getWidgetFactory();
final boolean readonly = BooleanUtils.toBoolean(
pageContext.getAttribute(AttributeKeys.READ_ONLY, "true"));
final boolean createNew = BooleanUtils.toBoolean(
pageContext.getAttribute(AttributeKeys.CREATE_NEW, "false"));
// get data or create new and handle error
Institution institution = null;
PageContext formContext = pageContext;
if (createNew) {
institution = this.restService
.getBuilder(NewInstitution.class)
.withQueryParam(Domain.INSTITUTION.ATTR_NAME, "[NEW-" + UUID.randomUUID() + "]")
.call()
.get(pageContext::notifyError);
formContext = pageContext.withEntityKey(institution.getEntityKey());
} else {
final String instId = pageContext.getAttribute(AttributeKeys.ENTITY_ID);
institution = this.restService
.getBuilder(GetInstitution.class)
.withURIVariable(API.PATH_VAR_MODEL_ID_NAME, instId)
.call()
.get(pageContext::notifyError);
}
if (institution == null) {
// TODO should here be a forward to institution list page for SEB Admin?
return;
}
// page grid
final Composite content = new Composite(formContext.getParent(), SWT.NONE);
final GridLayout contentLayout = new GridLayout();
contentLayout.marginLeft = 10;
content.setLayout(contentLayout);
content.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
// title
final Label pageTitle = widgetFactory.labelLocalizedTitle(
content, new LocTextKey(
"sebserver.institution.form.title",
institution.name));
pageTitle.setLayoutData(new GridData(SWT.TOP, SWT.LEFT, true, false));
ActionEventListener.injectListener(
pageTitle,
ActionDefinition.INSTITUTION_SAVE,
event -> {
final Entity entity = (Entity) event.source;
widgetFactory.injectI18n(pageTitle, new LocTextKey(
"sebserver.institution.form.title",
entity.getName()));
content.layout();
});
// The Institution form
final FormHandle<Institution> formHandle = this.pageFormService.getBuilder(
formContext.copyOf(content), 4)
.readonly(readonly)
.putStaticValue("id", institution.getModelId())
.addTextField(
Domain.INSTITUTION.ATTR_NAME,
"sebserver.institution.form.name",
institution.name, 2)
.addEmptyCell()
.addTextField(
Domain.INSTITUTION.ATTR_URL_SUFFIX,
"sebserver.institution.form.urlSuffix",
institution.urlSuffix, 2)
.addEmptyCell()
.addImageUpload(
Domain.INSTITUTION.ATTR_LOGO_IMAGE,
"sebserver.institution.form.logoImage",
institution.logoImage, 2)
.addEmptyCell()
.addTextField(
Domain.INSTITUTION.ATTR_URL_SUFFIX,
"sebserver.institution.form.urlSuffix",
institution.urlSuffix, 2)
.buildFor(
this.restService.getRestCall(SaveInstitution.class),
InstitutionActions.postSaveAdapter(pageContext));
// propagate content actions to action-pane
if (readonly) {
formContext.createAction(ActionDefinition.INSTITUTION_NEW)
.withExec(InstitutionActions::newInstitution)
.publish()
.createAction(ActionDefinition.INSTITUTION_MODIFY)
.withExec(InstitutionActions::editInstitution)
.publish();
if (!institution.isActive()) {
formContext.createAction(ActionDefinition.INSTITUTION_ACTIVATE)
.withExec(InstitutionActions::activateInstitution)
.publish();
} else {
formContext.createAction(ActionDefinition.INSTITUTION_DEACTIVATE)
.withExec(InstitutionActions::deactivateInstitution)
.publish();
}
} else {
formContext.createAction(ActionDefinition.INSTITUTION_SAVE)
.withExec(formHandle::postChanges)
.publish()
.createAction(ActionDefinition.INSTITUTION_CANCEL_MODIFY)
.withExec(InstitutionActions::cancelEditInstitution)
.withConfirm("sebserver.overall.action.modify.cancel.confirm")
.publish();
}
}
}

View file

@ -83,8 +83,13 @@ public class InstitutionList implements TemplateComposer {
pageContext.createAction(ActionDefinition.INSTITUTION_NEW)
.withExec(InstitutionActions::newInstitution)
.publish()
.createAction(ActionDefinition.INSTITUTION_VIEW)
.withSelectionSupplier(table::getSelection)
.withExec(InstitutionActions::viewInstitution)
.publish()
.createAction(ActionDefinition.INSTITUTION_MODIFY)
.withExec(InstitutionActions.editInstitution(table))
.withSelectionSupplier(table::getSelection)
.withExec(InstitutionActions::editInstitutionFromList)
.publish();
}

View file

@ -22,14 +22,15 @@ public interface ActionEventListener extends PageEventListener<ActionEvent> {
return type == ActionEvent.class;
}
static ActionEventListener of(final Consumer<ActionEvent> eventConsumer) {
return new ActionEventListener() {
@Override
public void notify(final ActionEvent event) {
eventConsumer.accept(event);
}
};
}
// static ActionEventListener of(final Consumer<ActionEvent> eventConsumer) {
// return new ActionEventListener() {
// @Override
// public void notify(final ActionEvent event) {
// eventConsumer.accept(event);
// }
// };
// }
//
static ActionEventListener of(
final Predicate<ActionEvent> predicate,

View file

@ -27,7 +27,9 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
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;
import ch.ethz.seb.sebserver.gui.service.widget.ImageUpload;
import ch.ethz.seb.sebserver.gui.service.widget.SingleSelection;
public final class Form implements FormBinding {
@ -40,9 +42,17 @@ public final class Form implements FormBinding {
private final Map<String, List<Form>> subLists = new LinkedHashMap<>();
private final Map<String, Set<String>> groups = new LinkedHashMap<>();
Form(final JSONMapper jsonMapper) {
private final EntityKey entityKey;
Form(final JSONMapper jsonMapper, final EntityKey entityKey) {
this.jsonMapper = jsonMapper;
this.objectRoot = this.jsonMapper.createObjectNode();
this.entityKey = entityKey;
}
@Override
public EntityKey entityKey() {
return this.entityKey;
}
@Override
@ -91,6 +101,10 @@ public final class Form implements FormBinding {
}
}
public void putField(final String name, final Label label, final ImageUpload imageUpload) {
this.formFields.put(name, createAccessor(label, imageUpload));
}
public void putSubForm(final String name, final Form form) {
this.subForms.put(name, form);
}
@ -190,6 +204,13 @@ public final class Form implements FormBinding {
@Override public void setValue(final String value) { singleSelection.select(value); }
};
}
private FormFieldAccessor createAccessor(final Label label, final ImageUpload imageUpload) {
return new FormFieldAccessor(label, imageUpload) {
@Override public String getValue() { return imageUpload.getImageBase64(); }
@Override public void setValue(final String value) { imageUpload.setImageBase64(value); }
};
}
//@formatter:on
public static abstract class FormFieldAccessor {
@ -215,7 +236,6 @@ public final class Form implements FormBinding {
public void setError(final String errorTooltip) {
if (!this.hasError) {
this.control.setData(RWT.CUSTOM_VARIANT, "error");
//this.control.setBackground(new Color(this.control.getDisplay(), 255, 0, 0, 50));
this.control.setToolTipText(errorTooltip);
this.hasError = true;
}
@ -224,7 +244,6 @@ public final class Form implements FormBinding {
public void resetError() {
if (this.hasError) {
this.control.setData(RWT.CUSTOM_VARIANT, null);
//this.control.setBackground(new Color(this.control.getDisplay(), 0, 0, 0, 0));
this.control.setToolTipText(null);
this.hasError = false;
}

View file

@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.gui.service.page.form;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.SWT;
@ -23,10 +24,13 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
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.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.WidgetFactory;
public class FormBuilder {
@ -42,6 +46,7 @@ public class FormBuilder {
private boolean readonly = false;
FormBuilder(
final EntityKey entityKey,
final JSONMapper jsonMapper,
final WidgetFactory widgetFactory,
final PolyglotPageService polyglotPageService,
@ -51,7 +56,7 @@ public class FormBuilder {
this.widgetFactory = widgetFactory;
this.polyglotPageService = polyglotPageService;
this.pageContext = pageContext;
this.form = new Form(jsonMapper);
this.form = new Form(jsonMapper, entityKey);
this.formParent = new Composite(pageContext.getParent(), SWT.NONE);
final GridLayout layout = new GridLayout(rows, true);
@ -181,8 +186,48 @@ public class FormBuilder {
return this;
}
public <T> FormHandle<T> buildFor(final RestCall<T> post) {
return new FormHandle<>(this.pageContext, this.form, post, this.polyglotPageService.getI18nSupport());
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);
if (this.readonly) {
imageUpload.setReadonly();
this.form.putField(name, lab, imageUpload);
} else {
this.form.putField(name, lab, imageUpload);
}
return this;
}
public <T> FormHandle<T> buildFor(
final RestCall<T> post,
final Function<T, T> postPostHandle) {
return new FormHandle<>(
this.pageContext,
this.form,
post,
(postPostHandle == null) ? Function.identity() : postPostHandle,
this.polyglotPageService.getI18nSupport());
}
}

View file

@ -9,13 +9,16 @@
package ch.ethz.seb.sebserver.gui.service.page.form;
import java.util.function.Consumer;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
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.action.Action;
import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
import ch.ethz.seb.sebserver.gui.service.page.form.Form.FormFieldAccessor;
@ -27,31 +30,38 @@ public class FormHandle<T> {
private static final Logger log = LoggerFactory.getLogger(FormHandle.class);
public static final String FIELD_VALIDATION_LOCTEXT_PREFIX = "org.sebserver.form.validation.fieldError.";
public static final String FIELD_VALIDATION_LOCTEXT_PREFIX = "sebserver.form.validation.fieldError.";
private final PageContext pageContext;
private final Form form;
private final RestCall<T> post;
private final Function<T, T> postPostHandle;
private final I18nSupport i18nSupport;
FormHandle(
final PageContext pageContext,
final Form form,
final RestCall<T> post,
final Function<T, T> postPostHandle,
final I18nSupport i18nSupport) {
this.pageContext = pageContext;
this.form = form;
this.post = post;
this.postPostHandle = postPostHandle;
this.i18nSupport = i18nSupport;
}
public void doAPIPost(final ActionDefinition action) {
public final Result<T> postChanges(final Action action) {
return doAPIPost(action.definition);
}
public Result<T> doAPIPost(final ActionDefinition action) {
this.form.process(
name -> true,
fieldAccessor -> fieldAccessor.resetError());
this.post
return this.post
.newBuilder()
.withFormBinding(this.form)
.call()
@ -71,7 +81,8 @@ public class FormHandle<T> {
log.error("Unexpected error while trying to post form: ", error);
this.pageContext.notifyError(error);
}
});
})
.map(this.postPostHandle);
}
private final void showValidationError(

View file

@ -12,12 +12,15 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
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.widget.WidgetFactory;
@Lazy
@Component
@GuiProfile
public class PageFormService {
private final JSONMapper jsonMapper;
@ -34,8 +37,26 @@ public class PageFormService {
this.polyglotPageService = polyglotPageService;
}
public FormBuilder getBuilder(final PageContext pageContext, final int rows) {
public FormBuilder getBuilder(
final PageContext pageContext,
final int rows) {
return new FormBuilder(
pageContext.getEntityKey(),
this.jsonMapper,
this.widgetFactory,
this.polyglotPageService,
pageContext,
rows);
}
public FormBuilder getBuilder(
final EntityKey entityKey,
final PageContext pageContext,
final int rows) {
return new FormBuilder(
entityKey,
this.jsonMapper,
this.widgetFactory,
this.polyglotPageService,

View file

@ -29,7 +29,7 @@ public class DefaultLoginPage implements PageDefinition {
@Override
public PageContext applyPageContext(final PageContext pageContext) {
return pageContext.withAttr(
return pageContext.withAttribute(
AttributeKeys.PAGE_TEMPLATE_COMPOSER_NAME,
SEBLogin.class.getName());
}

View file

@ -29,7 +29,7 @@ public class DefaultMainPage implements PageDefinition {
@Override
public PageContext applyPageContext(final PageContext pageContext) {
return pageContext.withAttr(
return pageContext.withAttribute(
AttributeKeys.PAGE_TEMPLATE_COMPOSER_NAME,
SEBMainPage.class.getName());
}

View file

@ -21,6 +21,7 @@ import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.MessageBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
@ -37,6 +38,7 @@ import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.WebserviceURIService;
import ch.ethz.seb.sebserver.gui.service.widget.Message;
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory.CustomVariant;
@ -138,8 +140,15 @@ public class DefaultPageLayout implements TemplateComposer {
MainPageState.clear();
// forward to login page with success message
pageContext.forwardToLoginPage(
pageContext.withAttr(AttributeKeys.LGOUT_SUCCESS, "true"));
pageContext.forwardToLoginPage(pageContext);
// show successful logout message
final MessageBox logoutSuccess = new Message(
pageContext.getShell(),
this.polyglotPageService.getI18nSupport().getText("sebserver.logout"),
this.polyglotPageService.getI18nSupport().getText("sebserver.logout.success.message"),
SWT.ICON_INFORMATION);
logoutSuccess.open(null);
});
}
}
@ -184,7 +193,7 @@ public class DefaultPageLayout implements TemplateComposer {
logo.setBackgroundImage(new Image(pageContext.getShell().getDisplay(), input));
} catch (final Exception e) {
log.warn("Get institutional logo failed: ", e);
log.warn("Get institutional logo failed: {}", e.getMessage());
logo.setData(RWT.CUSTOM_VARIANT, "bgLogo");
}

View file

@ -24,6 +24,8 @@ import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.APIMessageError;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.page.ComposerService;
@ -99,7 +101,7 @@ public class PageContextImpl implements PageContext {
this.composerService,
this.root,
parent,
this.attributes);
new HashMap<>(this.attributes));
}
@Override
@ -117,7 +119,7 @@ public class PageContextImpl implements PageContext {
}
@Override
public PageContext withAttr(final String key, final String value) {
public PageContext withAttribute(final String key, final String value) {
final Map<String, String> attrs = new HashMap<>();
attrs.putAll(this.attributes);
attrs.put(key, value);
@ -131,13 +133,15 @@ public class PageContextImpl implements PageContext {
}
@Override
public PageContext withSelection(final ActivitySelection selection) {
public PageContext withSelection(final ActivitySelection selection, final boolean clearAttributes) {
if (selection == null) {
return this;
}
final Map<String, String> attrs = new HashMap<>();
attrs.putAll(this.attributes);
if (!clearAttributes) {
attrs.putAll(this.attributes);
}
attrs.putAll(selection.getAttributes());
return new PageContextImpl(
@ -163,11 +167,59 @@ public class PageContextImpl implements PageContext {
}
}
@Override
public EntityKey getEntityKey() {
if (hasAttribute(AttributeKeys.ENTITY_ID) && hasAttribute(AttributeKeys.ENTITY_TYPE)) {
return new EntityKey(
getAttribute(AttributeKeys.ENTITY_ID),
EntityType.valueOf(getAttribute(AttributeKeys.ENTITY_TYPE)));
}
return null;
}
@Override
public EntityKey getParentEntityKey() {
if (hasAttribute(AttributeKeys.PARENT_ENTITY_ID) && hasAttribute(AttributeKeys.PARENT_ENTITY_TYPE)) {
return new EntityKey(
getAttribute(AttributeKeys.PARENT_ENTITY_ID),
EntityType.valueOf(getAttribute(AttributeKeys.PARENT_ENTITY_TYPE)));
}
return null;
}
@Override
public PageContext withEntityKey(final EntityKey entityKey) {
return withAttribute(AttributeKeys.ENTITY_ID, entityKey.modelId)
.withAttribute(AttributeKeys.ENTITY_TYPE, entityKey.entityType.name());
}
@Override
public PageContext withParentEntityKey(final EntityKey entityKey) {
return withAttribute(AttributeKeys.PARENT_ENTITY_ID, entityKey.modelId)
.withAttribute(AttributeKeys.PARENT_ENTITY_TYPE, entityKey.entityType.name());
}
@Override
public boolean hasAttribute(final String name) {
return this.attributes.containsKey(name);
}
@Override
public PageContext removeAttribute(final String name) {
final Map<String, String> attrs = new HashMap<>();
attrs.putAll(this.attributes);
attrs.remove(name);
return new PageContextImpl(
this.restService,
this.i18nSupport,
this.composerService,
this.root,
this.parent,
attrs);
}
@Override
@SuppressWarnings("unchecked")
public <T extends PageEvent> void publishPageEvent(final T event) {
@ -282,8 +334,9 @@ public class PageContextImpl implements PageContext {
}
@Override
public void notifyError(final Throwable error) {
public <T> T notifyError(final Throwable error) {
notifyError(error.getMessage(), error);
return null;
}
@Override
@ -298,9 +351,7 @@ public class PageContextImpl implements PageContext {
}
MainPageState.clear();
forwardToLoginPage(this.withAttr(
AttributeKeys.AUTHORIZATION_FAILURE,
error.getMessage()));
forwardToLoginPage(this);
return null;
}

View file

@ -25,7 +25,6 @@ import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.SEBServerAuthorizationContext;
@ -57,14 +56,15 @@ public class SEBLogin implements TemplateComposer {
public void compose(final PageContext pageContext) {
final Composite parent = pageContext.getParent();
if (pageContext.hasAttribute((AttributeKeys.LGOUT_SUCCESS))) {
final MessageBox logoutSuccess = new Message(
pageContext.getShell(),
this.i18nSupport.getText("sebserver.logout"),
this.i18nSupport.getText("sebserver.logout.success.message"),
SWT.ICON_INFORMATION);
logoutSuccess.open(null);
}
// if (pageContext.hasAttribute((AttributeKeys.LGOUT_SUCCESS))) {
// final MessageBox logoutSuccess = new Message(
// pageContext.getShell(),
// this.i18nSupport.getText("sebserver.logout"),
// this.i18nSupport.getText("sebserver.logout.success.message"),
// SWT.ICON_INFORMATION);
// logoutSuccess.open(null);
// pageContext = pageContext.removeAttribute(AttributeKeys.LGOUT_SUCCESS);
// }
final Composite loginGroup = new Composite(parent, SWT.NONE);
final GridLayout rowLayout = new GridLayout();

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2018 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.push;
import java.util.function.Predicate;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
/** ServerPushContext defines the state of a server push session.
*
* @author anhefti */
public final class ServerPushContext {
private final Composite anchor;
private final Predicate<ServerPushContext> runAgain;
public ServerPushContext(final Composite anchor) {
this(anchor, context -> false);
}
public ServerPushContext(
final Composite anchor,
final Predicate<ServerPushContext> runAgain) {
this.anchor = anchor;
this.runAgain = runAgain;
}
public boolean runAgain() {
return this.runAgain.test(this);
}
public boolean isDisposed() {
return this.anchor.isDisposed();
}
public Display getDisplay() {
return this.anchor.getDisplay();
}
public Composite getAnchor() {
return this.anchor;
}
public void layout() {
this.anchor.pack();
this.anchor.layout();
this.anchor.getParent().layout(true, true);
}
}

View file

@ -0,0 +1,80 @@
/*
* Copyright (c) 2018 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.push;
import java.util.function.Consumer;
import org.eclipse.rap.rwt.service.ServerPushSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@Lazy
@Service
public class ServerPushService {
private static final Logger log = LoggerFactory.getLogger(ServerPushService.class);
public void runServerPush(
final ServerPushContext context,
final Consumer<ServerPushContext> business,
final Consumer<ServerPushContext> update) {
final ServerPushSession pushSession = new ServerPushSession();
pushSession.start();
final Thread bgThread = new Thread(() -> {
while (!context.isDisposed() && context.runAgain()) {
try {
log.trace("Call business on Server Push Session on: {}", Thread.currentThread().getName());
business.accept(context);
} catch (final Exception e) {
log.error("Unexpected error while do business for server push service", e);
if (context.runAgain()) {
continue;
} else {
return;
}
}
if (!context.isDisposed()) {
log.trace("Call update on Server Push Session on: {}", Thread.currentThread().getName());
context.getDisplay().asyncExec(() -> {
try {
update.accept(context);
} catch (final Exception e) {
log.warn(
"Failed to update on Server Push Session {}. It seems that the UISession is not available anymore. This may source from a connection interruption",
Thread.currentThread().getName(), e);
}
});
}
}
log.info("Stop Server Push Session on: {}", Thread.currentThread().getName());
try {
pushSession.stop();
} catch (final Exception e) {
log.warn(
"Failed to stop Server Push Session on: {}. It seems that the UISession is not available anymore. This may source from a connection interruption",
Thread.currentThread().getName(), e);
}
});
log.info("Start new Server Push Session on: {}", bgThread.getName());
bgThread.setDaemon(true);
bgThread.start();
}
}

View file

@ -8,8 +8,12 @@
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
public interface FormBinding {
EntityKey entityKey();
String getFormAsJson();
}

View file

@ -28,6 +28,7 @@ import org.springframework.web.client.RestClientResponseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.Entity;
@ -111,6 +112,7 @@ public abstract class RestCall<T> {
}));
} catch (final Exception e) {
log.error("Unexpected error-response while webservice API call for: {}", builder, e);
restCallError.errors.add(APIMessage.ErrorMessage.UNEXPECTED.of(e));
}
return Result.ofError(restCallError);
@ -147,6 +149,7 @@ public abstract class RestCall<T> {
public RestCallBuilder withBody(final Object body) {
if (body instanceof String) {
this.body = String.valueOf(body);
return this;
}
try {
@ -189,8 +192,8 @@ public abstract class RestCall<T> {
}
public RestCallBuilder withFormBinding(final FormBinding formBinding) {
// TODO Auto-generated method stub
return this;
return withURIVariable(API.PATH_VAR_MODEL_ID_NAME, formBinding.entityKey().modelId)
.withBody(formBinding.getFormAsJson());
}
public RestCallBuilder onlyActive(final boolean active) {

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class ActivateInstitution extends RestCall<EntityProcessingReport> {
protected ActivateInstitution() {
super(
new TypeReference<EntityProcessingReport>() {
},
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.INSTITUTION_ENDPOINT + API.PATH_VAR_ACTIVE);
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class DeactivateInstitution extends RestCall<EntityProcessingReport> {
protected DeactivateInstitution() {
super(
new TypeReference<EntityProcessingReport>() {
},
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.INSTITUTION_ENDPOINT + API.PATH_VAR_INACTIVE);
}
}

View file

@ -31,7 +31,7 @@ public class GetInstitution extends RestCall<Institution> {
},
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.INSTITUTION_ENDPOINT);
API.INSTITUTION_ENDPOINT + API.PATH_VAR_MODEL_ID);
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.institution.Institution;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class SaveInstitution extends RestCall<Institution> {
protected SaveInstitution() {
super(
new TypeReference<Institution>() {
},
HttpMethod.PUT,
MediaType.APPLICATION_JSON_UTF8,
API.INSTITUTION_ENDPOINT + API.PATH_VAR_MODEL_ID);
}
}

View file

@ -8,6 +8,8 @@
package ch.ethz.seb.sebserver.gui.service.remote.webservice.auth;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
@ -44,6 +46,18 @@ public class CurrentUser {
return null;
}
public UserInfo getOrHandleError(final Function<Throwable, UserInfo> errorHandler) {
if (isAvailable()) {
return this.authContext
.getLoggedInUser()
.get(errorHandler);
}
log.warn("Current user requested but no user is currently logged in");
return null;
}
public boolean isAvailable() {
updateContext();
return this.authContext != null && this.authContext.isLoggedIn();

View file

@ -9,9 +9,9 @@
package ch.ethz.seb.sebserver.gui.service.table;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.swt.SWT;
@ -25,6 +25,7 @@ import org.eclipse.swt.widgets.TableItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.util.Utils;
@ -153,16 +154,16 @@ public class EntityTable<ROW extends Entity> extends Composite {
this.sortOrder);
}
public Collection<String> getSelection() {
public Set<String> getSelection() {
final TableItem[] selection = this.table.getSelection();
if (selection == null) {
return Collections.emptyList();
return Collections.emptySet();
}
return Arrays.asList(selection)
.stream()
.map(this::getRowDataId)
.collect(Collectors.toList());
.collect(Collectors.toSet());
}
private void createTableColumns() {
@ -227,7 +228,11 @@ public class EntityTable<ROW extends Entity> extends Composite {
// TODO set an image or HTML with checkbox
item.setText(index, String.valueOf(value));
} else {
item.setText(index, String.valueOf(value));
if (value != null) {
item.setText(index, String.valueOf(value));
} else {
item.setText(index, Constants.EMPTY_NOTE);
}
}
index++;
}

View file

@ -0,0 +1,149 @@
/*
* 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 java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import org.apache.commons.codec.binary.Base64InputStream;
import org.eclipse.rap.fileupload.FileDetails;
import org.eclipse.rap.fileupload.FileUploadHandler;
import org.eclipse.rap.fileupload.FileUploadReceiver;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.widgets.FileUpload;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
public class ImageUpload extends Composite {
private static final long serialVersionUID = 368264811155804533L;
private static final Logger log = LoggerFactory.getLogger(ImageUpload.class);
private final ServerPushService serverPushService;
final Composite imageCanvas;
final FileUpload fileUpload;
private String imageBase64 = null;
private boolean loadNewImage = false;
private boolean imageLoaded = false;
ImageUpload(final Composite parent, final ServerPushService serverPushService) {
super(parent, SWT.NONE);
super.setLayout(new GridLayout(2, false));
this.serverPushService = serverPushService;
this.fileUpload = new FileUpload(this, SWT.NONE);
this.fileUpload.setText("Select File");
this.fileUpload.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
this.imageCanvas = new Composite(this, SWT.NONE);
final GridData canvas = new GridData(SWT.FILL, SWT.FILL, true, true);
this.imageCanvas.setLayoutData(canvas);
final FileUploadHandler uploadHandler = new FileUploadHandler(new FileUploadReceiver() {
@Override
public void receive(final InputStream stream, final FileDetails details) throws IOException {
try {
final String contentType = details.getContentType();
if (contentType != null && contentType.startsWith("image")) {
ImageUpload.this.imageBase64 = Base64.getEncoder().encodeToString(stream.readAllBytes());
}
} catch (final Exception e) {
log.error("Error while trying to upload image", e);
} finally {
ImageUpload.this.imageLoaded = true;
stream.close();
}
}
});
this.fileUpload.addSelectionListener(new SelectionAdapter() {
private static final long serialVersionUID = -6776734104137568801L;
@Override
public void widgetSelected(final SelectionEvent event) {
ImageUpload.this.loadNewImage = true;
ImageUpload.this.imageLoaded = false;
ImageUpload.this.fileUpload.submit(uploadHandler.getUploadUrl());
ImageUpload.this.serverPushService.runServerPush(
new ServerPushContext(
ImageUpload.this,
runAgainContext -> {
final ImageUpload imageUpload = (ImageUpload) runAgainContext.getAnchor();
return imageUpload.loadNewImage && !imageUpload.imageLoaded;
}),
context -> {
try {
Thread.sleep(200);
} catch (final Exception e) {
e.printStackTrace();
}
},
context -> {
final ImageUpload imageUpload = (ImageUpload) context.getAnchor();
if (imageUpload.imageBase64 != null
&& imageUpload.loadNewImage
&& imageUpload.imageLoaded) {
final Base64InputStream input = new Base64InputStream(
new ByteArrayInputStream(
imageUpload.imageBase64.getBytes(StandardCharsets.UTF_8)),
false);
imageUpload.imageCanvas.setData(RWT.CUSTOM_VARIANT, "bgLogoNoImage");
imageUpload.imageCanvas.setBackgroundImage(new Image(context.getDisplay(), input));
context.layout();
imageUpload.layout();
imageUpload.loadNewImage = false;
}
});
}
});
}
public String getImageBase64() {
return this.imageBase64;
}
public void setImageBase64(final String imageBase64) {
if (imageBase64 == null) {
return;
}
this.imageBase64 = imageBase64;
final Base64InputStream input = new Base64InputStream(
new ByteArrayInputStream(imageBase64.getBytes(StandardCharsets.UTF_8)), false);
this.imageCanvas.setData(RWT.CUSTOM_VARIANT, "bgLogoNoImage");
this.imageCanvas.setBackgroundImage(new Image(super.getDisplay(), input));
}
public void setReadonly() {
this.fileUpload.setVisible(false);
this.fileUpload.setEnabled(false);
}
}

View file

@ -16,6 +16,7 @@ import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Device;
@ -39,6 +40,7 @@ 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;
@ -47,6 +49,7 @@ import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
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.push.ServerPushService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import ch.ethz.seb.sebserver.gui.service.table.TableBuilder;
@ -78,6 +81,10 @@ public class WidgetFactory {
MAXIMIZE("maximize.png"),
MINIMIZE("minimize.png"),
MODIFY_ACTION("editAction.png"),
CANCEL_ACTION("cancelEditAction.png"),
VIEW_ACTION("viewAction.png"),
ACTIVATE_ACTION("inactive.png"),
DEACTIVATE_ACTION("active.png"),
SAVE_ACTION("saveAction.png"),
NEW_ACTION("newAction.png"),
DELETE_ACTION("deleteAction.png"),
@ -108,10 +115,15 @@ public class WidgetFactory {
private final PolyglotPageService polyglotPageService;
private final I18nSupport i18nSupport;
private final ServerPushService serverPushService;
public WidgetFactory(
final PolyglotPageService polyglotPageService,
final ServerPushService serverPushService) {
public WidgetFactory(final PolyglotPageService polyglotPageService) {
this.polyglotPageService = polyglotPageService;
this.i18nSupport = polyglotPageService.getI18nSupport();
this.serverPushService = serverPushService;
}
public Button buttonLocalized(final Composite parent, final String locTextKey) {
@ -265,7 +277,7 @@ public class WidgetFactory {
public Label formValueLabel(final Composite parent, final String value, final int span) {
final Label label = new Label(parent, SWT.NONE);
label.setText(value);
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;
@ -280,7 +292,9 @@ public class WidgetFactory {
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false, hspan, vspan);
gridData.heightHint = 15;
textInput.setLayoutData(gridData);
textInput.setText(value);
if (value != null) {
textInput.setText(value);
}
return textInput;
}
@ -325,6 +339,31 @@ public class WidgetFactory {
return combo;
}
public ImageUpload formImageUpload(
final Composite parent,
final String value,
final LocTextKey locTextKey,
final int hspan, final int vspan) {
final ImageUpload imageUpload = imageUploadLocalized(parent, locTextKey);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false, hspan, vspan);
imageUpload.setLayoutData(gridData);
imageUpload.setImageBase64(value);
return imageUpload;
}
public ImageUpload imageUploadLocalized(final Composite parent, final LocTextKey locTextKey) {
final ImageUpload imageUpload = new ImageUpload(parent, this.serverPushService);
injectI18n(imageUpload, locTextKey);
return imageUpload;
}
public void injectI18n(final ImageUpload imageUpload, final LocTextKey locTextKey) {
final Consumer<ImageUpload> imageUploadFunction = imageUploadFunction(locTextKey, this.i18nSupport);
imageUpload.setData(POLYGLOT_WIDGET_FUNCTION_KEY, imageUploadFunction);
imageUploadFunction.accept(imageUpload);
}
public void injectI18n(final Label label, final LocTextKey locTextKey) {
injectI18n(label, locTextKey, null);
}
@ -409,6 +448,17 @@ public class WidgetFactory {
}
}
private static Consumer<ImageUpload> imageUploadFunction(
final LocTextKey locTextKey,
final I18nSupport i18nSupport) {
return imageUpload -> {
if (locTextKey != null) {
imageUpload.fileUpload.setText(i18nSupport.getText(locTextKey));
}
};
}
private static Consumer<Tree> treeFunction(final I18nSupport i18nSupport) {
return tree -> updateLocale(tree.getItems(), i18nSupport);
}

View file

@ -8,6 +8,7 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
/** Defines a authorization grant rule for a specified EntityType.

View file

@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
import java.security.Principal;
import java.util.function.Predicate;
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
import ch.ethz.seb.sebserver.gbl.util.Result;

View file

@ -20,11 +20,13 @@ import javax.annotation.PostConstruct;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.authorization.Privilege;
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.authorization.Privilege.RoleTypeKey;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.Privilege.RoleTypeKey;
@Lazy
@Service
@ -430,6 +432,7 @@ public class AuthorizationGrantServiceImpl implements AuthorizationGrantService
public void create() {
final RoleTypeKey roleTypeKey = new RoleTypeKey(this.entityType, this.userRole);
final Privilege roleTypeGrant = new Privilege(
roleTypeKey,
this.basePrivilege,
this.institutionalPrivilege,
this.ownerPrivilege);

View file

@ -8,6 +8,7 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
public class PermissionDeniedException extends RuntimeException {

View file

@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
@ -25,7 +26,6 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserRecordDynamic
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.GrantEntity;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkAction;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkAction.Type;

View file

@ -28,8 +28,9 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
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;
@ -41,7 +42,6 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.GrantEntity;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkAction;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkAction.Type;
@ -103,10 +103,8 @@ public abstract class EntityController<T extends GrantEntity, M extends GrantEnt
@RequestParam final MultiValueMap<String, String> allRequestParams) {
checkReadPrivilege(institutionId);
final FilterMap filterMap = new FilterMap(allRequestParams);
allRequestParams.putIfAbsent(
Entity.FILTER_ATTR_INSTITUTION,
Arrays.asList(String.valueOf(institutionId)));
final FilterMap filterMap = new FilterMap(allRequestParams)
.putIfAbsent(Entity.FILTER_ATTR_INSTITUTION, String.valueOf(institutionId));
return this.paginationService.getPage(
pageNumber,
@ -133,10 +131,8 @@ public abstract class EntityController<T extends GrantEntity, M extends GrantEnt
@RequestParam final MultiValueMap<String, String> allRequestParams) {
checkReadPrivilege(institutionId);
final FilterMap filterMap = new FilterMap(allRequestParams);
allRequestParams.putIfAbsent(
Entity.FILTER_ATTR_INSTITUTION,
Arrays.asList(String.valueOf(institutionId)));
final FilterMap filterMap = new FilterMap(allRequestParams)
.putIfAbsent(Entity.FILTER_ATTR_INSTITUTION, String.valueOf(institutionId));
return getAll(filterMap)
.getOrThrow()
@ -211,10 +207,10 @@ public abstract class EntityController<T extends GrantEntity, M extends GrantEnt
PrivilegeType.WRITE,
institutionId);
allRequestParams.putIfAbsent(
Domain.ATTR_INSTITUTION_ID,
Arrays.asList(String.valueOf(institutionId)));
final M requestModel = this.createNew(new POSTMapper(allRequestParams));
final POSTMapper postMap = new POSTMapper(allRequestParams)
.putIfAbsent(Domain.ATTR_INSTITUTION_ID, String.valueOf(institutionId));
final M requestModel = this.createNew(postMap);
return this.beanValidationService.validateBean(requestModel)
.flatMap(this.entityDAO::createNew)

View file

@ -31,6 +31,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP;
import ch.ethz.seb.sebserver.gbl.model.Entity;
@ -46,7 +47,6 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamic
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService.SortOrder;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;

View file

@ -22,6 +22,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
@ -30,7 +31,6 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.LmsSetupRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;

View file

@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
@ -23,7 +24,6 @@ import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;

View file

@ -18,6 +18,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
@ -27,7 +28,6 @@ import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;

View file

@ -6,6 +6,10 @@ sebserver.overall.version=SEB Server Version : {0}
sebserver.overall.imprint=Imprint
sebserver.overall.about=About
sebserver.overall.message.leave.without.save=You are leaving this page without saved changes!\nThe unsaved changes will be lost.\Are you sure to leave the page?
sebserver.overall.upload=Please Select
sebserver.overall.action.modify.cancel=Cancel
sebserver.overall.action.modify.cancel.confirm=Are you sure to cancel? Modifications will be lost.
################################
# Login Page
@ -42,11 +46,24 @@ sebserver.institution.list.title=Institutions
sebserver.institution.list.column.name=Name
sebserver.institution.list.column.urlSuffix=URL Suffix
sebserver.institution.list.column.active=Active
sebserver.institution.action.new=New Institution
sebserver.institution.action.modify=Edit Institution
sebserver.institution.action.delete=Delete Institution
sebserver.institution.info.pleaseSelect=Please Select an Institution from the Table first.
sebserver.institution.action.new=New Institution
sebserver.institution.action.view=View Institution
sebserver.institution.action.modify=Edit Institution
sebserver.institution.action.save=Save Institution
sebserver.institution.action.activate=Active
sebserver.institution.action.deactivate=Active
sebserver.institution.action.delete=Delete Institution
sebserver.institution.info.pleaseSelect=Please Select an Institution from the Table first.
sebserver.institution.form.title=Institution ({0})
sebserver.institution.form.name=Name
sebserver.institution.form.urlSuffix=URL Suffix
sebserver.institution.form.logoImage=Logo Image
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
################################
# Form validation
################################

View file

@ -163,7 +163,7 @@ Text.error {
color: #4a4a4a;
background-repeat: repeat;
background-position: left top;
background-color: #ff0000;
background-color: #A8322D;
background-image: none;
text-shadow: none;
box-shadow: none;
@ -258,7 +258,9 @@ Button {
/* Push Buttons */
Button[PUSH],
Button[PUSH]:default {
Button[PUSH]:default,
FileUpload,
FileUpload:default {
font: bold 12px Arial, Helvetica, sans-serif;
background-color: #0069B4;
background-gradient-color: #0069B4;
@ -270,14 +272,16 @@ Button[PUSH]:default {
text-shadow: none;
}
Button[PUSH]:pressed {
Button[PUSH]:pressed,
FileUpload:pressed {
background-color: #444;
color: #fff;
background-gradient-color: #444;
background-image: gradient( linear, left top, left bottom, from( #444 ), to( #444 ) );
}
Button[PUSH]:hover {
Button[PUSH]:hover,
FileUpload:hover {
background-color: #82BE1E;
background-gradient-color: #82BE1E;
background-image: gradient( linear, left top, left bottom, from( #82BE1E ), to( #82BE1E ) );
@ -285,7 +289,8 @@ Button[PUSH]:hover {
cursor: pointer;
}
Button[PUSH]:disabled {
Button[PUSH]:disabled,
FileUpload:disabled {
background-color: transparent;
border: 1px solid #EAECEE;
color: #c0c0c0;
@ -293,7 +298,7 @@ Button[PUSH]:disabled {
background-position: right;
}
Button-FocusIndicator[PUSH] {
Button-FocusIndicator[PUSH][BORDER] {
background-color: transparent;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gbl.model.institution;
import java.io.IOException;
import org.junit.Test;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
public class InstitutionTest {
@Test
public void test() throws JsonParseException, JsonMappingException, IOException {
final String testJson = "{\"id\":\"1\",\"name\":\"ETH Zürichrgerg\",\"urlSuffix\":\"\"}";
final JSONMapper mapper = new JSONMapper();
final Institution inst = mapper.readValue(testJson, Institution.class);
}
}

View file

@ -23,6 +23,7 @@ import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;