From 377df32f72eef1111d622d15b91acf7abe6da5fd Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 13 Feb 2019 14:40:22 +0100 Subject: [PATCH] SEBSERV-27 #Institution Form and actions, refactoring --- pom.xml | 13 +- .../java/ch/ethz/seb/sebserver/SEBServer.java | 15 ++ .../ch/ethz/seb/sebserver/gbl/api/API.java | 5 + .../seb/sebserver/gbl/api/POSTMapper.java | 30 ++- .../authorization/EntityPrivileges.java} | 6 +- .../authorization/Privilege.java | 8 +- .../gbl/authorization/PrivilegeLevel.java | 15 ++ .../authorization/PrivilegeType.java | 2 +- .../ethz/seb/sebserver/gbl/model/Entity.java | 5 + .../seb/sebserver/gbl/model/EntityKey.java | 7 + .../gbl/model/EntityProcessingReport.java | 12 ++ .../gbl/model/institution/Institution.java | 5 +- .../gui/service/page/PageContext.java | 27 ++- .../gui/service/page/action/Action.java | 6 +- .../service/page/action/ActionDefinition.java | 20 +- .../page/action/InstitutionActions.java | 127 +++++++------ .../service/page/activity/ActivitiesPane.java | 62 ++++--- .../page/activity/ActivitySelection.java | 18 +- .../service/page/content/InstitutionForm.java | 172 ++++++++++++++++++ .../service/page/content/InstitutionList.java | 7 +- .../page/event/ActionEventListener.java | 17 +- .../sebserver/gui/service/page/form/Form.java | 25 ++- .../gui/service/page/form/FormBuilder.java | 51 +++++- .../gui/service/page/form/FormHandle.java | 19 +- .../service/page/form/PageFormService.java | 23 ++- .../service/page/impl/DefaultLoginPage.java | 2 +- .../service/page/impl/DefaultMainPage.java | 2 +- .../service/page/impl/DefaultPageLayout.java | 15 +- .../service/page/impl/PageContextImpl.java | 67 ++++++- .../gui/service/page/impl/SEBLogin.java | 18 +- .../gui/service/push/ServerPushContext.java | 58 ++++++ .../gui/service/push/ServerPushService.java | 80 ++++++++ .../remote/webservice/api/FormBinding.java | 4 + .../remote/webservice/api/RestCall.java | 7 +- .../api/institution/ActivateInstitution.java | 37 ++++ .../institution/DeactivateInstitution.java | 37 ++++ .../api/institution/GetInstitution.java | 2 +- .../api/institution/SaveInstitution.java | 37 ++++ .../remote/webservice/auth/CurrentUser.java | 14 ++ .../gui/service/table/EntityTable.java | 15 +- .../gui/service/widget/ImageUpload.java | 149 +++++++++++++++ .../gui/service/widget/WidgetFactory.java | 56 +++++- .../authorization/AuthorizationGrantRule.java | 1 + .../AuthorizationGrantService.java | 1 + .../AuthorizationGrantServiceImpl.java | 5 +- .../PermissionDeniedException.java | 1 + .../api/ActivatableEntityController.java | 2 +- .../weblayer/api/EntityController.java | 24 +-- .../api/ExamAdministrationController.java | 2 +- .../weblayer/api/LmsSetupController.java | 2 +- .../weblayer/api/QuizImportController.java | 2 +- .../api/UserActivityLogController.java | 2 +- src/main/resources/messages.properties | 25 ++- src/main/resources/static/css/sebserver.css | 17 +- src/main/resources/static/images/active.png | Bin 0 -> 169 bytes .../resources/static/images/cancelAction.png | Bin 0 -> 246 bytes .../static/images/cancelEditAction.png | Bin 0 -> 168 bytes src/main/resources/static/images/inactive.png | Bin 0 -> 117 bytes .../resources/static/images/viewAction.png | Bin 0 -> 211 bytes .../model/institution/InstitutionTest.java | 30 +++ .../AuthorizationGrantServiceTest.java | 1 + 61 files changed, 1222 insertions(+), 190 deletions(-) rename src/main/java/ch/ethz/seb/sebserver/{gui/service/page/content/Institution.java => gbl/authorization/EntityPrivileges.java} (74%) rename src/main/java/ch/ethz/seb/sebserver/{webservice/servicelayer => gbl}/authorization/Privilege.java (90%) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gbl/authorization/PrivilegeLevel.java rename src/main/java/ch/ethz/seb/sebserver/{webservice/servicelayer => gbl}/authorization/PrivilegeType.java (93%) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/InstitutionForm.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/push/ServerPushContext.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/push/ServerPushService.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/ActivateInstitution.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/DeactivateInstitution.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/SaveInstitution.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/widget/ImageUpload.java create mode 100644 src/main/resources/static/images/active.png create mode 100644 src/main/resources/static/images/cancelAction.png create mode 100644 src/main/resources/static/images/cancelEditAction.png create mode 100644 src/main/resources/static/images/inactive.png create mode 100644 src/main/resources/static/images/viewAction.png create mode 100644 src/test/java/ch/ethz/seb/sebserver/gbl/model/institution/InstitutionTest.java diff --git a/pom.xml b/pom.xml index c7d905e6..de4569e5 100644 --- a/pom.xml +++ b/pom.xml @@ -152,10 +152,10 @@ ch/ethz/seb/sebserver/* - - - - + + + + @@ -270,6 +270,11 @@ org.eclipse.rap.rwt 3.5.0 + + org.eclipse.rap + org.eclipse.rap.fileupload + 3.7.0 + diff --git a/src/main/java/ch/ethz/seb/sebserver/SEBServer.java b/src/main/java/ch/ethz/seb/sebserver/SEBServer.java index 7b9e03ee..ffa57ef9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/SEBServer.java +++ b/src/main/java/ch/ethz/seb/sebserver/SEBServer.java @@ -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, diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index 3580a573..6ec6d19e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -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; + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/POSTMapper.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/POSTMapper.java index 76e1732e..903e9843 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/POSTMapper.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/POSTMapper.java @@ -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 params) { super(); - this.params = params; + this.params = new LinkedMultiValueMap<>(); + if (params != null) { + for (final Map.Entry> 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 putIfAbsent(final String name, final String value) { + this.params.putIfAbsent(name, Arrays.asList(value)); + return (T) this; + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/Institution.java b/src/main/java/ch/ethz/seb/sebserver/gbl/authorization/EntityPrivileges.java similarity index 74% rename from src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/Institution.java rename to src/main/java/ch/ethz/seb/sebserver/gbl/authorization/EntityPrivileges.java index c9cac7a3..1282a9f6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/Institution.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/authorization/EntityPrivileges.java @@ -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 { } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/Privilege.java b/src/main/java/ch/ethz/seb/sebserver/gbl/authorization/Privilege.java similarity index 90% rename from src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/Privilege.java rename to src/main/java/ch/ethz/seb/sebserver/gbl/authorization/Privilege.java index 5e6afef1..e89b0dc7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/Privilege.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/authorization/Privilege.java @@ -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; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/authorization/PrivilegeLevel.java b/src/main/java/ch/ethz/seb/sebserver/gbl/authorization/PrivilegeLevel.java new file mode 100644 index 00000000..4e2e11ff --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/authorization/PrivilegeLevel.java @@ -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 +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/PrivilegeType.java b/src/main/java/ch/ethz/seb/sebserver/gbl/authorization/PrivilegeType.java similarity index 93% rename from src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/PrivilegeType.java rename to src/main/java/ch/ethz/seb/sebserver/gbl/authorization/PrivilegeType.java index 9139c35e..b59da376 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/PrivilegeType.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/authorization/PrivilegeType.java @@ -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 { diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/Entity.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/Entity.java index d3bc93ff..67588d1d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/Entity.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/Entity.java @@ -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(), diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityKey.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityKey.java index 259d8aab..5a287d6e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityKey.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityKey.java @@ -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; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityProcessingReport.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityProcessingReport.java index 68a9c902..8d6b5580 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityProcessingReport.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityProcessingReport.java @@ -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; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/Institution.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/Institution.java index 36133067..1ca2ac72 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/Institution.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/Institution.java @@ -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) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java index ea1bc436..63de0b8a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java @@ -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 notifyError(Throwable error); T logoutOnError(Throwable error); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java index 250bb662..dbf08893 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java @@ -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()); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/ActionDefinition.java index ae4e915f..3d43e4f1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/ActionDefinition.java @@ -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( diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/InstitutionActions.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/InstitutionActions.java index 40efa674..8862b808 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/InstitutionActions.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/InstitutionActions.java @@ -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> editInstitution(final EntityTable fromTable) { - return action -> { - final Collection 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 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 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; + } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitiesPane.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitiesPane.java index 804be34b..76facaa7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitiesPane.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitiesPane.java @@ -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 activityActionHandler = new EnumMap<>(ActionDefinition.class); public ActivitiesPane( final WidgetFactory widgetFactory, final RestService restService, - final AuthorizationContextHolder authorizationContextHolder, + final CurrentUser currentUser, final Collection 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); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitySelection.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitySelection.java index 29a7f7e9..03eabe17 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitySelection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitySelection.java @@ -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 attributes; Consumer 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 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); - } - } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/InstitutionForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/InstitutionForm.java new file mode 100644 index 00000000..fc09f1d6 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/InstitutionForm.java @@ -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 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(); + + } + + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/InstitutionList.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/InstitutionList.java index c7b121b6..33b834fa 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/InstitutionList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/InstitutionList.java @@ -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(); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEventListener.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEventListener.java index 14f302e2..b018c425 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEventListener.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEventListener.java @@ -22,14 +22,15 @@ public interface ActionEventListener extends PageEventListener { return type == ActionEvent.class; } - static ActionEventListener of(final Consumer eventConsumer) { - return new ActionEventListener() { - @Override - public void notify(final ActionEvent event) { - eventConsumer.accept(event); - } - }; - } +// static ActionEventListener of(final Consumer eventConsumer) { +// return new ActionEventListener() { +// @Override +// public void notify(final ActionEvent event) { +// eventConsumer.accept(event); +// } +// }; +// } +// static ActionEventListener of( final Predicate predicate, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/Form.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/Form.java index 0c754a8b..7d07f9c3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/Form.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/Form.java @@ -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> subLists = new LinkedHashMap<>(); private final Map> 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; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/FormBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/FormBuilder.java index 8a62427c..eb78f6de 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/FormBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/FormBuilder.java @@ -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 FormHandle buildFor(final RestCall 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 FormHandle buildFor( + final RestCall post, + final Function postPostHandle) { + + return new FormHandle<>( + this.pageContext, + this.form, + post, + (postPostHandle == null) ? Function.identity() : postPostHandle, + this.polyglotPageService.getI18nSupport()); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/FormHandle.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/FormHandle.java index 0827e24f..92ec7804 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/FormHandle.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/FormHandle.java @@ -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 { 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 post; + private final Function postPostHandle; private final I18nSupport i18nSupport; FormHandle( final PageContext pageContext, final Form form, final RestCall post, + final Function 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 postChanges(final Action action) { + return doAPIPost(action.definition); + } + + public Result 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 { log.error("Unexpected error while trying to post form: ", error); this.pageContext.notifyError(error); } - }); + }) + .map(this.postPostHandle); } private final void showValidationError( diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/PageFormService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/PageFormService.java index e9d1c2dd..718ad67a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/PageFormService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/PageFormService.java @@ -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, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultLoginPage.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultLoginPage.java index d1bc02ae..1ab66970 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultLoginPage.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultLoginPage.java @@ -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()); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultMainPage.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultMainPage.java index 6735963d..20428438 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultMainPage.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultMainPage.java @@ -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()); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultPageLayout.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultPageLayout.java index 4bef2edb..49e9a9a1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultPageLayout.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultPageLayout.java @@ -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"); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java index b5f21e18..8f7b2a11 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java @@ -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 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 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 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 void publishPageEvent(final T event) { @@ -282,8 +334,9 @@ public class PageContextImpl implements PageContext { } @Override - public void notifyError(final Throwable error) { + public 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; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/SEBLogin.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/SEBLogin.java index 4468f90f..4468ff5d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/SEBLogin.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/SEBLogin.java @@ -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(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/push/ServerPushContext.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/push/ServerPushContext.java new file mode 100644 index 00000000..d4309ce3 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/push/ServerPushContext.java @@ -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 runAgain; + + public ServerPushContext(final Composite anchor) { + this(anchor, context -> false); + } + + public ServerPushContext( + final Composite anchor, + final Predicate 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); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/push/ServerPushService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/push/ServerPushService.java new file mode 100644 index 00000000..f9892627 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/push/ServerPushService.java @@ -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 business, + final Consumer 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(); + } +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/FormBinding.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/FormBinding.java index 5192e8d5..9193b177 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/FormBinding.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/FormBinding.java @@ -8,8 +8,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(); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java index 2257b69f..efea4f8e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java @@ -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 { })); } 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 { 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 { } 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) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/ActivateInstitution.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/ActivateInstitution.java new file mode 100644 index 00000000..ac0a33a9 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/ActivateInstitution.java @@ -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 { + + protected ActivateInstitution() { + super( + new TypeReference() { + }, + HttpMethod.POST, + MediaType.APPLICATION_FORM_URLENCODED, + API.INSTITUTION_ENDPOINT + API.PATH_VAR_ACTIVE); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/DeactivateInstitution.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/DeactivateInstitution.java new file mode 100644 index 00000000..534874c6 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/DeactivateInstitution.java @@ -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 { + + protected DeactivateInstitution() { + super( + new TypeReference() { + }, + HttpMethod.POST, + MediaType.APPLICATION_FORM_URLENCODED, + API.INSTITUTION_ENDPOINT + API.PATH_VAR_INACTIVE); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/GetInstitution.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/GetInstitution.java index 08e9c7c8..f748f883 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/GetInstitution.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/GetInstitution.java @@ -31,7 +31,7 @@ public class GetInstitution extends RestCall { }, HttpMethod.GET, MediaType.APPLICATION_FORM_URLENCODED, - API.INSTITUTION_ENDPOINT); + API.INSTITUTION_ENDPOINT + API.PATH_VAR_MODEL_ID); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/SaveInstitution.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/SaveInstitution.java new file mode 100644 index 00000000..380d1713 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/SaveInstitution.java @@ -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 { + + protected SaveInstitution() { + super( + new TypeReference() { + }, + HttpMethod.PUT, + MediaType.APPLICATION_JSON_UTF8, + API.INSTITUTION_ENDPOINT + API.PATH_VAR_MODEL_ID); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/CurrentUser.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/CurrentUser.java index 0725da60..86f5bda8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/CurrentUser.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/CurrentUser.java @@ -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 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(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/table/EntityTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/table/EntityTable.java index fa9d9851..fe7d258e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/table/EntityTable.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/table/EntityTable.java @@ -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 extends Composite { this.sortOrder); } - public Collection getSelection() { + public Set 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 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++; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/ImageUpload.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/ImageUpload.java new file mode 100644 index 00000000..e686b967 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/ImageUpload.java @@ -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); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/WidgetFactory.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/WidgetFactory.java index c3194a71..e3056745 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/WidgetFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/WidgetFactory.java @@ -16,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 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 imageUploadFunction( + final LocTextKey locTextKey, + final I18nSupport i18nSupport) { + + return imageUpload -> { + if (locTextKey != null) { + imageUpload.fileUpload.setText(i18nSupport.getText(locTextKey)); + } + }; + } + private static Consumer treeFunction(final I18nSupport i18nSupport) { return tree -> updateLocale(tree.getItems(), i18nSupport); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationGrantRule.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationGrantRule.java index 728fbdd3..9d61d8d8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationGrantRule.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationGrantRule.java @@ -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. diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationGrantService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationGrantService.java index 394eec7c..fba56339 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationGrantService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationGrantService.java @@ -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; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationGrantServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationGrantServiceImpl.java index 5ebc5d52..1bed9fbc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationGrantServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationGrantServiceImpl.java @@ -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); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/PermissionDeniedException.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/PermissionDeniedException.java index 91cee845..e82552f9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/PermissionDeniedException.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/PermissionDeniedException.java @@ -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 { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java index 8a3b0da7..ed4f13b0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java @@ -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; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java index f63d5b1e..f501c760 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java @@ -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 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 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 EntityControllerqfWe->Of!p>C2$lx-t}3TGH)H1c zm6n-Mg8|!mEtz!fONbxK*O`a95%q z>Kr(FhjG~RAIZ@da8rj>hG47`nk#Tgl7@|>%cpW(-B0Bf5q6r58-%(5{^IeSNjd_B wVEKKRWRBn0LT-~Rcq5lkn8sJxn=1Uq3y-*{|K{xSK>z>%07*qoM6N<$g0Jpy;{X5v literal 0 HcmV?d00001 diff --git a/src/main/resources/static/images/cancelEditAction.png b/src/main/resources/static/images/cancelEditAction.png new file mode 100644 index 0000000000000000000000000000000000000000..0240772a9cfee94f2aab7ecee60f6e2ea2a54af0 GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rhed`}n0kP60Ri7g8_JNPGb&Q5I0 zVE$OIu+O1QQG7vFsRNs3gGIuul!go5;?1iXc1U;L=Rd;wMB)mQXg-r9Q>yvo*IV-@ z$cTIo7IORR!B#psQ{a&GiF+!?7jT)pFKIsVLh|nv=_SrmdKI;Vst0I54UVgLXD literal 0 HcmV?d00001 diff --git a/src/main/resources/static/images/inactive.png b/src/main/resources/static/images/inactive.png new file mode 100644 index 0000000000000000000000000000000000000000..6864dd2994634b381901e7e3949c540d66528002 GIT binary patch literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|8&4O> ztd%lrzQ*v8U5Muw!?k9<2V5VRe7-iHS`gUm#=9s}EKt$(a?@9yC+WLSj O1_n=8KbLh*2~7asj3X5Q literal 0 HcmV?d00001 diff --git a/src/main/resources/static/images/viewAction.png b/src/main/resources/static/images/viewAction.png new file mode 100644 index 0000000000000000000000000000000000000000..484d70bfcf344265eb93bfdc90fc74e988f1243b GIT binary patch literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+0wn(&ce?|mW_h|ehEy=_y}-!k6e!?uQF$Sk zAmd+Nk%=rE4Ge;ej1yQGMLo1RGQ@M0ofHIg7cev}h@0@|38RVlF^hyh^+Mr_mqMA& zv{XDh@Ts`shLnW$@(Z7&?_Ld@9J--@;ntqH4_2%ZcCJ19-7K_g;l!@l3K`x>FI1MN zE^LhBVP%)CbdB|~sSn(>T+uAkM7*vlV(0Stk)?u4u`2&;^*@G9X>bnNd=ltN22WQ% Jmvv4FO#s}JN=X0! literal 0 HcmV?d00001 diff --git a/src/test/java/ch/ethz/seb/sebserver/gbl/model/institution/InstitutionTest.java b/src/test/java/ch/ethz/seb/sebserver/gbl/model/institution/InstitutionTest.java new file mode 100644 index 00000000..708522c6 --- /dev/null +++ b/src/test/java/ch/ethz/seb/sebserver/gbl/model/institution/InstitutionTest.java @@ -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); + } + +} diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationGrantServiceTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationGrantServiceTest.java index 5d1e2ed1..b430e141 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationGrantServiceTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationGrantServiceTest.java @@ -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;