fix exam config defaults, code cleanup

This commit is contained in:
anhefti 2020-03-03 15:42:00 +01:00
parent e7af727854
commit a076c62d46
46 changed files with 6878 additions and 6957 deletions

View file

@ -82,8 +82,8 @@ public class ResourceService {
private static final String MISSING_CLIENT_PING_NAME_KEY = "MISSING"; private static final String MISSING_CLIENT_PING_NAME_KEY = "MISSING";
public static final Comparator<Tuple<String>> RESOURCE_COMPARATOR = (t1, t2) -> t1._2.compareTo(t2._2); public static final Comparator<Tuple<String>> RESOURCE_COMPARATOR = Comparator.comparing(t -> t._2);
public static final Comparator<Tuple3<String>> RESOURCE_COMPARATOR_TUPLE_3 = (t1, t2) -> t1._2.compareTo(t2._2); public static final Comparator<Tuple3<String>> RESOURCE_COMPARATOR_TUPLE_3 = Comparator.comparing(t -> t._2);
public static final EnumSet<EntityType> ENTITY_TYPE_EXCLUDE_MAP = EnumSet.of( public static final EnumSet<EntityType> ENTITY_TYPE_EXCLUDE_MAP = EnumSet.of(
EntityType.ADDITIONAL_ATTRIBUTES, EntityType.ADDITIONAL_ATTRIBUTES,

View file

@ -1,50 +1,50 @@
/* /*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.page; package ch.ethz.seb.sebserver.gui.service.page;
import java.util.Collection; import java.util.Collection;
import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
public class FieldValidationError { public class FieldValidationError {
public final String messageCode; public final String messageCode;
public final String domainName; public final String domainName;
public final String fieldName; public final String fieldName;
public final String errorType; public final String errorType;
public final Collection<String> attributes; public final Collection<String> attributes;
public FieldValidationError(final APIMessage apiMessage) { public FieldValidationError(final APIMessage apiMessage) {
this( this(
apiMessage.messageCode, apiMessage.messageCode,
apiMessage.attributes.toArray(new String[apiMessage.attributes.size()])); apiMessage.attributes.toArray(new String[0]));
} }
public FieldValidationError( public FieldValidationError(
final String messageCode, final String messageCode,
final String[] attributes) { final String[] attributes) {
this.messageCode = messageCode; this.messageCode = messageCode;
this.attributes = Utils.immutableCollectionOf(attributes); this.attributes = Utils.immutableCollectionOf(attributes);
this.domainName = (attributes != null && attributes.length > 0) ? attributes[0] : null; this.domainName = (attributes != null && attributes.length > 0) ? attributes[0] : null;
this.fieldName = (attributes != null && attributes.length > 1) ? attributes[1] : null; this.fieldName = (attributes != null && attributes.length > 1) ? attributes[1] : null;
this.errorType = (attributes != null && attributes.length > 2) ? attributes[2] : null; this.errorType = (attributes != null && attributes.length > 2) ? attributes[2] : null;
} }
public String[] getAttributes() { public String[] getAttributes() {
if (this.attributes == null) { if (this.attributes == null) {
return new String[0]; return new String[0];
} }
return this.attributes.toArray(new String[this.attributes.size()]); return this.attributes.toArray(new String[0]);
} }
} }

View file

@ -1,292 +1,292 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.page; package ch.ethz.seb.sebserver.gui.service.page;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Shell;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gui.service.ResourceService; import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; 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.LocTextKey;
/** Holds a page-context and defines some convenient functionality for page handling */ /** Holds a page-context and defines some convenient functionality for page handling */
public interface PageContext { public interface PageContext {
Logger log = LoggerFactory.getLogger(PageContext.class); Logger log = LoggerFactory.getLogger(PageContext.class);
/** Defines attribute keys that can be used to store attribute values within the page context state */ /** Defines attribute keys that can be used to store attribute values within the page context state */
public interface AttributeKeys { interface AttributeKeys {
public static final String PAGE_TEMPLATE_COMPOSER_NAME = "ATTR_PAGE_TEMPLATE_COMPOSER_NAME"; String PAGE_TEMPLATE_COMPOSER_NAME = "ATTR_PAGE_TEMPLATE_COMPOSER_NAME";
public static final String READ_ONLY = "READ_ONLY"; String READ_ONLY = "READ_ONLY";
public static final String READ_ONLY_FROM = "READ_ONLY_FROM"; String READ_ONLY_FROM = "READ_ONLY_FROM";
public static final String ENTITY_ID = "ENTITY_ID"; String ENTITY_ID = "ENTITY_ID";
public static final String PARENT_ENTITY_ID = "PARENT_ENTITY_ID"; String PARENT_ENTITY_ID = "PARENT_ENTITY_ID";
public static final String ENTITY_TYPE = "ENTITY_TYPE"; String ENTITY_TYPE = "ENTITY_TYPE";
public static final String PARENT_ENTITY_TYPE = "PARENT_ENTITY_TYPE"; String PARENT_ENTITY_TYPE = "PARENT_ENTITY_TYPE";
public static final String IMPORT_FROM_QUIZ_DATA = "IMPORT_FROM_QUIZ_DATA"; String IMPORT_FROM_QUIZ_DATA = "IMPORT_FROM_QUIZ_DATA";
public static final String COPY_AS_TEMPLATE = "COPY_AS_TEMPLATE"; String COPY_AS_TEMPLATE = "COPY_AS_TEMPLATE";
public static final String CREATE_FROM_TEMPLATE = "CREATE_FROM_TEMPLATE"; String CREATE_FROM_TEMPLATE = "CREATE_FROM_TEMPLATE";
} }
/** The resource-bundle key of the generic load entity error message. */ /** The resource-bundle key of the generic load entity error message. */
public static final String GENERIC_LOAD_ERROR_TEXT_KEY = "sebserver.error.get.entity"; String GENERIC_LOAD_ERROR_TEXT_KEY = "sebserver.error.get.entity";
public static final String GENERIC_REMOVE_ERROR_TEXT_KEY = "sebserver.error.remove.entity"; String GENERIC_REMOVE_ERROR_TEXT_KEY = "sebserver.error.remove.entity";
public static final String GENERIC_SAVE_ERROR_TEXT_KEY = "sebserver.error.save.entity"; String GENERIC_SAVE_ERROR_TEXT_KEY = "sebserver.error.save.entity";
public static final String GENERIC_ACTIVATE_ERROR_TEXT_KEY = "sebserver.error.activate.entity"; String GENERIC_ACTIVATE_ERROR_TEXT_KEY = "sebserver.error.activate.entity";
public static final String GENERIC_IMPORT_ERROR_TEXT_KEY = "sebserver.error.import"; String GENERIC_IMPORT_ERROR_TEXT_KEY = "sebserver.error.import";
public static final LocTextKey SUCCESS_MSG_TITLE = LocTextKey SUCCESS_MSG_TITLE =
new LocTextKey("sebserver.page.message"); new LocTextKey("sebserver.page.message");
public static final LocTextKey UNEXPECTED_ERROR_KEY = LocTextKey UNEXPECTED_ERROR_KEY =
new LocTextKey("sebserver.error.action.unexpected.message"); new LocTextKey("sebserver.error.action.unexpected.message");
/** Get the I18nSupport service /** Get the I18nSupport service
* *
* @return the I18nSupport service */ * @return the I18nSupport service */
I18nSupport getI18nSupport(); I18nSupport getI18nSupport();
/** Use this to get the ComposerService used by this PageContext /** Use this to get the ComposerService used by this PageContext
* *
* @return the ComposerService used by this PageContext */ * @return the ComposerService used by this PageContext */
ComposerService composerService(); ComposerService composerService();
/** Get the RWT Shell that is bound within this PageContext /** Get the RWT Shell that is bound within this PageContext
* *
* @return the RWT Shell that is bound within this PageContext */ * @return the RWT Shell that is bound within this PageContext */
Shell getShell(); Shell getShell();
/** Get the page root Component. /** Get the page root Component.
* *
* @return the page root Component. */ * @return the page root Component. */
Composite getRoot(); Composite getRoot();
/** Get the Component that is currently set as parent during page tree compose /** Get the Component that is currently set as parent during page tree compose
* *
* @return the parent Component */ * @return the parent Component */
Composite getParent(); Composite getParent();
/** Get a copy of this PageContext. /** Get a copy of this PageContext.
* *
* @return */ * @return a deep copy of this PageContext */
PageContext copy(); PageContext copy();
/** Create a copy of this PageContext with a new parent Composite. /** Create a copy of this PageContext with a new parent Composite.
* The implementation should take care of the imutability of PageContext and return a copy with the new parent * The implementation should take care of the immutability of PageContext and return a copy with the new parent
* by leave this PageContext as is. * by leave this PageContext as is.
* *
* @param parent the new parent Composite * @param parent the new parent Composite
* @return a copy of this PageContext with a new parent Composite. */ * @return a copy of this PageContext with a new parent Composite. */
PageContext copyOf(Composite parent); PageContext copyOf(Composite parent);
/** Create a copy of this PageContext with and additionally page context attributes. /** Create a copy of this PageContext with and additionally page context attributes.
* The additionally page context attributes will get merged with them already defined * The additionally page context attributes will get merged with them already defined
* The implementation should take care of the imutability of PageContext and return a copy with the merge * The implementation should take care of the immutability of PageContext and return a copy with the merge
* by leave this and the given PageContext as is. * by leave this and the given PageContext as is.
* *
* @param attributes additionally page context attributes. * @param otherContext the other PageContext to copy the attributes from
* @return a copy of this PageContext with with and additionally page context attributes. */ * @return a copy of this PageContext with with and additionally page context attributes. */
PageContext copyOfAttributes(PageContext otherContext); PageContext copyOfAttributes(PageContext otherContext);
/** Adds the specified attribute to the existing page context attributes. /** Adds the specified attribute to the existing page context attributes.
* The implementation should take care of the imutability of PageContext and return a copy * The implementation should take care of the immutability of PageContext and return a copy
* by leave this PageContext as is. * by leave this PageContext as is.
* *
* @param key the key of the attribute * @param key the key of the attribute
* @param value the value of the attribute * @param value the value of the attribute
* @return this PageContext instance (builder pattern) */ * @return this PageContext instance (builder pattern) */
PageContext withAttribute(String key, String value); PageContext withAttribute(String key, String value);
/** Gets a copy of this PageContext with cleared attribute map. /** Gets a copy of this PageContext with cleared attribute map.
* *
* @return a copy of this PageContext with cleared attribute map. */ * @return a copy of this PageContext with cleared attribute map. */
PageContext clearAttributes(); PageContext clearAttributes();
/** Get the attribute value that is mapped to the given name or null of no mapping exists /** Get the attribute value that is mapped to the given name or null of no mapping exists
* *
* @param name the attribute name * @param name the attribute name
* @return the attribute value that is mapped to the given name or null if no mapping exists */ * @return the attribute value that is mapped to the given name or null if no mapping exists */
String getAttribute(String name); String getAttribute(String name);
/** Get the attribute value that is mapped to the given name or a default value if no mapping exists /** Get the attribute value that is mapped to the given name or a default value if no mapping exists
* *
* @param name the attribute name * @param name the attribute name
* @param def the default value * @param def the default value
* @return the attribute value that is mapped to the given name or null of no mapping exists */ * @return the attribute value that is mapped to the given name or null of no mapping exists */
String getAttribute(String name, String def); String getAttribute(String name, String def);
/** Indicates if the attribute with the key READ_ONLY is set to true within this PageContext /** Indicates if the attribute with the key READ_ONLY is set to true within this PageContext
* *
* @return true if the attribute with the key READ_ONLY is set to true */ * @return true if the attribute with the key READ_ONLY is set to true */
boolean isReadonly(); boolean isReadonly();
/** Gets an EntityKey for the base Entity that is associated within this PageContext by using /** Gets an EntityKey for the base Entity that is associated within this PageContext by using
* the attribute keys ENTITY_ID and ENTITY_TYPE to fetch the attribute values for an EntityKey * the attribute keys ENTITY_ID and ENTITY_TYPE to fetch the attribute values for an EntityKey
* *
* @return the EntityKey of the base Entity that is associated within this PageContext */ * @return the EntityKey of the base Entity that is associated within this PageContext */
EntityKey getEntityKey(); EntityKey getEntityKey();
/** Gets an EntityKey for the parent Entity that is associated within this PageContext by using /** Gets an EntityKey for the parent Entity that is associated within this PageContext by using
* the attribute keys PARENT_ENTITY_ID and PARENT_ENTITY_TYPE to fetch the attribute values for an EntityKey * the attribute keys PARENT_ENTITY_ID and PARENT_ENTITY_TYPE to fetch the attribute values for an EntityKey
* *
* @return the EntityKey of the parent Entity that is associated within this PageContext */ * @return the EntityKey of the parent Entity that is associated within this PageContext */
EntityKey getParentEntityKey(); EntityKey getParentEntityKey();
/** Adds a given EntityKey as base Entity key to a new PageContext that is returned as a copy of this PageContext. /** Adds a given EntityKey as base Entity key to a new PageContext that is returned as a copy of this PageContext.
* *
* @param entityKey the EntityKey to add as base Entity key * @param entityKey the EntityKey to add as base Entity key
* @return the new PageContext with the EntityKey added */ * @return the new PageContext with the EntityKey added */
PageContext withEntityKey(EntityKey entityKey); PageContext withEntityKey(EntityKey entityKey);
/** Adds a given EntityKey as parent Entity key to a new PageContext that is returned as a copy of this PageContext. /** Adds a given EntityKey as parent Entity key to a new PageContext that is returned as a copy of this PageContext.
* *
* @param entityKey the EntityKey to add as parent Entity key * @param entityKey the EntityKey to add as parent Entity key
* @return the new PageContext with the EntityKey added */ * @return the new PageContext with the EntityKey added */
PageContext withParentEntityKey(EntityKey entityKey); PageContext withParentEntityKey(EntityKey entityKey);
/** Create a copy of this PageContext and resets both entity keys attributes, the base and the parent EntityKey /** Create a copy of this PageContext and resets both entity keys attributes, the base and the parent EntityKey
* *
* @return copy of this PageContext with reseted EntityKey attributes (base and parent) */ * @return copy of this PageContext with reset EntityKey attributes (base and parent) */
PageContext clearEntityKeys(); PageContext clearEntityKeys();
/** Indicates if an attribute with the specified name exists within this PageContext /** Indicates if an attribute with the specified name exists within this PageContext
* *
* @param name the name of the attribute * @param name the name of the attribute
* @return true if the attribute with the specified name exists within this PageContext */ * @return true if the attribute with the specified name exists within this PageContext */
boolean hasAttribute(String name); boolean hasAttribute(String name);
/** Returns a new PageContext with the removed attribute by name /** Returns a new PageContext with the removed attribute by name
* *
* @param name the name of the attribute to remove * @param name the name of the attribute to remove
* @return a copy of this PageContext with the removed attribute */ * @return a copy of this PageContext with the removed attribute */
PageContext removeAttribute(String name); PageContext removeAttribute(String name);
/** Apply a confirm dialog with a specified confirm message and a callback code /** Apply a confirm dialog with a specified confirm message and a callback code
* block that will be executed on users OK selection. * block that will be executed on users OK selection.
* *
* @param confirmMessage the localized confirm message key * @param confirmMessage the localized confirm message key
* @param onOK callback code block that will be called on users selection */ * @param callback callback code block that will be called on users selection */
void applyConfirmDialog(LocTextKey confirmMessage, final Consumer<Boolean> callback); void applyConfirmDialog(LocTextKey confirmMessage, final Consumer<Boolean> callback);
/** This can be used to forward to a defined page. /** This can be used to forward to a defined page.
* *
* @param pageDefinition the defined page */ * @param pageDefinition the defined page */
void forwardToPage(PageDefinition pageDefinition); void forwardToPage(PageDefinition pageDefinition);
/** Forward to main page */ /** Forward to main page */
void forwardToMainPage(); void forwardToMainPage();
/** Forward to login page */ /** Forward to login page */
void forwardToLoginPage(); void forwardToLoginPage();
/** Notify an error dialog to the user with specified error message and /** Notify an error dialog to the user with specified error message and
* optional exception instance * optional exception instance
* *
* @param errorMessage the error message to display * @param errorMessage the error message to display
* @param error the error as Exception */ * @param error the error as Exception */
void notifyError(LocTextKey errorMessage, Exception error); void notifyError(LocTextKey errorMessage, Exception error);
/** Notify a generic load error to the user by pop-up /** Notify a generic load error to the user by pop-up
* *
* @param entityType the type of the entity * @param entityType the type of the entity
* @param error the original error */ * @param error the original error */
default void notifyLoadError(final EntityType entityType, final Exception error) { default void notifyLoadError(final EntityType entityType, final Exception error) {
notifyError( notifyError(
new LocTextKey( new LocTextKey(
GENERIC_LOAD_ERROR_TEXT_KEY, GENERIC_LOAD_ERROR_TEXT_KEY,
getI18nSupport().getText(ResourceService.getEntityTypeNameKey(entityType))), getI18nSupport().getText(ResourceService.getEntityTypeNameKey(entityType))),
error); error);
} }
/** Notify a generic remove error to the user by pop-up /** Notify a generic remove error to the user by pop-up
* *
* @param entityType the type of the entity * @param entityType the type of the entity
* @param error the original error */ * @param error the original error */
default void notifyRemoveError(final EntityType entityType, final Exception error) { default void notifyRemoveError(final EntityType entityType, final Exception error) {
notifyError( notifyError(
new LocTextKey( new LocTextKey(
GENERIC_REMOVE_ERROR_TEXT_KEY, GENERIC_REMOVE_ERROR_TEXT_KEY,
getI18nSupport().getText(ResourceService.getEntityTypeNameKey(entityType))), getI18nSupport().getText(ResourceService.getEntityTypeNameKey(entityType))),
error); error);
} }
/** Notify a generic save error to the user by pop-up /** Notify a generic save error to the user by pop-up
* *
* @param entityType the type of the entity * @param entityType the type of the entity
* @param error the original error */ * @param error the original error */
default void notifySaveError(final EntityType entityType, final Exception error) { default void notifySaveError(final EntityType entityType, final Exception error) {
notifyError( notifyError(
new LocTextKey( new LocTextKey(
GENERIC_SAVE_ERROR_TEXT_KEY, GENERIC_SAVE_ERROR_TEXT_KEY,
getI18nSupport().getText(ResourceService.getEntityTypeNameKey(entityType))), getI18nSupport().getText(ResourceService.getEntityTypeNameKey(entityType))),
error); error);
} }
/** Notify a generic activation error to the user by pop-up /** Notify a generic activation error to the user by pop-up
* *
* @param entityType the type of the entity * @param entityType the type of the entity
* @param error the original error */ * @param error the original error */
default void notifyActivationError(final EntityType entityType, final Exception error) { default void notifyActivationError(final EntityType entityType, final Exception error) {
notifyError( notifyError(
new LocTextKey( new LocTextKey(
GENERIC_ACTIVATE_ERROR_TEXT_KEY, GENERIC_ACTIVATE_ERROR_TEXT_KEY,
getI18nSupport().getText(ResourceService.getEntityTypeNameKey(entityType))), getI18nSupport().getText(ResourceService.getEntityTypeNameKey(entityType))),
error); error);
} }
/** Notify a generic import error to the user by pop-up /** Notify a generic import error to the user by pop-up
* *
* @param entityType the type of the entity * @param entityType the type of the entity
* @param error the original error */ * @param error the original error */
default void notifyImportError(final EntityType entityType, final Exception error) { default void notifyImportError(final EntityType entityType, final Exception error) {
notifyError( notifyError(
new LocTextKey( new LocTextKey(
GENERIC_IMPORT_ERROR_TEXT_KEY, GENERIC_IMPORT_ERROR_TEXT_KEY,
getI18nSupport().getText(ResourceService.getEntityTypeNameKey(entityType))), getI18nSupport().getText(ResourceService.getEntityTypeNameKey(entityType))),
error); error);
} }
/** Notify a generic unexpected error to the user by pop-up /** Notify a generic unexpected error to the user by pop-up
* *
* @param error the original error */ * @param error the original error */
default void notifyUnexpectedError(final Exception error) { default void notifyUnexpectedError(final Exception error) {
notifyError(UNEXPECTED_ERROR_KEY, error); notifyError(UNEXPECTED_ERROR_KEY, error);
} }
/** Publish and shows a message to the user with the given localized title and /** Publish and shows a message to the user with the given localized title and
* localized message. The message text can also be HTML text as far as RWT supports it. * localized message. The message text can also be HTML text as far as RWT supports it.
* *
* @param title the localized text key of the title message * @param title the localized text key of the title message
* @param message the localized text key of the message */ * @param message the localized text key of the message */
void publishPageMessage(LocTextKey title, LocTextKey message); void publishPageMessage(LocTextKey title, LocTextKey message);
/** Publish an information message to the user with the given localized message. /** Publish an information message to the user with the given localized message.
* The message text can also be HTML text as far as RWT supports it * The message text can also be HTML text as far as RWT supports it
* *
* @param message the localized text key of the message */ * @param message the localized text key of the message */
default void publishInfo(final LocTextKey message) { default void publishInfo(final LocTextKey message) {
publishPageMessage(new LocTextKey("sebserver.page.message"), message); publishPageMessage(new LocTextKey("sebserver.page.message"), message);
} }
/** Publish and shows a formatted PageMessageException to the user. /** Publish and shows a formatted PageMessageException to the user.
* *
* @param pme the PageMessageException */ * @param pme the PageMessageException */
void publishPageMessage(PageMessageException pme); void publishPageMessage(PageMessageException pme);
} }

View file

@ -1,16 +1,20 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.page; package ch.ethz.seb.sebserver.gui.service.page;
public interface PageDefinition { /** Defines a global SEB Server page */
public interface PageDefinition {
Class<? extends TemplateComposer> composer();
/** Get the type class of the TemplateComposer that composes the page.
PageContext applyPageContext(PageContext pageContext); *
} * @return the type class of the TemplateComposer that composes the page. */
Class<? extends TemplateComposer> composer();
PageContext applyPageContext(PageContext pageContext);
}

View file

@ -363,7 +363,7 @@ public interface PageService {
return content; return content;
} }
/** Used to update the crolledComposite when some if its content has dynamically changed /** Used to update the scrolledComposite when some if its content has dynamically changed
* its dimensions. * its dimensions.
* *
* @param composite The Component that changed its dimensions */ * @param composite The Component that changed its dimensions */

View file

@ -1,30 +1,30 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.page; package ch.ethz.seb.sebserver.gui.service.page;
public interface PageStateDefinition { public interface PageStateDefinition {
enum Type { enum Type {
UNDEFINED, UNDEFINED,
LIST_VIEW, LIST_VIEW,
FORM_VIEW, FORM_VIEW,
FORM_EDIT, FORM_EDIT,
FORM_IN_TIME_EDIT FORM_IN_TIME_EDIT
} }
String name(); String name();
Type type(); Type type();
public Class<? extends TemplateComposer> contentPaneComposer(); Class<? extends TemplateComposer> contentPaneComposer();
public Class<? extends TemplateComposer> actionPaneComposer(); Class<? extends TemplateComposer> actionPaneComposer();
Activity activityAnchor(); Activity activityAnchor();
} }

View file

@ -1,19 +1,27 @@
/* /*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.page; package ch.ethz.seb.sebserver.gui.service.page;
public interface TemplateComposer { /** interface defining a RAP page template composer */
public interface TemplateComposer {
default boolean validate(final PageContext pageContext) {
return true; /** Validate given PageContext for completeness to compose a specific TemplateComposer implementation
} * Default returns always true.
* @param pageContext The PageContext instance to check
void compose(PageContext pageContext); * @return true if the PageContext contains all mandatory data to compose this page template */
default boolean validate(final PageContext pageContext) {
} return true;
}
/** Compose a specific page template for the given PageContext
*
* @param pageContext The PageContext instance */
void compose(PageContext pageContext);
}

View file

@ -1,366 +1,362 @@
/* /*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.page.impl; package ch.ethz.seb.sebserver.gui.service.page.impl;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import org.apache.commons.codec.binary.Base64InputStream; import org.apache.commons.codec.binary.Base64InputStream;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.client.service.UrlLauncher; import org.eclipse.rap.rwt.client.service.UrlLauncher;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.MessageBox; import org.eclipse.swt.widgets.MessageBox;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; 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.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService; 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.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys; import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; 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.AuthorizationContextHolder;
import ch.ethz.seb.sebserver.gui.widget.Message; import ch.ethz.seb.sebserver.gui.widget.Message;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
@Lazy @Lazy
@Component @Component
public class DefaultPageLayout implements TemplateComposer { public class DefaultPageLayout implements TemplateComposer {
private static final Logger log = LoggerFactory.getLogger(DefaultPageLayout.class); private static final Logger log = LoggerFactory.getLogger(DefaultPageLayout.class);
private static final LocTextKey ABOUT_TEXT_KEY = new LocTextKey("sebserver.overall.about"); private static final LocTextKey ABOUT_TEXT_KEY = new LocTextKey("sebserver.overall.about");
private static final LocTextKey IMPRINT_TEXT_KEY = new LocTextKey("sebserver.overall.imprint"); private static final LocTextKey IMPRINT_TEXT_KEY = new LocTextKey("sebserver.overall.imprint");
private static final LocTextKey HELP_TEXT_KEY = new LocTextKey("sebserver.overall.help"); private static final LocTextKey HELP_TEXT_KEY = new LocTextKey("sebserver.overall.help");
private static final LocTextKey ABOUT_MARKUP_TEXT_KEY = new LocTextKey("sebserver.overall.about.markup"); private static final LocTextKey ABOUT_MARKUP_TEXT_KEY = new LocTextKey("sebserver.overall.about.markup");
private static final LocTextKey IMPRINT_MARKUP_TEXT_KEY = new LocTextKey("sebserver.overall.imprint.markup"); private static final LocTextKey IMPRINT_MARKUP_TEXT_KEY = new LocTextKey("sebserver.overall.imprint.markup");
private static final LocTextKey HELP_LINK_TEXT_KEY = new LocTextKey("sebserver.overall.help.link"); private static final LocTextKey HELP_LINK_TEXT_KEY = new LocTextKey("sebserver.overall.help.link");
public static final int LOGO_IMAGE_MAX_WIDTH = 400; public static final int LOGO_IMAGE_MAX_WIDTH = 400;
public static final int LOGO_IMAGE_MAX_HEIGHT = 80; public static final int LOGO_IMAGE_MAX_HEIGHT = 80;
private final WidgetFactory widgetFactory; private final WidgetFactory widgetFactory;
private final PolyglotPageService polyglotPageService; private final PolyglotPageService polyglotPageService;
private final AuthorizationContextHolder authorizationContextHolder; private final AuthorizationContextHolder authorizationContextHolder;
private final PageService pageService; private final PageService pageService;
private final String sebServerVersion; private final String sebServerVersion;
private final boolean multilingual; private final boolean multilingual;
public DefaultPageLayout( public DefaultPageLayout(
final PageService pageService, final PageService pageService,
final Environment environment) { final Environment environment) {
this.widgetFactory = pageService.getWidgetFactory(); this.widgetFactory = pageService.getWidgetFactory();
this.polyglotPageService = pageService.getPolyglotPageService(); this.polyglotPageService = pageService.getPolyglotPageService();
this.authorizationContextHolder = pageService.getAuthorizationContextHolder(); this.authorizationContextHolder = pageService.getAuthorizationContextHolder();
this.pageService = pageService; this.pageService = pageService;
this.sebServerVersion = environment.getProperty("sebserver.version", Constants.EMPTY_NOTE); this.sebServerVersion = environment.getProperty("sebserver.version", Constants.EMPTY_NOTE);
this.multilingual = BooleanUtils.toBoolean(environment.getProperty("sebserver.gui.multilingual", "false")); this.multilingual = BooleanUtils.toBoolean(environment.getProperty("sebserver.gui.multilingual", "false"));
} }
@Override @Override
public boolean validate(final PageContext pageContext) { public boolean validate(final PageContext pageContext) {
return pageContext.hasAttribute(AttributeKeys.PAGE_TEMPLATE_COMPOSER_NAME); return pageContext.hasAttribute(AttributeKeys.PAGE_TEMPLATE_COMPOSER_NAME);
} }
@Override @Override
public void compose(final PageContext pageContext) { public void compose(final PageContext pageContext) {
final GridLayout skeletonLayout = new GridLayout(); final GridLayout skeletonLayout = new GridLayout();
skeletonLayout.marginBottom = 0; skeletonLayout.marginBottom = 0;
skeletonLayout.marginLeft = 0; skeletonLayout.marginLeft = 0;
skeletonLayout.marginRight = 0; skeletonLayout.marginRight = 0;
skeletonLayout.marginTop = 0; skeletonLayout.marginTop = 0;
skeletonLayout.marginHeight = 0; skeletonLayout.marginHeight = 0;
skeletonLayout.marginWidth = 0; skeletonLayout.marginWidth = 0;
skeletonLayout.verticalSpacing = 0; skeletonLayout.verticalSpacing = 0;
skeletonLayout.horizontalSpacing = 0; skeletonLayout.horizontalSpacing = 0;
pageContext.getParent().setLayout(skeletonLayout); pageContext.getParent().setLayout(skeletonLayout);
composeHeader(pageContext); composeHeader(pageContext);
composeLogoBar(pageContext); composeLogoBar(pageContext);
composeContent(pageContext); composeContent(pageContext);
composeFooter(pageContext); composeFooter(pageContext);
this.polyglotPageService.setDefaultPageLocale(pageContext.getRoot()); this.polyglotPageService.setDefaultPageLocale(pageContext.getRoot());
} }
private void composeHeader(final PageContext pageContext) { private void composeHeader(final PageContext pageContext) {
final Composite header = new Composite(pageContext.getParent(), SWT.NONE); final Composite header = new Composite(pageContext.getParent(), SWT.NONE);
final GridLayout gridLayout = new GridLayout(); final GridLayout gridLayout = new GridLayout();
gridLayout.marginRight = 50; gridLayout.marginRight = 50;
gridLayout.marginLeft = 50; gridLayout.marginLeft = 50;
header.setLayout(gridLayout); header.setLayout(gridLayout);
final GridData headerCell = new GridData(SWT.FILL, SWT.TOP, true, false); final GridData headerCell = new GridData(SWT.FILL, SWT.TOP, true, false);
headerCell.minimumHeight = 40; headerCell.minimumHeight = 40;
headerCell.heightHint = 40; headerCell.heightHint = 40;
header.setLayoutData(headerCell); header.setLayoutData(headerCell);
header.setData(RWT.CUSTOM_VARIANT, "header"); header.setData(RWT.CUSTOM_VARIANT, "header");
final Composite headerRight = new Composite(header, SWT.NONE); final Composite headerRight = new Composite(header, SWT.NONE);
headerRight.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, true)); headerRight.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, true));
final GridLayout headerRightGrid = new GridLayout(2, false); final GridLayout headerRightGrid = new GridLayout(2, false);
headerRightGrid.marginHeight = 0; headerRightGrid.marginHeight = 0;
headerRightGrid.marginWidth = 0; headerRightGrid.marginWidth = 0;
headerRightGrid.horizontalSpacing = 20; headerRightGrid.horizontalSpacing = 20;
headerRight.setLayout(headerRightGrid); headerRight.setLayout(headerRightGrid);
headerRight.setData(RWT.CUSTOM_VARIANT, "header"); headerRight.setData(RWT.CUSTOM_VARIANT, "header");
if (this.authorizationContextHolder.getAuthorizationContext().isLoggedIn()) { if (this.authorizationContextHolder.getAuthorizationContext().isLoggedIn()) {
final Label username = new Label(headerRight, SWT.NONE); final Label username = new Label(headerRight, SWT.NONE);
username.setData(RWT.CUSTOM_VARIANT, "header"); username.setData(RWT.CUSTOM_VARIANT, "header");
username.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, true)); username.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, true));
username.setText(this.authorizationContextHolder username.setText(this.authorizationContextHolder
.getAuthorizationContext() .getAuthorizationContext()
.getLoggedInUser() .getLoggedInUser()
.get(t -> this.pageService.logoutOnError(t, pageContext)).username); .get(t -> this.pageService.logoutOnError(t, pageContext)).username);
final Button logout = this.widgetFactory.buttonLocalized(headerRight, "sebserver.logout"); final Button logout = this.widgetFactory.buttonLocalized(headerRight, "sebserver.logout");
logout.setLayoutData(new GridData(SWT.RIGHT, SWT.FILL, true, true)); logout.setLayoutData(new GridData(SWT.RIGHT, SWT.FILL, true, true));
logout.setData(RWT.CUSTOM_VARIANT, "header"); logout.setData(RWT.CUSTOM_VARIANT, "header");
logout.addListener(SWT.Selection, event -> { logout.addListener(SWT.Selection, event -> {
this.pageService.logout(pageContext); this.pageService.logout(pageContext);
// show successful logout message // show successful logout message
final MessageBox logoutSuccess = new Message( final MessageBox logoutSuccess = new Message(
pageContext.getShell(), pageContext.getShell(),
this.polyglotPageService.getI18nSupport().getText("sebserver.logout"), this.polyglotPageService.getI18nSupport().getText("sebserver.logout"),
this.polyglotPageService.getI18nSupport().getText("sebserver.logout.success.message"), this.polyglotPageService.getI18nSupport().getText("sebserver.logout.success.message"),
SWT.ICON_INFORMATION, SWT.ICON_INFORMATION,
pageContext.getI18nSupport()); pageContext.getI18nSupport());
logoutSuccess.open(null); logoutSuccess.open(null);
// TODO Try to invalidate RWT's user session. // TODO Try to invalidate RWT's user session.
// This seems to be more difficult then expected and just invalidate the HttpSession dosn't work // This seems to be more difficult then expected and just invalidate the HttpSession doesn't work
// Try to send a redirect JSON to the client: https://bugs.eclipse.org/bugs/show_bug.cgi?id=388249 // Try to send a redirect JSON to the client: https://bugs.eclipse.org/bugs/show_bug.cgi?id=388249
}); });
} }
} }
private void composeLogoBar(final PageContext pageContext) { private void composeLogoBar(final PageContext pageContext) {
final Composite logoBar = new Composite(pageContext.getParent(), SWT.NONE); final Composite logoBar = new Composite(pageContext.getParent(), SWT.NONE);
final GridData logoBarCell = new GridData(SWT.FILL, SWT.TOP, false, false); final GridData logoBarCell = new GridData(SWT.FILL, SWT.TOP, false, false);
logoBarCell.minimumHeight = 80; logoBarCell.minimumHeight = 80;
logoBarCell.heightHint = 80; logoBarCell.heightHint = 80;
logoBar.setLayoutData(logoBarCell); logoBar.setLayoutData(logoBarCell);
logoBar.setData(RWT.CUSTOM_VARIANT, "logo"); logoBar.setData(RWT.CUSTOM_VARIANT, "logo");
final GridLayout logoBarLayout = new GridLayout(2, false); final GridLayout logoBarLayout = new GridLayout(2, false);
logoBarLayout.horizontalSpacing = 0; logoBarLayout.horizontalSpacing = 0;
logoBarLayout.marginHeight = 0; logoBarLayout.marginHeight = 0;
logoBar.setLayout(logoBarLayout); logoBar.setLayout(logoBarLayout);
final Composite logo = new Composite(logoBar, SWT.NONE); final Composite logo = new Composite(logoBar, SWT.NONE);
final GridData logoCell = new GridData(SWT.LEFT, SWT.CENTER, true, true); final GridData logoCell = new GridData(SWT.LEFT, SWT.CENTER, true, true);
logoCell.minimumHeight = LOGO_IMAGE_MAX_HEIGHT; logoCell.minimumHeight = LOGO_IMAGE_MAX_HEIGHT;
logoCell.heightHint = LOGO_IMAGE_MAX_HEIGHT; logoCell.heightHint = LOGO_IMAGE_MAX_HEIGHT;
logoCell.minimumWidth = LOGO_IMAGE_MAX_WIDTH; logoCell.minimumWidth = LOGO_IMAGE_MAX_WIDTH;
logoCell.horizontalIndent = 50; logoCell.horizontalIndent = 50;
logo.setLayoutData(logoCell); logo.setLayoutData(logoCell);
// try to get institutional logo first. If no success, use default logo // try to get institutional logo first. If no success, use default logo
loadInstitutionalLogo(pageContext, logo); loadInstitutionalLogo(pageContext, logo);
final Composite langSupport = new Composite(logoBar, SWT.NONE); final Composite langSupport = new Composite(logoBar, SWT.NONE);
final GridData langSupportCell = new GridData(SWT.RIGHT, SWT.CENTER, false, false); final GridData langSupportCell = new GridData(SWT.RIGHT, SWT.CENTER, false, false);
langSupportCell.heightHint = 20; langSupportCell.heightHint = 20;
logoCell.horizontalIndent = 50; logoCell.horizontalIndent = 50;
langSupport.setLayoutData(langSupportCell); langSupport.setLayoutData(langSupportCell);
langSupport.setData(RWT.CUSTOM_VARIANT, "logo"); langSupport.setData(RWT.CUSTOM_VARIANT, "logo");
final RowLayout rowLayout = new RowLayout(SWT.HORIZONTAL); final RowLayout rowLayout = new RowLayout(SWT.HORIZONTAL);
rowLayout.spacing = 7; rowLayout.spacing = 7;
rowLayout.marginRight = 70; rowLayout.marginRight = 70;
langSupport.setLayout(rowLayout); langSupport.setLayout(rowLayout);
if (this.multilingual) { if (this.multilingual) {
this.polyglotPageService.createLanguageSelector(pageContext.copyOf(langSupport)); this.polyglotPageService.createLanguageSelector(pageContext.copyOf(langSupport));
} }
} }
private void composeContent(final PageContext pageContext) { private void composeContent(final PageContext pageContext) {
final Composite contentBackground = new Composite(pageContext.getParent(), SWT.NONE); final Composite contentBackground = new Composite(pageContext.getParent(), SWT.NONE);
contentBackground.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); contentBackground.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
contentBackground.setData(RWT.CUSTOM_VARIANT, "bgContent"); contentBackground.setData(RWT.CUSTOM_VARIANT, "bgContent");
final GridLayout innerGrid = new GridLayout(); final GridLayout innerGrid = new GridLayout();
innerGrid.marginLeft = 50; innerGrid.marginLeft = 50;
innerGrid.marginRight = 50; innerGrid.marginRight = 50;
innerGrid.marginHeight = 0; innerGrid.marginHeight = 0;
innerGrid.marginWidth = 0; innerGrid.marginWidth = 0;
contentBackground.setLayout(innerGrid); contentBackground.setLayout(innerGrid);
final Composite content = new Composite(contentBackground, SWT.NONE); final Composite content = new Composite(contentBackground, SWT.NONE);
content.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); content.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
content.setData(RWT.CUSTOM_VARIANT, "content"); content.setData(RWT.CUSTOM_VARIANT, "content");
final GridLayout contentGrid = new GridLayout(); final GridLayout contentGrid = new GridLayout();
contentGrid.marginHeight = 0; contentGrid.marginHeight = 0;
contentGrid.marginWidth = 0; contentGrid.marginWidth = 0;
content.setLayout(contentGrid); content.setLayout(contentGrid);
final Composite contentInner = new Composite(content, SWT.NONE); final Composite contentInner = new Composite(content, SWT.NONE);
contentInner.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true)); contentInner.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
final GridLayout gridLayout = new GridLayout(); final GridLayout gridLayout = new GridLayout();
gridLayout.marginHeight = 0; gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0; gridLayout.marginWidth = 0;
contentInner.setLayout(gridLayout); contentInner.setLayout(gridLayout);
final String contentComposerName = pageContext.getAttribute( final String contentComposerName = pageContext.getAttribute(
AttributeKeys.PAGE_TEMPLATE_COMPOSER_NAME); AttributeKeys.PAGE_TEMPLATE_COMPOSER_NAME);
pageContext.composerService().compose( pageContext.composerService().compose(
contentComposerName, contentComposerName,
pageContext.copyOf(contentInner)); pageContext.copyOf(contentInner));
} }
private void composeFooter(final PageContext pageContext) { private void composeFooter(final PageContext pageContext) {
final Composite footerBar = new Composite(pageContext.getParent(), SWT.NONE); final Composite footerBar = new Composite(pageContext.getParent(), SWT.NONE);
final GridData footerCell = new GridData(SWT.FILL, SWT.BOTTOM, false, false); final GridData footerCell = new GridData(SWT.FILL, SWT.BOTTOM, false, false);
footerCell.minimumHeight = 30; footerCell.minimumHeight = 30;
footerCell.heightHint = 30; footerCell.heightHint = 30;
footerBar.setLayoutData(footerCell); footerBar.setLayoutData(footerCell);
footerBar.setData(RWT.CUSTOM_VARIANT, "bgFooter"); footerBar.setData(RWT.CUSTOM_VARIANT, "bgFooter");
final GridLayout innerBarGrid = new GridLayout(); final GridLayout innerBarGrid = new GridLayout();
innerBarGrid.marginHeight = 0; innerBarGrid.marginHeight = 0;
innerBarGrid.marginWidth = 0; innerBarGrid.marginWidth = 0;
innerBarGrid.marginLeft = 50; innerBarGrid.marginLeft = 50;
innerBarGrid.marginRight = 50; innerBarGrid.marginRight = 50;
footerBar.setLayout(innerBarGrid); footerBar.setLayout(innerBarGrid);
final Composite footer = new Composite(footerBar, SWT.NONE); final Composite footer = new Composite(footerBar, SWT.NONE);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true); final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
footer.setLayoutData(gridData); footer.setLayoutData(gridData);
final GridLayout footerGrid = new GridLayout(2, false); final GridLayout footerGrid = new GridLayout(2, false);
footerGrid.marginHeight = 0; footerGrid.marginHeight = 0;
footerGrid.marginWidth = 0; footerGrid.marginWidth = 0;
footerGrid.horizontalSpacing = 0; footerGrid.horizontalSpacing = 0;
footer.setLayout(footerGrid); footer.setLayout(footerGrid);
footer.setData(RWT.CUSTOM_VARIANT, "footer"); footer.setData(RWT.CUSTOM_VARIANT, "footer");
final Composite footerLeft = new Composite(footer, SWT.NONE); final Composite footerLeft = new Composite(footer, SWT.NONE);
footerLeft.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, true)); footerLeft.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, true));
footerLeft.setData(RWT.CUSTOM_VARIANT, "footer"); footerLeft.setData(RWT.CUSTOM_VARIANT, "footer");
RowLayout rowLayout = new RowLayout(SWT.HORIZONTAL); RowLayout rowLayout = new RowLayout(SWT.HORIZONTAL);
rowLayout.marginLeft = 20; rowLayout.marginLeft = 20;
rowLayout.spacing = 20; rowLayout.spacing = 20;
footerLeft.setLayout(rowLayout); footerLeft.setLayout(rowLayout);
final Composite footerRight = new Composite(footer, SWT.NONE); final Composite footerRight = new Composite(footer, SWT.NONE);
footerRight.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, true)); footerRight.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, true));
footerRight.setData(RWT.CUSTOM_VARIANT, "footer"); footerRight.setData(RWT.CUSTOM_VARIANT, "footer");
rowLayout = new RowLayout(SWT.HORIZONTAL); rowLayout = new RowLayout(SWT.HORIZONTAL);
rowLayout.marginRight = 20; rowLayout.marginRight = 20;
footerRight.setLayout(rowLayout); footerRight.setLayout(rowLayout);
final I18nSupport i18nSupport = this.widgetFactory.getI18nSupport(); final I18nSupport i18nSupport = this.widgetFactory.getI18nSupport();
if (StringUtils.isNoneBlank(i18nSupport.getText(IMPRINT_TEXT_KEY, ""))) { if (StringUtils.isNoneBlank(i18nSupport.getText(IMPRINT_TEXT_KEY, ""))) {
final Label imprint = this.widgetFactory.labelLocalized( final Label imprint = this.widgetFactory.labelLocalized(
footerLeft, footerLeft,
CustomVariant.FOOTER, CustomVariant.FOOTER,
IMPRINT_TEXT_KEY); IMPRINT_TEXT_KEY);
imprint.addListener(SWT.MouseUp, event -> { imprint.addListener(SWT.MouseUp, event -> {
try { try {
pageContext.publishPageMessage(IMPRINT_TEXT_KEY, IMPRINT_MARKUP_TEXT_KEY); pageContext.publishPageMessage(IMPRINT_TEXT_KEY, IMPRINT_MARKUP_TEXT_KEY);
} catch (final Exception e) { } catch (final Exception e) {
log.error("Invalid markup for 'Imprint'", e); log.error("Invalid markup for 'Imprint'", e);
} }
}); });
} }
if (StringUtils.isNoneBlank(i18nSupport.getText(ABOUT_TEXT_KEY, ""))) { if (StringUtils.isNoneBlank(i18nSupport.getText(ABOUT_TEXT_KEY, ""))) {
final Label about = this.widgetFactory.labelLocalized( final Label about = this.widgetFactory.labelLocalized(
footerLeft, footerLeft,
CustomVariant.FOOTER, CustomVariant.FOOTER,
ABOUT_TEXT_KEY); ABOUT_TEXT_KEY);
about.addListener(SWT.MouseUp, event -> { about.addListener(SWT.MouseUp, event -> {
try { try {
pageContext.publishPageMessage(ABOUT_TEXT_KEY, ABOUT_MARKUP_TEXT_KEY); pageContext.publishPageMessage(ABOUT_TEXT_KEY, ABOUT_MARKUP_TEXT_KEY);
} catch (final Exception e) { } catch (final Exception e) {
log.error("Invalid markup for 'About'", e); log.error("Invalid markup for 'About'", e);
} }
}); });
} }
if (StringUtils.isNoneBlank(i18nSupport.getText(HELP_TEXT_KEY, ""))) { if (StringUtils.isNoneBlank(i18nSupport.getText(HELP_TEXT_KEY, ""))) {
final Label help = this.widgetFactory.labelLocalized( final Label help = this.widgetFactory.labelLocalized(
footerLeft, footerLeft,
CustomVariant.FOOTER, CustomVariant.FOOTER,
HELP_TEXT_KEY); HELP_TEXT_KEY);
help.addListener(SWT.MouseUp, event -> { help.addListener(SWT.MouseUp, event -> {
try { try {
final String link = i18nSupport.getText(HELP_LINK_TEXT_KEY, ""); final String link = i18nSupport.getText(HELP_LINK_TEXT_KEY, "");
if (StringUtils.isNoneBlank(link)) { if (StringUtils.isNoneBlank(link)) {
final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class); final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class);
urlLauncher.openURL(link); urlLauncher.openURL(link);
} }
} catch (final Exception e) { } catch (final Exception e) {
log.error("Invalid Help link", e); log.error("Invalid Help link", e);
} }
}); });
} }
this.widgetFactory.labelLocalized( this.widgetFactory.labelLocalized(
footerRight, footerRight,
CustomVariant.FOOTER, CustomVariant.FOOTER,
new LocTextKey("sebserver.overall.version", this.sebServerVersion)); new LocTextKey("sebserver.overall.version", this.sebServerVersion));
} }
private void loadInstitutionalLogo(final PageContext pageContext, final Composite logo) { private void loadInstitutionalLogo(final PageContext pageContext, final Composite logo) {
logo.setData(RWT.CUSTOM_VARIANT, "bgLogo"); logo.setData(RWT.CUSTOM_VARIANT, "bgLogo");
try { try {
final String imageBase64 = (String) RWT.getUISession() final String imageBase64 = (String) RWT.getUISession()
.getHttpSession() .getHttpSession()
.getAttribute(API.PARAM_LOGO_IMAGE); .getAttribute(API.PARAM_LOGO_IMAGE);
if (StringUtils.isBlank(imageBase64)) { if (StringUtils.isBlank(imageBase64)) {
return; return;
} }
final Base64InputStream input = new Base64InputStream( final Base64InputStream input = new Base64InputStream(
new ByteArrayInputStream(imageBase64.getBytes(StandardCharsets.UTF_8)), new ByteArrayInputStream(imageBase64.getBytes(StandardCharsets.UTF_8)),
false); false);
final Display display = pageContext.getShell().getDisplay(); final Display display = pageContext.getShell().getDisplay();
final Image image = new Image(display, input); final Image image = new Image(display, input);
final Rectangle imageBounds = image.getBounds(); final Rectangle imageBounds = image.getBounds();
final int width = (imageBounds.width > LOGO_IMAGE_MAX_WIDTH) final int width = Math.min(imageBounds.width, LOGO_IMAGE_MAX_WIDTH);
? LOGO_IMAGE_MAX_WIDTH final int height = Math.min(imageBounds.height, LOGO_IMAGE_MAX_HEIGHT);
: imageBounds.width; final ImageData imageData = image.getImageData().scaledTo(width, height);
final int height = (imageBounds.height > LOGO_IMAGE_MAX_HEIGHT)
? LOGO_IMAGE_MAX_HEIGHT logo.setData(RWT.CUSTOM_VARIANT, "bgLogoNoImage");
: imageBounds.height; logo.setBackgroundImage(new Image(display, imageData));
final ImageData imageData = image.getImageData().scaledTo(width, height);
} catch (final Exception e) {
logo.setData(RWT.CUSTOM_VARIANT, "bgLogoNoImage"); log.warn("Get institutional logo failed: {}", e.getMessage());
logo.setBackgroundImage(new Image(display, imageData)); }
}
} catch (final Exception e) {
log.warn("Get institutional logo failed: {}", e.getMessage()); }
}
}
}

View file

@ -1,225 +1,220 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.page.impl; package ch.ethz.seb.sebserver.gui.service.page.impl;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Dialog; import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Shell;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer; import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
public class ModalInputDialog<T> extends Dialog { public class ModalInputDialog<T> extends Dialog {
private static final long serialVersionUID = -3448614119078234374L; private static final long serialVersionUID = -3448614119078234374L;
public static final int DEFAULT_DIALOG_WIDTH = 400; public static final int DEFAULT_DIALOG_WIDTH = 400;
public static final int DEFAULT_DIALOG_HEIGHT = 600; public static final int DEFAULT_DIALOG_HEIGHT = 600;
public static final int DEFAULT_DIALOG_BUTTON_WIDTH = 100; public static final int DEFAULT_DIALOG_BUTTON_WIDTH = 100;
public static final int LARGE_DIALOG_WIDTH = 600; public static final int LARGE_DIALOG_WIDTH = 600;
public static final int VERY_LARGE_DIALOG_WIDTH = 800; public static final int VERY_LARGE_DIALOG_WIDTH = 800;
private static final LocTextKey CANCEL_TEXT_KEY = private static final LocTextKey CANCEL_TEXT_KEY =
new LocTextKey("sebserver.overall.action.cancel"); new LocTextKey("sebserver.overall.action.cancel");
private static final LocTextKey OK_TEXT_KEY = private static final LocTextKey OK_TEXT_KEY =
new LocTextKey("sebserver.overall.action.ok"); new LocTextKey("sebserver.overall.action.ok");
private static final LocTextKey CLOSE_TEXT_KEY = private static final LocTextKey CLOSE_TEXT_KEY =
new LocTextKey("sebserver.overall.action.close"); new LocTextKey("sebserver.overall.action.close");
private final WidgetFactory widgetFactory; private final WidgetFactory widgetFactory;
private int dialogWidth = DEFAULT_DIALOG_WIDTH; private int dialogWidth = DEFAULT_DIALOG_WIDTH;
private int dialogHeight = DEFAULT_DIALOG_HEIGHT; private int dialogHeight = DEFAULT_DIALOG_HEIGHT;
private int buttonWidth = DEFAULT_DIALOG_BUTTON_WIDTH; private int buttonWidth = DEFAULT_DIALOG_BUTTON_WIDTH;
public ModalInputDialog( public ModalInputDialog(
final Shell parent, final Shell parent,
final WidgetFactory widgetFactory) { final WidgetFactory widgetFactory) {
super(parent, SWT.BORDER | SWT.TITLE | SWT.APPLICATION_MODAL | SWT.CLOSE); super(parent, SWT.BORDER | SWT.TITLE | SWT.APPLICATION_MODAL | SWT.CLOSE);
this.widgetFactory = widgetFactory; this.widgetFactory = widgetFactory;
} }
public ModalInputDialog<T> setDialogWidth(final int dialogWidth) { public ModalInputDialog<T> setDialogWidth(final int dialogWidth) {
this.dialogWidth = dialogWidth; this.dialogWidth = dialogWidth;
return this; return this;
} }
public ModalInputDialog<T> setLargeDialogWidth() { public ModalInputDialog<T> setLargeDialogWidth() {
this.dialogWidth = LARGE_DIALOG_WIDTH; this.dialogWidth = LARGE_DIALOG_WIDTH;
return this; return this;
} }
public ModalInputDialog<T> setVeryLargeDialogWidth() { public ModalInputDialog<T> setVeryLargeDialogWidth() {
this.dialogWidth = VERY_LARGE_DIALOG_WIDTH; this.dialogWidth = VERY_LARGE_DIALOG_WIDTH;
return this; return this;
} }
public ModalInputDialog<T> setDialogHeight(final int dialogHeight) { public ModalInputDialog<T> setDialogHeight(final int dialogHeight) {
this.dialogHeight = dialogHeight; this.dialogHeight = dialogHeight;
return this; return this;
} }
public ModalInputDialog<T> setButtonWidth(final int buttonWidth) { public ModalInputDialog<T> setButtonWidth(final int buttonWidth) {
this.buttonWidth = buttonWidth; this.buttonWidth = buttonWidth;
return this; return this;
} }
public void open( public void open(
final LocTextKey title, final LocTextKey title,
final ModalInputDialogComposer<T> contentComposer) { final ModalInputDialogComposer<T> contentComposer) {
open( open(
title, title,
(Predicate<T>) t -> true, t -> true,
() -> { () -> {
}, contentComposer); }, contentComposer);
} }
public void open( public void open(
final LocTextKey title, final LocTextKey title,
final Consumer<T> callback, final Consumer<T> callback,
final Runnable cancelCallback, final Runnable cancelCallback,
final ModalInputDialogComposer<T> contentComposer) { final ModalInputDialogComposer<T> contentComposer) {
final Predicate<T> predicate = result -> { final Predicate<T> predicate = result -> {
callback.accept(result); callback.accept(result);
return true; return true;
}; };
open(title, predicate, cancelCallback, contentComposer); open(title, predicate, cancelCallback, contentComposer);
} }
public void open( public void open(
final LocTextKey title, final LocTextKey title,
final Predicate<T> callback, final Predicate<T> callback,
final Runnable cancelCallback, final Runnable cancelCallback,
final ModalInputDialogComposer<T> contentComposer) { final ModalInputDialogComposer<T> contentComposer) {
// Create the selection dialog window // Create the selection dialog window
final Shell shell = new Shell(getParent(), getStyle()); final Shell shell = new Shell(getParent(), getStyle());
shell.setText(getText()); shell.setText(getText());
shell.setData(RWT.CUSTOM_VARIANT, CustomVariant.MESSAGE.key); shell.setData(RWT.CUSTOM_VARIANT, CustomVariant.MESSAGE.key);
shell.setText(this.widgetFactory.getI18nSupport().getText(title)); shell.setText(this.widgetFactory.getI18nSupport().getText(title));
shell.setLayout(new GridLayout(2, true)); shell.setLayout(new GridLayout(2, true));
final GridData gridData2 = new GridData(SWT.FILL, SWT.TOP, false, false); final GridData gridData2 = new GridData(SWT.FILL, SWT.TOP, false, false);
shell.setLayoutData(gridData2); shell.setLayoutData(gridData2);
final Composite main = new Composite(shell, SWT.NONE); final Composite main = new Composite(shell, SWT.NONE);
main.setLayout(new GridLayout()); main.setLayout(new GridLayout());
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true); final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
gridData.horizontalSpan = 2; gridData.horizontalSpan = 2;
gridData.widthHint = this.dialogWidth; gridData.widthHint = this.dialogWidth;
main.setLayoutData(gridData); main.setLayoutData(gridData);
final Supplier<T> valueSuppier = contentComposer.compose(main); final Supplier<T> valueSupplier = contentComposer.compose(main);
gridData.heightHint = calcDialogHeight(main); gridData.heightHint = calcDialogHeight(main);
final Button ok = this.widgetFactory.buttonLocalized(shell, OK_TEXT_KEY); final Button ok = this.widgetFactory.buttonLocalized(shell, OK_TEXT_KEY);
GridData data = new GridData(GridData.HORIZONTAL_ALIGN_END); GridData data = new GridData(GridData.HORIZONTAL_ALIGN_END);
data.widthHint = this.buttonWidth; data.widthHint = this.buttonWidth;
ok.setLayoutData(data); ok.setLayoutData(data);
ok.addListener(SWT.Selection, event -> { ok.addListener(SWT.Selection, event -> {
if (valueSuppier != null) { if (valueSupplier != null) {
final T result = valueSuppier.get(); final T result = valueSupplier.get();
if (callback.test(result)) { if (callback.test(result)) {
shell.close(); shell.close();
} }
} else { } else {
shell.close(); shell.close();
} }
}); });
shell.setDefaultButton(ok); shell.setDefaultButton(ok);
final Button cancel = this.widgetFactory.buttonLocalized(shell, CANCEL_TEXT_KEY); final Button cancel = this.widgetFactory.buttonLocalized(shell, CANCEL_TEXT_KEY);
data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING); data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
data.widthHint = this.buttonWidth; data.widthHint = this.buttonWidth;
cancel.setLayoutData(data); cancel.setLayoutData(data);
cancel.addListener(SWT.Selection, event -> { cancel.addListener(SWT.Selection, event -> {
if (cancelCallback != null) { if (cancelCallback != null) {
cancelCallback.run(); cancelCallback.run();
} }
shell.close(); shell.close();
}); });
finishUp(shell); finishUp(shell);
} }
public void open( public void open(
final LocTextKey title, final LocTextKey title,
final PageContext pageContext, final PageContext pageContext,
final Consumer<PageContext> contentComposer) { final Consumer<PageContext> contentComposer) {
// Create the info dialog window // Create the info dialog window
final Shell shell = new Shell(getParent(), getStyle()); final Shell shell = new Shell(getParent(), getStyle());
shell.setText(getText()); shell.setText(getText());
shell.setData(RWT.CUSTOM_VARIANT, CustomVariant.MESSAGE.key); shell.setData(RWT.CUSTOM_VARIANT, CustomVariant.MESSAGE.key);
shell.setText(this.widgetFactory.getI18nSupport().getText(title)); shell.setText(this.widgetFactory.getI18nSupport().getText(title));
shell.setLayout(new GridLayout()); shell.setLayout(new GridLayout());
final GridData gridData2 = new GridData(SWT.FILL, SWT.TOP, true, true); final GridData gridData2 = new GridData(SWT.FILL, SWT.TOP, true, true);
shell.setLayoutData(gridData2); shell.setLayoutData(gridData2);
final Composite main = new Composite(shell, SWT.NONE); final Composite main = new Composite(shell, SWT.NONE);
main.setLayout(new GridLayout()); main.setLayout(new GridLayout());
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true); final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
gridData.widthHint = this.dialogWidth; gridData.widthHint = this.dialogWidth;
main.setLayoutData(gridData); main.setLayoutData(gridData);
contentComposer.accept(pageContext.copyOf(main)); contentComposer.accept(pageContext.copyOf(main));
gridData.heightHint = calcDialogHeight(main); gridData.heightHint = calcDialogHeight(main);
final Button close = this.widgetFactory.buttonLocalized(shell, CLOSE_TEXT_KEY); final Button close = this.widgetFactory.buttonLocalized(shell, CLOSE_TEXT_KEY);
final GridData data = new GridData(GridData.HORIZONTAL_ALIGN_CENTER); final GridData data = new GridData(GridData.HORIZONTAL_ALIGN_CENTER);
data.widthHint = this.buttonWidth; data.widthHint = this.buttonWidth;
close.setLayoutData(data); close.setLayoutData(data);
close.addListener(SWT.Selection, event -> { close.addListener(SWT.Selection, event -> shell.close());
shell.close();
}); finishUp(shell);
}
finishUp(shell);
} private void finishUp(final Shell shell) {
shell.pack();
private void finishUp(final Shell shell) { final Rectangle bounds = shell.getBounds();
shell.pack(); final Rectangle bounds2 = super.getParent().getDisplay().getBounds();
final Rectangle bounds = shell.getBounds(); bounds.x = (bounds2.width - bounds.width) / 2;
final Rectangle bounds2 = super.getParent().getDisplay().getBounds(); bounds.y = (bounds2.height - bounds.height) / 2;
bounds.x = (bounds2.width - bounds.width) / 2; shell.setBounds(bounds);
bounds.y = (bounds2.height - bounds.height) / 2;
shell.setBounds(bounds); shell.open();
}
shell.open();
} private int calcDialogHeight(final Composite main) {
final int actualHeight = main.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
private int calcDialogHeight(final Composite main) { final int displayHeight = main.getDisplay().getClientArea().height;
final int actualHeight = main.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; final int availableHeight = (displayHeight < actualHeight + 100)
final int displayHeight = main.getDisplay().getClientArea().height; ? displayHeight - 100
final int availableHeight = (displayHeight < actualHeight + 100) : actualHeight;
? displayHeight - 100 return Math.min(availableHeight, this.dialogHeight);
: actualHeight; }
final int height = (availableHeight > this.dialogHeight)
? this.dialogHeight }
: availableHeight;
return height;
}
}

View file

@ -1,346 +1,340 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.page.impl; package ch.ethz.seb.sebserver.gui.service.page.impl;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.rap.rwt.widgets.DialogCallback; import org.eclipse.rap.rwt.widgets.DialogCallback;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.MessageBox; import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Shell;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.APIMessageError; import ch.ethz.seb.sebserver.gbl.api.APIMessageError;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; 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.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.ComposerService; import ch.ethz.seb.sebserver.gui.service.page.ComposerService;
import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageDefinition; import ch.ethz.seb.sebserver.gui.service.page.PageDefinition;
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException; import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
import ch.ethz.seb.sebserver.gui.widget.Message; import ch.ethz.seb.sebserver.gui.widget.Message;
public class PageContextImpl implements PageContext { public class PageContextImpl implements PageContext {
private static final Logger log = LoggerFactory.getLogger(PageContextImpl.class); private static final Logger log = LoggerFactory.getLogger(PageContextImpl.class);
private final I18nSupport i18nSupport; private final I18nSupport i18nSupport;
private final ComposerService composerService; private final ComposerService composerService;
private final Composite root; private final Composite root;
private final Composite parent; private final Composite parent;
private final Map<String, String> attributes; private final Map<String, String> attributes;
PageContextImpl( PageContextImpl(
final I18nSupport i18nSupport, final I18nSupport i18nSupport,
final ComposerService composerService, final ComposerService composerService,
final Composite root, final Composite root,
final Composite parent, final Composite parent,
final Map<String, String> attributes) { final Map<String, String> attributes) {
this.i18nSupport = i18nSupport; this.i18nSupport = i18nSupport;
this.composerService = composerService; this.composerService = composerService;
this.root = root; this.root = root;
this.parent = parent; this.parent = parent;
this.attributes = Utils.immutableMapOf(attributes); this.attributes = Utils.immutableMapOf(attributes);
} }
@Override @Override
public I18nSupport getI18nSupport() { public I18nSupport getI18nSupport() {
return this.i18nSupport; return this.i18nSupport;
} }
@Override @Override
public Shell getShell() { public Shell getShell() {
if (this.root == null) { if (this.root == null) {
return null; return null;
} }
return this.root.getShell(); return this.root.getShell();
} }
@Override @Override
public ComposerService composerService() { public ComposerService composerService() {
return this.composerService; return this.composerService;
} }
@Override @Override
public Composite getRoot() { public Composite getRoot() {
return this.root; return this.root;
} }
@Override @Override
public Composite getParent() { public Composite getParent() {
return this.parent; return this.parent;
} }
@Override @Override
public PageContext copy() { public PageContext copy() {
return copyOf(this.parent); return copyOf(this.parent);
} }
@Override @Override
public PageContext copyOf(final Composite parent) { public PageContext copyOf(final Composite parent) {
return new PageContextImpl( return new PageContextImpl(
this.i18nSupport, this.i18nSupport,
this.composerService, this.composerService,
this.root, this.root,
parent, parent,
new HashMap<>(this.attributes)); new HashMap<>(this.attributes));
} }
@Override @Override
public PageContext copyOfAttributes(final PageContext otherContext) { public PageContext copyOfAttributes(final PageContext otherContext) {
final Map<String, String> attrs = new HashMap<>(); final Map<String, String> attrs = new HashMap<>();
attrs.putAll(this.attributes); attrs.putAll(this.attributes);
attrs.putAll(((PageContextImpl) otherContext).attributes); attrs.putAll(((PageContextImpl) otherContext).attributes);
return new PageContextImpl( return new PageContextImpl(
this.i18nSupport, this.i18nSupport,
this.composerService, this.composerService,
this.root, this.root,
this.parent, this.parent,
attrs); attrs);
} }
@Override @Override
public PageContext withAttribute(final String key, final String value) { public PageContext withAttribute(final String key, final String value) {
final Map<String, String> attrs = new HashMap<>(); final Map<String, String> attrs = new HashMap<>(this.attributes);
attrs.putAll(this.attributes); attrs.put(key, value);
attrs.put(key, value); return new PageContextImpl(
return new PageContextImpl( this.i18nSupport,
this.i18nSupport, this.composerService,
this.composerService, this.root,
this.root, this.parent,
this.parent, attrs);
attrs); }
}
@Override
@Override public String getAttribute(final String name) {
public String getAttribute(final String name) { return this.attributes.get(name);
return this.attributes.get(name); }
}
@Override
@Override public String getAttribute(final String name, final String def) {
public String getAttribute(final String name, final String def) { return this.attributes.getOrDefault(name, def);
if (this.attributes.containsKey(name)) { }
return this.attributes.get(name);
} else { @Override
return def; public boolean isReadonly() {
} return BooleanUtils.toBoolean(getAttribute(AttributeKeys.READ_ONLY, "true"));
} }
@Override @Override
public boolean isReadonly() { public EntityKey getEntityKey() {
return BooleanUtils.toBoolean(getAttribute(AttributeKeys.READ_ONLY, "true")); if (hasAttribute(AttributeKeys.ENTITY_ID) && hasAttribute(AttributeKeys.ENTITY_TYPE)) {
} return new EntityKey(
getAttribute(AttributeKeys.ENTITY_ID),
@Override EntityType.valueOf(getAttribute(AttributeKeys.ENTITY_TYPE)));
public EntityKey getEntityKey() { }
if (hasAttribute(AttributeKeys.ENTITY_ID) && hasAttribute(AttributeKeys.ENTITY_TYPE)) {
return new EntityKey( return null;
getAttribute(AttributeKeys.ENTITY_ID), }
EntityType.valueOf(getAttribute(AttributeKeys.ENTITY_TYPE)));
} @Override
public EntityKey getParentEntityKey() {
return null; if (hasAttribute(AttributeKeys.PARENT_ENTITY_ID) && hasAttribute(AttributeKeys.PARENT_ENTITY_TYPE)) {
} return new EntityKey(
getAttribute(AttributeKeys.PARENT_ENTITY_ID),
@Override EntityType.valueOf(getAttribute(AttributeKeys.PARENT_ENTITY_TYPE)));
public EntityKey getParentEntityKey() { }
if (hasAttribute(AttributeKeys.PARENT_ENTITY_ID) && hasAttribute(AttributeKeys.PARENT_ENTITY_TYPE)) {
return new EntityKey( return null;
getAttribute(AttributeKeys.PARENT_ENTITY_ID), }
EntityType.valueOf(getAttribute(AttributeKeys.PARENT_ENTITY_TYPE)));
} @Override
public PageContext withEntityKey(final EntityKey entityKey) {
return null; if (entityKey == null) {
} return removeAttribute(AttributeKeys.ENTITY_ID)
.removeAttribute(AttributeKeys.ENTITY_TYPE);
@Override }
public PageContext withEntityKey(final EntityKey entityKey) { return withAttribute(AttributeKeys.ENTITY_ID, entityKey.modelId)
if (entityKey == null) { .withAttribute(AttributeKeys.ENTITY_TYPE, entityKey.entityType.name());
return removeAttribute(AttributeKeys.ENTITY_ID) }
.removeAttribute(AttributeKeys.ENTITY_TYPE);
} @Override
return withAttribute(AttributeKeys.ENTITY_ID, entityKey.modelId) public PageContext withParentEntityKey(final EntityKey entityKey) {
.withAttribute(AttributeKeys.ENTITY_TYPE, entityKey.entityType.name()); if (entityKey == null) {
} return removeAttribute(AttributeKeys.PARENT_ENTITY_ID)
.removeAttribute(AttributeKeys.PARENT_ENTITY_TYPE);
@Override }
public PageContext withParentEntityKey(final EntityKey entityKey) { return withAttribute(AttributeKeys.PARENT_ENTITY_ID, entityKey.modelId)
if (entityKey == null) { .withAttribute(AttributeKeys.PARENT_ENTITY_TYPE, entityKey.entityType.name());
return removeAttribute(AttributeKeys.PARENT_ENTITY_ID) }
.removeAttribute(AttributeKeys.PARENT_ENTITY_TYPE);
} @Override
return withAttribute(AttributeKeys.PARENT_ENTITY_ID, entityKey.modelId) public PageContext clearEntityKeys() {
.withAttribute(AttributeKeys.PARENT_ENTITY_TYPE, entityKey.entityType.name()); return withEntityKey(null)
} .withParentEntityKey(null);
}
@Override
public PageContext clearEntityKeys() { @Override
return withEntityKey(null) public boolean hasAttribute(final String name) {
.withParentEntityKey(null); return this.attributes.containsKey(name);
} }
@Override @Override
public boolean hasAttribute(final String name) { public PageContext removeAttribute(final String name) {
return this.attributes.containsKey(name); final Map<String, String> attrs = new HashMap<>(this.attributes);
} attrs.remove(name);
return new PageContextImpl(
@Override this.i18nSupport,
public PageContext removeAttribute(final String name) { this.composerService,
final Map<String, String> attrs = new HashMap<>(); this.root,
attrs.putAll(this.attributes); this.parent,
attrs.remove(name); attrs);
return new PageContextImpl( }
this.i18nSupport,
this.composerService, @Override
this.root, public PageContext clearAttributes() {
this.parent, return new PageContextImpl(
attrs); this.i18nSupport,
} this.composerService,
this.root,
@Override this.parent,
public PageContext clearAttributes() { null);
return new PageContextImpl( }
this.i18nSupport,
this.composerService, @Override
this.root, public void applyConfirmDialog(final LocTextKey confirmMessage, final Consumer<Boolean> callback) {
this.parent, final Message messageBox = new Message(
null); this.root.getShell(),
} this.i18nSupport.getText("sebserver.dialog.confirm.title"),
this.i18nSupport.getText(confirmMessage),
@Override SWT.OK | SWT.CANCEL,
public void applyConfirmDialog(final LocTextKey confirmMessage, final Consumer<Boolean> callback) { this.i18nSupport);
final Message messageBox = new Message( messageBox.setMarkupEnabled(true);
this.root.getShell(), messageBox.open(new ConfirmDialogCallback(callback));
this.i18nSupport.getText("sebserver.dialog.confirm.title"), }
this.i18nSupport.getText(confirmMessage),
SWT.OK | SWT.CANCEL, @Override
this.i18nSupport); public void forwardToPage(final PageDefinition pageDefinition) {
messageBox.setMarkupEnabled(true); this.composerService.compose(
messageBox.open(new ConfirmDialogCallback(callback)); pageDefinition.composer(),
} pageDefinition.applyPageContext(copyOf(this.root)));
}
@Override
public void forwardToPage(final PageDefinition pageDefinition) { @Override
this.composerService.compose( public void forwardToMainPage() {
pageDefinition.composer(), forwardToPage(this.composerService.mainPage());
pageDefinition.applyPageContext(copyOf(this.root))); }
}
@Override
@Override public void forwardToLoginPage() {
public void forwardToMainPage() { this.clearAttributes()
forwardToPage(this.composerService.mainPage()); .forwardToPage(this.composerService.loginPage());
} }
@Override @Override
public void forwardToLoginPage() { public void publishPageMessage(final LocTextKey title, final LocTextKey message) {
this.clearAttributes() final MessageBox messageBox = new Message(
.forwardToPage(this.composerService.loginPage()); getShell(),
} (title != null)
? this.i18nSupport.getText(title)
@Override : "",
public void publishPageMessage(final LocTextKey title, final LocTextKey message) { this.i18nSupport.getText(message),
final MessageBox messageBox = new Message( SWT.NONE,
getShell(), this.i18nSupport);
(title != null)
? this.i18nSupport.getText(title) messageBox.setMarkupEnabled(true);
: "", messageBox.open(null);
this.i18nSupport.getText(message), }
SWT.NONE,
this.i18nSupport); @Override
public void publishPageMessage(final PageMessageException pme) {
messageBox.setMarkupEnabled(true); final MessageBox messageBox = new Message(
messageBox.open(null); getShell(),
} this.i18nSupport.getText("sebserver.page.message"),
this.i18nSupport.getText(pme.getMessageKey()),
@Override SWT.NONE,
public void publishPageMessage(final PageMessageException pme) { this.i18nSupport);
final MessageBox messageBox = new Message( messageBox.setMarkupEnabled(true);
getShell(), messageBox.open(null);
this.i18nSupport.getText("sebserver.page.message"), }
this.i18nSupport.getText(pme.getMessageKey()),
SWT.NONE, @Override
this.i18nSupport); public void notifyError(final LocTextKey message, final Exception error) {
messageBox.setMarkupEnabled(true);
messageBox.open(null); log.error("Unexpected GUI error notified: {}", error.getMessage());
}
final String errorMessage = message != null
@Override ? this.i18nSupport.getText(message)
public void notifyError(final LocTextKey message, final Exception error) { : error.getMessage();
log.error("Unexpected GUI error notified: {}", error.getMessage()); if (error instanceof APIMessageError) {
final List<APIMessage> errorMessages = ((APIMessageError) error).getErrorMessages();
final String errorMessage = message != null final MessageBox messageBox = new Message(
? this.i18nSupport.getText(message) getShell(),
: error.getMessage(); this.i18nSupport.getText("sebserver.error.unexpected"),
APIMessage.toHTML(errorMessage, errorMessages),
if (error instanceof APIMessageError) { SWT.ERROR,
final List<APIMessage> errorMessages = ((APIMessageError) error).getErrorMessages(); this.i18nSupport);
final MessageBox messageBox = new Message( messageBox.setMarkupEnabled(true);
getShell(), messageBox.open(null);
this.i18nSupport.getText("sebserver.error.unexpected"), return;
APIMessage.toHTML(errorMessage, errorMessages), }
SWT.ERROR,
this.i18nSupport); final MessageBox messageBox = new Message(
messageBox.setMarkupEnabled(true); getShell(),
messageBox.open(null); this.i18nSupport.getText("sebserver.error.unexpected"),
return; Utils.formatHTMLLines(errorMessage + "<br/><br/> Cause: " + error.getMessage()),
} SWT.ERROR,
this.i18nSupport);
final MessageBox messageBox = new Message( messageBox.open(null);
getShell(), }
this.i18nSupport.getText("sebserver.error.unexpected"),
Utils.formatHTMLLines(errorMessage + "<br/><br/> Cause: " + error.getMessage()), @Override
SWT.ERROR, public String toString() {
this.i18nSupport); return "PageContextImpl [root=" + this.root + ", parent=" + this.parent + ", attributes=" + this.attributes
messageBox.open(null); + "]";
} }
@Override private static final class ConfirmDialogCallback implements DialogCallback {
public String toString() { private static final long serialVersionUID = 1491270214433492441L;
return "PageContextImpl [root=" + this.root + ", parent=" + this.parent + ", attributes=" + this.attributes private final Consumer<Boolean> onOK;
+ "]";
} private ConfirmDialogCallback(final Consumer<Boolean> onOK) {
this.onOK = onOK;
private static final class ConfirmDialogCallback implements DialogCallback { }
private static final long serialVersionUID = 1491270214433492441L;
private final Consumer<Boolean> onOK; @Override
public void dialogClosed(final int returnCode) {
private ConfirmDialogCallback(final Consumer<Boolean> onOK) { if (returnCode == SWT.OK) {
this.onOK = onOK; try {
} this.onOK.accept(true);
} catch (final Exception e) {
@Override log.error(
public void dialogClosed(final int returnCode) { "Unexpected on confirm callback execution. This should not happen, please secure the given onOK Runnable",
if (returnCode == SWT.OK) { e);
try { this.onOK.accept(false);
this.onOK.accept(true); }
} catch (final Exception e) { } else {
log.error( this.onOK.accept(false);
"Unexpected on confirm callback execution. This should not happen, plase secure the given onOK Runnable", }
e); }
this.onOK.accept(false); }
}
} else { }
this.onOK.accept(false);
}
}
}
}

View file

@ -227,7 +227,7 @@ public class PageServiceImpl implements PageService {
final int dependencies = (int) entities.stream() final int dependencies = (int) entities.stream()
.flatMap(entity -> { .flatMap(entity -> {
final RestCall<Set<EntityKey>>.RestCallBuilder builder = final RestCall<Set<EntityKey>>.RestCallBuilder builder =
restService.<Set<EntityKey>>getBuilder( restService.getBuilder(
entity.entityType(), entity.entityType(),
CallType.GET_DEPENDENCIES); CallType.GET_DEPENDENCIES);
@ -366,7 +366,7 @@ public class PageServiceImpl implements PageService {
final boolean logoutSuccessful = this.currentUser.logout(); final boolean logoutSuccessful = this.currentUser.logout();
if (!logoutSuccessful) { if (!logoutSuccessful) {
log.warn("Failed to logout. See logfiles for more information"); log.warn("Failed to logout. See log-files for more information");
} }
} catch (final Exception e) { } catch (final Exception e) {

View file

@ -1,104 +1,104 @@
/* /*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.push; package ch.ethz.seb.sebserver.gui.service.push;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.eclipse.rap.rwt.service.ServerPushSession; import org.eclipse.rap.rwt.service.ServerPushSession;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
/** Puts RAP's server-push functionality in a well defined service by using a context /** Puts RAP's server-push functionality in a well defined service by using a context
* as state holder and the possibility to split the server-push process into two * as state holder and the possibility to split the server-push process into two
* separated processes, a business-process to get and update business data and the * separated processes, a business-process to get and update business data and the
* an update-process to update the UI after according to updated data */ * an update-process to update the UI after according to updated data */
@Lazy @Lazy
@Service @Service
public class ServerPushService { public class ServerPushService {
private static final Logger log = LoggerFactory.getLogger(ServerPushService.class); private static final Logger log = LoggerFactory.getLogger(ServerPushService.class);
public void runServerPush( public void runServerPush(
final ServerPushContext context, final ServerPushContext context,
final long intervalPause, final long intervalPause,
final Consumer<ServerPushContext> update) { final Consumer<ServerPushContext> update) {
this.runServerPush(context, intervalPause, null, update); this.runServerPush(context, intervalPause, null, update);
} }
public void runServerPush( public void runServerPush(
final ServerPushContext context, final ServerPushContext context,
final long intervalPause, final long intervalPause,
final Consumer<ServerPushContext> business, final Consumer<ServerPushContext> business,
final Consumer<ServerPushContext> update) { final Consumer<ServerPushContext> update) {
final ServerPushSession pushSession = new ServerPushSession(); final ServerPushSession pushSession = new ServerPushSession();
pushSession.start(); pushSession.start();
final Thread bgThread = new Thread(() -> { final Thread bgThread = new Thread(() -> {
while (!context.isDisposed() && context.runAgain()) { while (!context.isDisposed() && context.runAgain()) {
try { try {
Thread.sleep(intervalPause); Thread.sleep(intervalPause);
} catch (final Exception e) { } catch (final Exception e) {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("unexpected error while sleep: ", e); log.debug("unexpected error while sleep: ", e);
} }
} }
if (business != null) { if (business != null) {
try { try {
log.trace("Call business on Server Push Session on: {}", Thread.currentThread().getName()); log.trace("Call business on Server Push Session on: {}", Thread.currentThread().getName());
business.accept(context); business.accept(context);
} catch (final Exception e) { } catch (final Exception e) {
log.error("Unexpected error while do business for server push service", e); log.error("Unexpected error while do business for server push service", e);
if (context.runAgain()) { if (context.runAgain()) {
continue; continue;
} else { } else {
return; return;
} }
} }
} }
if (!context.isDisposed()) { if (!context.isDisposed()) {
log.trace("Call update on Server Push Session on: {}", Thread.currentThread().getName()); log.trace("Call update on Server Push Session on: {}", Thread.currentThread().getName());
context.getDisplay().asyncExec(() -> { context.getDisplay().asyncExec(() -> {
try { try {
update.accept(context); update.accept(context);
} catch (final Exception e) { } catch (final Exception e) {
log.warn( log.warn(
"Failed to update on Server Push Session {}. It seems that the UISession is not available anymore. " "Failed to update on Server Push Session {}. It seems that the UISession is not available anymore. "
+ "This may source from a connection interruption.", + "This may source from a connection interruption. cause: {}",
Thread.currentThread().getName(), e.getMessage()); Thread.currentThread().getName(), e.getMessage());
} }
}); });
} }
} }
log.info("Stop Server Push Session on: {}", Thread.currentThread().getName()); log.info("Stop Server Push Session on: {}", Thread.currentThread().getName());
try { try {
pushSession.stop(); pushSession.stop();
} catch (final Exception e) { } catch (final Exception e) {
log.warn( 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", "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); Thread.currentThread().getName(), e);
} }
}); });
log.info("Start new Server Push Session on: {}", bgThread.getName()); log.info("Start new Server Push Session on: {}", bgThread.getName());
bgThread.setDaemon(true); bgThread.setDaemon(true);
bgThread.start(); bgThread.start();
} }
} }

View file

@ -1,119 +1,116 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.remote.download; package ch.ethz.seb.sebserver.gui.service.remote.download;
import java.io.IOException; import ch.ethz.seb.sebserver.gbl.Constants;
import java.util.Collection; import ch.ethz.seb.sebserver.gbl.api.API;
import java.util.Map; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import java.util.function.Function; import org.apache.commons.lang3.StringUtils;
import java.util.stream.Collectors; import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.service.ServiceHandler;
import javax.servlet.ServletException; import org.slf4j.Logger;
import javax.servlet.http.HttpServletRequest; import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletResponse; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.RWT; import javax.servlet.http.HttpServletRequest;
import org.eclipse.rap.rwt.service.ServiceHandler; import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger; import java.util.Collection;
import org.slf4j.LoggerFactory; import java.util.Map;
import org.springframework.context.annotation.Lazy; import java.util.function.Function;
import org.springframework.stereotype.Service; import java.util.stream.Collectors;
import ch.ethz.seb.sebserver.gbl.Constants; /** Implements a eclipse RAP ServiceHandler to handle downloads */
import ch.ethz.seb.sebserver.gbl.api.API; @Lazy
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; @Service
@GuiProfile
@Lazy public class DownloadService implements ServiceHandler {
@Service
@GuiProfile private static final Logger log = LoggerFactory.getLogger(DownloadService.class);
public class DownloadService implements ServiceHandler {
public static final String DOWNLOAD_SERVICE_NAME = "DOWNLOAD_SERVICE";
private static final Logger log = LoggerFactory.getLogger(DownloadService.class); public static final String HANDLER_NAME_PARAMETER = "download-handler-name";
public static final String DOWNLOAD_FILE_NAME = "download-file-name";
public static final String DOWNLOAD_SERVICE_NAME = "DOWNLOAD_SERVICE";
public static final String HANDLER_NAME_PARAMETER = "download-handler-name"; private final Map<String, DownloadServiceHandler> handler;
public static final String DOWNLOAD_FILE_NAME = "download-file-name";
protected DownloadService(final Collection<DownloadServiceHandler> handler) {
private final Map<String, DownloadServiceHandler> handler; this.handler = handler
.stream()
protected DownloadService(final Collection<DownloadServiceHandler> handler) { .collect(Collectors.toMap(
this.handler = handler h -> h.getClass().getSimpleName(),
.stream() Function.identity()));
.collect(Collectors.toMap( }
h -> h.getClass().getSimpleName(),
Function.identity())); @Override
} public void service(
final HttpServletRequest request,
@Override final HttpServletResponse response) {
public void service(
final HttpServletRequest request, log.debug("Received download service request: {}", request.getRequestURI());
final HttpServletResponse response) throws IOException, ServletException {
final String handlerName = request.getParameter(HANDLER_NAME_PARAMETER);
log.debug("Received download service request: {}", request.getRequestURI()); if (StringUtils.isBlank(handlerName)) {
log.error("Missing request parameter {}. Ignoring download service request",
final String handlerName = request.getParameter(HANDLER_NAME_PARAMETER); HANDLER_NAME_PARAMETER);
if (StringUtils.isBlank(handlerName)) { return;
log.error("Missing request parameter {}. Ignoring download service request", }
HANDLER_NAME_PARAMETER);
return; if (!this.handler.containsKey(handlerName)) {
} log.error("Missing DownloadServiceHandler with name {}. Ignoring download service request",
handlerName);
if (!this.handler.containsKey(handlerName)) { return;
log.error("Missing DownloadServiceHandler with name {}. Ignoring download service request", }
handlerName);
return; this.handler
} .get(handlerName)
.processDownload(request, response);
this.handler }
.get(handlerName)
.processDownload(request, response); public String createDownloadURL(
} final String modelId,
final Class<? extends DownloadServiceHandler> handlerClass,
public String createDownloadURL( final String downloadFileName) {
final String modelId,
final Class<? extends DownloadServiceHandler> handlerClass, return createDownloadURL(modelId, null, handlerClass, downloadFileName);
final String downloadFileName) { }
return createDownloadURL(modelId, null, handlerClass, downloadFileName); public String createDownloadURL(
} final String modelId,
final String parentModelId,
public String createDownloadURL( final Class<? extends DownloadServiceHandler> handlerClass,
final String modelId, final String downloadFileName) {
final String parentModelId,
final Class<? extends DownloadServiceHandler> handlerClass, final StringBuilder url = new StringBuilder()
final String downloadFileName) { .append(RWT.getServiceManager()
.getServiceHandlerUrl(DownloadService.DOWNLOAD_SERVICE_NAME))
final StringBuilder url = new StringBuilder() .append(Constants.FORM_URL_ENCODED_SEPARATOR)
.append(RWT.getServiceManager() .append(API.PARAM_MODEL_ID)
.getServiceHandlerUrl(DownloadService.DOWNLOAD_SERVICE_NAME)) .append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR)
.append(Constants.FORM_URL_ENCODED_SEPARATOR) .append(modelId)
.append(API.PARAM_MODEL_ID) .append(Constants.FORM_URL_ENCODED_SEPARATOR)
.append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR) .append(DownloadService.HANDLER_NAME_PARAMETER)
.append(modelId) .append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR)
.append(Constants.FORM_URL_ENCODED_SEPARATOR) .append(handlerClass.getSimpleName())
.append(DownloadService.HANDLER_NAME_PARAMETER) .append(Constants.FORM_URL_ENCODED_SEPARATOR)
.append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR) .append(DownloadService.DOWNLOAD_FILE_NAME)
.append(handlerClass.getSimpleName()) .append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR)
.append(Constants.FORM_URL_ENCODED_SEPARATOR) .append(downloadFileName);
.append(DownloadService.DOWNLOAD_FILE_NAME)
.append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR) if (StringUtils.isNotBlank(parentModelId)) {
.append(downloadFileName); url.append(Constants.FORM_URL_ENCODED_SEPARATOR)
.append(API.PARAM_PARENT_MODEL_ID)
if (StringUtils.isNotBlank(parentModelId)) { .append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR)
url.append(Constants.FORM_URL_ENCODED_SEPARATOR) .append(parentModelId);
.append(API.PARAM_PARENT_MODEL_ID) }
.append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR)
.append(parentModelId); return url.toString();
} }
return url.toString(); }
}
}

View file

@ -1,18 +1,23 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.remote.download; package ch.ethz.seb.sebserver.gui.service.remote.download;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
public interface DownloadServiceHandler { /** Interface defining a service to handle downloads */
public interface DownloadServiceHandler {
void processDownload(final HttpServletRequest request, final HttpServletResponse response);
/** Process a requested download
} *
* @param request The download HttpServletRequest
* @param response the response to send the download to */
void processDownload(final HttpServletRequest request, final HttpServletResponse response);
}

View file

@ -1,69 +1,69 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.remote.download; package ch.ethz.seb.sebserver.gui.service.remote.download;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import org.apache.tomcat.util.http.fileupload.IOUtils; import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.ExportClientConfig; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.ExportClientConfig;
@Lazy @Lazy
@Component @Component
@GuiProfile @GuiProfile
public class SebClientConfigDownload extends AbstractDownloadServiceHandler { public class SebClientConfigDownload extends AbstractDownloadServiceHandler {
private static final Logger log = LoggerFactory.getLogger(SebClientConfigDownload.class); private static final Logger log = LoggerFactory.getLogger(SebClientConfigDownload.class);
private final RestService restService; private final RestService restService;
public final String downloadFileName; public final String downloadFileName;
protected SebClientConfigDownload( protected SebClientConfigDownload(
final RestService restService, final RestService restService,
@Value("${sebserver.gui.seb.client.config.download.filename}") final String downloadFileName) { @Value("${sebserver.gui.seb.client.config.download.filename}") final String downloadFileName) {
this.restService = restService; this.restService = restService;
this.downloadFileName = downloadFileName; this.downloadFileName = downloadFileName;
} }
@Override @Override
protected void webserviceCall(final String modelId, final String parentModelId, final OutputStream downloadOut) { protected void webserviceCall(final String modelId, final String parentModelId, final OutputStream downloadOut) {
final InputStream input = this.restService.getBuilder(ExportClientConfig.class) final InputStream input = this.restService.getBuilder(ExportClientConfig.class)
.withURIVariable(API.PARAM_MODEL_ID, modelId) .withURIVariable(API.PARAM_MODEL_ID, modelId)
.call() .call()
.getOrThrow(); .getOrThrow();
try { try {
IOUtils.copyLarge(input, downloadOut); IOUtils.copyLarge(input, downloadOut);
} catch (final IOException e) { } catch (final IOException e) {
log.error( log.error(
"Unexpected error while streaming incomming config data from web-service to output-stream of download response: ", "Unexpected error while streaming incoming config data from web-service to output-stream of download response: ",
e); e);
} finally { } finally {
try { try {
downloadOut.flush(); downloadOut.flush();
downloadOut.close(); downloadOut.close();
} catch (final IOException e) { } catch (final IOException e) {
log.error("Unexpected error while trying to close download output-stream"); log.error("Unexpected error while trying to close download output-stream");
} }
} }
} }
} }

View file

@ -1,64 +1,64 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.remote.download; package ch.ethz.seb.sebserver.gui.service.remote.download;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import org.apache.tomcat.util.http.fileupload.IOUtils; import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.ExportExamConfig; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.ExportExamConfig;
@Lazy @Lazy
@Component @Component
@GuiProfile @GuiProfile
public class SebExamConfigDownload extends AbstractDownloadServiceHandler { public class SebExamConfigDownload extends AbstractDownloadServiceHandler {
private static final Logger log = LoggerFactory.getLogger(SebExamConfigDownload.class); private static final Logger log = LoggerFactory.getLogger(SebExamConfigDownload.class);
private final RestService restService; private final RestService restService;
protected SebExamConfigDownload(final RestService restService) { protected SebExamConfigDownload(final RestService restService) {
this.restService = restService; this.restService = restService;
} }
@Override @Override
protected void webserviceCall(final String modelId, final String parentModelId, final OutputStream downloadOut) { protected void webserviceCall(final String modelId, final String parentModelId, final OutputStream downloadOut) {
final InputStream input = this.restService.getBuilder(ExportExamConfig.class) final InputStream input = this.restService.getBuilder(ExportExamConfig.class)
.withURIVariable(API.PARAM_MODEL_ID, modelId) .withURIVariable(API.PARAM_MODEL_ID, modelId)
.withURIVariable(API.PARAM_PARENT_MODEL_ID, parentModelId) .withURIVariable(API.PARAM_PARENT_MODEL_ID, parentModelId)
.call() .call()
.getOrThrow(); .getOrThrow();
try { try {
IOUtils.copyLarge(input, downloadOut); IOUtils.copyLarge(input, downloadOut);
} catch (final IOException e) { } catch (final IOException e) {
log.error( log.error(
"Unexpected error while streaming incomming config data from web-service to output-stream of download response: ", "Unexpected error while streaming incoming config data from web-service to output-stream of download response: ",
e); e);
} finally { } finally {
try { try {
downloadOut.flush(); downloadOut.flush();
downloadOut.close(); downloadOut.close();
} catch (final IOException e) { } catch (final IOException e) {
log.error("Unexpected error while trying to close download output-stream"); log.error("Unexpected error while trying to close download output-stream");
} }
} }
} }
} }

View file

@ -1,63 +1,63 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.remote.download; package ch.ethz.seb.sebserver.gui.service.remote.download;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import org.apache.tomcat.util.http.fileupload.IOUtils; import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ExportPlainXML; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ExportPlainXML;
@Lazy @Lazy
@Component @Component
@GuiProfile @GuiProfile
public class SebExamConfigPlaintextDownload extends AbstractDownloadServiceHandler { public class SebExamConfigPlaintextDownload extends AbstractDownloadServiceHandler {
private static final Logger log = LoggerFactory.getLogger(SebExamConfigPlaintextDownload.class); private static final Logger log = LoggerFactory.getLogger(SebExamConfigPlaintextDownload.class);
private final RestService restService; private final RestService restService;
protected SebExamConfigPlaintextDownload(final RestService restService) { protected SebExamConfigPlaintextDownload(final RestService restService) {
this.restService = restService; this.restService = restService;
} }
@Override @Override
protected void webserviceCall(final String modelId, final String parentModelId, final OutputStream downloadOut) { protected void webserviceCall(final String modelId, final String parentModelId, final OutputStream downloadOut) {
final InputStream input = this.restService.getBuilder(ExportPlainXML.class) final InputStream input = this.restService.getBuilder(ExportPlainXML.class)
.withURIVariable(API.PARAM_MODEL_ID, modelId) .withURIVariable(API.PARAM_MODEL_ID, modelId)
.call() .call()
.getOrThrow(); .getOrThrow();
try { try {
IOUtils.copyLarge(input, downloadOut); IOUtils.copyLarge(input, downloadOut);
} catch (final IOException e) { } catch (final IOException e) {
log.error( log.error(
"Unexpected error while streaming incomming config data from web-service to output-stream of download response: ", "Unexpected error while streaming incoming config data from web-service to output-stream of download response: ",
e); e);
} finally { } finally {
try { try {
downloadOut.flush(); downloadOut.flush();
downloadOut.close(); downloadOut.close();
} catch (final IOException e) { } catch (final IOException e) {
log.error("Unexpected error while trying to close download output-stream"); log.error("Unexpected error while trying to close download output-stream");
} }
} }
} }
} }

View file

@ -1,49 +1,45 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api; package ch.ethz.seb.sebserver.gui.service.remote.webservice.api;
import java.io.InputStream; import java.io.InputStream;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequest;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
public abstract class AbstractExportCall extends RestCall<InputStream> { public abstract class AbstractExportCall extends RestCall<InputStream> {
protected AbstractExportCall( protected AbstractExportCall(
final TypeKey<InputStream> typeKey, final TypeKey<InputStream> typeKey,
final HttpMethod httpMethod, final HttpMethod httpMethod,
final MediaType contentType, final MediaType contentType,
final String path) { final String path) {
super(typeKey, httpMethod, contentType, path); super(typeKey, httpMethod, contentType, path);
} }
@Override @Override
protected Result<InputStream> exchange(final RestCallBuilder builder) { protected Result<InputStream> exchange(final RestCallBuilder builder) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> builder
.getRestTemplate()
return builder .execute(
.getRestTemplate() builder.buildURI(),
.execute( this.httpMethod,
builder.buildURI(), (final ClientHttpRequest requestCallback) -> {
this.httpMethod, },
(final ClientHttpRequest requestCallback) -> { response -> IOUtils.toBufferedInputStream(response.getBody()),
}, builder.getURIVariables()));
response -> IOUtils.toBufferedInputStream(response.getBody()), }
builder.getURIVariables());
}
});
}
}

View file

@ -1,17 +1,25 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api; package ch.ethz.seb.sebserver.gui.service.remote.webservice.api;
public interface FormBinding { /** Defines a form binding to get form parameter and values either in JSON format
* or as form-url-encoded string */
String getFormAsJson(); public interface FormBinding {
String getFormUrlEncoded(); /** Get the form parameter and values in JSON format
*
} * @return the form parameter and values in JSON format */
String getFormAsJson();
/** Get the form parameter and values in form-url-encoded string format.
*
* @return the form parameter and values in form-url-encoded string format.*/
String getFormUrlEncoded();
}

View file

@ -1,422 +1,420 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api; package ch.ethz.seb.sebserver.gui.service.remote.webservice.api;
import java.io.IOException; import ch.ethz.seb.sebserver.gbl.Constants;
import java.io.InputStream; import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import java.util.Arrays; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import java.util.HashMap; import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import java.util.List; import ch.ethz.seb.sebserver.gbl.model.Entity;
import java.util.Map; import ch.ethz.seb.sebserver.gbl.model.Page;
import java.util.function.Function; import ch.ethz.seb.sebserver.gbl.model.PageSortOrder;
import ch.ethz.seb.sebserver.gbl.util.Result;
import org.apache.commons.lang3.StringUtils; import ch.ethz.seb.sebserver.gbl.util.Utils;
import org.slf4j.Logger; import com.fasterxml.jackson.core.JsonParseException;
import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.core.io.InputStreamResource; import com.fasterxml.jackson.core.type.TypeReference;
import org.springframework.http.HttpEntity; import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders; import org.slf4j.Logger;
import org.springframework.http.HttpMethod; import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus; import org.springframework.core.io.InputStreamResource;
import org.springframework.http.MediaType; import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity; import org.springframework.http.HttpHeaders;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.http.HttpMethod;
import org.springframework.util.MultiValueMap; import org.springframework.http.HttpStatus;
import org.springframework.web.client.RestClientResponseException; import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate; import org.springframework.http.ResponseEntity;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import com.fasterxml.jackson.core.JsonParseException; import org.springframework.web.client.RestClientResponseException;
import com.fasterxml.jackson.core.JsonProcessingException; import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.core.type.TypeReference; import org.springframework.web.util.UriComponentsBuilder;
import com.fasterxml.jackson.databind.JsonMappingException;
import java.io.IOException;
import ch.ethz.seb.sebserver.gbl.Constants; import java.io.InputStream;
import ch.ethz.seb.sebserver.gbl.api.APIMessage; import java.util.Arrays;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import java.util.HashMap;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import java.util.List;
import ch.ethz.seb.sebserver.gbl.model.Entity; import java.util.Map;
import ch.ethz.seb.sebserver.gbl.model.Page; import java.util.function.Function;
import ch.ethz.seb.sebserver.gbl.model.PageSortOrder;
import ch.ethz.seb.sebserver.gbl.util.Result; public abstract class RestCall<T> {
import ch.ethz.seb.sebserver.gbl.util.Utils;
private static final Logger log = LoggerFactory.getLogger(RestCall.class);
public abstract class RestCall<T> {
public enum CallType {
private static final Logger log = LoggerFactory.getLogger(RestCall.class); UNDEFINED,
GET_SINGLE,
public enum CallType { GET_PAGE,
UNDEFINED, GET_NAMES,
GET_SINGLE, GET_DEPENDENCIES,
GET_PAGE, GET_LIST,
GET_NAMES, NEW,
GET_DEPENDENCIES, REGISTER,
GET_LIST, SAVE,
NEW, DELETE,
REGISTER, ACTIVATION_ACTIVATE,
SAVE, ACTIVATION_DEACTIVATE
DELETE, }
ACTIVATION_ACTIVATE,
ACTIVATION_DEACTIVATE protected RestService restService;
} protected JSONMapper jsonMapper;
protected TypeKey<T> typeKey;
protected RestService restService; protected final HttpMethod httpMethod;
protected JSONMapper jsonMapper; protected final MediaType contentType;
protected TypeKey<T> typeKey; protected final String path;
protected final HttpMethod httpMethod;
protected final MediaType contentType; protected RestCall(
protected final String path; final TypeKey<T> typeKey,
final HttpMethod httpMethod,
protected RestCall( final MediaType contentType,
final TypeKey<T> typeKey, final String path) {
final HttpMethod httpMethod,
final MediaType contentType, this.typeKey = typeKey;
final String path) { this.httpMethod = httpMethod;
this.contentType = contentType;
this.typeKey = typeKey; this.path = path;
this.httpMethod = httpMethod;
this.contentType = contentType; }
this.path = path;
protected RestCall<T> init(
} final RestService restService,
final JSONMapper jsonMapper) {
protected RestCall<T> init(
final RestService restService, this.restService = restService;
final JSONMapper jsonMapper) { this.jsonMapper = jsonMapper;
return this;
this.restService = restService; }
this.jsonMapper = jsonMapper;
return this; public EntityType getEntityType() {
} if (this.typeKey != null) {
return this.typeKey.entityType;
public EntityType getEntityType() { }
if (this.typeKey != null) {
return this.typeKey.entityType; return null;
} }
return null; protected Result<T> exchange(final RestCallBuilder builder) {
}
log.debug("Call webservice API on {} for {}", this.path, builder);
protected Result<T> exchange(final RestCallBuilder builder) {
try {
log.debug("Call webservice API on {} for {}", this.path, builder); final ResponseEntity<String> responseEntity = builder.restTemplate
.exchange(
try { builder.buildURI(),
final ResponseEntity<String> responseEntity = builder.restTemplate this.httpMethod,
.exchange( builder.buildRequestEntity(),
builder.buildURI(), String.class,
this.httpMethod, builder.uriVariables);
builder.buildRequestEntity(),
String.class, if (responseEntity.getStatusCode() == HttpStatus.OK) {
builder.uriVariables);
if (log.isTraceEnabled()) {
if (responseEntity.getStatusCode() == HttpStatus.OK) { log.trace("response body --> {}" + responseEntity.getBody());
}
if (log.isTraceEnabled()) {
log.trace("response body --> {}" + responseEntity.getBody()); if (!responseEntity.hasBody()) {
} return Result.ofEmpty();
}
if (!responseEntity.hasBody()) {
return Result.ofEmpty(); return Result.of(RestCall.this.jsonMapper.readValue(
} responseEntity.getBody(),
RestCall.this.typeKey.typeRef));
return Result.of(RestCall.this.jsonMapper.readValue(
responseEntity.getBody(), } else {
RestCall.this.typeKey.typeRef)); return handleRestCallError(responseEntity);
}
} else { } catch (final RestClientResponseException responseError) {
return handleRestCallError(responseEntity);
} final RestCallError restCallError = new RestCallError("Unexpected error while rest call", responseError);
} catch (final RestClientResponseException responseError) { try {
final RestCallError restCallError = new RestCallError("Unexpected error while rest call", responseError); final String responseBody = responseError.getResponseBodyAsString();
try { restCallError.errors.addAll(RestCall.this.jsonMapper.readValue(
responseBody,
final String responseBody = responseError.getResponseBodyAsString(); new TypeReference<List<APIMessage>>() {
restCallError.errors.addAll(RestCall.this.jsonMapper.readValue( }));
responseBody,
new TypeReference<List<APIMessage>>() { } catch (final IOException e) {
})); restCallError.errors.add(APIMessage.ErrorMessage.UNEXPECTED.of(
responseError,
} catch (final IOException e) { "NO RESPONSE AVAILABLE" + " cause: " + e.getMessage(),
restCallError.errors.add(APIMessage.ErrorMessage.UNEXPECTED.of( String.valueOf(builder)));
responseError, }
"NO RESPONSE AVAILABLE" + " cause: " + e.getMessage(),
String.valueOf(builder))); return Result.ofError(restCallError);
} } catch (final Exception e) {
final RestCallError restCallError = new RestCallError("Unexpected error while rest call", e);
return Result.ofError(restCallError); restCallError.errors.add(APIMessage.ErrorMessage.UNEXPECTED.of(
} catch (final Exception e) { e,
final RestCallError restCallError = new RestCallError("Unexpected error while rest call", e); "NO RESPONSE AVAILABLE",
restCallError.errors.add(APIMessage.ErrorMessage.UNEXPECTED.of( String.valueOf(builder)));
e, return Result.ofError(e);
"NO RESPONSE AVAILABLE", }
String.valueOf(builder))); }
return Result.ofError(e);
} public RestCallBuilder newBuilder() {
} return new RestCallBuilder(
this.restService.getWebserviceAPIRestTemplate(),
public RestCallBuilder newBuilder() { this.restService.getWebserviceURIBuilder());
return new RestCallBuilder( }
this.restService.getWebserviceAPIRestTemplate(),
this.restService.getWebserviceURIBuilder()); public RestCall<T>.RestCallBuilder newBuilder(final RestCall<?>.RestCallBuilder builder) {
} return new RestCallBuilder(builder);
}
public RestCall<T>.RestCallBuilder newBuilder(final RestCall<?>.RestCallBuilder builder) {
return new RestCallBuilder(builder); private Result<T> handleRestCallError(final ResponseEntity<String> responseEntity)
} throws IOException {
private Result<T> handleRestCallError(final ResponseEntity<String> responseEntity) final RestCallError restCallError =
throws IOException, JsonParseException, JsonMappingException { new RestCallError("Response Entity: " + responseEntity.toString());
final RestCallError restCallError = try {
new RestCallError("Response Entity: " + responseEntity.toString()); restCallError.errors.addAll(RestCall.this.jsonMapper.readValue(
responseEntity.getBody(),
try { new TypeReference<List<APIMessage>>() {
restCallError.errors.addAll(RestCall.this.jsonMapper.readValue( }));
responseEntity.getBody(), } catch (final JsonParseException jpe) {
new TypeReference<List<APIMessage>>() { if (responseEntity.getStatusCode() == HttpStatus.UNAUTHORIZED) {
})); restCallError.errors.add(APIMessage.ErrorMessage.UNAUTHORIZED.of(responseEntity.getBody()));
} catch (final JsonParseException jpe) { } else {
if (responseEntity.getStatusCode() == HttpStatus.UNAUTHORIZED) { restCallError.errors.add(APIMessage.ErrorMessage.GENERIC.of(responseEntity.getBody()));
restCallError.errors.add(APIMessage.ErrorMessage.UNAUTHORIZED.of(responseEntity.getBody())); }
} else { }
restCallError.errors.add(APIMessage.ErrorMessage.GENERIC.of(responseEntity.getBody()));
} log.debug(
} "Webservice answered with well defined error- or validation-failure-response: ",
restCallError);
log.debug(
"Webservice answered with well defined error- or validation-failure-response: ", return Result.ofError(restCallError);
restCallError); }
return Result.ofError(restCallError); public class RestCallBuilder {
}
private RestTemplate restTemplate;
public class RestCallBuilder { private UriComponentsBuilder uriComponentsBuilder;
private final HttpHeaders httpHeaders;
private RestTemplate restTemplate; private String body = null;
private UriComponentsBuilder uriComponentsBuilder; private InputStream streamingBody = null;
private final HttpHeaders httpHeaders;
private String body = null; private final MultiValueMap<String, String> queryParams;
private InputStream streamingBody = null; private final Map<String, String> uriVariables;
private final MultiValueMap<String, String> queryParams; protected RestCallBuilder(final RestTemplate restTemplate, final UriComponentsBuilder uriComponentsBuilder) {
private final Map<String, String> uriVariables; this.restTemplate = restTemplate;
this.uriComponentsBuilder = uriComponentsBuilder;
protected RestCallBuilder(final RestTemplate restTemplate, final UriComponentsBuilder uriComponentsBuilder) { this.httpHeaders = new HttpHeaders();
this.restTemplate = restTemplate; this.queryParams = new LinkedMultiValueMap<>();
this.uriComponentsBuilder = uriComponentsBuilder; this.uriVariables = new HashMap<>();
this.httpHeaders = new HttpHeaders(); this.httpHeaders.set(
this.queryParams = new LinkedMultiValueMap<>(); HttpHeaders.CONTENT_TYPE,
this.uriVariables = new HashMap<>(); RestCall.this.contentType.toString());
this.httpHeaders.set( }
HttpHeaders.CONTENT_TYPE,
RestCall.this.contentType.toString()); public RestCallBuilder(final RestCall<?>.RestCallBuilder builder) {
} this.restTemplate = builder.restTemplate;
this.uriComponentsBuilder = builder.uriComponentsBuilder;
public RestCallBuilder(final RestCall<?>.RestCallBuilder builder) { this.httpHeaders = builder.httpHeaders;
this.restTemplate = builder.restTemplate; this.body = builder.body;
this.uriComponentsBuilder = builder.uriComponentsBuilder; this.streamingBody = builder.streamingBody;
this.httpHeaders = builder.httpHeaders; this.queryParams = new LinkedMultiValueMap<>(builder.queryParams);
this.body = builder.body; this.uriVariables = new HashMap<>(builder.uriVariables);
this.queryParams = new LinkedMultiValueMap<>(builder.queryParams); }
this.uriVariables = new HashMap<>(builder.uriVariables);
} public RestTemplate getRestTemplate() {
return this.restTemplate;
public RestTemplate getRestTemplate() { }
return this.restTemplate;
} public RestCallBuilder withRestTemplate(final RestTemplate restTemplate) {
this.restTemplate = restTemplate;
public RestCallBuilder withRestTemplate(final RestTemplate restTemplate) { return this;
this.restTemplate = restTemplate; }
return this;
} public RestCallBuilder withUriComponentsBuilder(final UriComponentsBuilder uriComponentsBuilder) {
this.uriComponentsBuilder = uriComponentsBuilder;
public RestCallBuilder withUriComponentsBuilder(final UriComponentsBuilder uriComponentsBuilder) { return this;
this.uriComponentsBuilder = uriComponentsBuilder; }
return this;
} public RestCallBuilder withHeaders(final HttpHeaders headers) {
this.httpHeaders.addAll(headers);
public RestCallBuilder withHeaders(final HttpHeaders headers) { return this;
this.httpHeaders.addAll(headers); }
return this;
} public RestCallBuilder withHeader(final String name, final String value) {
this.httpHeaders.set(name, value);
public RestCallBuilder withHeader(final String name, final String value) { return this;
this.httpHeaders.set(name, value); }
return this;
} public RestCallBuilder withHeaders(final MultiValueMap<String, String> params) {
this.httpHeaders.addAll(params);
public RestCallBuilder withHeaders(final MultiValueMap<String, String> params) { return this;
this.httpHeaders.addAll(params); }
return this;
} public RestCallBuilder apply(final Function<RestCallBuilder, RestCallBuilder> f) {
return f.apply(this);
public RestCallBuilder apply(final Function<RestCallBuilder, RestCallBuilder> f) { }
return f.apply(this);
} public RestCallBuilder withBody(final Object body) {
if (body == null) {
public RestCallBuilder withBody(final Object body) { this.body = null;
if (body == null) { return this;
this.body = null; }
return this;
} if (body instanceof String) {
this.body = String.valueOf(body);
if (body instanceof String) { return this;
this.body = String.valueOf(body); }
return this;
} if (body instanceof InputStream) {
this.streamingBody = (InputStream) body;
if (body instanceof InputStream) { return this;
this.streamingBody = (InputStream) body; }
return this;
} try {
this.body = RestCall.this.jsonMapper.writeValueAsString(body);
try { } catch (final JsonProcessingException e) {
this.body = RestCall.this.jsonMapper.writeValueAsString(body); log.error("Error while trying to parse body json object: " + body);
} catch (final JsonProcessingException e) { }
log.error("Error while trying to parse body json object: " + body);
} return this;
}
return this;
} public RestCallBuilder withURIVariable(final String name, final String value) {
this.uriVariables.put(name, value);
public RestCallBuilder withURIVariable(final String name, final String value) { return this;
this.uriVariables.put(name, value); }
return this;
} public RestCallBuilder withQueryParam(final String name, final String value) {
this.queryParams.put(name, Arrays.asList(value));
public RestCallBuilder withQueryParam(final String name, final String value) { return this;
this.queryParams.put(name, Arrays.asList(value)); }
return this;
} public RestCallBuilder withQueryParams(final MultiValueMap<String, String> params) {
if (params != null) {
public RestCallBuilder withQueryParams(final MultiValueMap<String, String> params) { this.queryParams.putAll(params);
if (params != null) { }
this.queryParams.putAll(params); return this;
} }
return this;
} public RestCallBuilder withFormParam(final String name, final String value) {
final String encodedVal = Utils.encodeFormURL_UTF_8(value);
public RestCallBuilder withFormParam(final String name, final String value) { if (StringUtils.isBlank(this.body)) {
final String encodedVal = Utils.encodeFormURL_UTF_8(value); this.body = name + Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR + encodedVal;
if (StringUtils.isBlank(this.body)) { } else {
this.body = name + Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR + encodedVal; this.body = this.body + Constants.FORM_URL_ENCODED_SEPARATOR + name +
} else { Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR + encodedVal;
this.body = this.body + Constants.FORM_URL_ENCODED_SEPARATOR + name + }
Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR + encodedVal;
} return this;
}
return this;
} public RestCallBuilder withPaging(final int pageNumber, final int pageSize) {
this.queryParams.put(Page.ATTR_PAGE_NUMBER, Arrays.asList(String.valueOf(pageNumber)));
public RestCallBuilder withPaging(final int pageNumber, final int pageSize) { this.queryParams.put(Page.ATTR_PAGE_SIZE, Arrays.asList(String.valueOf(pageSize)));
this.queryParams.put(Page.ATTR_PAGE_NUMBER, Arrays.asList(String.valueOf(pageNumber))); return this;
this.queryParams.put(Page.ATTR_PAGE_SIZE, Arrays.asList(String.valueOf(pageSize))); }
return this;
} public RestCallBuilder withSorting(final String column, final PageSortOrder order) {
if (column != null) {
public RestCallBuilder withSorting(final String column, final PageSortOrder order) { this.queryParams.put(Page.ATTR_SORT, Arrays.asList(order.encode(column)));
if (column != null) { }
this.queryParams.put(Page.ATTR_SORT, Arrays.asList(order.encode(column))); return this;
} }
return this;
} public RestCallBuilder withFormBinding(final FormBinding formBinding) {
if (RestCall.this.httpMethod == HttpMethod.PUT) {
public RestCallBuilder withFormBinding(final FormBinding formBinding) { return withBody(formBinding.getFormAsJson());
if (RestCall.this.httpMethod == HttpMethod.PUT) { } else {
return withBody(formBinding.getFormAsJson()); this.body = formBinding.getFormUrlEncoded();
} else { return this;
this.body = formBinding.getFormUrlEncoded(); }
return this; }
}
} public RestCallBuilder onlyActive(final boolean active) {
this.queryParams.put(Entity.FILTER_ATTR_ACTIVE, Arrays.asList(String.valueOf(active)));
public RestCallBuilder onlyActive(final boolean active) { return this;
this.queryParams.put(Entity.FILTER_ATTR_ACTIVE, Arrays.asList(String.valueOf(active))); }
return this;
} public final Result<T> call() {
return RestCall.this.exchange(this);
public final Result<T> call() { }
return RestCall.this.exchange(this);
} public String buildURI() {
return this.uriComponentsBuilder
public String buildURI() { .cloneBuilder()
return this.uriComponentsBuilder .path(RestCall.this.path)
.cloneBuilder() .queryParams(this.queryParams)
.path(RestCall.this.path) .toUriString();
.queryParams(this.queryParams) }
.toUriString();
} public HttpEntity<?> buildRequestEntity() {
if (this.streamingBody != null) {
public HttpEntity<?> buildRequestEntity() { return new HttpEntity<>(new InputStreamResource(this.streamingBody), this.httpHeaders);
if (this.streamingBody != null) { } else if (this.body != null) {
return new HttpEntity<>(new InputStreamResource(this.streamingBody), this.httpHeaders); return new HttpEntity<>(this.body, this.httpHeaders);
} else if (this.body != null) { } else {
return new HttpEntity<>(this.body, this.httpHeaders); return new HttpEntity<>(this.httpHeaders);
} else { }
return new HttpEntity<>(this.httpHeaders); }
}
} public Map<String, String> getURIVariables() {
return Utils.immutableMapOf(this.uriVariables);
public Map<String, String> getURIVariables() { }
return Utils.immutableMapOf(this.uriVariables);
} @Override
public String toString() {
@Override return "RestCallBuilder [httpHeaders=" + this.httpHeaders + ", body=" + this.body + ", queryParams="
public String toString() { + this.queryParams
return "RestCallBuilder [httpHeaders=" + this.httpHeaders + ", body=" + this.body + ", queryParams=" + ", uriVariables=" + this.uriVariables + "]";
+ this.queryParams }
+ ", uriVariables=" + this.uriVariables + "]";
} }
} public static final class TypeKey<T> {
final CallType callType;
public static final class TypeKey<T> { final EntityType entityType;
final CallType callType; private final TypeReference<T> typeRef;
final EntityType entityType;
private final TypeReference<T> typeRef; public TypeKey(
final CallType callType,
public TypeKey( final EntityType entityType,
final CallType callType, final TypeReference<T> typeRef) {
final EntityType entityType,
final TypeReference<T> typeRef) { this.callType = callType;
this.entityType = entityType;
this.callType = callType; this.typeRef = typeRef;
this.entityType = entityType; }
this.typeRef = typeRef;
} @Override
public int hashCode() {
@Override final int prime = 31;
public int hashCode() { int result = 1;
final int prime = 31; result = prime * result + ((this.callType == null) ? 0 : this.callType.hashCode());
int result = 1; result = prime * result + ((this.entityType == null) ? 0 : this.entityType.hashCode());
result = prime * result + ((this.callType == null) ? 0 : this.callType.hashCode()); return result;
result = prime * result + ((this.entityType == null) ? 0 : this.entityType.hashCode()); }
return result;
} @Override
public boolean equals(final Object obj) {
@Override if (this == obj)
public boolean equals(final Object obj) { return true;
if (this == obj) if (obj == null)
return true; return false;
if (obj == null) if (getClass() != obj.getClass())
return false; return false;
if (getClass() != obj.getClass()) final TypeKey<?> other = (TypeKey<?>) obj;
return false; if (this.callType != other.callType)
final TypeKey<?> other = (TypeKey<?>) obj; return false;
if (this.callType != other.callType) if (this.entityType != other.entityType)
return false; return false;
if (this.entityType != other.entityType) return true;
return false; }
return true; }
}
} }
}

View file

@ -1,61 +1,59 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api; package ch.ethz.seb.sebserver.gui.service.remote.webservice.api;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.APIMessageError; import ch.ethz.seb.sebserver.gbl.api.APIMessageError;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
public class RestCallError extends RuntimeException implements APIMessageError { public class RestCallError extends RuntimeException implements APIMessageError {
private static final long serialVersionUID = -5201349295667957490L; private static final long serialVersionUID = -5201349295667957490L;
final List<APIMessage> errors; final List<APIMessage> errors;
public RestCallError(final String message, final Throwable cause) { public RestCallError(final String message, final Throwable cause) {
super(message, cause); super(message, cause);
this.errors = new ArrayList<>(); this.errors = new ArrayList<>();
} }
public RestCallError(final String message, final Collection<APIMessage> apiErrors) { public RestCallError(final String message, final Collection<APIMessage> apiErrors) {
super(message); super(message);
this.errors = Utils.immutableListOf(apiErrors); this.errors = Utils.immutableListOf(apiErrors);
} }
public RestCallError(final String message) { public RestCallError(final String message) {
super(message); super(message);
this.errors = new ArrayList<>(); this.errors = new ArrayList<>();
} }
@Override @Override
public List<APIMessage> getErrorMessages() { public List<APIMessage> getErrorMessages() {
return this.errors; return this.errors;
} }
public boolean hasErrorMessages() { public boolean hasErrorMessages() {
return !this.errors.isEmpty(); return !this.errors.isEmpty();
} }
public boolean isFieldValidationError() { public boolean isFieldValidationError() {
return this.errors return this.errors
.stream() .stream()
.filter(error -> APIMessage.ErrorMessage.FIELD_VALIDATION.isOf(error)) .anyMatch(APIMessage.ErrorMessage.FIELD_VALIDATION::isOf);
.findFirst() }
.isPresent();
} @Override
public String toString() {
@Override return "RestCallError [errors=" + this.errors + "]";
public String toString() { }
return "RestCallError [errors=" + this.errors + "]"; }
}
}

View file

@ -1,74 +1,74 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api; package ch.ethz.seb.sebserver.gui.service.remote.webservice.api;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall.CallType; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall.CallType;
/** Interface to SEB Server webservice API thought RestCall's /** Interface to SEB Server webservice API thought RestCall's
* or thought Spring's RestTemplate on lower level. * or thought Spring's RestTemplate on lower level.
* *
* A RestCall's can be used to call a specified SEB Server webservice API endpoint with given request parameter. * A RestCall's can be used to call a specified SEB Server webservice API endpoint with given request parameter.
* This service collects all the available RestCalls and map them by Class type or EntityType and CallType. * This service collects all the available RestCalls and map them by Class type or EntityType and CallType.
* *
* For Example if one want to get a certain User-Account by API request on SEB Server webservice API: * For Example if one want to get a certain User-Account by API request on SEB Server webservice API:
* *
* <pre> * <pre>
* UserInfo userInfo = RestService.getBuilder(GetUserAccount.class) * UserInfo userInfo = RestService.getBuilder(GetUserAccount.class)
* .withURIVariable(API.PARAM_MODEL_ID, user-account-id) adds an URI path variable * .withURIVariable(API.PARAM_MODEL_ID, user-account-id) adds an URI path variable
* .withQueryParam(API.PARAM_INSTITUTION_ID, institutionId) adds a URI query parameter * .withQueryParam(API.PARAM_INSTITUTION_ID, institutionId) adds a URI query parameter
* .call() executes the API request call * .call() executes the API request call
* .get(pageContext::notifyError) gets the result or notify an error to the user if happened * .get(pageContext::notifyError) gets the result or notify an error to the user if happened
* </pre> * </pre>
*/ */
public interface RestService { public interface RestService {
/** Get Spring's RestTemplate that is used within this service. /** Get Spring's RestTemplate that is used within this service.
* *
* @return Spring's RestTemplate that is used within this service. */ * @return Spring's RestTemplate that is used within this service. */
RestTemplate getWebserviceAPIRestTemplate(); RestTemplate getWebserviceAPIRestTemplate();
/** Get Spring's UriComponentsBuilder that is used within this service. /** Get Spring's UriComponentsBuilder that is used within this service.
* *
* @return Spring's UriComponentsBuilder that is used within this service. */ * @return Spring's UriComponentsBuilder that is used within this service. */
UriComponentsBuilder getWebserviceURIBuilder(); UriComponentsBuilder getWebserviceURIBuilder();
/** Get a certain RestCall by Class type. /** Get a certain RestCall by Class type.
* *
* @param type the Class type of the RestCall * @param type the Class type of the RestCall
* @return RestCall instance */ * @return RestCall instance */
<T> RestCall<T> getRestCall(Class<? extends RestCall<T>> type); <T> RestCall<T> getRestCall(Class<? extends RestCall<T>> type);
/** Get a certain RestCall by EntityType and CallType. /** Get a certain RestCall by EntityType and CallType.
* NOTE not all RestCall can be get within this method. Only the ones that have a defined CallType * NOTE not all RestCall can be get within this method. Only the ones that have a defined CallType
* *
* @param entityType The EntityType of the RestCall * @param entityType The EntityType of the RestCall
* @param callType The CallType of the RestCall (not UNDEFINDED) * @param callType The CallType of the RestCall (not UNDEFINED)
* @return RestCall instance */ * @return RestCall instance */
<T> RestCall<T> getRestCall(EntityType entityType, CallType callType); <T> RestCall<T> getRestCall(EntityType entityType, CallType callType);
/** Get a certain RestCallBuilder by RestCall Class type. /** Get a certain RestCallBuilder by RestCall Class type.
* *
* @param type the Class type of the RestCall * @param type the Class type of the RestCall
* @return RestCallBuilder instance to build a dedicated call and execute it */ * @return RestCallBuilder instance to build a dedicated call and execute it */
<T> RestCall<T>.RestCallBuilder getBuilder(Class<? extends RestCall<T>> type); <T> RestCall<T>.RestCallBuilder getBuilder(Class<? extends RestCall<T>> type);
/** Get a certain RestCallBuilder by EntityType and CallType. /** Get a certain RestCallBuilder by EntityType and CallType.
* *
* @param entityType The EntityType of the RestCall to get a builder for * @param entityType The EntityType of the RestCall to get a builder for
* @param callType The CallType of the RestCall to get a builder for (not UNDEFINDED) * @param callType The CallType of the RestCall to get a builder for (not UNDEFINED)
* @return RestCallBuilder instance to build a dedicated call and execute it */ * @return RestCallBuilder instance to build a dedicated call and execute it */
<T> RestCall<T>.RestCallBuilder getBuilder( <T> RestCall<T>.RestCallBuilder getBuilder(
EntityType entityType, EntityType entityType,
CallType callType); CallType callType);
} }

View file

@ -1,40 +1,41 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount; package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component @Lazy
@GuiProfile @Component
public class SaveUserAccount extends RestCall<UserInfo> { @GuiProfile
public class SaveUserAccount extends RestCall<UserInfo> {
public SaveUserAccount() {
super(new TypeKey<>( public SaveUserAccount() {
CallType.SAVE, super(new TypeKey<>(
EntityType.USER, CallType.SAVE,
new TypeReference<UserInfo>() { EntityType.USER,
}), new TypeReference<UserInfo>() {
HttpMethod.PUT, }),
MediaType.APPLICATION_JSON_UTF8, HttpMethod.PUT,
API.USER_ACCOUNT_ENDPOINT); MediaType.APPLICATION_JSON_UTF8,
} API.USER_ACCOUNT_ENDPOINT);
}
}
}

View file

@ -1,348 +1,344 @@
/* /*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.remote.webservice.auth; package ch.ethz.seb.sebserver.gui.service.remote.webservice.auth;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.authorization.Privilege; import ch.ethz.seb.sebserver.gbl.api.authorization.Privilege;
import ch.ethz.seb.sebserver.gbl.api.authorization.Privilege.RoleTypeKey; import ch.ethz.seb.sebserver.gbl.api.authorization.Privilege.RoleTypeKey;
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType; import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.GrantEntity; import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
@Component @Component
@GuiProfile @GuiProfile
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS) @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class CurrentUser { public class CurrentUser {
private static final Logger log = LoggerFactory.getLogger(CurrentUser.class); private static final Logger log = LoggerFactory.getLogger(CurrentUser.class);
private final AuthorizationContextHolder authorizationContextHolder; private final AuthorizationContextHolder authorizationContextHolder;
private SEBServerAuthorizationContext authContext = null; private SEBServerAuthorizationContext authContext = null;
private Map<RoleTypeKey, Privilege> privileges = null; private Map<RoleTypeKey, Privilege> privileges = null;
private final Map<String, String> attributes; private final Map<String, String> attributes;
public CurrentUser(final AuthorizationContextHolder authorizationContextHolder) { public CurrentUser(final AuthorizationContextHolder authorizationContextHolder) {
this.authorizationContextHolder = authorizationContextHolder; this.authorizationContextHolder = authorizationContextHolder;
this.attributes = new HashMap<>(); this.attributes = new HashMap<>();
} }
public void putAttribute(final String name, final String value) { public void putAttribute(final String name, final String value) {
this.attributes.put(name, value); this.attributes.put(name, value);
} }
public String getAttribute(final String name) { public String getAttribute(final String name) {
return this.attributes.get(name); return this.attributes.get(name);
} }
public AuthorizationContextHolder getAuthorizationContextHolder() { public AuthorizationContextHolder getAuthorizationContextHolder() {
return this.authorizationContextHolder; return this.authorizationContextHolder;
} }
public UserInfo get() { public UserInfo get() {
if (isAvailable()) { if (isAvailable()) {
return this.authContext return this.authContext
.getLoggedInUser() .getLoggedInUser()
.getOrThrow(); .getOrThrow();
} }
return handleIllegalSessionState(); return handleIllegalSessionState();
} }
public UserInfo getOrHandleError(final Function<Exception, UserInfo> errorHandler) { public UserInfo getOrHandleError(final Function<Exception, UserInfo> errorHandler) {
if (isAvailable()) { if (isAvailable()) {
return this.authContext return this.authContext
.getLoggedInUser() .getLoggedInUser()
.get(errorHandler); .get(errorHandler);
} }
return handleIllegalSessionState(); return handleIllegalSessionState();
} }
private UserInfo handleIllegalSessionState() { private UserInfo handleIllegalSessionState() {
log.warn("Current user requested but no user is currently logged in"); log.warn("Current user requested but no user is currently logged in");
this.logout(); this.logout();
throw new IllegalUserSessionStateException("User not logged in"); throw new IllegalUserSessionStateException("User not logged in");
} }
public GrantCheck grantCheck(final EntityType entityType) { public GrantCheck grantCheck(final EntityType entityType) {
return new GrantCheck(entityType); return new GrantCheck(entityType);
} }
public EntityGrantCheck entityGrantCheck(final GrantEntity grantEntity) { public EntityGrantCheck entityGrantCheck(final GrantEntity grantEntity) {
return new EntityGrantCheck(grantEntity); return new EntityGrantCheck(grantEntity);
} }
public boolean hasBasePrivilege(final PrivilegeType privilegeType, final EntityType entityType) { public boolean hasBasePrivilege(final PrivilegeType privilegeType, final EntityType entityType) {
return hasPrivilege(privilegeType, entityType, null, null); return hasPrivilege(privilegeType, entityType, null, null);
} }
public boolean hasInstitutionalPrivilege(final PrivilegeType privilegeType, final EntityType entityType) { public boolean hasInstitutionalPrivilege(final PrivilegeType privilegeType, final EntityType entityType) {
final UserInfo userInfo = get(); final UserInfo userInfo = get();
return hasPrivilege(privilegeType, entityType, userInfo.institutionId, null); return hasPrivilege(privilegeType, entityType, userInfo.institutionId, null);
} }
public boolean hasPrivilege( public boolean hasPrivilege(
final PrivilegeType privilegeType, final PrivilegeType privilegeType,
final EntityType entityType, final EntityType entityType,
final Long institutionId, final Long institutionId,
final String ownerId) { final String ownerId) {
if (loadPrivileges()) { if (loadPrivileges()) {
try { try {
final UserInfo userInfo = get(); final UserInfo userInfo = get();
return userInfo return userInfo
.getRoles() .getRoles()
.stream() .stream()
.map(roleName -> UserRole.valueOf(roleName)) .map(UserRole::valueOf)
.map(role -> new RoleTypeKey(entityType, role)) .map(role -> new RoleTypeKey(entityType, role))
.map(key -> this.privileges.get(key)) .map(key -> this.privileges.get(key))
.filter(priv -> (priv != null) && priv.hasGrant( .anyMatch(privilege -> (privilege != null) && privilege.hasGrant(
userInfo.uuid, userInfo.uuid,
userInfo.institutionId, userInfo.institutionId,
privilegeType, privilegeType,
institutionId, institutionId,
ownerId)) ownerId));
.findFirst() } catch (final Exception e) {
.isPresent(); log.error("Failed to verify privilege: PrivilegeType {} EntityType {}",
} catch (final Exception e) { privilegeType, entityType, e);
log.error("Failed to verify privilege: PrivilegeType {} EntityType {}", }
privilegeType, entityType, e); }
}
} return false;
}
return false;
} public boolean hasPrivilege(
final PrivilegeType privilegeType,
public boolean hasPrivilege( final GrantEntity grantEntity) {
final PrivilegeType privilegeType,
final GrantEntity grantEntity) { if (loadPrivileges()) {
final EntityType entityType = grantEntity.entityType();
if (loadPrivileges()) { try {
final EntityType entityType = grantEntity.entityType(); final UserInfo userInfo = get();
try { return userInfo.getRoles()
final UserInfo userInfo = get(); .stream()
return userInfo.getRoles() .map(UserRole::valueOf)
.stream() .map(role -> new RoleTypeKey(entityType, role))
.map(roleName -> UserRole.valueOf(roleName)) .map(key -> this.privileges.get(key))
.map(role -> new RoleTypeKey(entityType, role)) .anyMatch(privilege -> (privilege != null) && privilege.hasGrant(
.map(key -> this.privileges.get(key)) userInfo.uuid,
.filter(priv -> (priv != null) && priv.hasGrant( userInfo.institutionId,
userInfo.uuid, privilegeType,
userInfo.institutionId, grantEntity.getInstitutionId(),
privilegeType, grantEntity.getOwnerId()));
grantEntity.getInstitutionId(), } catch (final Exception e) {
grantEntity.getOwnerId())) log.error("Failed to verify privilege: PrivilegeType {} EntityType {}",
.findFirst() privilegeType, entityType, e);
.isPresent(); }
} catch (final Exception e) { }
log.error("Failed to verify privilege: PrivilegeType {} EntityType {}",
privilegeType, entityType, e); return false;
} }
}
public boolean isAvailable() {
return false; updateContext();
} return this.authContext != null && this.authContext.isLoggedIn();
}
public boolean isAvailable() {
updateContext(); public void refresh(final UserInfo userInfo) {
return this.authContext != null && this.authContext.isLoggedIn(); this.authContext.refreshUser(userInfo);
} }
public void refresh(final UserInfo userInfo) { public boolean logout() {
this.authContext.refreshUser(userInfo); if (this.attributes != null) {
} this.attributes.clear();
}
public boolean logout() {
if (this.attributes != null) { this.privileges = null;
this.attributes.clear();
} if (isAvailable()) {
if (this.authContext.logout()) {
this.privileges = null; this.authContext = null;
return true;
if (isAvailable()) { } else {
if (this.authContext.logout()) { return false;
this.authContext = null; }
return true; } else {
} else { try {
return false; return this.authorizationContextHolder
} .getAuthorizationContext()
} else { .logout();
try { } catch (final Exception e) {
return this.authorizationContextHolder log.warn("Unexpected error while logout: {}", e.getMessage());
.getAuthorizationContext() return false;
.logout(); }
} catch (final Exception e) { }
log.warn("Unexpected error while logout: {}", e.getMessage()); }
return false;
} private void updateContext() {
} if (this.authContext == null || !this.authContext.isValid()) {
} this.authContext = this.authorizationContextHolder.getAuthorizationContext();
}
private void updateContext() { }
if (this.authContext == null || !this.authContext.isValid()) {
this.authContext = this.authorizationContextHolder.getAuthorizationContext(); private boolean loadPrivileges() {
} if (this.privileges != null) {
} return true;
}
private boolean loadPrivileges() {
if (this.privileges != null) { updateContext();
return true; if (this.authContext != null) {
} try {
final WebserviceURIService webserviceURIService =
updateContext(); this.authorizationContextHolder.getWebserviceURIService();
if (this.authContext != null) { final ResponseEntity<Collection<Privilege>> exchange = this.authContext.getRestTemplate()
try { .exchange(
final WebserviceURIService webserviceURIService = webserviceURIService.getURIBuilder()
this.authorizationContextHolder.getWebserviceURIService(); .path(API.PRIVILEGES_ENDPOINT)
final ResponseEntity<Collection<Privilege>> exchange = this.authContext.getRestTemplate() .toUriString(),
.exchange( HttpMethod.GET,
webserviceURIService.getURIBuilder() HttpEntity.EMPTY,
.path(API.PRIVILEGES_ENDPOINT) Constants.TYPE_REFERENCE_PRIVILEGES);
.toUriString(),
HttpMethod.GET, if (exchange.getStatusCodeValue() == HttpStatus.OK.value()) {
HttpEntity.EMPTY, final Collection<Privilege> privileges = exchange.getBody();
Constants.TYPE_REFERENCE_PRIVILEGES); if (privileges != null) {
this.privileges = privileges
if (exchange.getStatusCodeValue() == HttpStatus.OK.value()) { .stream()
final Collection<Privilege> privileges = exchange.getBody(); .reduce(new HashMap<>(),
if (privileges != null) { (map, privilege) -> {
this.privileges = privileges map.put(privilege.roleTypeKey, privilege);
.stream() return map;
.reduce(new HashMap<RoleTypeKey, Privilege>(), },
(map, priv) -> { (map1, map2) -> {
map.put(priv.roleTypeKey, priv); map1.putAll(map2);
return map; return map1;
}, });
(map1, map2) -> { } else {
map1.putAll(map2); log.error("Failed to get Privileges from webservice API: {}", exchange);
return map1; return false;
}); }
} else {
log.error("Failed to get Privileges from webservice API: {}", exchange); return true;
return false; } else {
} log.error("Failed to get Privileges from webservice API: {}", exchange);
return false;
return true; }
} else {
log.error("Failed to get Privileges from webservice API: {}", exchange); } catch (final Exception e) {
return false; log.error("Failed to get Privileges from webservice API: ", e);
} return false;
}
} catch (final Exception e) { } else {
log.error("Failed to get Privileges from webservice API: ", e); log.error("Failed to get Privileges from webservice API. No AuthorizationContext available");
return false; return false;
} }
} else { }
log.error("Failed to get Privileges from webservice API. No AuthorizationContext available");
return false; /** Wrapper can be used for base and institutional grant checks for a specified EntityType */
} public class GrantCheck {
} private final EntityType entityType;
/** Wrapper can be used for base and institutional grant checks for a specified EntityType */ protected GrantCheck(final EntityType entityType) {
public class GrantCheck { this.entityType = entityType;
private final EntityType entityType; }
protected GrantCheck(final EntityType entityType) { /** Checks the base read-only privilege grant
this.entityType = entityType; *
} * @return true on read-only privilege grant on wrapped EntityType */
public boolean r() {
/** Checks the base read-only privilege grant return hasBasePrivilege(PrivilegeType.READ, this.entityType);
* }
* @return true on read-only privilege grant on wrapped EntityType */
public boolean r() { /** Checks the base modify privilege grant
return hasBasePrivilege(PrivilegeType.READ, this.entityType); *
} * @return true on modify privilege grant on wrapped EntityType */
public boolean m() {
/** Checks the base modify privilege grant return hasBasePrivilege(PrivilegeType.MODIFY, this.entityType);
* }
* @return true on modify privilege grant on wrapped EntityType */
public boolean m() { /** Checks the base write privilege grant
return hasBasePrivilege(PrivilegeType.MODIFY, this.entityType); *
} * @return true on write privilege grant on wrapped EntityType */
public boolean w() {
/** Checks the base write privilege grant return hasBasePrivilege(PrivilegeType.WRITE, this.entityType);
* }
* @return true on write privilege grant on wrapped EntityType */
public boolean w() { /** Checks the institutional read-only privilege grant
return hasBasePrivilege(PrivilegeType.WRITE, this.entityType); *
} * @return true institutional read-only privilege grant on wrapped EntityType */
public boolean ir() {
/** Checks the institutional read-only privilege grant return hasInstitutionalPrivilege(PrivilegeType.READ, this.entityType);
* }
* @return true institutional read-only privilege grant on wrapped EntityType */
public boolean ir() { /** Checks the institutional modify privilege grant
return hasInstitutionalPrivilege(PrivilegeType.READ, this.entityType); *
} * @return true institutional modify privilege grant on wrapped EntityType */
public boolean im() {
/** Checks the institutional modify privilege grant return hasInstitutionalPrivilege(PrivilegeType.MODIFY, this.entityType);
* }
* @return true institutional modify privilege grant on wrapped EntityType */
public boolean im() { /** Checks the institutional write privilege grant
return hasInstitutionalPrivilege(PrivilegeType.MODIFY, this.entityType); *
} * @return true institutional write privilege grant on wrapped EntityType */
public boolean iw() {
/** Checks the institutional write privilege grant return hasInstitutionalPrivilege(PrivilegeType.WRITE, this.entityType);
* }
* @return true institutional write privilege grant on wrapped EntityType */ }
public boolean iw() {
return hasInstitutionalPrivilege(PrivilegeType.WRITE, this.entityType); /** Wrapper can be used for Entity based grant checks */
} public class EntityGrantCheck {
} private final GrantEntity grantEntity;
/** Wrapper can be used for Entity based grant checks */ protected EntityGrantCheck(final GrantEntity grantEntity) {
public class EntityGrantCheck { this.grantEntity = grantEntity;
private final GrantEntity grantEntity; }
protected EntityGrantCheck(final GrantEntity grantEntity) { /** Checks the read-only privilege grant for wrapped grantEntity
this.grantEntity = grantEntity; *
} * @return true on read-only privilege grant for wrapped grantEntity */
public boolean r() {
/** Checks the read-only privilege grant for wrapped grantEntity return hasPrivilege(PrivilegeType.READ, this.grantEntity);
* }
* @return true on read-only privilege grant for wrapped grantEntity */
public boolean r() { /** Checks the modify privilege grant for wrapped grantEntity
return hasPrivilege(PrivilegeType.READ, this.grantEntity); *
} * @return true on modify privilege grant for wrapped grantEntity */
public boolean m() {
/** Checks the modify privilege grant for wrapped grantEntity return hasPrivilege(PrivilegeType.MODIFY, this.grantEntity);
* }
* @return true on modify privilege grant for wrapped grantEntity */
public boolean m() { /** Checks the write privilege grant for wrapped grantEntity
return hasPrivilege(PrivilegeType.MODIFY, this.grantEntity); *
} * @return true on write privilege grant for wrapped grantEntity */
public boolean w() {
/** Checks the write privilege grant for wrapped grantEntity return hasPrivilege(PrivilegeType.WRITE, this.grantEntity);
* }
* @return true on write privilege grant for wrapped grantEntity */ }
public boolean w() {
return hasPrivilege(PrivilegeType.WRITE, this.grantEntity); }
}
}
}

View file

@ -1,333 +1,331 @@
/* /*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.remote.webservice.auth; package ch.ethz.seb.sebserver.gui.service.remote.webservice.auth;
import java.io.IOException; import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
import java.net.URI; import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import java.nio.charset.Charset; import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import java.util.Arrays; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import java.util.Collections; import ch.ethz.seb.sebserver.gbl.util.Result;
import java.util.List; import ch.ethz.seb.sebserver.gbl.util.Utils;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpSession; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired;
import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Value;
import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpMethod;
import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus;
import org.springframework.context.annotation.Lazy; import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.HttpStatus; import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.ResponseEntity; import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.security.access.AccessDeniedException;
import org.springframework.http.client.ClientHttpResponse; import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.oauth2.client.http.OAuth2ErrorHandler;
import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException;
import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.http.OAuth2ErrorHandler; import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest;
import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException; import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; import org.springframework.stereotype.Component;
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; import org.springframework.web.client.RequestCallback;
import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.web.client.ResponseExtractor;
import org.springframework.stereotype.Component; import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RequestCallback; import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.ResponseExtractor;
import org.springframework.web.client.RestClientException; import javax.servlet.http.HttpSession;
import org.springframework.web.client.RestTemplate; import java.io.IOException;
import java.net.URI;
import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService; import java.nio.charset.StandardCharsets;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; import java.util.Arrays;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import java.util.Collections;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import java.util.List;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils; @Lazy
@Component
@Lazy @GuiProfile
@Component public class OAuth2AuthorizationContextHolder implements AuthorizationContextHolder {
@GuiProfile
public class OAuth2AuthorizationContextHolder implements AuthorizationContextHolder { private static final Logger log = LoggerFactory.getLogger(OAuth2AuthorizationContextHolder.class);
private static final Logger log = LoggerFactory.getLogger(OAuth2AuthorizationContextHolder.class); private static final String CONTEXT_HOLDER_ATTRIBUTE = "CONTEXT_HOLDER_ATTRIBUTE";
private static final String CONTEXT_HOLDER_ATTRIBUTE = "CONTEXT_HOLDER_ATTRIBUTE"; private final String guiClientId;
private final String guiClientSecret;
private final String guiClientId; private final WebserviceURIService webserviceURIService;
private final String guiClientSecret; private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
private final WebserviceURIService webserviceURIService;
private final ClientHttpRequestFactoryService clientHttpRequestFactoryService; @Autowired
public OAuth2AuthorizationContextHolder(
@Autowired @Value("${sebserver.webservice.api.admin.clientId}") final String guiClientId,
public OAuth2AuthorizationContextHolder( @Value("${sebserver.webservice.api.admin.clientSecret}") final String guiClientSecret,
@Value("${sebserver.webservice.api.admin.clientId}") final String guiClientId, final WebserviceURIService webserviceURIService,
@Value("${sebserver.webservice.api.admin.clientSecret}") final String guiClientSecret, final ClientHttpRequestFactoryService clientHttpRequestFactoryService) {
final WebserviceURIService webserviceURIService,
final ClientHttpRequestFactoryService clientHttpRequestFactoryService) { this.guiClientId = guiClientId;
this.guiClientSecret = guiClientSecret;
this.guiClientId = guiClientId; this.webserviceURIService = webserviceURIService;
this.guiClientSecret = guiClientSecret; this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
this.webserviceURIService = webserviceURIService; }
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
} @Override
public WebserviceURIService getWebserviceURIService() {
@Override return this.webserviceURIService;
public WebserviceURIService getWebserviceURIService() { }
return this.webserviceURIService;
} @Override
public SEBServerAuthorizationContext getAuthorizationContext(final HttpSession session) {
@Override log.debug("Trying to get OAuth2AuthorizationContext from HttpSession: {}", session.getId());
public SEBServerAuthorizationContext getAuthorizationContext(final HttpSession session) {
log.debug("Trying to get OAuth2AuthorizationContext from HttpSession: {}", session.getId()); OAuth2AuthorizationContext context =
(OAuth2AuthorizationContext) session.getAttribute(CONTEXT_HOLDER_ATTRIBUTE);
OAuth2AuthorizationContext context =
(OAuth2AuthorizationContext) session.getAttribute(CONTEXT_HOLDER_ATTRIBUTE); if (context == null || !context.valid) {
log.debug(
if (context == null || !context.valid) { "OAuth2AuthorizationContext for HttpSession: {} is not present or is invalid. "
log.debug( + "Create new OAuth2AuthorizationContext for this session",
"OAuth2AuthorizationContext for HttpSession: {} is not present or is invalid. " session.getId());
+ "Create new OAuth2AuthorizationContext for this session",
session.getId()); final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService
.getClientHttpRequestFactory()
final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService .getOrThrow();
.getClientHttpRequestFactory()
.getOrThrow(); context = new OAuth2AuthorizationContext(
this.guiClientId,
context = new OAuth2AuthorizationContext( this.guiClientSecret,
this.guiClientId, this.webserviceURIService,
this.guiClientSecret, clientHttpRequestFactory);
this.webserviceURIService,
clientHttpRequestFactory); session.setAttribute(CONTEXT_HOLDER_ATTRIBUTE, context);
}
session.setAttribute(CONTEXT_HOLDER_ATTRIBUTE, context);
} return context;
}
return context;
} private static final class DisposableOAuth2RestTemplate extends OAuth2RestTemplate {
private static final class DisposableOAuth2RestTemplate extends OAuth2RestTemplate { private boolean enabled = true;
private boolean enabled = true; public DisposableOAuth2RestTemplate(final OAuth2ProtectedResourceDetails resource) {
super(
public DisposableOAuth2RestTemplate(final OAuth2ProtectedResourceDetails resource) { resource,
super( new DefaultOAuth2ClientContext(new DefaultAccessTokenRequest()));
resource, }
new DefaultOAuth2ClientContext(new DefaultAccessTokenRequest()));
} @Override
protected <T> T doExecute(
@Override final URI url,
protected <T> T doExecute( final HttpMethod method,
final URI url, final RequestCallback requestCallback,
final HttpMethod method, final ResponseExtractor<T> responseExtractor) throws RestClientException {
final RequestCallback requestCallback,
final ResponseExtractor<T> responseExtractor) throws RestClientException { if (this.enabled) {
return super.doExecute(url, method, requestCallback, responseExtractor);
if (this.enabled) { } else {
return super.doExecute(url, method, requestCallback, responseExtractor); throw new IllegalStateException(
} else { "Error: Forbidden execution call on disabled DisposableOAuth2RestTemplate");
throw new IllegalStateException( }
"Error: Forbidden execution call on disabled DisposableOAuth2RestTemplate"); }
} }
}
} private static final class OAuth2AuthorizationContext implements SEBServerAuthorizationContext {
private static final class OAuth2AuthorizationContext implements SEBServerAuthorizationContext { private static final String GRANT_TYPE = "password";
private static final List<String> SCOPES = Collections.unmodifiableList(
private static final String GRANT_TYPE = "password"; Arrays.asList("read", "write"));
private static final List<String> SCOPES = Collections.unmodifiableList(
Arrays.asList("read", "write")); private boolean valid = true;
private boolean valid = true; private final ResourceOwnerPasswordResourceDetails resource;
private final DisposableOAuth2RestTemplate restTemplate;
private final ResourceOwnerPasswordResourceDetails resource; private final String revokeTokenURI;
private final DisposableOAuth2RestTemplate restTemplate; private final String currentUserURI;
private final String revokeTokenURI;
private final String currentUserURI; private Result<UserInfo> loggedInUser = null;
private Result<UserInfo> loggedInUser = null; OAuth2AuthorizationContext(
final String guiClientId,
OAuth2AuthorizationContext( final String guiClientSecret,
final String guiClientId, final WebserviceURIService webserviceURIService,
final String guiClientSecret, final ClientHttpRequestFactory clientHttpRequestFactory) {
final WebserviceURIService webserviceURIService,
final ClientHttpRequestFactory clientHttpRequestFactory) { this.resource = new ResourceOwnerPasswordResourceDetails();
this.resource.setAccessTokenUri(webserviceURIService.getOAuthTokenURI());
this.resource = new ResourceOwnerPasswordResourceDetails(); this.resource.setClientId(guiClientId);
this.resource.setAccessTokenUri(webserviceURIService.getOAuthTokenURI()); this.resource.setClientSecret(guiClientSecret);
this.resource.setClientId(guiClientId); this.resource.setGrantType(GRANT_TYPE);
this.resource.setClientSecret(guiClientSecret); this.resource.setScope(SCOPES);
this.resource.setGrantType(GRANT_TYPE);
this.resource.setScope(SCOPES); this.restTemplate = new DisposableOAuth2RestTemplate(this.resource);
this.restTemplate.setRequestFactory(clientHttpRequestFactory);
this.restTemplate = new DisposableOAuth2RestTemplate(this.resource); this.restTemplate.setErrorHandler(new ErrorHandler(this.resource));
this.restTemplate.setRequestFactory(clientHttpRequestFactory); this.restTemplate
this.restTemplate.setErrorHandler(new ErrorHandler(this.resource)); .getMessageConverters()
this.restTemplate .add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8));
.getMessageConverters()
.add(0, new StringHttpMessageConverter(Charset.forName("UTF-8"))); this.revokeTokenURI = webserviceURIService.getOAuthRevokeTokenURI();
this.currentUserURI = webserviceURIService.getCurrentUserRequestURI();
this.revokeTokenURI = webserviceURIService.getOAuthRevokeTokenURI(); }
this.currentUserURI = webserviceURIService.getCurrentUserRequestURI();
} @Override
public boolean isValid() {
@Override return this.valid;
public boolean isValid() { }
return this.valid;
} @Override
public boolean isLoggedIn() {
@Override final OAuth2AccessToken accessToken = this.restTemplate.getOAuth2ClientContext().getAccessToken();
public boolean isLoggedIn() { if (accessToken == null || StringUtils.isEmpty(accessToken.toString())) {
final OAuth2AccessToken accessToken = this.restTemplate.getOAuth2ClientContext().getAccessToken(); return false;
if (accessToken == null || StringUtils.isEmpty(accessToken.toString())) { }
return false;
} try {
final ResponseEntity<String> forEntity =
try { this.restTemplate.getForEntity(this.currentUserURI, String.class);
final ResponseEntity<String> forEntity = if (forEntity.getStatusCode() != HttpStatus.OK) {
this.restTemplate.getForEntity(this.currentUserURI, String.class); return false;
if (forEntity.getStatusCode() != HttpStatus.OK) { }
return false; } catch (final Exception e) {
} log.error("Failed to verify logged in user: {}", e.getMessage());
} catch (final Exception e) { return false;
log.error("Failed to verify logged in user: {}", e.getMessage()); }
return false;
} return true;
}
return true;
} @Override
public boolean login(final String username, final CharSequence password) {
@Override if (!this.valid || this.isLoggedIn()) {
public boolean login(final String username, final CharSequence password) { return false;
if (!this.valid || this.isLoggedIn()) { }
return false;
} this.resource.setUsername(username);
this.resource.setPassword(Utils.toString(password));
this.resource.setUsername(username);
this.resource.setPassword(Utils.toString(password)); log.debug("Trying to login for user: {}", username);
log.debug("Trying to login for user: {}", username); try {
this.restTemplate.getAccessToken();
try { log.debug("Got token for user: {}", username);
this.restTemplate.getAccessToken(); this.loggedInUser = getLoggedInUser();
log.debug("Got token for user: {}", username); return true;
this.loggedInUser = getLoggedInUser(); } catch (final OAuth2AccessDeniedException | AccessDeniedException e) {
return true; log.info("Access Denied for user: {}", username);
} catch (final OAuth2AccessDeniedException | AccessDeniedException e) { return false;
log.info("Access Denied for user: {}", username); }
return false; }
}
} @Override
public boolean logout() {
@Override // set this context invalid to force creation of a new context on next request
public boolean logout() { this.valid = false;
// set this context invalid to force creation of a new context on next request this.loggedInUser = null;
this.valid = false; if (this.restTemplate.getAccessToken() != null) {
this.loggedInUser = null; // delete the access-token (and refresh-token) on authentication server side
if (this.restTemplate.getAccessToken() != null) { this.restTemplate.delete(this.revokeTokenURI);
// delete the access-token (and refresh-token) on authentication server side // delete the access-token within the RestTemplate
this.restTemplate.delete(this.revokeTokenURI); this.restTemplate.getOAuth2ClientContext().setAccessToken(null);
// delete the access-token within the RestTemplate }
this.restTemplate.getOAuth2ClientContext().setAccessToken(null); // mark the RestTemplate as disposed
} this.restTemplate.enabled = false;
// mark the RestTemplate as disposed return true;
this.restTemplate.enabled = false; }
return true;
} @Override
public RestTemplate getRestTemplate() {
@Override return this.restTemplate;
public RestTemplate getRestTemplate() { }
return this.restTemplate;
} @Override
public void refreshUser(final UserInfo userInfo) {
@Override // delete the access-token (and refresh-token) on authentication server side
public void refreshUser(final UserInfo userInfo) { this.restTemplate.delete(this.revokeTokenURI);
// delete the access-token (and refresh-token) on authentication server side // delete the access-token within the RestTemplate
this.restTemplate.delete(this.revokeTokenURI); this.restTemplate.getOAuth2ClientContext().setAccessToken(null);
// delete the access-token within the RestTemplate // check if username has changed
this.restTemplate.getOAuth2ClientContext().setAccessToken(null); if (!userInfo.username.equals(getLoggedInUser().get().username)) {
// check if username has changed // Set new username to be able to request new access token
if (!userInfo.username.equals(getLoggedInUser().get().username)) { this.resource.setUsername(userInfo.username);
// Set new username to be able to request new access token }
this.resource.setUsername(userInfo.username);
} // and request new access token
this.restTemplate.getAccessToken();
// and request new access token // and reset logged in user by getting actual one from webservice
this.restTemplate.getAccessToken(); this.loggedInUser = null;
// and reset logged in user by getting actual one from webservice getLoggedInUser()
this.loggedInUser = null; .getOrThrow();
getLoggedInUser() }
.getOrThrow();
} @Override
public Result<UserInfo> getLoggedInUser() {
@Override if (this.loggedInUser != null) {
public Result<UserInfo> getLoggedInUser() { return this.loggedInUser;
if (this.loggedInUser != null) { }
return this.loggedInUser;
} log.debug("Request logged in User from SEBserver web-service API");
log.debug("Request logged in User from SEBserver web-service API"); try {
if (isValid() && isLoggedIn()) {
try { final ResponseEntity<UserInfo> response =
if (isValid() && isLoggedIn()) { this.restTemplate
final ResponseEntity<UserInfo> response = .getForEntity(this.currentUserURI, UserInfo.class);
this.restTemplate if (response.getStatusCode() == HttpStatus.OK) {
.getForEntity(this.currentUserURI, UserInfo.class); this.loggedInUser = Result.of(response.getBody());
if (response.getStatusCode() == HttpStatus.OK) { return this.loggedInUser;
this.loggedInUser = Result.of(response.getBody()); } else {
return this.loggedInUser; log.error("Unexpected error response: {}", response);
} else { return Result.ofError(new IllegalStateException(
log.error("Unexpected error response: {}", response); "Http Request responded with status: " + response.getStatusCode()));
return Result.ofError(new IllegalStateException( }
"Http Request responded with status: " + response.getStatusCode())); } else {
} return Result.ofError(
} else { new IllegalStateException("Logged in User requested on invalid or not logged in "));
return Result.ofError( }
new IllegalStateException("Logged in User requested on invalid or not logged in ")); } catch (final AccessDeniedException | OAuth2AccessDeniedException ade) {
} log.error("Acccess denied while trying to request logged in User from API", ade);
} catch (final AccessDeniedException | OAuth2AccessDeniedException ade) { return Result.ofError(ade);
log.error("Acccess denied while trying to request logged in User from API", ade); } catch (final Exception e) {
return Result.ofError(ade); log.error("Unexpected error while trying to request logged in User from API", e);
} catch (final Exception e) { return Result.ofError(
log.error("Unexpected error while trying to request logged in User from API", e); new RuntimeException("Unexpected error while trying to request logged in User from API", e));
return Result.ofError( }
new RuntimeException("Unexpected error while trying to request logged in User from API", e)); }
}
} @Override
public boolean hasRole(final UserRole role) {
@Override if (!isValid() || !isLoggedIn()) {
public boolean hasRole(final UserRole role) { return false;
if (!isValid() || !isLoggedIn()) { }
return false;
} return getLoggedInUser()
.getOrThrow().roles
return getLoggedInUser() .contains(role.name());
.getOrThrow().roles }
.contains(role.name());
} private static final class ErrorHandler extends OAuth2ErrorHandler {
private ErrorHandler(final OAuth2ProtectedResourceDetails resource) {
private static final class ErrorHandler extends OAuth2ErrorHandler { super(resource);
private ErrorHandler(final OAuth2ProtectedResourceDetails resource) { }
super(resource);
} @Override
public boolean hasError(final ClientHttpResponse response) throws IOException {
@Override try {
public boolean hasError(final ClientHttpResponse response) throws IOException { final HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode());
try { return (statusCode != null && statusCode.series().equals(HttpStatus.Series.SERVER_ERROR));
final HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode()); } catch (final Exception e) {
return (statusCode != null && statusCode.series().equals(HttpStatus.Series.SERVER_ERROR)); log.error("Unexpected: ", e);
} catch (final Exception e) { return super.hasError(response);
log.error("Unexpected: ", e); }
return super.hasError(response); }
} }
}
} }
}
}
}

View file

@ -1,66 +1,66 @@
/* /*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.remote.webservice.auth; package ch.ethz.seb.sebserver.gui.service.remote.webservice.auth;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
/** Defines functionality for the SEB Server webservice authorization context used to /** Defines functionality for the SEB Server webservice authorization context used to
* manage a user session on GUI service. */ * manage a user session on GUI service. */
public interface SEBServerAuthorizationContext { public interface SEBServerAuthorizationContext {
/** Indicates if this authorization context is still valid /** Indicates if this authorization context is still valid
* *
* @return true if the SEBServerAuthorizationContext is valid. False of not. */ * @return true if the SEBServerAuthorizationContext is valid. False of not. */
boolean isValid(); boolean isValid();
/** Indicated whether a user is logged in within this authorization context or not. /** Indicated whether a user is logged in within this authorization context or not.
* *
* @return whether a user is logged in within this authorization context or not */ * @return whether a user is logged in within this authorization context or not */
boolean isLoggedIn(); boolean isLoggedIn();
/** Requests a login with username and password on SEB Server webservice. /** Requests a login with username and password on SEB Server webservice.
* This uses OAuth 2 and Springs OAuth2RestTemplate to exchange user/client credentials * This uses OAuth 2 and Springs OAuth2RestTemplate to exchange user/client credentials
* with an access and refresh token. * with an access and refresh token.
* *
* @param username the username for login * @param username the username for login
* @param password the password for login * @param password the password for login
* @return */ * @return true if login was successful, false if no */
boolean login(String username, CharSequence password); boolean login(String username, CharSequence password);
/** Requests a logout on SEB Server webservice if a user is currently logged in /** Requests a logout on SEB Server webservice if a user is currently logged in
* This uses OAuth 2 and Springs OAuth2RestTemplate to make a revoke token request for the * This uses OAuth 2 and Springs OAuth2RestTemplate to make a revoke token request for the
* currently logged in user and also invalidates this SEBServerAuthorizationContext * currently logged in user and also invalidates this SEBServerAuthorizationContext
* *
* @return true if logout was successful */ * @return true if logout was successful */
boolean logout(); boolean logout();
/** Gets a Result of the UserInfo data of currently logged in user or of an error if no user is logged in /** Gets a Result of the UserInfo data of currently logged in user or of an error if no user is logged in
* or there was an unexpected error while trying to get the user information. * or there was an unexpected error while trying to get the user information.
* *
* @return Result of logged in user data or of an error on fail */ * @return Result of logged in user data or of an error on fail */
Result<UserInfo> getLoggedInUser(); Result<UserInfo> getLoggedInUser();
void refreshUser(UserInfo userInfo); void refreshUser(UserInfo userInfo);
/** Returns true if a current logged in user has the specified role. /** Returns true if a current logged in user has the specified role.
* *
* @param role the UserRole to check * @param role the UserRole to check
* @return true if a current logged in user has the specified role */ * @return true if a current logged in user has the specified role */
boolean hasRole(UserRole role); boolean hasRole(UserRole role);
/** Get the underling RestTemplate to connect and communicate with the SEB Server webservice. /** Get the underling RestTemplate to connect and communicate with the SEB Server webservice.
* *
* @return the underling RestTemplate to connect and communicate with the SEB Server webservice */ * @return the underling RestTemplate to connect and communicate with the SEB Server webservice */
RestTemplate getRestTemplate(); RestTemplate getRestTemplate();
} }

View file

@ -1,114 +1,114 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.remote.webservice.auth; package ch.ethz.seb.sebserver.gui.service.remote.webservice.auth;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
public class WebserviceConnectionData { public class WebserviceConnectionData {
final String id; final String id;
final String webserviceProtocol; final String webserviceProtocol;
final String webserviceServerAdress; final String webserviceServerAddress;
final String webserviceServerPort; final String webserviceServerPort;
final String webserviceAPIPath; final String webserviceAPIPath;
final String webserviceServerAddress; final String webserviceServerURL;
private final UriComponentsBuilder webserviceURIBuilder; private final UriComponentsBuilder webserviceURIBuilder;
protected WebserviceConnectionData( protected WebserviceConnectionData(
final String id, final String id,
final String webserviceProtocol, final String webserviceProtocol,
final String webserviceServerAdress, final String webserviceServerAddress,
final String webserviceServerPort, final String webserviceServerPort,
final String webserviceAPIPath) { final String webserviceAPIPath) {
this.id = id; this.id = id;
this.webserviceProtocol = webserviceProtocol; this.webserviceProtocol = webserviceProtocol;
this.webserviceServerAdress = webserviceServerAdress; this.webserviceServerAddress = webserviceServerAddress;
this.webserviceServerPort = webserviceServerPort; this.webserviceServerPort = webserviceServerPort;
this.webserviceAPIPath = webserviceAPIPath; this.webserviceAPIPath = webserviceAPIPath;
this.webserviceServerAddress = webserviceProtocol + "://" + webserviceServerAdress + ":" + webserviceServerPort; this.webserviceServerURL = webserviceProtocol + "://" + webserviceServerAddress + ":" + webserviceServerPort;
this.webserviceURIBuilder = UriComponentsBuilder this.webserviceURIBuilder = UriComponentsBuilder
.fromHttpUrl(webserviceProtocol + "://" + webserviceServerAdress) .fromHttpUrl(webserviceProtocol + "://" + webserviceServerAddress)
.port(webserviceServerPort) .port(webserviceServerPort)
.path(webserviceAPIPath); .path(webserviceAPIPath);
} }
public String getId() { public String getId() {
return this.id; return this.id;
} }
public String getWebserviceProtocol() { public String getWebserviceProtocol() {
return this.webserviceProtocol; return this.webserviceProtocol;
} }
public String getWebserviceServerAdress() { public String getWebserviceServerAddress() {
return this.webserviceServerAdress; return this.webserviceServerAddress;
} }
public String getWebserviceServerPort() { public String getWebserviceServerPort() {
return this.webserviceServerPort; return this.webserviceServerPort;
} }
public String getWebserviceAPIPath() { public String getWebserviceAPIPath() {
return this.webserviceAPIPath; return this.webserviceAPIPath;
} }
public String getWebserviceServerAddress() { public String getWebserviceServerURL() {
return this.webserviceServerAddress; return this.webserviceServerURL;
} }
public UriComponentsBuilder getWebserviceURIBuilder() { public UriComponentsBuilder getWebserviceURIBuilder() {
return this.webserviceURIBuilder.cloneBuilder(); return this.webserviceURIBuilder.cloneBuilder();
} }
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;
int result = 1; int result = 1;
result = prime * result + ((this.id == null) ? 0 : this.id.hashCode()); result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
return result; return result;
} }
@Override @Override
public boolean equals(final Object obj) { public boolean equals(final Object obj) {
if (this == obj) if (this == obj)
return true; return true;
if (obj == null) if (obj == null)
return false; return false;
if (getClass() != obj.getClass()) if (getClass() != obj.getClass())
return false; return false;
final WebserviceConnectionData other = (WebserviceConnectionData) obj; final WebserviceConnectionData other = (WebserviceConnectionData) obj;
if (this.id == null) { if (this.id == null) {
if (other.id != null) if (other.id != null)
return false; return false;
} else if (!this.id.equals(other.id)) } else if (!this.id.equals(other.id))
return false; return false;
return true; return true;
} }
@Override @Override
public String toString() { public String toString() {
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
builder.append("WebserviceConnectionData [id="); builder.append("WebserviceConnectionData [id=");
builder.append(this.id); builder.append(this.id);
builder.append(", webserviceProtocol="); builder.append(", webserviceProtocol=");
builder.append(this.webserviceProtocol); builder.append(this.webserviceProtocol);
builder.append(", webserviceServerAdress="); builder.append(", webserviceServerAddress=");
builder.append(this.webserviceServerAdress); builder.append(this.webserviceServerAddress);
builder.append(", webserviceServerPort="); builder.append(", webserviceServerPort=");
builder.append(this.webserviceServerPort); builder.append(this.webserviceServerPort);
builder.append(", webserviceAPIPath="); builder.append(", webserviceAPIPath=");
builder.append(this.webserviceAPIPath); builder.append(this.webserviceAPIPath);
builder.append("]"); builder.append("]");
return builder.toString(); return builder.toString();
} }
} }

View file

@ -1,69 +1,69 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.remote.webservice.auth; package ch.ethz.seb.sebserver.gui.service.remote.webservice.auth;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
@Component @Component
@GuiProfile @GuiProfile
public class WebserviceURIService { public class WebserviceURIService {
private final String servletContextPath; private final String servletContextPath;
private final String webserviceServerAddress; private final String webserviceServerAddress;
private final UriComponentsBuilder webserviceURIBuilder; private final UriComponentsBuilder webserviceURIBuilder;
public WebserviceURIService( public WebserviceURIService(
@Value("${sebserver.gui.webservice.protocol}") final String webserviceProtocol, @Value("${sebserver.gui.webservice.protocol}") final String webserviceProtocol,
@Value("${sebserver.gui.webservice.address}") final String webserviceServerAdress, @Value("${sebserver.gui.webservice.address}") final String webserviceServerAddress,
@Value("${sebserver.gui.webservice.port}") final String webserviceServerPort, @Value("${sebserver.gui.webservice.port}") final String webserviceServerPort,
@Value("${server.servlet.context-path}") final String servletContextPath, @Value("${server.servlet.context-path}") final String servletContextPath,
@Value("${sebserver.gui.webservice.apipath}") final String webserviceAPIPath) { @Value("${sebserver.gui.webservice.apipath}") final String webserviceAPIPath) {
this.servletContextPath = servletContextPath; this.servletContextPath = servletContextPath;
this.webserviceServerAddress = webserviceProtocol + "://" + webserviceServerAdress + ":" + webserviceServerPort; this.webserviceServerAddress = webserviceProtocol + "://" + webserviceServerAddress + ":" + webserviceServerPort;
this.webserviceURIBuilder = UriComponentsBuilder this.webserviceURIBuilder = UriComponentsBuilder
.fromHttpUrl(webserviceProtocol + "://" + webserviceServerAdress) .fromHttpUrl(webserviceProtocol + "://" + webserviceServerAddress)
.port(webserviceServerPort) .port(webserviceServerPort)
.path(servletContextPath) .path(servletContextPath)
.path(webserviceAPIPath); .path(webserviceAPIPath);
} }
public String getWebserviceServerAddress() { public String getWebserviceServerAddress() {
return this.webserviceServerAddress; return this.webserviceServerAddress;
} }
public UriComponentsBuilder getURIBuilder() { public UriComponentsBuilder getURIBuilder() {
return this.webserviceURIBuilder.cloneBuilder(); return this.webserviceURIBuilder.cloneBuilder();
} }
public String getOAuthTokenURI() { public String getOAuthTokenURI() {
return UriComponentsBuilder.fromHttpUrl(this.webserviceServerAddress) return UriComponentsBuilder.fromHttpUrl(this.webserviceServerAddress)
.path(this.servletContextPath) .path(this.servletContextPath)
.path(API.OAUTH_TOKEN_ENDPOINT) .path(API.OAUTH_TOKEN_ENDPOINT)
.toUriString(); .toUriString();
} }
public String getOAuthRevokeTokenURI() { public String getOAuthRevokeTokenURI() {
return UriComponentsBuilder.fromHttpUrl(this.webserviceServerAddress) return UriComponentsBuilder.fromHttpUrl(this.webserviceServerAddress)
.path(this.servletContextPath) .path(this.servletContextPath)
.path(API.OAUTH_REVOKE_TOKEN_ENDPOINT) .path(API.OAUTH_REVOKE_TOKEN_ENDPOINT)
.toUriString(); .toUriString();
} }
public String getCurrentUserRequestURI() { public String getCurrentUserRequestURI() {
return getURIBuilder() return getURIBuilder()
.path(API.CURRENT_USER_ENDPOINT) .path(API.CURRENT_USER_ENDPOINT)
.toUriString(); .toUriString();
} }
} }

View file

@ -49,12 +49,10 @@ public class ClientConnectionDetails {
private static final int NUMBER_OF_NONE_INDICATOR_ROWS = 3; private static final int NUMBER_OF_NONE_INDICATOR_ROWS = 3;
private final PageService pageService;
private final ResourceService resourceService; private final ResourceService resourceService;
private final Exam exam;
private final EnumMap<IndicatorType, IndicatorData> indicatorMapping; private final EnumMap<IndicatorType, IndicatorData> indicatorMapping;
private final RestCall<ClientConnectionData>.RestCallBuilder restCallBuilder; private final RestCall<ClientConnectionData>.RestCallBuilder restCallBuilder;
private final FormHandle<?> formhandle; private final FormHandle<?> formHandle;
private final ColorData colorData; private final ColorData colorData;
private ClientConnectionData connectionData = null; private ClientConnectionData connectionData = null;
@ -69,9 +67,7 @@ public class ClientConnectionDetails {
final Display display = pageContext.getRoot().getDisplay(); final Display display = pageContext.getRoot().getDisplay();
this.pageService = pageService;
this.resourceService = pageService.getResourceService(); this.resourceService = pageService.getResourceService();
this.exam = exam;
this.restCallBuilder = restCallBuilder; this.restCallBuilder = restCallBuilder;
this.colorData = new ColorData(display); this.colorData = new ColorData(display);
this.indicatorMapping = IndicatorData.createFormIndicators( this.indicatorMapping = IndicatorData.createFormIndicators(
@ -80,12 +76,12 @@ public class ClientConnectionDetails {
this.colorData, this.colorData,
NUMBER_OF_NONE_INDICATOR_ROWS); NUMBER_OF_NONE_INDICATOR_ROWS);
final FormBuilder formBuilder = this.pageService.formBuilder(pageContext) final FormBuilder formBuilder = pageService.formBuilder(pageContext)
.readonly(true) .readonly(true)
.addField(FormBuilder.text( .addField(FormBuilder.text(
QuizData.QUIZ_ATTR_NAME, QuizData.QUIZ_ATTR_NAME,
EXAM_NAME_TEXT_KEY, EXAM_NAME_TEXT_KEY,
this.exam.getName())) exam.getName()))
.addField(FormBuilder.text( .addField(FormBuilder.text(
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID, Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
CONNECTION_ID_TEXT_KEY, CONNECTION_ID_TEXT_KEY,
@ -112,7 +108,7 @@ public class ClientConnectionDetails {
.withDefaultLabel(indData.indicator.name)) .withDefaultLabel(indData.indicator.name))
.addEmptyCell()); .addEmptyCell());
this.formhandle = formBuilder.build(); this.formHandle = formBuilder.build();
} }
public void updateData() { public void updateData() {
@ -136,7 +132,7 @@ public class ClientConnectionDetails {
return; return;
} }
final Form form = this.formhandle.getForm(); final Form form = this.formHandle.getForm();
form.setFieldValue( form.setFieldValue(
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID, Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
this.connectionData.clientConnection.userSessionId); this.connectionData.clientConnection.userSessionId);

View file

@ -371,7 +371,7 @@ public final class ClientConnectionTable {
private void sortTable() { private void sortTable() {
this.tableMapping = this.tableMapping.entrySet() this.tableMapping = this.tableMapping.entrySet()
.stream() .stream()
.sorted((e1, e2) -> e1.getValue().compareTo(e2.getValue())) .sorted(Entry.comparingByValue())
.collect(Collectors.toMap( .collect(Collectors.toMap(
Entry::getKey, Entry::getKey,
Entry::getValue, Entry::getValue,
@ -398,13 +398,12 @@ public final class ClientConnectionTable {
final String attribute = this.resourceService final String attribute = this.resourceService
.getCurrentUser() .getCurrentUser()
.getAttribute(USER_SESSION_STATUS_FILTER_ATTRIBUTE); .getAttribute(USER_SESSION_STATUS_FILTER_ATTRIBUTE);
this.statusFilter.clear();
if (attribute != null) { if (attribute != null) {
this.statusFilter.clear();
Arrays.asList(StringUtils.split(attribute, Constants.LIST_SEPARATOR)) Arrays.asList(StringUtils.split(attribute, Constants.LIST_SEPARATOR))
.forEach(name -> this.statusFilter.add(ConnectionStatus.valueOf(name))); .forEach(name -> this.statusFilter.add(ConnectionStatus.valueOf(name)));
} else { } else {
this.statusFilter.clear();
this.statusFilter.add(ConnectionStatus.DISABLED); this.statusFilter.add(ConnectionStatus.DISABLED);
} }
} catch (final Exception e) { } catch (final Exception e) {

View file

@ -1,100 +1,99 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.session; package ch.ethz.seb.sebserver.gui.service.session;
import java.util.ArrayList; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import java.util.Collection; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
import java.util.Collections; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
import java.util.EnumMap; import ch.ethz.seb.sebserver.gbl.util.Utils;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Color; import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Display;
import java.util.ArrayList;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import java.util.Collection;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; import java.util.Comparator;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold; import java.util.EnumMap;
import ch.ethz.seb.sebserver.gbl.util.Utils;
final class IndicatorData {
final class IndicatorData {
final int index;
final int index; final int tableIndex;
final int tableIndex; final Indicator indicator;
final Indicator indicator; final Color defaultColor;
final Color defaultColor; final Color defaultTextColor;
final Color defaultTextColor; final ThresholdColor[] thresholdColor;
final ThresholdColor[] thresholdColor;
protected IndicatorData(
protected IndicatorData( final Indicator indicator,
final Indicator indicator, final int index,
final int index, final int tableIndex,
final int tableIndex, final ColorData colorData,
final ColorData colorData, final Display display) {
final Display display) {
this.indicator = indicator;
this.indicator = indicator; this.index = index;
this.index = index; this.tableIndex = tableIndex;
this.tableIndex = tableIndex; this.defaultColor = new Color(display, Utils.toRGB(indicator.defaultColor), 255);
this.defaultColor = new Color(display, Utils.toRGB(indicator.defaultColor), 255); this.defaultTextColor = Utils.darkColor(this.defaultColor.getRGB())
this.defaultTextColor = Utils.darkColor(this.defaultColor.getRGB()) ? colorData.darkColor
? colorData.darkColor : colorData.lightColor;
: colorData.lightColor;
this.thresholdColor = new ThresholdColor[indicator.thresholds.size()];
this.thresholdColor = new ThresholdColor[indicator.thresholds.size()]; final ArrayList<Threshold> sortedThresholds = new ArrayList<>(indicator.thresholds);
final ArrayList<Threshold> sortedThresholds = new ArrayList<>(indicator.thresholds); sortedThresholds.sort(Comparator.comparing(t -> t.value));
Collections.sort(sortedThresholds, (t1, t2) -> t1.value.compareTo(t2.value)); for (int i = 0; i < indicator.thresholds.size(); i++) {
for (int i = 0; i < indicator.thresholds.size(); i++) { this.thresholdColor[i] = new ThresholdColor(sortedThresholds.get(i), display, colorData);
this.thresholdColor[i] = new ThresholdColor(sortedThresholds.get(i), display, colorData); }
} }
}
static EnumMap<IndicatorType, IndicatorData> createFormIndicators(
static final EnumMap<IndicatorType, IndicatorData> createFormIndicators( final Collection<Indicator> indicators,
final Collection<Indicator> indicators, final Display display,
final Display display, final ColorData colorData,
final ColorData colorData, final int tableIndexOffset) {
final int tableIndexOffset) {
final EnumMap<IndicatorType, IndicatorData> indicatorMapping = new EnumMap<>(IndicatorType.class);
final EnumMap<IndicatorType, IndicatorData> indicatorMapping = new EnumMap<>(IndicatorType.class); int i = 0;
int i = 0; for (final Indicator indicator : indicators) {
for (final Indicator indicator : indicators) { indicatorMapping.put(indicator.type, new IndicatorData(
indicatorMapping.put(indicator.type, new IndicatorData( indicator,
indicator, i,
i, i + tableIndexOffset,
i + tableIndexOffset, colorData,
colorData, display));
display)); i++;
i++; }
} return indicatorMapping;
return indicatorMapping; }
}
static int getWeight(final IndicatorData indicatorData, final double value) {
static final int getWeight(final IndicatorData indicatorData, final double value) { for (int j = 0; j < indicatorData.thresholdColor.length; j++) {
for (int j = 0; j < indicatorData.thresholdColor.length; j++) { if (value < indicatorData.thresholdColor[j].value) {
if (value < indicatorData.thresholdColor[j].value) { return (j == 0) ? -1 : j - 1;
return (j == 0) ? -1 : j - 1; }
} }
}
return indicatorData.thresholdColor.length - 1;
return indicatorData.thresholdColor.length - 1; }
}
static final class ThresholdColor {
static final class ThresholdColor { final double value;
final double value; final Color color;
final Color color; final Color textColor;
final Color textColor;
protected ThresholdColor(final Threshold threshold, final Display display, final ColorData colorData) {
protected ThresholdColor(final Threshold threshold, final Display display, final ColorData colorData) { this.value = threshold.value;
this.value = threshold.value; this.color = new Color(display, Utils.toRGB(threshold.color), 255);
this.color = new Color(display, Utils.toRGB(threshold.color), 255); this.textColor = Utils.darkColor(this.color.getRGB())
this.textColor = Utils.darkColor(this.color.getRGB()) ? colorData.darkColor
? colorData.darkColor : colorData.lightColor;
: colorData.lightColor; }
} }
}
} }

View file

@ -1,165 +1,160 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.session; package ch.ethz.seb.sebserver.gui.service.session;
import java.util.Collection; import java.util.Collection;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction; import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction;
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.InstructionType; import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.InstructionType;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.DisableClientConnection; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.DisableClientConnection;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.PropagateInstruction; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.PropagateInstruction;
@Lazy @Lazy
@Service @Service
@GuiProfile @GuiProfile
public class InstructionProcessor { public class InstructionProcessor {
private static final Logger log = LoggerFactory.getLogger(InstructionProcessor.class); private static final Logger log = LoggerFactory.getLogger(InstructionProcessor.class);
private final RestService restService; private final RestService restService;
private final JSONMapper jsonMapper; private final JSONMapper jsonMapper;
protected InstructionProcessor(final PageService pageService) { protected InstructionProcessor(final PageService pageService) {
this.restService = pageService.getRestService(); this.restService = pageService.getRestService();
this.jsonMapper = pageService.getJSONMapper(); this.jsonMapper = pageService.getJSONMapper();
} }
public void propagateSebQuitInstruction( public void propagateSebQuitInstruction(
final Long examId, final Long examId,
final String connectionToken, final String connectionToken,
final PageContext pageContext) { final PageContext pageContext) {
propagateSebQuitInstruction( propagateSebQuitInstruction(
examId, examId,
p -> Stream.of(connectionToken).collect(Collectors.toSet()), p -> Stream.of(connectionToken).collect(Collectors.toSet()),
pageContext); pageContext);
} }
public void propagateSebQuitInstruction( public void propagateSebQuitInstruction(
final Long examId, final Long examId,
final Function<Predicate<ClientConnection>, Set<String>> selectionFunction, final Function<Predicate<ClientConnection>, Set<String>> selectionFunction,
final PageContext pageContext) { final PageContext pageContext) {
final Set<String> connectionTokens = selectionFunction final Set<String> connectionTokens = selectionFunction
.apply(ClientConnection.getStatusPredicate(ConnectionStatus.ACTIVE)); .apply(ClientConnection.getStatusPredicate(ConnectionStatus.ACTIVE));
if (connectionTokens.isEmpty()) { if (connectionTokens.isEmpty()) {
// TODO message // TODO message
return; return;
} }
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Propagate SEB quit instruction for exam: {} and connections: {}", log.debug("Propagate SEB quit instruction for exam: {} and connections: {}",
examId, examId,
connectionTokens); connectionTokens);
} }
final ClientInstruction clientInstruction = new ClientInstruction( final ClientInstruction clientInstruction = new ClientInstruction(
null, null,
examId, examId,
InstructionType.SEB_QUIT, InstructionType.SEB_QUIT,
StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR), StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR),
null); null);
processInstruction(() -> { processInstruction(() -> this.restService.getBuilder(PropagateInstruction.class)
return this.restService.getBuilder(PropagateInstruction.class) .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(examId))
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(examId)) .withBody(clientInstruction)
.withBody(clientInstruction) .call()
.call() .getOrThrow(),
.getOrThrow(); pageContext);
},
pageContext); }
} public void disableConnection(
final Long examId,
public void disableConnection( final Function<Predicate<ClientConnection>, Set<String>> selectionFunction,
final Long examId, final PageContext pageContext) {
final Function<Predicate<ClientConnection>, Set<String>> selectionFunction,
final PageContext pageContext) { final Set<String> connectionTokens = selectionFunction
.apply(ClientConnection.getStatusPredicate(
final Set<String> connectionTokens = selectionFunction ConnectionStatus.CONNECTION_REQUESTED,
.apply(ClientConnection.getStatusPredicate( ConnectionStatus.UNDEFINED,
ConnectionStatus.CONNECTION_REQUESTED, ConnectionStatus.CLOSED,
ConnectionStatus.UNDEFINED, ConnectionStatus.AUTHENTICATED));
ConnectionStatus.CLOSED,
ConnectionStatus.AUTHENTICATED)); if (connectionTokens.isEmpty()) {
return;
if (connectionTokens.isEmpty()) { }
// TOOD message
return; if (log.isDebugEnabled()) {
} log.debug("Disable SEB client connections for exam: {} and connections: {}",
examId,
if (log.isDebugEnabled()) { connectionTokens);
log.debug("Disable SEB client connections for exam: {} and connections: {}", }
examId,
connectionTokens); processInstruction(() -> this.restService.getBuilder(DisableClientConnection.class)
} .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(examId))
.withFormParam(
processInstruction(() -> { Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
return this.restService.getBuilder(DisableClientConnection.class) StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR))
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(examId)) .call()
.withFormParam( .getOrThrow(),
Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN, pageContext);
StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR))
.call() }
.getOrThrow();
}, private void processInstruction(final Supplier<String> apiCall, final PageContext pageContext) {
pageContext); try {
final String response = apiCall.get();
}
if (StringUtils.isNotBlank(response)) {
private void processInstruction(final Supplier<String> apiCall, final PageContext pageContext) { try {
try { final Collection<APIMessage> errorMessage = this.jsonMapper.readValue(
final String response = apiCall.get(); response,
Constants.TYPE_REFERENCE_API_MESSAGE);
if (StringUtils.isNotBlank(response)) {
try { pageContext.notifyUnexpectedError(new RestCallError(
final Collection<APIMessage> errorMessage = this.jsonMapper.readValue( "Failed to propagate SEB client instruction: ",
response, errorMessage));
Constants.TYPE_REFERENCE_API_MESSAGE);
} catch (final Exception e) {
pageContext.notifyUnexpectedError(new RestCallError( log.error("Failed to parse error response: {}", response);
"Failed to propagate SEB client instruction: ", }
errorMessage)); }
} catch (final Exception e) {
} catch (final Exception e) { log.error("Failed to propagate SEB client instruction: ", e);
log.error("Failed to parse error response: {}", response); }
} }
}
} catch (final Exception e) { }
log.error("Failed to propagate SEB client instruction: ", e);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,174 +1,174 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.table; package ch.ethz.seb.sebserver.gui.table;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.TableItem;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.Page; import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; 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;
import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction; import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
public class TableBuilder<ROW extends Entity> { public class TableBuilder<ROW extends Entity> {
private final String name; private final String name;
private final PageService pageService; private final PageService pageService;
final RestCall<Page<ROW>> restCall; final RestCall<Page<ROW>> restCall;
private final MultiValueMap<String, String> staticQueryParams; private final MultiValueMap<String, String> staticQueryParams;
final List<ColumnDefinition<ROW>> columns = new ArrayList<>(); final List<ColumnDefinition<ROW>> columns = new ArrayList<>();
LocTextKey emptyMessage; LocTextKey emptyMessage;
private Function<EntityTable<ROW>, PageAction> defaultActionFunction; private Function<EntityTable<ROW>, PageAction> defaultActionFunction;
private int pageSize = -1; private int pageSize = -1;
private int type = SWT.NONE; private int type = SWT.NONE;
private boolean hideNavigation = false; private boolean hideNavigation = false;
private Function<RestCall<Page<ROW>>.RestCallBuilder, RestCall<Page<ROW>>.RestCallBuilder> restCallAdapter; private Function<RestCall<Page<ROW>>.RestCallBuilder, RestCall<Page<ROW>>.RestCallBuilder> restCallAdapter;
private BiConsumer<TableItem, ROW> rowDecorator; private BiConsumer<TableItem, ROW> rowDecorator;
private Consumer<Set<ROW>> selectionListener; private Consumer<Set<ROW>> selectionListener;
private boolean markupEnabled = false; private boolean markupEnabled = false;
public TableBuilder( public TableBuilder(
final String name, final String name,
final PageService pageService, final PageService pageService,
final RestCall<Page<ROW>> restCall) { final RestCall<Page<ROW>> restCall) {
this.name = name; this.name = name;
this.pageService = pageService; this.pageService = pageService;
this.restCall = restCall; this.restCall = restCall;
this.staticQueryParams = new LinkedMultiValueMap<>(); this.staticQueryParams = new LinkedMultiValueMap<>();
} }
public TableBuilder<ROW> hideNavigation() { public TableBuilder<ROW> hideNavigation() {
this.hideNavigation = true; this.hideNavigation = true;
return this; return this;
} }
public TableBuilder<ROW> withEmptyMessage(final LocTextKey emptyMessage) { public TableBuilder<ROW> withEmptyMessage(final LocTextKey emptyMessage) {
this.emptyMessage = emptyMessage; this.emptyMessage = emptyMessage;
return this; return this;
} }
public TableBuilder<ROW> withPaging(final int pageSize) { public TableBuilder<ROW> withPaging(final int pageSize) {
this.pageSize = pageSize; this.pageSize = pageSize;
return this; return this;
} }
public TableBuilder<ROW> withColumn(final ColumnDefinition<ROW> columnDefinition) { public TableBuilder<ROW> withColumn(final ColumnDefinition<ROW> columnDefinition) {
this.columns.add(columnDefinition); this.columns.add(columnDefinition);
return this; return this;
} }
public TableBuilder<ROW> withMarkup() { public TableBuilder<ROW> withMarkup() {
this.markupEnabled = true; this.markupEnabled = true;
return this; return this;
} }
public TableBuilder<ROW> withColumnIf( public TableBuilder<ROW> withColumnIf(
final BooleanSupplier condition, final BooleanSupplier condition,
final Supplier<ColumnDefinition<ROW>> columnDefSupplier) { final Supplier<ColumnDefinition<ROW>> columnDefSupplier) {
if (condition != null && condition.getAsBoolean()) { if (condition != null && condition.getAsBoolean()) {
this.columns.add(columnDefSupplier.get()); this.columns.add(columnDefSupplier.get());
} }
return this; return this;
} }
public TableBuilder<ROW> withRestCallAdapter( public TableBuilder<ROW> withRestCallAdapter(
final Function<RestCall<Page<ROW>>.RestCallBuilder, RestCall<Page<ROW>>.RestCallBuilder> adapter) { final Function<RestCall<Page<ROW>>.RestCallBuilder, RestCall<Page<ROW>>.RestCallBuilder> adapter) {
this.restCallAdapter = adapter; this.restCallAdapter = adapter;
return this; return this;
} }
public TableBuilder<ROW> withMultiselection() { public TableBuilder<ROW> withMultiSelection() {
this.type |= SWT.MULTI; this.type |= SWT.MULTI;
return this; return this;
} }
public TableBuilder<ROW> withSelectionListener(final Consumer<Set<ROW>> selectionListener) { public TableBuilder<ROW> withSelectionListener(final Consumer<Set<ROW>> selectionListener) {
this.selectionListener = selectionListener; this.selectionListener = selectionListener;
return this; return this;
} }
public TableBuilder<ROW> withStaticFilter(final String name, final String value) { public TableBuilder<ROW> withStaticFilter(final String name, final String value) {
this.staticQueryParams.add(name, value); this.staticQueryParams.add(name, value);
return this; return this;
} }
public TableBuilder<ROW> withDefaultActionIf( public TableBuilder<ROW> withDefaultActionIf(
final BooleanSupplier condition, final BooleanSupplier condition,
final Supplier<PageAction> actionSupplier) { final Supplier<PageAction> actionSupplier) {
if (condition.getAsBoolean()) { if (condition.getAsBoolean()) {
return withDefaultAction(actionSupplier.get()); return withDefaultAction(actionSupplier.get());
} }
return this; return this;
} }
public TableBuilder<ROW> withDefaultAction(final PageAction action) { public TableBuilder<ROW> withDefaultAction(final PageAction action) {
this.defaultActionFunction = table -> PageAction.copyOf(action); this.defaultActionFunction = table -> PageAction.copyOf(action);
return this; return this;
} }
public TableBuilder<ROW> withDefaultActionIf( public TableBuilder<ROW> withDefaultActionIf(
final BooleanSupplier condition, final BooleanSupplier condition,
final Function<EntityTable<ROW>, PageAction> defaultActionFunction) { final Function<EntityTable<ROW>, PageAction> defaultActionFunction) {
if (condition.getAsBoolean()) { if (condition.getAsBoolean()) {
return withDefaultAction(defaultActionFunction); return withDefaultAction(defaultActionFunction);
} }
return this; return this;
} }
public TableBuilder<ROW> withDefaultAction(final Function<EntityTable<ROW>, PageAction> defaultActionFunction) { public TableBuilder<ROW> withDefaultAction(final Function<EntityTable<ROW>, PageAction> defaultActionFunction) {
this.defaultActionFunction = defaultActionFunction; this.defaultActionFunction = defaultActionFunction;
return this; return this;
} }
public TableBuilder<ROW> withRowDecorator(final BiConsumer<TableItem, ROW> rowDecorator) { public TableBuilder<ROW> withRowDecorator(final BiConsumer<TableItem, ROW> rowDecorator) {
this.rowDecorator = rowDecorator; this.rowDecorator = rowDecorator;
return this; return this;
} }
public EntityTable<ROW> compose(final PageContext pageContext) { public EntityTable<ROW> compose(final PageContext pageContext) {
return new EntityTable<>( return new EntityTable<>(
this.name, this.name,
this.markupEnabled, this.markupEnabled,
this.type, this.type,
pageContext, pageContext,
this.restCall, this.restCall,
this.restCallAdapter, this.restCallAdapter,
this.pageService, this.pageService,
this.columns, this.columns,
this.pageSize, this.pageSize,
this.emptyMessage, this.emptyMessage,
this.defaultActionFunction, this.defaultActionFunction,
this.hideNavigation, this.hideNavigation,
this.staticQueryParams, this.staticQueryParams,
this.rowDecorator, this.rowDecorator,
this.selectionListener); this.selectionListener);
} }
} }

View file

@ -49,7 +49,7 @@ public class TableFilter<ROW extends Entity> {
private static final LocTextKey DATE_TO_TEXT = new LocTextKey("sebserver.overall.date.to"); private static final LocTextKey DATE_TO_TEXT = new LocTextKey("sebserver.overall.date.to");
private static final LocTextKey ALL_TEXT = new LocTextKey("sebserver.overall.status.all"); private static final LocTextKey ALL_TEXT = new LocTextKey("sebserver.overall.status.all");
public static enum CriteriaType { public enum CriteriaType {
TEXT, TEXT,
SINGLE_SELECTION, SINGLE_SELECTION,
DATE, DATE,
@ -82,7 +82,7 @@ public class TableFilter<ROW extends Entity> {
public MultiValueMap<String, String> getFilterParameter() { public MultiValueMap<String, String> getFilterParameter() {
return this.components return this.components
.stream() .stream()
.reduce(new LinkedMultiValueMap<String, String>(), .reduce(new LinkedMultiValueMap<>(),
(map, comp) -> comp.putFilterParameter(map), (map, comp) -> comp.putFilterParameter(map),
(map1, map2) -> { (map1, map2) -> {
map1.putAll(map2); map1.putAll(map2);
@ -92,8 +92,7 @@ public class TableFilter<ROW extends Entity> {
public void reset() { public void reset() {
this.components this.components
.stream() .forEach(FilterComponent::reset);
.forEach(comp -> comp.reset());
} }
private void buildComponents() { private void buildComponents() {
@ -158,7 +157,7 @@ public class TableFilter<ROW extends Entity> {
.append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR) .append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR)
.append(filter.getValue()) .append(filter.getValue())
.append(Constants.LIST_SEPARATOR), .append(Constants.LIST_SEPARATOR),
(sb1, sb2) -> sb1.append(sb2)); StringBuilder::append);
if (builder.length() > 0) { if (builder.length() > 0) {
builder.deleteCharAt(builder.length() - 1); builder.deleteCharAt(builder.length() - 1);
} }
@ -171,22 +170,19 @@ public class TableFilter<ROW extends Entity> {
} }
try { try {
Arrays.asList(StringUtils.split( Arrays.stream(StringUtils.split(
attribute, attribute,
Constants.LIST_SEPARATOR_CHAR)) Constants.LIST_SEPARATOR_CHAR))
.stream()
.map(nameValue -> StringUtils.split( .map(nameValue -> StringUtils.split(
nameValue, nameValue,
Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR)) Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR))
.forEach(nameValue -> { .forEach(nameValue -> this.components
this.components .stream()
.stream() .filter(filter -> nameValue[0].equals(filter.attribute.columnName))
.filter(filter -> nameValue[0].equals(filter.attribute.columnName)) .findFirst()
.findFirst() .ifPresent(filter -> filter.setValue((nameValue.length > 1)
.ifPresent(filter -> filter.setValue((nameValue.length > 1) ? nameValue[1]
? nameValue[1] : StringUtils.EMPTY)));
: StringUtils.EMPTY));
});
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to set filter attributes: ", e); log.error("Failed to set filter attributes: ", e);
} }
@ -208,9 +204,7 @@ public class TableFilter<ROW extends Entity> {
ImageIcon.SEARCH, ImageIcon.SEARCH,
inner, inner,
new LocTextKey("sebserver.overall.action.filter"), new LocTextKey("sebserver.overall.action.filter"),
event -> { event -> this.entityTable.applyFilter());
this.entityTable.applyFilter();
});
imageButton.setLayoutData(gridData); imageButton.setLayoutData(gridData);
final Label imageButton2 = this.entityTable.widgetFactory.imageButton( final Label imageButton2 = this.entityTable.widgetFactory.imageButton(
ImageIcon.CANCEL, ImageIcon.CANCEL,
@ -276,7 +270,7 @@ public class TableFilter<ROW extends Entity> {
} }
} }
private class NullFilter extends FilterComponent { private static class NullFilter extends FilterComponent {
private Label label; private Label label;
@ -493,7 +487,6 @@ public class TableFilter<ROW extends Entity> {
private class DateRange extends FilterComponent { private class DateRange extends FilterComponent {
private Composite innerComposite; private Composite innerComposite;
//private final GridData rw1 = new GridData(SWT.FILL, SWT.FILL, true, true);
private DateTime fromDateSelector; private DateTime fromDateSelector;
private DateTime toDateSelector; private DateTime toDateSelector;
private DateTime fromTimeSelector; private DateTime fromTimeSelector;

View file

@ -1,189 +1,179 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.table; package ch.ethz.seb.sebserver.gui.table;
import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gbl.model.Page; import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
public class TableNavigator { public class TableNavigator {
private final static int PAGE_NAV_SIZE = 9; private final static int PAGE_NAV_SIZE = 9;
private final Composite composite; private final Composite composite;
private final EntityTable<?> entityTable; private final EntityTable<?> entityTable;
TableNavigator(final EntityTable<?> entityTable) { TableNavigator(final EntityTable<?> entityTable) {
this.composite = new Composite(entityTable.composite, SWT.NONE); this.composite = new Composite(entityTable.composite, SWT.NONE);
final GridData gridData = new GridData(SWT.LEFT, SWT.CENTER, true, true); final GridData gridData = new GridData(SWT.LEFT, SWT.CENTER, true, true);
this.composite.setLayoutData(gridData); this.composite.setLayoutData(gridData);
final GridLayout layout = new GridLayout(3, false); final GridLayout layout = new GridLayout(3, false);
this.composite.setLayout(layout); this.composite.setLayout(layout);
this.entityTable = entityTable; this.entityTable = entityTable;
} }
public Page<?> update(final Page<?> pageData) { public Page<?> update(final Page<?> pageData) {
// clear all // clear all
PageService.clearComposite(this.composite); PageService.clearComposite(this.composite);
if (pageData.isEmpty()) { if (pageData.isEmpty()) {
// show empty message // show empty message
if (this.entityTable.emptyMessage != null) { if (this.entityTable.emptyMessage != null) {
this.entityTable.widgetFactory.labelLocalized( this.entityTable.widgetFactory.labelLocalized(
this.composite, this.composite,
CustomVariant.TEXT_H3, CustomVariant.TEXT_H3,
this.entityTable.emptyMessage); this.entityTable.emptyMessage);
} }
return pageData; return pageData;
} }
if (this.entityTable.hideNavigation) { if (this.entityTable.hideNavigation) {
return pageData; return pageData;
} }
final int pageNumber = pageData.getPageNumber(); final int pageNumber = pageData.getPageNumber();
final int numberOfPages = pageData.getNumberOfPages(); final int numberOfPages = pageData.getNumberOfPages();
createPagingHeader(pageNumber, numberOfPages); createPagingHeader(pageNumber, numberOfPages);
final Composite numNav = new Composite(this.composite, SWT.NONE); final Composite numNav = new Composite(this.composite, SWT.NONE);
final GridData gridData = new GridData(SWT.CENTER, SWT.TOP, true, false); final GridData gridData = new GridData(SWT.CENTER, SWT.TOP, true, false);
numNav.setLayoutData(gridData); numNav.setLayoutData(gridData);
final GridLayout rowLayout = new GridLayout(PAGE_NAV_SIZE + 5, true); final GridLayout rowLayout = new GridLayout(PAGE_NAV_SIZE + 5, true);
numNav.setLayout(rowLayout); numNav.setLayout(rowLayout);
if (numberOfPages > 1) { if (numberOfPages > 1) {
createBackwardLabel(pageNumber > 1, pageNumber, numNav); createBackwardLabel(pageNumber > 1, pageNumber, numNav);
final int pageNavSize = (numberOfPages > PAGE_NAV_SIZE) ? PAGE_NAV_SIZE : numberOfPages; final int pageNavSize = Math.min(numberOfPages, PAGE_NAV_SIZE);
final int half = pageNavSize / 2; final int half = pageNavSize / 2;
int start = pageNumber - half; int start = pageNumber - half;
if (start < 1) { if (start < 1) {
start = 1; start = 1;
} }
int end = start + pageNavSize; int end = start + pageNavSize;
if (end > numberOfPages) { if (end > numberOfPages) {
end = numberOfPages + 1; end = numberOfPages + 1;
start = end - pageNavSize; start = end - pageNavSize;
} }
for (int i = start; i < end; i++) { for (int i = start; i < end; i++) {
createPageNumberLabel(i, i != pageNumber, numNav); createPageNumberLabel(i, i != pageNumber, numNav);
} }
createForwardLabel(pageNumber < numberOfPages, pageNumber, numberOfPages, numNav); createForwardLabel(pageNumber < numberOfPages, pageNumber, numberOfPages, numNav);
} }
return pageData; return pageData;
} }
private void createPagingHeader(final int page, final int of) { private void createPagingHeader(final int page, final int of) {
final Label pageHeader = new Label(this.composite, SWT.NONE); final Label pageHeader = new Label(this.composite, SWT.NONE);
final GridData gridData = new GridData(SWT.CENTER, SWT.CENTER, true, false); final GridData gridData = new GridData(SWT.CENTER, SWT.CENTER, true, false);
gridData.widthHint = 100; gridData.widthHint = 100;
gridData.minimumWidth = 100; gridData.minimumWidth = 100;
gridData.heightHint = 16; gridData.heightHint = 16;
pageHeader.setLayoutData(gridData); pageHeader.setLayoutData(gridData);
pageHeader.setText("Page " + page + " / " + of); pageHeader.setText("Page " + page + " / " + of);
} }
private void createPageNumberLabel( private void createPageNumberLabel(
final int page, final int page,
final boolean selectable, final boolean selectable,
final Composite parent) { final Composite parent) {
final GridData rowData = new GridData(22, 16); final GridData rowData = new GridData(22, 16);
final Label pageLabel = new Label(parent, SWT.NONE); final Label pageLabel = new Label(parent, SWT.NONE);
pageLabel.setText(" " + String.valueOf(page) + " "); pageLabel.setText(" " + page + " ");
pageLabel.setLayoutData(rowData); pageLabel.setLayoutData(rowData);
pageLabel.setAlignment(SWT.CENTER); pageLabel.setAlignment(SWT.CENTER);
if (selectable) { if (selectable) {
pageLabel.setData(RWT.CUSTOM_VARIANT, CustomVariant.LIST_NAVIGATION.key); pageLabel.setData(RWT.CUSTOM_VARIANT, CustomVariant.LIST_NAVIGATION.key);
pageLabel.addListener(SWT.MouseDown, event -> { pageLabel.addListener(SWT.MouseDown, event -> this.entityTable.selectPage(page));
this.entityTable.selectPage(page); }
}); }
}
} private void createForwardLabel(
final boolean visible,
private void createForwardLabel( final int pageNumber,
final boolean visible, final int numberOfPages,
final int pageNumber, final Composite parent) {
final int numberOfPages,
final Composite parent) { final GridData rowData = new GridData(22, 16);
final Label forward = new Label(parent, SWT.NONE);
final GridData rowData = new GridData(22, 16); forward.setText(">");
final Label forward = new Label(parent, SWT.NONE); forward.setData(RWT.CUSTOM_VARIANT, CustomVariant.LIST_NAVIGATION.key);
forward.setText(">"); forward.setLayoutData(rowData);
forward.setData(RWT.CUSTOM_VARIANT, CustomVariant.LIST_NAVIGATION.key); forward.setAlignment(SWT.CENTER);
forward.setLayoutData(rowData); if (visible) {
forward.setAlignment(SWT.CENTER); forward.addListener(SWT.MouseDown, event -> this.entityTable.selectPage(pageNumber + 1));
if (visible) { } else {
forward.addListener(SWT.MouseDown, event -> { forward.setVisible(false);
this.entityTable.selectPage(pageNumber + 1); }
});
} else { final Label end = new Label(parent, SWT.NONE);
forward.setVisible(false); end.setText(">>");
} end.setData(RWT.CUSTOM_VARIANT, CustomVariant.LIST_NAVIGATION.key);
end.setLayoutData(rowData);
final Label end = new Label(parent, SWT.NONE); end.setAlignment(SWT.CENTER);
end.setText(">>"); if (visible) {
end.setData(RWT.CUSTOM_VARIANT, CustomVariant.LIST_NAVIGATION.key); end.addListener(SWT.MouseDown, event -> this.entityTable.selectPage(numberOfPages));
end.setLayoutData(rowData); } else {
end.setAlignment(SWT.CENTER); end.setVisible(false);
if (visible) { }
end.addListener(SWT.MouseDown, event -> { }
this.entityTable.selectPage(numberOfPages);
}); private void createBackwardLabel(
} else { final boolean visible,
end.setVisible(false); final int pageNumber,
} final Composite parent) {
}
final GridData rowData = new GridData(22, 16);
private void createBackwardLabel( final Label start = new Label(parent, SWT.NONE);
final boolean visible, start.setText("<<");
final int pageNumber, start.setLayoutData(rowData);
final Composite parent) { start.setAlignment(SWT.CENTER);
start.setData(RWT.CUSTOM_VARIANT, CustomVariant.LIST_NAVIGATION.key);
final GridData rowData = new GridData(22, 16); if (visible) {
final Label start = new Label(parent, SWT.NONE); start.addListener(SWT.MouseDown, event -> this.entityTable.selectPage(1));
start.setText("<<"); } else {
start.setLayoutData(rowData); start.setVisible(false);
start.setAlignment(SWT.CENTER); }
start.setData(RWT.CUSTOM_VARIANT, CustomVariant.LIST_NAVIGATION.key);
if (visible) { final Label backward = new Label(parent, SWT.NONE);
start.addListener(SWT.MouseDown, event -> { backward.setText("<");
this.entityTable.selectPage(1); backward.setLayoutData(rowData);
}); backward.setAlignment(SWT.CENTER);
} else { backward.setData(RWT.CUSTOM_VARIANT, CustomVariant.LIST_NAVIGATION.key);
start.setVisible(false); if (visible) {
} backward.addListener(SWT.MouseDown, event -> this.entityTable.selectPage(pageNumber - 1));
} else {
final Label backward = new Label(parent, SWT.NONE); backward.setVisible(false);
backward.setText("<"); }
backward.setLayoutData(rowData);
backward.setAlignment(SWT.CENTER); }
backward.setData(RWT.CUSTOM_VARIANT, CustomVariant.LIST_NAVIGATION.key);
if (visible) { }
backward.addListener(SWT.MouseDown, event -> {
this.entityTable.selectPage(pageNumber - 1);
});
} else {
backward.setVisible(false);
}
}
}

View file

@ -1,204 +1,202 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.widget; package ch.ethz.seb.sebserver.gui.widget;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.PipedInputStream; import java.io.PipedInputStream;
import java.io.PipedOutputStream; import java.io.PipedOutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.eclipse.rap.fileupload.FileDetails; import org.eclipse.rap.fileupload.FileDetails;
import org.eclipse.rap.fileupload.FileUploadHandler; import org.eclipse.rap.fileupload.FileUploadHandler;
import org.eclipse.rap.fileupload.FileUploadReceiver; import org.eclipse.rap.fileupload.FileUploadReceiver;
import org.eclipse.rap.rwt.widgets.FileUpload; import org.eclipse.rap.rwt.widgets.FileUpload;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Label;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; 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.LocTextKey;
public class FileUploadSelection extends Composite { public class FileUploadSelection extends Composite {
private static final Logger log = LoggerFactory.getLogger(FileUploadSelection.class); private static final Logger log = LoggerFactory.getLogger(FileUploadSelection.class);
private static final long serialVersionUID = 5800153475027387363L; private static final long serialVersionUID = 5800153475027387363L;
private static final LocTextKey PLEASE_SELECT_TEXT = private static final LocTextKey PLEASE_SELECT_TEXT =
new LocTextKey("sebserver.overall.upload"); new LocTextKey("sebserver.overall.upload");
private final I18nSupport i18nSupport; private final I18nSupport i18nSupport;
private final List<String> supportedFileExtensions = new ArrayList<>(); private final List<String> supportedFileExtensions = new ArrayList<>();
private final boolean readonly; private final boolean readonly;
private final FileUpload fileUpload; private final FileUpload fileUpload;
private final Label fileName; private final Label fileName;
private Consumer<String> errorHandler; private Consumer<String> errorHandler;
private InputStream inputStream; private InputStream inputStream;
private final FileUploadHandler uploadHandler; private final FileUploadHandler uploadHandler;
private final InputReceiver inputReceiver; private final InputReceiver inputReceiver;
public FileUploadSelection( public FileUploadSelection(
final Composite parent, final Composite parent,
final I18nSupport i18nSupport, final I18nSupport i18nSupport,
final boolean readonly) { final boolean readonly) {
super(parent, SWT.NONE); super(parent, SWT.NONE);
final GridLayout gridLayout = new GridLayout(2, false); final GridLayout gridLayout = new GridLayout(2, false);
gridLayout.horizontalSpacing = 0; gridLayout.horizontalSpacing = 0;
gridLayout.marginHeight = 0; gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0; gridLayout.marginWidth = 0;
gridLayout.verticalSpacing = 0; gridLayout.verticalSpacing = 0;
super.setLayout(gridLayout); super.setLayout(gridLayout);
this.i18nSupport = i18nSupport; this.i18nSupport = i18nSupport;
this.readonly = readonly; this.readonly = readonly;
if (readonly) { if (readonly) {
this.fileName = new Label(this, SWT.NONE); this.fileName = new Label(this, SWT.NONE);
this.fileName.setText(i18nSupport.getText(PLEASE_SELECT_TEXT)); this.fileName.setText(i18nSupport.getText(PLEASE_SELECT_TEXT));
this.fileName.setLayoutData(new GridData()); this.fileName.setLayoutData(new GridData());
this.fileUpload = null; this.fileUpload = null;
this.uploadHandler = null; this.uploadHandler = null;
this.inputReceiver = null; this.inputReceiver = null;
} else { } else {
this.fileUpload = new FileUpload(this, SWT.NONE); this.fileUpload = new FileUpload(this, SWT.NONE);
this.fileUpload.setImage(WidgetFactory.ImageIcon.IMPORT.getImage(parent.getDisplay())); this.fileUpload.setImage(WidgetFactory.ImageIcon.IMPORT.getImage(parent.getDisplay()));
this.fileUpload.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false)); this.fileUpload.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
this.fileUpload.setToolTipText(Utils.formatLineBreaks(this.i18nSupport.getText(PLEASE_SELECT_TEXT))); this.fileUpload.setToolTipText(Utils.formatLineBreaks(this.i18nSupport.getText(PLEASE_SELECT_TEXT)));
this.inputReceiver = new InputReceiver(); this.inputReceiver = new InputReceiver();
this.uploadHandler = new FileUploadHandler(this.inputReceiver); this.uploadHandler = new FileUploadHandler(this.inputReceiver);
this.fileName = new Label(this, SWT.NONE); this.fileName = new Label(this, SWT.NONE);
this.fileName.setText(i18nSupport.getText(PLEASE_SELECT_TEXT)); this.fileName.setText(i18nSupport.getText(PLEASE_SELECT_TEXT));
this.fileName.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); this.fileName.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
this.fileUpload.addListener(SWT.Selection, event -> { this.fileUpload.addListener(SWT.Selection, event -> {
final String fileName = FileUploadSelection.this.fileUpload.getFileName(); final String fileName = FileUploadSelection.this.fileUpload.getFileName();
if (fileName == null || !fileSupported(fileName)) { if (fileName == null || !fileSupported(fileName)) {
if (FileUploadSelection.this.errorHandler != null) { if (FileUploadSelection.this.errorHandler != null) {
final String text = i18nSupport.getText(new LocTextKey( final String text = i18nSupport.getText(new LocTextKey(
"sebserver.overall.upload.unsupported.file", "sebserver.overall.upload.unsupported.file",
this.supportedFileExtensions.toString()), this.supportedFileExtensions.toString()),
"Unsupported image file type selected"); "Unsupported image file type selected");
FileUploadSelection.this.errorHandler.accept(text); FileUploadSelection.this.errorHandler.accept(text);
} }
return; return;
} }
FileUploadSelection.this.fileUpload.submit(this.uploadHandler.getUploadUrl()); FileUploadSelection.this.fileUpload.submit(this.uploadHandler.getUploadUrl());
FileUploadSelection.this.fileName.setText(fileName); FileUploadSelection.this.fileName.setText(fileName);
FileUploadSelection.this.errorHandler.accept(null); FileUploadSelection.this.errorHandler.accept(null);
}); });
} }
} }
public void close() { public void close() {
if (this.inputReceiver != null) { if (this.inputReceiver != null) {
this.inputReceiver.close(); this.inputReceiver.close();
} }
} }
@Override @Override
public void dispose() { public void dispose() {
if (this.uploadHandler != null) { if (this.uploadHandler != null) {
this.uploadHandler.dispose(); this.uploadHandler.dispose();
} }
super.dispose(); super.dispose();
} }
public String getFileName() { public String getFileName() {
if (this.fileName != null) { if (this.fileName != null) {
return this.fileName.getText(); return this.fileName.getText();
} }
return Constants.EMPTY_NOTE; return Constants.EMPTY_NOTE;
} }
public void setFileName(final String fileName) { public void setFileName(final String fileName) {
if (this.fileName != null && fileName != null) { if (this.fileName != null && fileName != null) {
this.fileName.setText(fileName); this.fileName.setText(fileName);
} }
} }
public InputStream getInputStream() { public InputStream getInputStream() {
return this.inputStream; return this.inputStream;
} }
@Override @Override
public void update() { public void update() {
if (this.inputStream != null) { if (this.inputStream != null) {
this.fileName.setText(this.i18nSupport.getText(PLEASE_SELECT_TEXT)); this.fileName.setText(this.i18nSupport.getText(PLEASE_SELECT_TEXT));
} }
if (!this.readonly) { if (!this.readonly) {
this.fileUpload.setToolTipText(Utils.formatLineBreaks(this.i18nSupport.getText(PLEASE_SELECT_TEXT))); this.fileUpload.setToolTipText(Utils.formatLineBreaks(this.i18nSupport.getText(PLEASE_SELECT_TEXT)));
} }
} }
public FileUploadSelection setErrorHandler(final Consumer<String> errorHandler) { public FileUploadSelection setErrorHandler(final Consumer<String> errorHandler) {
this.errorHandler = errorHandler; this.errorHandler = errorHandler;
return this; return this;
} }
public FileUploadSelection withSupportFor(final String fileExtension) { public FileUploadSelection withSupportFor(final String fileExtension) {
this.supportedFileExtensions.add(fileExtension); this.supportedFileExtensions.add(fileExtension);
return this; return this;
} }
private boolean fileSupported(final String fileName) { private boolean fileSupported(final String fileName) {
return this.supportedFileExtensions return this.supportedFileExtensions
.stream() .stream()
.filter(fileType -> fileName.toUpperCase(Locale.ROOT) .anyMatch(fileType -> fileName.toUpperCase(Locale.ROOT)
.endsWith(fileType.toUpperCase(Locale.ROOT))) .endsWith(fileType.toUpperCase(Locale.ROOT)));
.findFirst() }
.isPresent();
} private final class InputReceiver extends FileUploadReceiver {
private PipedInputStream pIn = null;
private final class InputReceiver extends FileUploadReceiver { private PipedOutputStream pOut = null;
private PipedInputStream pIn = null;
private PipedOutputStream pOut = null; @Override
public void receive(final InputStream stream, final FileDetails details) throws IOException {
@Override if (this.pIn != null || this.pOut != null) {
public void receive(final InputStream stream, final FileDetails details) throws IOException { throw new IllegalStateException("InputReceiver already in use");
if (this.pIn != null || this.pOut != null) { }
throw new IllegalStateException("InputReceiver already in use");
} this.pIn = new PipedInputStream();
this.pOut = new PipedOutputStream(this.pIn);
this.pIn = new PipedInputStream();
this.pOut = new PipedOutputStream(this.pIn); FileUploadSelection.this.inputStream = this.pIn;
FileUploadSelection.this.inputStream = this.pIn; try {
IOUtils.copyLarge(stream, this.pOut);
try { } catch (final Exception e) {
IOUtils.copyLarge(stream, this.pOut); log.warn("IO error: {}", e.getMessage());
} catch (final Exception e) { } finally {
log.warn("IO error: {}", e.getMessage()); close();
} finally { }
close(); }
}
} void close() {
IOUtils.closeQuietly(this.pOut);
void close() { }
IOUtils.closeQuietly(this.pOut); }
}
} }
}

View file

@ -1,422 +1,421 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.widget; package ch.ethz.seb.sebserver.gui.widget;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Text;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon;
public class GridTable extends Composite { public class GridTable extends Composite {
private static final long serialVersionUID = 8515260041931976458L; private static final long serialVersionUID = 8515260041931976458L;
private static final Logger log = LoggerFactory.getLogger(GridTable.class); private static final Logger log = LoggerFactory.getLogger(GridTable.class);
public static final Set<AttributeType> SUPPORTED_TYPES = EnumSet.of( public static final Set<AttributeType> SUPPORTED_TYPES = EnumSet.of(
AttributeType.CHECKBOX, AttributeType.CHECKBOX,
AttributeType.TEXT_FIELD); AttributeType.TEXT_FIELD);
private static final int ACTION_COLUMN_WIDTH = 20; private static final int ACTION_COLUMN_WIDTH = 20;
private final WidgetFactory widgetFactory; private final WidgetFactory widgetFactory;
private final List<Column> columns; private final List<Column> columns;
private final Label addAction; private final Label addAction;
private final List<Row> rows; private final List<Row> rows;
private final String locTextKeyPrefix; private final String locTextKeyPrefix;
private Listener listener; private Listener listener;
public GridTable( public GridTable(
final Composite parent, final Composite parent,
final List<ColumnDef> columnDefs, final List<ColumnDef> columnDefs,
final String locTextKeyPrefix, final String locTextKeyPrefix,
final WidgetFactory widgetFactory) { final WidgetFactory widgetFactory) {
super(parent, SWT.NONE); super(parent, SWT.NONE);
this.widgetFactory = widgetFactory; this.widgetFactory = widgetFactory;
this.locTextKeyPrefix = locTextKeyPrefix; this.locTextKeyPrefix = locTextKeyPrefix;
final GridLayout gridLayout = new GridLayout(columnDefs.size() + 1, false); final GridLayout gridLayout = new GridLayout(columnDefs.size() + 1, false);
gridLayout.verticalSpacing = 1; gridLayout.verticalSpacing = 1;
gridLayout.marginLeft = 0; gridLayout.marginLeft = 0;
gridLayout.marginHeight = 5; gridLayout.marginHeight = 5;
gridLayout.marginWidth = 0; gridLayout.marginWidth = 0;
gridLayout.horizontalSpacing = 0; gridLayout.horizontalSpacing = 0;
this.setLayout(gridLayout); this.setLayout(gridLayout);
this.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); this.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
this.columns = new ArrayList<>(); this.columns = new ArrayList<>();
for (final ColumnDef columnDef : columnDefs) { for (final ColumnDef columnDef : columnDefs) {
final Label label = widgetFactory.labelLocalized( final Label label = widgetFactory.labelLocalized(
this, this,
new LocTextKey(locTextKeyPrefix + columnDef.name)); new LocTextKey(locTextKeyPrefix + columnDef.name));
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true); final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
label.setLayoutData(gridData); label.setLayoutData(gridData);
this.columns.add(new Column(columnDef, gridData)); this.columns.add(new Column(columnDef, gridData));
} }
this.addAction = widgetFactory.imageButton( this.addAction = widgetFactory.imageButton(
ImageIcon.ADD_BOX, ImageIcon.ADD_BOX,
this, this,
new LocTextKey(locTextKeyPrefix + "addAction"), new LocTextKey(locTextKeyPrefix + "addAction"),
this::addRow); this::addRow);
final GridData gridData = new GridData(SWT.CENTER, SWT.FILL, true, true); final GridData gridData = new GridData(SWT.CENTER, SWT.FILL, true, true);
gridData.widthHint = ACTION_COLUMN_WIDTH; gridData.widthHint = ACTION_COLUMN_WIDTH;
this.addAction.setLayoutData(gridData); this.addAction.setLayoutData(gridData);
this.rows = new ArrayList<>(); this.rows = new ArrayList<>();
this.addListener(SWT.Resize, this::adaptColumnWidth); this.addListener(SWT.Resize, this::adaptColumnWidth);
} }
public void setListener(final Listener listener) { public void setListener(final Listener listener) {
this.listener = listener; this.listener = listener;
} }
void addRow(final Event event) { void addRow(final Event event) {
final List<ControlAdapter> row = new ArrayList<>(); final List<ControlAdapter> row = new ArrayList<>();
for (final Column column : this.columns) { for (final Column column : this.columns) {
row.add(createCell(column, column.columnDef.defaultValue)); row.add(createCell(column, column.columnDef.defaultValue));
} }
this.rows.add(new Row(row)); this.rows.add(new Row(row));
this.adaptLayout(); this.adaptLayout();
if (this.listener != null) { if (this.listener != null) {
this.listener.handleEvent(new Event()); this.listener.handleEvent(new Event());
} }
} }
public void adaptLayout() { public void adaptLayout() {
this.getParent().getParent().layout(true, true); this.getParent().getParent().layout(true, true);
PageService.updateScrolledComposite(this); PageService.updateScrolledComposite(this);
} }
void addRow(final String values) { void addRow(final String values) {
if (StringUtils.isBlank(values)) { if (StringUtils.isBlank(values)) {
return; return;
} }
final Map<String, String> nameValueMap = new HashMap<>(); final Map<String, String> nameValueMap = new HashMap<>();
for (final String valueMap : StringUtils.split(values, Constants.EMBEDDED_LIST_SEPARATOR)) { for (final String valueMap : StringUtils.split(values, Constants.EMBEDDED_LIST_SEPARATOR)) {
final String[] nameValue = StringUtils.split(valueMap, Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR); final String[] nameValue = StringUtils.split(valueMap, Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR);
if (nameValue.length > 1) { if (nameValue.length > 1) {
nameValueMap.put(nameValue[0], nameValue[1]); nameValueMap.put(nameValue[0], nameValue[1]);
} else { } else {
nameValueMap.put(nameValue[0], null); nameValueMap.put(nameValue[0], null);
} }
} }
final List<ControlAdapter> row = new ArrayList<>(); final List<ControlAdapter> row = new ArrayList<>();
for (final Column column : this.columns) { for (final Column column : this.columns) {
row.add(createCell(column, nameValueMap.get(column.columnDef.name))); row.add(createCell(column, nameValueMap.get(column.columnDef.name)));
} }
this.rows.add(new Row(row)); this.rows.add(new Row(row));
} }
void deleteRow(final Row row) { void deleteRow(final Row row) {
if (this.rows.remove(row)) { if (this.rows.remove(row)) {
row.dispose(); row.dispose();
} }
this.adaptLayout(); this.adaptLayout();
} }
public String getValue() { public String getValue() {
return StringUtils.join( return StringUtils.join(
this.rows this.rows
.stream() .stream()
.map(row -> row.getValue()) .map(Row::getValue)
.collect(Collectors.toList()), .collect(Collectors.toList()),
Constants.LIST_SEPARATOR); Constants.LIST_SEPARATOR);
} }
public void setValue(final String value) { public void setValue(final String value) {
clearTable(); clearTable();
if (StringUtils.isBlank(value)) { if (StringUtils.isBlank(value)) {
return; return;
} }
for (final String val : StringUtils.split(value, Constants.LIST_SEPARATOR)) { for (final String val : StringUtils.split(value, Constants.LIST_SEPARATOR)) {
addRow(val); addRow(val);
} }
this.adaptLayout(); this.adaptLayout();
} }
private ControlAdapter createCell(final Column column, final String value) { private ControlAdapter createCell(final Column column, final String value) {
switch (column.columnDef.type) { switch (column.columnDef.type) {
case CHECKBOX: { case CHECKBOX: {
final CheckBox checkBox = new CheckBox(this, column.columnDef, this.listener); final CheckBox checkBox = new CheckBox(this, column.columnDef, this.listener);
checkBox.setValue(value); checkBox.setValue(value);
return checkBox; return checkBox;
} }
case TEXT_FIELD: { case TEXT_FIELD: {
final TextField textField = new TextField(this, column.columnDef, this.listener); final TextField textField = new TextField(this, column.columnDef, this.listener);
textField.setValue(value); textField.setValue(value);
return textField; return textField;
} }
default: { default: {
return new Dummy(this, column.columnDef); return new Dummy(this, column.columnDef);
} }
} }
} }
private void clearTable() { private void clearTable() {
for (final Row row : this.rows) { for (final Row row : this.rows) {
row.dispose(); row.dispose();
} }
this.rows.clear(); this.rows.clear();
} }
private void adaptColumnWidth(final Event event) { private void adaptColumnWidth(final Event event) {
try { try {
final int currentTableWidth = super.getClientArea().width - ACTION_COLUMN_WIDTH; final int currentTableWidth = super.getClientArea().width - ACTION_COLUMN_WIDTH;
final int widthUnit = currentTableWidth / this.columns final int widthUnit = currentTableWidth / this.columns
.stream() .stream()
.reduce(0, .reduce(0,
(i, c2) -> i + c2.columnDef.widthFactor, (i, c2) -> i + c2.columnDef.widthFactor,
(i1, i2) -> i1 + i2); Integer::sum);
this.columns this.columns
.stream() .forEach(c -> c.header.widthHint = c.columnDef.widthFactor * widthUnit);
.forEach(c -> c.header.widthHint = c.columnDef.widthFactor * widthUnit);
super.layout(true, true);
super.layout(true, true); } catch (final Exception e) {
} catch (final Exception e) { log.warn("Failed to adaptColumnWidth: ", e);
log.warn("Failed to adaptColumnWidth: ", e); }
} }
}
final class Row {
final class Row { final List<ControlAdapter> cells;
final List<ControlAdapter> cells; final Label removeAction;
final Label removeAction;
protected Row(final List<ControlAdapter> cells) {
protected Row(final List<ControlAdapter> cells) { this.cells = cells;
this.cells = cells; this.removeAction = GridTable.this.widgetFactory.imageButton(
this.removeAction = GridTable.this.widgetFactory.imageButton( ImageIcon.REMOVE_BOX,
ImageIcon.REMOVE_BOX, GridTable.this,
GridTable.this, new LocTextKey(GridTable.this.locTextKeyPrefix + "removeAction"),
new LocTextKey(GridTable.this.locTextKeyPrefix + "removeAction"), event -> deleteRow(this));
event -> deleteRow(this)); final GridData gridData = new GridData(SWT.CENTER, SWT.CENTER, true, true);
final GridData gridData = new GridData(SWT.CENTER, SWT.CENTER, true, true); gridData.widthHint = ACTION_COLUMN_WIDTH;
gridData.widthHint = ACTION_COLUMN_WIDTH; this.removeAction.setLayoutData(gridData);
this.removeAction.setLayoutData(gridData); }
}
void dispose() {
void dispose() { for (final ControlAdapter cell : this.cells) {
for (final ControlAdapter cell : this.cells) { cell.dispose();
cell.dispose(); }
} this.removeAction.dispose();
this.removeAction.dispose(); }
}
String getValue() {
String getValue() { return StringUtils.join(
return StringUtils.join( this.cells
this.cells .stream()
.stream() .map(cell -> cell.columnDef().name + Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR
.map(cell -> cell.columnDef().name + Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR + cell.getValue())
+ cell.getValue()) .collect(Collectors.toList()),
.collect(Collectors.toList()), Constants.EMBEDDED_LIST_SEPARATOR);
Constants.EMBEDDED_LIST_SEPARATOR); }
} }
}
public static final class ColumnDef {
public static final class ColumnDef { final int widthFactor;
final int widthFactor; final String name;
final String name; final AttributeType type;
final AttributeType type; final String defaultValue;
final String defaultValue;
protected ColumnDef(
protected ColumnDef( final int widthFactor,
final int widthFactor, final String name,
final String name, final AttributeType type,
final AttributeType type, final String defaultValue) {
final String defaultValue) {
this.widthFactor = widthFactor;
this.widthFactor = widthFactor; this.name = name;
this.name = name; this.type = type;
this.type = type; this.defaultValue = defaultValue;
this.defaultValue = defaultValue; }
}
public static ColumnDef fromString(
public static final ColumnDef fromString( final String string,
final String string, final Map<String, String> defaultValueMap) {
final Map<String, String> defaultValueMap) {
if (StringUtils.isBlank(string)) {
if (StringUtils.isBlank(string)) { return null;
return null; }
}
final String[] split = StringUtils.split(string, Constants.COMPLEX_VALUE_SEPARATOR);
final String[] split = StringUtils.split(string, Constants.COMPLEX_VALUE_SEPARATOR); final AttributeType attributeType = AttributeType.valueOf(split[2]);
final AttributeType attributeType = AttributeType.valueOf(split[2]); if (!SUPPORTED_TYPES.contains(attributeType)) {
if (!SUPPORTED_TYPES.contains(attributeType)) { throw new UnsupportedOperationException(
throw new UnsupportedOperationException( "The AttributeType : " + attributeType + " is not supported yet");
"The AttributeType : " + attributeType + " is not supported yet"); }
}
return new ColumnDef(
return new ColumnDef( Integer.parseInt(split[0]),
Integer.parseInt(split[0]), split[1],
split[1], attributeType,
attributeType, defaultValueMap.get(split[1]));
defaultValueMap.get(split[1])); }
} }
}
private static class Column {
private static class Column { final ColumnDef columnDef;
final ColumnDef columnDef; final GridData header;
final GridData header;
protected Column(final ColumnDef columnDef, final GridData header) {
protected Column(final ColumnDef columnDef, final GridData header) { this.columnDef = columnDef;
this.columnDef = columnDef; this.header = header;
this.header = header; }
} }
}
interface ControlAdapter {
interface ControlAdapter { String getValue();
String getValue();
void setValue(String value);
void setValue(String value);
void dispose();
void dispose();
ColumnDef columnDef();
ColumnDef columnDef(); }
}
private static class Dummy implements ControlAdapter {
private static class Dummy implements ControlAdapter {
private final Label label;
private final Label label; private final ColumnDef columnDef;
private final ColumnDef columnDef;
Dummy(final Composite parent, final ColumnDef columnDef) {
Dummy(final Composite parent, final ColumnDef columnDef) { this.label = new Label(parent, SWT.NONE);
this.label = new Label(parent, SWT.NONE); this.label.setText("unsupported");
this.label.setText("unsupported"); this.columnDef = columnDef;
this.columnDef = columnDef; }
}
@Override
@Override public String getValue() {
public String getValue() { return null;
return null; }
}
@Override
@Override public void setValue(final String value) {
public void setValue(final String value) { }
}
@Override
@Override public void dispose() {
public void dispose() { this.label.dispose();
this.label.dispose(); }
}
@Override
@Override public ColumnDef columnDef() {
public ColumnDef columnDef() { return this.columnDef;
return this.columnDef; }
} }
}
private static class CheckBox implements ControlAdapter {
private static class CheckBox implements ControlAdapter {
private final Button checkboxButton;
private final Button checkboxButton; private final ColumnDef columnDef;
private final ColumnDef columnDef;
CheckBox(final Composite parent, final ColumnDef columnDef, final Listener listener) {
CheckBox(final Composite parent, final ColumnDef columnDef, final Listener listener) { this.checkboxButton = new Button(parent, SWT.CHECK);
this.checkboxButton = new Button(parent, SWT.CHECK); this.checkboxButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
this.checkboxButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); this.columnDef = columnDef;
this.columnDef = columnDef; if (listener != null) {
if (listener != null) { this.checkboxButton.addListener(SWT.Selection, listener);
this.checkboxButton.addListener(SWT.Selection, listener); }
} }
}
@Override
@Override public String getValue() {
public String getValue() { return this.checkboxButton.getSelection()
return this.checkboxButton.getSelection() ? Constants.TRUE_STRING
? Constants.TRUE_STRING : Constants.FALSE_STRING;
: Constants.FALSE_STRING; }
}
@Override
@Override public void setValue(final String value) {
public void setValue(final String value) { this.checkboxButton.setSelection(BooleanUtils.toBoolean(value));
this.checkboxButton.setSelection(BooleanUtils.toBoolean(value)); }
}
@Override
@Override public void dispose() {
public void dispose() { this.checkboxButton.dispose();
this.checkboxButton.dispose(); }
}
@Override
@Override public ColumnDef columnDef() {
public ColumnDef columnDef() { return this.columnDef;
return this.columnDef; }
} }
}
private static class TextField implements ControlAdapter {
private static class TextField implements ControlAdapter {
private final Text _textField;
private final Text _textField; private final ColumnDef columnDef;
private final ColumnDef columnDef;
TextField(final Composite parent, final ColumnDef columnDef, final Listener listener) {
TextField(final Composite parent, final ColumnDef columnDef, final Listener listener) { this._textField = new Text(parent, SWT.LEFT | SWT.BORDER);
this._textField = new Text(parent, SWT.LEFT | SWT.BORDER); this._textField.setData(RWT.CUSTOM_VARIANT, CustomVariant.CONFIG_INPUT_READONLY.key);
this._textField.setData(RWT.CUSTOM_VARIANT, CustomVariant.CONFIG_INPUT_READONLY.key); this._textField.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
this._textField.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); this.columnDef = columnDef;
this.columnDef = columnDef; this._textField.addListener(SWT.FocusOut, listener);
this._textField.addListener(SWT.FocusOut, listener); this._textField.addListener(SWT.Traverse, listener);
this._textField.addListener(SWT.Traverse, listener); }
}
@Override
@Override public String getValue() {
public String getValue() { return this._textField.getText();
return this._textField.getText(); }
}
@Override
@Override public void setValue(final String value) {
public void setValue(final String value) { this._textField.setText((value != null) ? value : "");
this._textField.setText((value != null) ? value : ""); }
}
@Override
@Override public void dispose() {
public void dispose() { this._textField.dispose();
this._textField.dispose(); }
}
@Override
@Override public ColumnDef columnDef() {
public ColumnDef columnDef() { return this.columnDef;
return this.columnDef; }
} }
}
}
}

View file

@ -1,222 +1,213 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.widget; package ch.ethz.seb.sebserver.gui.widget;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.apache.commons.codec.binary.Base64InputStream; import org.apache.commons.codec.binary.Base64InputStream;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.fileupload.FileDetails; import org.eclipse.rap.fileupload.FileDetails;
import org.eclipse.rap.fileupload.FileUploadHandler; import org.eclipse.rap.fileupload.FileUploadHandler;
import org.eclipse.rap.fileupload.FileUploadReceiver; import org.eclipse.rap.fileupload.FileUploadReceiver;
import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.widgets.FileUpload; import org.eclipse.rap.rwt.widgets.FileUpload;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext; import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
import ch.ethz.seb.sebserver.gui.service.push.ServerPushService; import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
public final class ImageUploadSelection extends Composite { public final class ImageUploadSelection extends Composite {
private static final long serialVersionUID = 368264811155804533L; private static final long serialVersionUID = 368264811155804533L;
private static final Logger log = LoggerFactory.getLogger(ImageUploadSelection.class); private static final Logger log = LoggerFactory.getLogger(ImageUploadSelection.class);
public static final Set<String> SUPPORTED_IMAGE_FILES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( public static final Set<String> SUPPORTED_IMAGE_FILES = Set.of(".png", ".jpg", ".jpeg");
".png",
".jpg", private final ServerPushService serverPushService;
".jpeg")));
private final Composite imageCanvas;
private final ServerPushService serverPushService; private final FileUpload fileUpload;
private final int maxWidth;
private final Composite imageCanvas; private final int maxHeight;
private final FileUpload fileUpload;
private final int maxWidth; private Consumer<String> errorHandler;
private final int maxHeight; private String imageBase64 = null;
private boolean loadNewImage = false;
private Consumer<String> errorHandler; private boolean imageLoaded = false;
private String imageBase64 = null;
private boolean loadNewImage = false; ImageUploadSelection(
private boolean imageLoaded = false; final Composite parent,
final ServerPushService serverPushService,
ImageUploadSelection( final I18nSupport i18nSupport,
final Composite parent, final boolean readonly,
final ServerPushService serverPushService, final int maxWidth,
final I18nSupport i18nSupport, final int maxHeight) {
final boolean readonly,
final int maxWidth, super(parent, SWT.NONE);
final int maxHeight) { final GridLayout gridLayout = new GridLayout(1, false);
gridLayout.horizontalSpacing = 0;
super(parent, SWT.NONE); gridLayout.marginHeight = 0;
final GridLayout gridLayout = new GridLayout(1, false); gridLayout.marginWidth = 0;
gridLayout.horizontalSpacing = 0; gridLayout.marginLeft = 0;
gridLayout.marginHeight = 0; gridLayout.verticalSpacing = 0;
gridLayout.marginWidth = 0; super.setLayout(gridLayout);
gridLayout.marginLeft = 0;
gridLayout.verticalSpacing = 0; this.serverPushService = serverPushService;
super.setLayout(gridLayout); this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
this.serverPushService = serverPushService;
this.maxWidth = maxWidth; if (!readonly) {
this.maxHeight = maxHeight; this.fileUpload = new FileUpload(this, SWT.NONE);
this.fileUpload.setImage(WidgetFactory.ImageIcon.IMPORT.getImage(parent.getDisplay()));
if (!readonly) { final GridData gridData = new GridData(SWT.LEFT, SWT.TOP, false, false);
this.fileUpload = new FileUpload(this, SWT.NONE); gridData.horizontalIndent = 0;
this.fileUpload.setImage(WidgetFactory.ImageIcon.IMPORT.getImage(parent.getDisplay())); this.fileUpload.setLayoutData(gridData);
final GridData gridData = new GridData(SWT.LEFT, SWT.TOP, false, false);
gridData.horizontalIndent = 0; final FileUploadHandler uploadHandler = new FileUploadHandler(new ImageReceiver());
this.fileUpload.setLayoutData(gridData); this.fileUpload.addListener(SWT.Selection, event -> {
final String fileName = ImageUploadSelection.this.fileUpload.getFileName();
final FileUploadHandler uploadHandler = new FileUploadHandler(new ImageReceiver()); if (fileName == null || !fileSupported(fileName)) {
this.fileUpload.addListener(SWT.Selection, event -> { if (ImageUploadSelection.this.errorHandler != null) {
final String fileName = ImageUploadSelection.this.fileUpload.getFileName(); final String text = i18nSupport.getText(
if (fileName == null || !fileSupported(fileName)) { "sebserver.institution.form.logoImage.unsupportedFileType",
if (ImageUploadSelection.this.errorHandler != null) { "Unsupported image file type selected");
final String text = i18nSupport.getText( ImageUploadSelection.this.errorHandler.accept(text);
"sebserver.institution.form.logoImage.unsupportedFileType", }
"Unsupported image file type selected");
ImageUploadSelection.this.errorHandler.accept(text); log.warn("Unsupported image file selected: {}", fileName);
}
return;
log.warn("Unsupported image file selected: {}", fileName); }
ImageUploadSelection.this.loadNewImage = true;
return; ImageUploadSelection.this.imageLoaded = false;
} ImageUploadSelection.this.fileUpload.submit(uploadHandler.getUploadUrl());
ImageUploadSelection.this.loadNewImage = true;
ImageUploadSelection.this.imageLoaded = false; ImageUploadSelection.this.serverPushService.runServerPush(
ImageUploadSelection.this.fileUpload.submit(uploadHandler.getUploadUrl()); new ServerPushContext(ImageUploadSelection.this, ImageUploadSelection::uploadInProgress),
200,
ImageUploadSelection.this.serverPushService.runServerPush( ImageUploadSelection::update);
new ServerPushContext(ImageUploadSelection.this, ImageUploadSelection::uploadInProgress), });
200, } else {
ImageUploadSelection::update); this.fileUpload = null;
}); }
} else {
this.fileUpload = null; this.imageCanvas = new Composite(this, SWT.NONE);
} final GridData canvas = new GridData(SWT.FILL, SWT.FILL, true, true);
this.imageCanvas.setLayoutData(canvas);
this.imageCanvas = new Composite(this, SWT.NONE); }
final GridData canvas = new GridData(SWT.FILL, SWT.FILL, true, true);
this.imageCanvas.setLayoutData(canvas); public void setErrorHandler(final Consumer<String> errorHandler) {
} this.errorHandler = errorHandler;
}
public void setErrorHandler(final Consumer<String> errorHandler) {
this.errorHandler = errorHandler; public void setSelectionText(final String text) {
} if (this.fileUpload != null) {
this.fileUpload.setToolTipText(Utils.formatLineBreaks(text));
public void setSelectionText(final String text) { }
if (this.fileUpload != null) { }
this.fileUpload.setToolTipText(Utils.formatLineBreaks(text));
} public String getImageBase64() {
} return this.imageBase64;
}
public String getImageBase64() {
return this.imageBase64; public void setImageBase64(final String imageBase64) {
} if (StringUtils.isBlank(imageBase64)) {
return;
public void setImageBase64(final String imageBase64) { }
if (StringUtils.isBlank(imageBase64)) {
return; this.imageBase64 = imageBase64;
} final Base64InputStream input = new Base64InputStream(
new ByteArrayInputStream(imageBase64.getBytes(StandardCharsets.UTF_8)), false);
this.imageBase64 = imageBase64;
final Base64InputStream input = new Base64InputStream( setImage(this, input);
new ByteArrayInputStream(imageBase64.getBytes(StandardCharsets.UTF_8)), false); }
setImage(this, input); private static boolean uploadInProgress(final ServerPushContext context) {
} final ImageUploadSelection imageUpload = (ImageUploadSelection) context.getAnchor();
return imageUpload.loadNewImage && !imageUpload.imageLoaded;
private static final boolean uploadInProgress(final ServerPushContext context) { }
final ImageUploadSelection imageUpload = (ImageUploadSelection) context.getAnchor();
return imageUpload.loadNewImage && !imageUpload.imageLoaded; private static void update(final ServerPushContext context) {
} final ImageUploadSelection imageUpload = (ImageUploadSelection) context.getAnchor();
if (imageUpload.imageBase64 != null
private static final void update(final ServerPushContext context) { && imageUpload.loadNewImage
final ImageUploadSelection imageUpload = (ImageUploadSelection) context.getAnchor(); && imageUpload.imageLoaded) {
if (imageUpload.imageBase64 != null
&& imageUpload.loadNewImage final Base64InputStream input = new Base64InputStream(
&& imageUpload.imageLoaded) { new ByteArrayInputStream(
imageUpload.imageBase64.getBytes(StandardCharsets.UTF_8)),
final Base64InputStream input = new Base64InputStream( false);
new ByteArrayInputStream(
imageUpload.imageBase64.getBytes(StandardCharsets.UTF_8)), setImage(imageUpload, input);
false); context.layout();
imageUpload.layout();
setImage(imageUpload, input); imageUpload.loadNewImage = false;
context.layout(); imageUpload.errorHandler.accept(null);
imageUpload.layout(); }
imageUpload.loadNewImage = false; }
imageUpload.errorHandler.accept(null);
} private static void setImage(final ImageUploadSelection imageUpload, final Base64InputStream input) {
} imageUpload.imageCanvas.setData(RWT.CUSTOM_VARIANT, "bgLogoNoImage");
private static void setImage(final ImageUploadSelection imageUpload, final Base64InputStream input) { final Image image = new Image(imageUpload.imageCanvas.getDisplay(), input);
imageUpload.imageCanvas.setData(RWT.CUSTOM_VARIANT, "bgLogoNoImage"); final Rectangle imageBounds = image.getBounds();
final int width = Math.min(imageBounds.width, imageUpload.maxWidth);
final Image image = new Image(imageUpload.imageCanvas.getDisplay(), input); final int height = Math.min(imageBounds.height, imageUpload.maxHeight);
final Rectangle imageBounds = image.getBounds(); final ImageData imageData = image.getImageData().scaledTo(width, height);
final int width = (imageBounds.width > imageUpload.maxWidth) imageUpload.imageCanvas.setBackgroundImage(new Image(imageUpload.imageCanvas.getDisplay(), imageData));
? imageUpload.maxWidth }
: imageBounds.width;
final int height = (imageBounds.height > imageUpload.maxHeight) private static boolean fileSupported(final String fileName) {
? imageUpload.maxHeight return SUPPORTED_IMAGE_FILES
: imageBounds.height; .stream()
final ImageData imageData = image.getImageData().scaledTo(width, height); .anyMatch(fileType -> fileName.toUpperCase(Locale.ROOT)
imageUpload.imageCanvas.setBackgroundImage(new Image(imageUpload.imageCanvas.getDisplay(), imageData)); .endsWith(fileType.toUpperCase(Locale.ROOT)));
} }
private static boolean fileSupported(final String fileName) { private final class ImageReceiver extends FileUploadReceiver {
return SUPPORTED_IMAGE_FILES @Override
.stream() public void receive(final InputStream stream, final FileDetails details) throws IOException {
.filter(fileType -> fileName.toUpperCase(Locale.ROOT)
.endsWith(fileType.toUpperCase(Locale.ROOT))) try {
.findFirst() final String contentType = details.getContentType();
.isPresent(); if (contentType != null && contentType.startsWith("image")) {
} ImageUploadSelection.this.imageBase64 = Base64.getEncoder()
.encodeToString(IOUtils.toByteArray(stream));
private final class ImageReceiver extends FileUploadReceiver { }
@Override } catch (final Exception e) {
public void receive(final InputStream stream, final FileDetails details) throws IOException { log.error("Error while trying to upload image", e);
} finally {
try { ImageUploadSelection.this.imageLoaded = true;
final String contentType = details.getContentType(); stream.close();
if (contentType != null && contentType.startsWith("image")) { }
ImageUploadSelection.this.imageBase64 = Base64.getEncoder() }
.encodeToString(IOUtils.toByteArray(stream)); }
}
} catch (final Exception e) { }
log.error("Error while trying to upload image", e);
} finally {
ImageUploadSelection.this.imageLoaded = true;
stream.close();
}
}
}
}

View file

@ -8,12 +8,11 @@
package ch.ethz.seb.sebserver.gui.widget; package ch.ethz.seb.sebserver.gui.widget;
import java.util.Arrays; import ch.ethz.seb.sebserver.gbl.Constants;
import java.util.LinkedHashMap; import ch.ethz.seb.sebserver.gbl.util.Tuple;
import java.util.List; import ch.ethz.seb.sebserver.gbl.util.Tuple3;
import java.util.Map; import ch.ethz.seb.sebserver.gbl.util.Utils;
import java.util.stream.Collectors; import ch.ethz.seb.sebserver.gui.service.page.PageService;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
@ -22,11 +21,10 @@ import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Listener;
import ch.ethz.seb.sebserver.gbl.Constants; import java.util.Arrays;
import ch.ethz.seb.sebserver.gbl.util.Tuple; import java.util.LinkedHashMap;
import ch.ethz.seb.sebserver.gbl.util.Tuple3; import java.util.List;
import ch.ethz.seb.sebserver.gbl.util.Utils; import java.util.Map;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
public final class MultiSelectionCheckbox extends Composite implements Selection { public final class MultiSelectionCheckbox extends Composite implements Selection {
@ -121,7 +119,7 @@ public final class MultiSelectionCheckbox extends Composite implements Selection
.stream() .stream()
.filter(Button::getSelection) .filter(Button::getSelection)
.map(button -> (String) button.getData(OPTION_VALUE)) .map(button -> (String) button.getData(OPTION_VALUE))
.collect(Collectors.toList()).toArray()); .toArray());
} }
@Override @Override

View file

@ -1,252 +1,230 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.widget; package ch.ethz.seb.sebserver.gui.widget;
import java.util.ArrayList; import ch.ethz.seb.sebserver.gbl.Constants;
import java.util.Arrays; import ch.ethz.seb.sebserver.gbl.util.Tuple;
import java.util.Collection; import ch.ethz.seb.sebserver.gui.service.page.PageService;
import java.util.List; import org.apache.commons.lang3.StringUtils;
import java.util.Optional; import org.eclipse.rap.rwt.widgets.DropDown;
import java.util.stream.Collectors; import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.apache.commons.lang3.StringUtils; import org.eclipse.swt.layout.GridLayout;
import org.eclipse.rap.rwt.widgets.DropDown; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Event; import org.slf4j.Logger;
import org.eclipse.swt.widgets.Label; import org.slf4j.LoggerFactory;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text; import java.util.ArrayList;
import org.slf4j.Logger; import java.util.Arrays;
import org.slf4j.LoggerFactory; import java.util.List;
import java.util.Optional;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.util.Tuple; public final class MultiSelectionCombo extends Composite implements Selection {
import ch.ethz.seb.sebserver.gui.service.page.PageService;
private static final Logger log = LoggerFactory.getLogger(MultiSelectionCombo.class);
public final class MultiSelectionCombo extends Composite implements Selection { private static final long serialVersionUID = -7787134114963647332L;
private static final Logger log = LoggerFactory.getLogger(MultiSelectionCombo.class); private final WidgetFactory widgetFactory;
private static final long serialVersionUID = -7787134114963647332L;
private final List<Control> selectionControls = new ArrayList<>();
private final WidgetFactory widgetFactory;
private final List<Tuple<String>> valueMapping = new ArrayList<>();
private final List<Control> selectionControls = new ArrayList<>(); private final List<Tuple<String>> availableValues = new ArrayList<>();
private final List<Tuple<String>> selectedValues = new ArrayList<>();
private final List<Tuple<String>> valueMapping = new ArrayList<>();
private final List<Tuple<String>> availableValues = new ArrayList<>(); private final DropDown dropDown;
private final List<Tuple<String>> selectedValues = new ArrayList<>(); private final Text textInput;
private final GridData textCell;
private final DropDown dropDown; private final Composite updateAnchor;
private final Text textInput;
private final GridData textCell; private Listener listener = null;
private final Composite updateAnchor;
MultiSelectionCombo(
private Listener listener = null; final Composite parent,
final WidgetFactory widgetFactory,
MultiSelectionCombo( final String locTextPrefix,
final Composite parent, final Composite updateAnchor) {
final WidgetFactory widgetFactory,
final String locTextPrefix, super(parent, SWT.NONE);
final Composite updateAnchor) { this.widgetFactory = widgetFactory;
super(parent, SWT.NONE); final GridLayout gridLayout = new GridLayout();
this.widgetFactory = widgetFactory; gridLayout.verticalSpacing = 1;
gridLayout.marginLeft = 0;
final GridLayout gridLayout = new GridLayout(); gridLayout.marginHeight = 0;
gridLayout.verticalSpacing = 1; gridLayout.marginWidth = 0;
gridLayout.marginLeft = 0; gridLayout.horizontalSpacing = 0;
gridLayout.marginHeight = 0; setLayout(gridLayout);
gridLayout.marginWidth = 0;
gridLayout.horizontalSpacing = 0; this.addListener(SWT.Resize, this::adaptColumnWidth);
setLayout(gridLayout); this.textInput = widgetFactory.textInput(this);
this.textCell = new GridData(SWT.LEFT, SWT.CENTER, true, true);
this.addListener(SWT.Resize, this::adaptColumnWidth); this.textInput.setLayoutData(this.textCell);
this.textInput = widgetFactory.textInput(this); this.dropDown = new DropDown(this.textInput, SWT.NONE);
this.textCell = new GridData(SWT.LEFT, SWT.CENTER, true, true); this.textInput.addListener(SWT.FocusIn, event -> openDropDown());
this.textInput.setLayoutData(this.textCell); this.textInput.addListener(SWT.Modify, event -> openDropDown());
this.dropDown = new DropDown(this.textInput, SWT.NONE); this.dropDown.addListener(SWT.Selection, event -> {
this.textInput.addListener(SWT.FocusIn, event -> { final int selectionIndex = this.dropDown.getSelectionIndex();
openDropDown(); if (selectionIndex >= 0) {
}); final String selectedItem = this.dropDown.getItems()[selectionIndex];
this.textInput.addListener(SWT.Modify, event -> { addSelection(itemForName(selectedItem));
openDropDown(); }
}); });
this.dropDown.addListener(SWT.Selection, event -> {
final int selectionIndex = this.dropDown.getSelectionIndex(); this.updateAnchor = updateAnchor;
if (selectionIndex >= 0) { }
final String selectedItem = this.dropDown.getItems()[selectionIndex];
addSelection(itemForName(selectedItem)); private void openDropDown() {
} final String text = this.textInput.getText();
}); if (text == null) {
this.dropDown.setVisible(false);
this.updateAnchor = updateAnchor; return;
} }
this.dropDown.setItems(this.availableValues
private void openDropDown() { .stream()
final String text = this.textInput.getText(); .filter(it -> it._2 != null && it._2.startsWith(text))
if (text == null) { .map(t -> t._2).toArray(String[]::new));
this.dropDown.setVisible(false); this.dropDown.setSelectionIndex(0);
return; this.dropDown.setVisible(true);
} }
final Collection<String> items = this.availableValues
.stream() @Override
.filter(it -> it._2 != null && it._2.startsWith(text)) public Type type() {
.map(t -> t._2) return Type.MULTI_COMBO;
.collect(Collectors.toList()); }
this.dropDown.setItems(items.toArray(new String[items.size()]));
this.dropDown.setSelectionIndex(0); @Override
this.dropDown.setVisible(true); public void setSelectionListener(final Listener listener) {
} this.listener = listener;
}
@Override
public Type type() { @Override
return Type.MULTI_COMBO; public void applyNewMapping(final List<Tuple<String>> mapping) {
} this.valueMapping.clear();
this.valueMapping.addAll(mapping);
@Override this.clear();
public void setSelectionListener(final Listener listener) { }
this.listener = listener;
} @Override
public void select(final String keys) {
@Override clear();
public void applyNewMapping(final List<Tuple<String>> mapping) { if (StringUtils.isBlank(keys)) {
this.valueMapping.clear(); return;
this.valueMapping.addAll(mapping); }
this.clear();
} Arrays.stream(StringUtils.split(keys, Constants.LIST_SEPARATOR))
.map(this::itemForId)
@Override .forEach(this::addSelection);
public void select(final String keys) { }
clear();
if (StringUtils.isBlank(keys)) { @Override
return; public String getSelectionValue() {
} if (this.selectedValues.isEmpty()) {
return null;
Arrays.asList(StringUtils.split(keys, Constants.LIST_SEPARATOR)) }
.stream() return this.selectedValues
.map(this::itemForId) .stream()
.forEach(this::addSelection); .map(t -> t._1)
} .reduce("", (s1, s2) -> {
if (!StringUtils.isBlank(s1)) {
@Override return s1.concat(Constants.LIST_SEPARATOR).concat(s2);
public String getSelectionValue() { } else {
if (this.selectedValues.isEmpty()) { return s1.concat(s2);
return null; }
} });
return this.selectedValues }
.stream()
.map(t -> t._1) @Override
.reduce("", (s1, s2) -> { public void clear() {
if (!StringUtils.isBlank(s1)) { this.selectedValues.clear();
return s1.concat(Constants.LIST_SEPARATOR).concat(s2); this.selectionControls
} else { .forEach(Control::dispose);
return s1.concat(s2); this.selectionControls.clear();
} this.availableValues.clear();
}); this.availableValues.addAll(this.valueMapping);
} }
@Override private void addSelection(final Tuple<String> item) {
public void clear() { if (item == null) {
this.selectedValues.clear(); return;
this.selectionControls }
.stream()
.forEach(Control::dispose); this.selectedValues.add(item);
this.selectionControls.clear(); final Label label = this.widgetFactory.label(this, item._2);
this.availableValues.clear(); label.setData(OPTION_VALUE, item._2);
this.availableValues.addAll(this.valueMapping); final GridData textCell = new GridData(SWT.LEFT, SWT.CENTER, true, true);
} label.setLayoutData(textCell);
label.addListener(SWT.MouseDoubleClick, this::removeComboSelection);
private void addSelection(final Tuple<String> item) { this.selectionControls.add(label);
if (item == null) {
return; this.availableValues.remove(item);
} PageService.updateScrolledComposite(this);
this.updateAnchor.layout(true, true);
this.selectedValues.add(item);
final Label label = this.widgetFactory.label(this, item._2); }
label.setData(OPTION_VALUE, item._2);
final GridData textCell = new GridData(SWT.LEFT, SWT.CENTER, true, true); private void removeComboSelection(final Event event) {
label.setLayoutData(textCell); if (event.widget == null) {
label.addListener(SWT.MouseDoubleClick, event -> { return;
removeComboSelection(event); }
});
this.selectionControls.add(label); final String selectionKey = (String) event.widget.getData(OPTION_VALUE);
final Optional<Control> findFirst = this.selectionControls.stream()
this.availableValues.remove(item); .filter(t -> selectionKey.equals(t.getData(OPTION_VALUE)))
PageService.updateScrolledComposite(this); .findFirst();
this.updateAnchor.layout(true, true); if (!findFirst.isPresent()) {
return;
} }
private void removeComboSelection(final Event event) { final Control control = findFirst.get();
if (event.widget == null) { final int indexOf = this.selectionControls.indexOf(control);
return; this.selectionControls.remove(control);
} control.dispose();
final String selectionKey = (String) event.widget.getData(OPTION_VALUE); final Tuple<String> value = this.selectedValues.remove(indexOf);
final Optional<Control> findFirst = this.selectionControls.stream() this.availableValues.add(value);
.filter(t -> selectionKey.equals(t.getData(OPTION_VALUE)))
.findFirst(); PageService.updateScrolledComposite(this);
if (!findFirst.isPresent()) { this.updateAnchor.layout(true, true);
return; if (this.listener != null) {
} this.listener.handleEvent(event);
}
final Control control = findFirst.get(); }
final int indexOf = this.selectionControls.indexOf(control);
this.selectionControls.remove(control); private void adaptColumnWidth(final Event event) {
control.dispose(); try {
this.textCell.widthHint = this.getClientArea().width;
final Tuple<String> value = this.selectedValues.remove(indexOf); this.layout();
this.availableValues.add(value); } catch (final Exception e) {
log.warn("Failed to adaptColumnWidth: ", e);
PageService.updateScrolledComposite(this); }
this.updateAnchor.layout(true, true); }
if (this.listener != null) {
this.listener.handleEvent(event); private Tuple<String> itemForName(final String name) {
} final Optional<Tuple<String>> findFirst = this.availableValues
} .stream()
.filter(it -> it._2 != null && it._2.equals(name))
private void adaptColumnWidth(final Event event) { .findFirst();
try { return findFirst.orElse(null);
final int currentTableWidth = this.getClientArea().width; }
this.textCell.widthHint = currentTableWidth;
this.layout(); private Tuple<String> itemForId(final String id) {
} catch (final Exception e) { final Optional<Tuple<String>> findFirst = this.availableValues
log.warn("Failed to adaptColumnWidth: ", e); .stream()
} .filter(it -> it._1 != null && it._1.equals(id))
} .findFirst();
return findFirst.orElse(null);
private Tuple<String> itemForName(final String name) { }
final Optional<Tuple<String>> findFirst = this.availableValues
.stream() }
.filter(it -> it._2 != null && it._2.equals(name))
.findFirst();
if (findFirst.isPresent()) {
return findFirst.get();
}
return null;
}
private Tuple<String> itemForId(final String id) {
final Optional<Tuple<String>> findFirst = this.availableValues
.stream()
.filter(it -> it._1 != null && it._1.equals(id))
.findFirst();
if (findFirst.isPresent()) {
return findFirst.get();
}
return null;
}
}

View file

@ -1,122 +1,121 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.widget; package ch.ethz.seb.sebserver.gui.widget;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Listener;
import ch.ethz.seb.sebserver.gbl.util.Tuple; import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.PageService;
public final class RadioSelection extends Composite implements Selection { public final class RadioSelection extends Composite implements Selection {
private static final long serialVersionUID = 7937242481193100852L; private static final long serialVersionUID = 7937242481193100852L;
private Listener listener = null; private Listener listener = null;
private final Map<String, Button> radioButtons; private final Map<String, Button> radioButtons;
RadioSelection(final Composite parent) { RadioSelection(final Composite parent) {
super(parent, SWT.NONE); super(parent, SWT.NONE);
final GridLayout gridLayout = new GridLayout(1, true); final GridLayout gridLayout = new GridLayout(1, true);
gridLayout.verticalSpacing = 1; gridLayout.verticalSpacing = 1;
gridLayout.marginLeft = 0; gridLayout.marginLeft = 0;
gridLayout.marginHeight = 0; gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0; gridLayout.marginWidth = 0;
setLayout(gridLayout); setLayout(gridLayout);
this.radioButtons = new LinkedHashMap<>(); this.radioButtons = new LinkedHashMap<>();
} }
@Override @Override
public Type type() { public Type type() {
return Type.RADIO; return Type.RADIO;
} }
@Override @Override
public void applyNewMapping(final List<Tuple<String>> mapping) { public void applyNewMapping(final List<Tuple<String>> mapping) {
final String selectionValue = getSelectionValue(); final String selectionValue = getSelectionValue();
this.radioButtons.clear(); this.radioButtons.clear();
PageService.clearComposite(this); PageService.clearComposite(this);
for (final Tuple<String> tuple : mapping) { for (final Tuple<String> tuple : mapping) {
final Button button = new Button(this, SWT.RADIO); final Button button = new Button(this, SWT.RADIO);
button.setText(tuple._2); button.setText(tuple._2);
final GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, true); final GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, true);
button.setLayoutData(gridData); button.setLayoutData(gridData);
button.setData(OPTION_VALUE, tuple._1); button.setData(OPTION_VALUE, tuple._1);
button.addListener(SWT.Selection, event -> { button.addListener(SWT.Selection, event -> {
if (this.listener != null) { if (this.listener != null) {
this.listener.handleEvent(event); this.listener.handleEvent(event);
} }
}); });
this.radioButtons.put(tuple._1, button); this.radioButtons.put(tuple._1, button);
} }
if (StringUtils.isNotBlank(selectionValue)) { if (StringUtils.isNotBlank(selectionValue)) {
select(selectionValue); select(selectionValue);
} }
} }
@Override @Override
public void applyToolTipsForItems(final List<Tuple<String>> mapping) { public void applyToolTipsForItems(final List<Tuple<String>> mapping) {
mapping mapping
.stream() .stream()
.filter(tuple -> StringUtils.isNotBlank(tuple._2)) .filter(tuple -> StringUtils.isNotBlank(tuple._2))
.forEach(tuple -> { .forEach(tuple -> {
final Button button = this.radioButtons.get(tuple._1); final Button button = this.radioButtons.get(tuple._1);
if (button != null) { if (button != null) {
button.setToolTipText(Utils.formatLineBreaks(tuple._2)); button.setToolTipText(Utils.formatLineBreaks(tuple._2));
} }
}); });
} }
@Override @Override
public void select(final String key) { public void select(final String key) {
clear(); clear();
if (StringUtils.isNotBlank(key) && this.radioButtons.containsKey(key)) { if (StringUtils.isNotBlank(key) && this.radioButtons.containsKey(key)) {
this.radioButtons.get(key).setSelection(true); this.radioButtons.get(key).setSelection(true);
} }
} }
@Override @Override
public String getSelectionValue() { public String getSelectionValue() {
return this.radioButtons return this.radioButtons
.values() .values()
.stream() .stream()
.filter(button -> button.getSelection()) .filter(Button::getSelection)
.findFirst() .findFirst()
.map(button -> (String) button.getData(OPTION_VALUE)) .map(button -> (String) button.getData(OPTION_VALUE))
.orElse(null); .orElse(null);
} }
@Override @Override
public void clear() { public void clear() {
this.radioButtons this.radioButtons
.values() .values()
.stream() .forEach(button -> button.setSelection(false));
.forEach(button -> button.setSelection(false));
}
}
@Override
@Override public void setSelectionListener(final Listener listener) {
public void setSelectionListener(final Listener listener) { this.listener = listener;
this.listener = listener; }
}
}
}

View file

@ -1,65 +1,65 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.widget; package ch.ethz.seb.sebserver.gui.widget;
import java.util.List; import java.util.List;
import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Listener;
import ch.ethz.seb.sebserver.gbl.util.Tuple; import ch.ethz.seb.sebserver.gbl.util.Tuple;
public interface Selection { public interface Selection {
static final String OPTION_VALUE = "OPTION_VALUE"; String OPTION_VALUE = "OPTION_VALUE";
enum Type { enum Type {
SINGLE, SINGLE,
SINGLE_COMBO, SINGLE_COMBO,
RADIO, RADIO,
MULTI, MULTI,
MULTI_COMBO, MULTI_COMBO,
MULTI_CHECKBOX, MULTI_CHECKBOX,
COLOR, COLOR,
} }
Type type(); Type type();
void applyNewMapping(final List<Tuple<String>> mapping); void applyNewMapping(final List<Tuple<String>> mapping);
void select(final String keys); void select(final String keys);
String getSelectionValue(); String getSelectionValue();
default String getSelectionReadableValue() { default String getSelectionReadableValue() {
return getSelectionValue(); return getSelectionValue();
} }
void clear(); void clear();
void setVisible(boolean visible); void setVisible(boolean visible);
void setSelectionListener(Listener listener); void setSelectionListener(Listener listener);
void setToolTipText(String tooltipText); void setToolTipText(String tooltipText);
default void applyToolTipsForItems(final List<Tuple<String>> mapping) { default void applyToolTipsForItems(final List<Tuple<String>> mapping) {
throw new UnsupportedOperationException("Must be implemented for this specific Selection"); throw new UnsupportedOperationException("Must be implemented for this specific Selection");
} }
default Control adaptToControl() { default Control adaptToControl() {
return (Control) this; return (Control) this;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
default <T extends Selection> T getTypeInstance() { default <T extends Selection> T getTypeInstance() {
return (T) this; return (T) this;
} }
} }

View file

@ -87,7 +87,7 @@ public final class SingleSelection extends Combo implements Selection {
@Override @Override
public void clear() { public void clear() {
super.clearSelection(); super.clearSelection();
super.setItems(this.valueMapping.toArray(new String[this.valueMapping.size()])); super.setItems(this.valueMapping.toArray(new String[0]));
} }
@Override @Override

View file

@ -175,7 +175,7 @@ INSERT INTO configuration_attribute VALUES
(304, 'enablePrivateClipboard', 'CHECKBOX', null, null, null, null, 'true'), (304, 'enablePrivateClipboard', 'CHECKBOX', null, null, null, null, 'true'),
(305, 'enableLogging', 'CHECKBOX', null, null, null, null, 'false'), (305, 'enableLogging', 'CHECKBOX', null, null, null, null, 'false'),
(306, 'logDirectoryWin', 'TEXT_FIELD', null, null, null, null, ''), (306, 'logDirectoryWin', 'TEXT_FIELD', null, null, null, null, ''),
(307, 'logDirectoryOSX', 'TEXT_FIELD', null, null, null, null, 'NSTemporaryDirectory'), (307, 'logDirectoryOSX', 'TEXT_FIELD', null, null, null, null, 'F'),
(308, 'minMacOSVersion', 'SINGLE_SELECTION', null, '0,1,2,3,4,5,6,7', null, null, '0'), (308, 'minMacOSVersion', 'SINGLE_SELECTION', null, '0,1,2,3,4,5,6,7', null, null, '0'),
(309, 'enableAppSwitcherCheck', 'CHECKBOX', null, null, null, null, 'true'), (309, 'enableAppSwitcherCheck', 'CHECKBOX', null, null, null, null, 'true'),
(310, 'forceAppFolderInstall', 'CHECKBOX', null, null, null, null, 'true'), (310, 'forceAppFolderInstall', 'CHECKBOX', null, null, null, null, 'true'),
@ -263,7 +263,7 @@ INSERT INTO configuration_attribute VALUES
(927, 'mobileStatusBarAppearanceExtended', 'SINGLE_SELECTION', null, '0,1,2,3,4', null, null, '1'), (927, 'mobileStatusBarAppearanceExtended', 'SINGLE_SELECTION', null, '0,1,2,3,4', null, null, '1'),
(928, 'newBrowserWindowShowURL', 'SINGLE_SELECTION', null, '0,1,2,3', null, null, '1'), (928, 'newBrowserWindowShowURL', 'SINGLE_SELECTION', null, '0,1,2,3', null, null, '1'),
(929, 'pinEmbeddedCertificates', 'CHECKBOX', null, null, null, null, 'false'), (929, 'pinEmbeddedCertificates', 'CHECKBOX', null, null, null, null, 'false'),
(930, 'sendBrowserExamKey', 'CHECKBOX', null, null, null, null, 'false'), (930, 'sendBrowserExamKey', 'CHECKBOX', null, null, null, null, 'true'),
(931, 'showNavigationButtons', 'CHECKBOX', null, null, null, null, 'false'), (931, 'showNavigationButtons', 'CHECKBOX', null, null, null, null, 'false'),
(932, 'showScanQRCodeButton', 'CHECKBOX', null, null, null, null, 'false'), (932, 'showScanQRCodeButton', 'CHECKBOX', null, null, null, null, 'false'),
(933, 'startResource', 'TEXT_FIELD', null, null, null, null, ''), (933, 'startResource', 'TEXT_FIELD', null, null, null, null, ''),

File diff suppressed because it is too large Load diff