Merge branch 'master' of https://github.com/SafeExamBrowser/seb-server.git
This commit is contained in:
		
						commit
						cccbc48805
					
				
					 61 changed files with 7744 additions and 7773 deletions
				
			
		|  | @ -1,41 +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; | package ch.ethz.seb.sebserver.gui; | ||||||
| 
 | 
 | ||||||
| import org.springframework.boot.context.event.ApplicationReadyEvent; | import org.springframework.boot.context.event.ApplicationReadyEvent; | ||||||
| import org.springframework.context.ApplicationListener; | import org.springframework.context.ApplicationListener; | ||||||
| import org.springframework.stereotype.Component; | import org.springframework.stereotype.Component; | ||||||
| 
 | 
 | ||||||
| import ch.ethz.seb.sebserver.SEBServerInit; | import ch.ethz.seb.sebserver.SEBServerInit; | ||||||
| import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; | import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; | ||||||
| 
 | 
 | ||||||
| @Component | @Component | ||||||
| @GuiProfile | @GuiProfile | ||||||
| public class GuiInit implements ApplicationListener<ApplicationReadyEvent> { | public class GuiInit implements ApplicationListener<ApplicationReadyEvent> { | ||||||
| 
 | 
 | ||||||
|     private final SEBServerInit sebServerInit; |     private final SEBServerInit sebServerInit; | ||||||
| 
 | 
 | ||||||
|     protected GuiInit(final SEBServerInit sebServerInit) { |     protected GuiInit(final SEBServerInit sebServerInit) { | ||||||
|         this.sebServerInit = sebServerInit; |         this.sebServerInit = sebServerInit; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void onApplicationEvent(final ApplicationReadyEvent event) { |     public void onApplicationEvent(final ApplicationReadyEvent event) { | ||||||
| 
 | 
 | ||||||
|         this.sebServerInit.init(); |         this.sebServerInit.init(); | ||||||
| 
 | 
 | ||||||
|         SEBServerInit.INIT_LOGGER.info("---->"); |         SEBServerInit.INIT_LOGGER.info("---->"); | ||||||
|         SEBServerInit.INIT_LOGGER.info("---->  **** GUI Service starting up... ****"); |         SEBServerInit.INIT_LOGGER.info("---->  **** GUI Service starting up... ****"); | ||||||
| 
 | 
 | ||||||
|         SEBServerInit.INIT_LOGGER.info("---->"); |         SEBServerInit.INIT_LOGGER.info("---->"); | ||||||
|         SEBServerInit.INIT_LOGGER.info("---->  GUI Service sucessfully successfully started up!"); |         SEBServerInit.INIT_LOGGER.info("---->  GUI Service successfully successfully started up!"); | ||||||
|         SEBServerInit.INIT_LOGGER.info("---->"); |         SEBServerInit.INIT_LOGGER.info("---->"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -73,12 +73,12 @@ public final class InstitutionalAuthenticationEntryPoint implements Authenticati | ||||||
|         this.webserviceURIService = webserviceURIService; |         this.webserviceURIService = webserviceURIService; | ||||||
|         this.clientHttpRequestFactoryService = clientHttpRequestFactoryService; |         this.clientHttpRequestFactoryService = clientHttpRequestFactoryService; | ||||||
| 
 | 
 | ||||||
|         String _defaultLogo = null; |         String _defaultLogo; | ||||||
|         if (!Constants.NO_NAME.equals(defaultLogoFileName)) { |         if (!Constants.NO_NAME.equals(defaultLogoFileName)) { | ||||||
|             try { |             try { | ||||||
| 
 | 
 | ||||||
|                 final String extension = ImageUploadSelection.SUPPORTED_IMAGE_FILES.stream() |                 final String extension = ImageUploadSelection.SUPPORTED_IMAGE_FILES.stream() | ||||||
|                         .filter(ext -> defaultLogoFileName.endsWith(ext)) |                         .filter(defaultLogoFileName::endsWith) | ||||||
|                         .findFirst() |                         .findFirst() | ||||||
|                         .orElse(null); |                         .orElse(null); | ||||||
| 
 | 
 | ||||||
|  | @ -141,7 +141,7 @@ public final class InstitutionalAuthenticationEntryPoint implements Authenticati | ||||||
|                                 : null); |                                 : null); | ||||||
| 
 | 
 | ||||||
|                 if (log.isDebugEnabled()) { |                 if (log.isDebugEnabled()) { | ||||||
|                     log.debug("Known and active gui entrypoint requested:", institutions); |                     log.debug("Known and active gui entrypoint requested: {}", institutions); | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 final String logoImageBase64 = requestLogoImage(institutionalEndpoint); |                 final String logoImageBase64 = requestLogoImage(institutionalEndpoint); | ||||||
|  | @ -184,9 +184,7 @@ public final class InstitutionalAuthenticationEntryPoint implements Authenticati | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             return requestURI.substring( |             return requestURI.substring(requestURI.lastIndexOf(Constants.SLASH) + 1); | ||||||
|                     requestURI.lastIndexOf(Constants.SLASH) + 1, |  | ||||||
|                     requestURI.length()); |  | ||||||
|         } catch (final Exception e) { |         } catch (final Exception e) { | ||||||
|             log.error("Failed to extract institutional URL suffix: {}", e.getMessage()); |             log.error("Failed to extract institutional URL suffix: {}", e.getMessage()); | ||||||
|             return null; |             return null; | ||||||
|  | @ -231,12 +229,12 @@ public final class InstitutionalAuthenticationEntryPoint implements Authenticati | ||||||
|             if (exchange.getStatusCodeValue() == HttpStatus.OK.value()) { |             if (exchange.getStatusCodeValue() == HttpStatus.OK.value()) { | ||||||
|                 return exchange.getBody(); |                 return exchange.getBody(); | ||||||
|             } else { |             } else { | ||||||
|                 log.warn("Failed to verify insitution from requested entrypoint url: {}, response: {}", |                 log.warn("Failed to verify institution from requested entrypoint url: {}, response: {}", | ||||||
|                         institutionalEndpoint, |                         institutionalEndpoint, | ||||||
|                         exchange); |                         exchange); | ||||||
|             } |             } | ||||||
|         } catch (final Exception e) { |         } catch (final Exception e) { | ||||||
|             log.warn("Failed to verify insitution from requested entrypoint url: {}", |             log.warn("Failed to verify institution from requested entrypoint url: {}", | ||||||
|                     institutionalEndpoint, |                     institutionalEndpoint, | ||||||
|                     e); |                     e); | ||||||
|         } |         } | ||||||
|  | @ -245,7 +243,7 @@ public final class InstitutionalAuthenticationEntryPoint implements Authenticati | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** TODO this seems not to work as expected. Different Theme is only possible in RAP on different |     /** TODO this seems not to work as expected. Different Theme is only possible in RAP on different | ||||||
|      * entry-points and since entry-points are statically defined within the RAPConficuration |      * entry-points and since entry-points are statically defined within the RAPConfiguration | ||||||
|      * there is no possibility to apply them dynamically within an institution so far. |      * there is no possibility to apply them dynamically within an institution so far. | ||||||
|      * |      * | ||||||
|      * @param institutionalEndpoint |      * @param institutionalEndpoint | ||||||
|  |  | ||||||
|  | @ -1,164 +1,164 @@ | ||||||
| /* | /* | ||||||
|  * 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; | package ch.ethz.seb.sebserver.gui; | ||||||
| 
 | 
 | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| 
 | 
 | ||||||
| import javax.servlet.ServletContext; | import javax.servlet.ServletContext; | ||||||
| import javax.servlet.http.HttpSession; | import javax.servlet.http.HttpSession; | ||||||
| 
 | 
 | ||||||
| import org.eclipse.rap.rwt.RWT; | import org.eclipse.rap.rwt.RWT; | ||||||
| import org.eclipse.rap.rwt.application.AbstractEntryPoint; | import org.eclipse.rap.rwt.application.AbstractEntryPoint; | ||||||
| import org.eclipse.rap.rwt.application.Application; | import org.eclipse.rap.rwt.application.Application; | ||||||
| import org.eclipse.rap.rwt.application.ApplicationConfiguration; | import org.eclipse.rap.rwt.application.ApplicationConfiguration; | ||||||
| import org.eclipse.rap.rwt.application.EntryPoint; | import org.eclipse.rap.rwt.application.EntryPoint; | ||||||
| import org.eclipse.rap.rwt.application.EntryPointFactory; | import org.eclipse.rap.rwt.application.EntryPointFactory; | ||||||
| import org.eclipse.rap.rwt.client.WebClient; | import org.eclipse.rap.rwt.client.WebClient; | ||||||
| import org.eclipse.rap.rwt.internal.theme.ThemeUtil; | import org.eclipse.rap.rwt.internal.theme.ThemeUtil; | ||||||
| import org.eclipse.rap.rwt.service.ServiceManager; | import org.eclipse.rap.rwt.service.ServiceManager; | ||||||
| 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 org.springframework.web.context.WebApplicationContext; | import org.springframework.web.context.WebApplicationContext; | ||||||
| import org.springframework.web.context.support.WebApplicationContextUtils; | import org.springframework.web.context.support.WebApplicationContextUtils; | ||||||
| 
 | 
 | ||||||
| import ch.ethz.seb.sebserver.gui.service.remote.download.DownloadService; | import ch.ethz.seb.sebserver.gui.service.remote.download.DownloadService; | ||||||
| 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.service.remote.webservice.auth.SEBServerAuthorizationContext; | import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.SEBServerAuthorizationContext; | ||||||
| 
 | 
 | ||||||
| public class RAPConfiguration implements ApplicationConfiguration { | public class RAPConfiguration implements ApplicationConfiguration { | ||||||
| 
 | 
 | ||||||
|     private static final String DEFAULT_THEME_NAME = "sebserver"; |     private static final String DEFAULT_THEME_NAME = "sebserver"; | ||||||
|     private static final Logger log = LoggerFactory.getLogger(RAPConfiguration.class); |     private static final Logger log = LoggerFactory.getLogger(RAPConfiguration.class); | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void configure(final Application application) { |     public void configure(final Application application) { | ||||||
|         try { |         try { | ||||||
| 
 | 
 | ||||||
|             // TODO get file path from properties |             // TODO get file path from properties | ||||||
|             //application.addStyleSheet(RWT.DEFAULT_THEME_ID, "static/css/sebserver.css"); |             //application.addStyleSheet(RWT.DEFAULT_THEME_ID, "static/css/sebserver.css"); | ||||||
|             application.addStyleSheet(DEFAULT_THEME_NAME, "resource/theme/default.css"); |             application.addStyleSheet(DEFAULT_THEME_NAME, "resource/theme/default.css"); | ||||||
|             application.addStyleSheet(DEFAULT_THEME_NAME, "static/css/sebserver.css"); |             application.addStyleSheet(DEFAULT_THEME_NAME, "static/css/sebserver.css"); | ||||||
|             application.addStyleSheet("sms", "resource/theme/default.css"); |             application.addStyleSheet("sms", "resource/theme/default.css"); | ||||||
|             application.addStyleSheet("sms", "static/css/sms.css"); |             application.addStyleSheet("sms", "static/css/sms.css"); | ||||||
| 
 | 
 | ||||||
|             final Map<String, String> properties = new HashMap<>(); |             final Map<String, String> properties = new HashMap<>(); | ||||||
|             properties.put(WebClient.PAGE_TITLE, "SEB Server"); |             properties.put(WebClient.PAGE_TITLE, "SEB Server"); | ||||||
|             properties.put(WebClient.BODY_HTML, "<big>Loading Application<big>"); |             properties.put(WebClient.BODY_HTML, "<big>Loading Application<big>"); | ||||||
|             properties.put(WebClient.THEME_ID, DEFAULT_THEME_NAME); |             properties.put(WebClient.THEME_ID, DEFAULT_THEME_NAME); | ||||||
|             //        properties.put(WebClient.FAVICON, "icons/favicon.png"); |             //        properties.put(WebClient.FAVICON, "icons/favicon.png"); | ||||||
|             application.addEntryPoint("/gui", new RAPSpringEntryPointFactory(), properties); |             application.addEntryPoint("/gui", new RAPSpringEntryPointFactory(), properties); | ||||||
| 
 | 
 | ||||||
|         } catch (final RuntimeException re) { |         } catch (final RuntimeException re) { | ||||||
|             throw re; |             throw re; | ||||||
|         } catch (final Exception e) { |         } catch (final Exception e) { | ||||||
|             log.error("Error during CSS parsing. Please check the custom CSS files for errors.", e); |             log.error("Error during CSS parsing. Please check the custom CSS files for errors.", e); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static interface EntryPointService { |     public interface EntryPointService { | ||||||
| 
 | 
 | ||||||
|         void loadLoginPage(final Composite parent); |         void loadLoginPage(final Composite parent); | ||||||
| 
 | 
 | ||||||
|         void loadMainPage(final Composite parent); |         void loadMainPage(final Composite parent); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static final class RAPSpringEntryPointFactory implements EntryPointFactory { |     public static final class RAPSpringEntryPointFactory implements EntryPointFactory { | ||||||
| 
 | 
 | ||||||
|         private boolean initialized = false; |         private boolean initialized = false; | ||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
|         public EntryPoint create() { |         public EntryPoint create() { | ||||||
| 
 | 
 | ||||||
|             return new AbstractEntryPoint() { |             return new AbstractEntryPoint() { | ||||||
| 
 | 
 | ||||||
|                 private static final long serialVersionUID = -1299125117752916270L; |                 private static final long serialVersionUID = -1299125117752916270L; | ||||||
| 
 | 
 | ||||||
|                 @Override |                 @Override | ||||||
|                 protected void createContents(final Composite parent) { |                 protected void createContents(final Composite parent) { | ||||||
|                     final HttpSession httpSession = RWT |                     final HttpSession httpSession = RWT | ||||||
|                             .getUISession(parent.getDisplay()) |                             .getUISession(parent.getDisplay()) | ||||||
|                             .getHttpSession(); |                             .getHttpSession(); | ||||||
| 
 | 
 | ||||||
|                     log.debug("Create new GUI entrypoint. HttpSession: " + httpSession); |                     log.debug("Create new GUI entrypoint. HttpSession: " + httpSession); | ||||||
|                     if (httpSession == null) { |                     if (httpSession == null) { | ||||||
|                         log.error("HttpSession not available from RWT.getUISession().getHttpSession()"); |                         log.error("HttpSession not available from RWT.getUISession().getHttpSession()"); | ||||||
|                         throw new IllegalStateException( |                         throw new IllegalStateException( | ||||||
|                                 "HttpSession not available from RWT.getUISession().getHttpSession()"); |                                 "HttpSession not available from RWT.getUISession().getHttpSession()"); | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     final Object themeId = httpSession.getAttribute("themeId"); |                     final Object themeId = httpSession.getAttribute("themeId"); | ||||||
|                     if (themeId != null) { |                     if (themeId != null) { | ||||||
|                         ThemeUtil.setCurrentThemeId(RWT.getUISession(parent.getDisplay()), String.valueOf(themeId)); |                         ThemeUtil.setCurrentThemeId(RWT.getUISession(parent.getDisplay()), String.valueOf(themeId)); | ||||||
|                         parent.redraw(); |                         parent.redraw(); | ||||||
|                         parent.layout(true); |                         parent.layout(true); | ||||||
|                         parent.redraw(); |                         parent.redraw(); | ||||||
| 
 | 
 | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     final WebApplicationContext webApplicationContext = getWebApplicationContext(httpSession); |                     final WebApplicationContext webApplicationContext = getWebApplicationContext(httpSession); | ||||||
|                     initSpringBasedRAPServices(webApplicationContext); |                     initSpringBasedRAPServices(webApplicationContext); | ||||||
| 
 | 
 | ||||||
|                     final EntryPointService entryPointService = webApplicationContext |                     final EntryPointService entryPointService = webApplicationContext | ||||||
|                             .getBean(EntryPointService.class); |                             .getBean(EntryPointService.class); | ||||||
| 
 | 
 | ||||||
|                     if (isAuthenticated(httpSession, webApplicationContext)) { |                     if (isAuthenticated(httpSession, webApplicationContext)) { | ||||||
|                         entryPointService.loadMainPage(parent); |                         entryPointService.loadMainPage(parent); | ||||||
|                     } else { |                     } else { | ||||||
|                         entryPointService.loadLoginPage(parent); |                         entryPointService.loadLoginPage(parent); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }; |             }; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private void initSpringBasedRAPServices(final WebApplicationContext webApplicationContext) { |         private void initSpringBasedRAPServices(final WebApplicationContext webApplicationContext) { | ||||||
|             if (!this.initialized) { |             if (!this.initialized) { | ||||||
|                 try { |                 try { | ||||||
|                     final ServiceManager manager = RWT.getServiceManager(); |                     final ServiceManager manager = RWT.getServiceManager(); | ||||||
|                     final DownloadService downloadService = webApplicationContext.getBean(DownloadService.class); |                     final DownloadService downloadService = webApplicationContext.getBean(DownloadService.class); | ||||||
|                     manager.registerServiceHandler(DownloadService.DOWNLOAD_SERVICE_NAME, downloadService); |                     manager.registerServiceHandler(DownloadService.DOWNLOAD_SERVICE_NAME, downloadService); | ||||||
|                     this.initialized = true; |                     this.initialized = true; | ||||||
|                 } catch (final IllegalArgumentException iae) { |                 } catch (final IllegalArgumentException iae) { | ||||||
|                     log.warn("Failed to register DownloadService on ServiceManager. Already registered: ", iae); |                     log.warn("Failed to register DownloadService on ServiceManager. Already registered: ", iae); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private boolean isAuthenticated( |         private boolean isAuthenticated( | ||||||
|                 final HttpSession httpSession, |                 final HttpSession httpSession, | ||||||
|                 final WebApplicationContext webApplicationContext) { |                 final WebApplicationContext webApplicationContext) { | ||||||
| 
 | 
 | ||||||
|             final AuthorizationContextHolder authorizationContextHolder = webApplicationContext |             final AuthorizationContextHolder authorizationContextHolder = webApplicationContext | ||||||
|                     .getBean(AuthorizationContextHolder.class); |                     .getBean(AuthorizationContextHolder.class); | ||||||
|             final SEBServerAuthorizationContext authorizationContext = authorizationContextHolder |             final SEBServerAuthorizationContext authorizationContext = authorizationContextHolder | ||||||
|                     .getAuthorizationContext(httpSession); |                     .getAuthorizationContext(httpSession); | ||||||
|             return authorizationContext.isValid() && authorizationContext.isLoggedIn(); |             return authorizationContext.isValid() && authorizationContext.isLoggedIn(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private WebApplicationContext getWebApplicationContext(final HttpSession httpSession) { |         private WebApplicationContext getWebApplicationContext(final HttpSession httpSession) { | ||||||
|             try { |             try { | ||||||
|                 final ServletContext servletContext = httpSession.getServletContext(); |                 final ServletContext servletContext = httpSession.getServletContext(); | ||||||
| 
 | 
 | ||||||
|                 log.debug("Initialize Spring-Context on Servlet-Context: " + servletContext); |                 log.debug("Initialize Spring-Context on Servlet-Context: " + servletContext); | ||||||
| 
 | 
 | ||||||
|                 return WebApplicationContextUtils |                 return WebApplicationContextUtils | ||||||
|                         .getRequiredWebApplicationContext(servletContext); |                         .getRequiredWebApplicationContext(servletContext); | ||||||
| 
 | 
 | ||||||
|             } catch (final RuntimeException e) { |             } catch (final RuntimeException e) { | ||||||
|                 log.error("Failed to initialize Spring-Context on HttpSession: " + httpSession); |                 log.error("Failed to initialize Spring-Context on HttpSession: " + httpSession); | ||||||
|                 throw e; |                 throw e; | ||||||
|             } catch (final Exception e) { |             } catch (final Exception e) { | ||||||
|                 log.error("Failed to initialize Spring-Context on HttpSession: " + httpSession); |                 log.error("Failed to initialize Spring-Context on HttpSession: " + httpSession); | ||||||
|                 throw new RuntimeException("Failed to initialize Spring-Context on HttpSession: " + httpSession); |                 throw new RuntimeException("Failed to initialize Spring-Context on HttpSession: " + httpSession); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|     }; |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,83 +1,81 @@ | ||||||
| /* | /* | ||||||
|  * 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; | package ch.ethz.seb.sebserver.gui; | ||||||
| 
 | 
 | ||||||
| import javax.servlet.ServletContext; | import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; | ||||||
| import javax.servlet.ServletContextListener; | import org.eclipse.rap.rwt.engine.RWTServlet; | ||||||
| import javax.servlet.ServletException; | import org.eclipse.rap.rwt.engine.RWTServletContextListener; | ||||||
| 
 | import org.slf4j.Logger; | ||||||
| import org.eclipse.rap.rwt.engine.RWTServlet; | import org.slf4j.LoggerFactory; | ||||||
| import org.eclipse.rap.rwt.engine.RWTServletContextListener; | import org.springframework.beans.factory.annotation.Value; | ||||||
| import org.slf4j.Logger; | import org.springframework.boot.web.servlet.ServletContextInitializer; | ||||||
| import org.slf4j.LoggerFactory; | import org.springframework.boot.web.servlet.ServletListenerRegistrationBean; | ||||||
| import org.springframework.beans.factory.annotation.Value; | import org.springframework.boot.web.servlet.ServletRegistrationBean; | ||||||
| import org.springframework.boot.web.servlet.ServletContextInitializer; | import org.springframework.context.MessageSource; | ||||||
| import org.springframework.boot.web.servlet.ServletListenerRegistrationBean; | import org.springframework.context.annotation.Bean; | ||||||
| import org.springframework.boot.web.servlet.ServletRegistrationBean; | import org.springframework.context.annotation.Configuration; | ||||||
| import org.springframework.context.MessageSource; | import org.springframework.context.support.ReloadableResourceBundleMessageSource; | ||||||
| import org.springframework.context.annotation.Bean; | 
 | ||||||
| import org.springframework.context.annotation.Configuration; | import javax.servlet.ServletContext; | ||||||
| import org.springframework.context.support.ReloadableResourceBundleMessageSource; | import javax.servlet.ServletContextListener; | ||||||
| 
 | 
 | ||||||
| import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; | @Configuration | ||||||
| 
 | @GuiProfile | ||||||
| @Configuration | public class RAPSpringConfig { | ||||||
| @GuiProfile | 
 | ||||||
| public class RAPSpringConfig { |     private static final Logger log = LoggerFactory.getLogger(RAPSpringConfig.class); | ||||||
| 
 | 
 | ||||||
|     private static final Logger log = LoggerFactory.getLogger(RAPSpringConfig.class); |     @Value("${sebserver.gui.entrypoint}") | ||||||
| 
 |     private String entrypoint; | ||||||
|     @Value("${sebserver.gui.entrypoint}") | 
 | ||||||
|     private String entrypoint; |     @Value("${sebserver.gui.external.messages:messages}") | ||||||
| 
 |     private String externalMessagesPath; | ||||||
|     @Value("${sebserver.gui.external.messages:messages}") | 
 | ||||||
|     private String externalMessagesPath; |     @Bean | ||||||
| 
 |     public ServletContextInitializer initializer() { | ||||||
|     @Bean |         return new RAPServletContextInitializer(); | ||||||
|     public ServletContextInitializer initializer() { |     } | ||||||
|         return new RAPServletContextInitializer(); | 
 | ||||||
|     } |     @Bean | ||||||
| 
 |     public ServletListenerRegistrationBean<ServletContextListener> listenerRegistrationBean() { | ||||||
|     @Bean |         final ServletListenerRegistrationBean<ServletContextListener> bean = | ||||||
|     public ServletListenerRegistrationBean<ServletContextListener> listenerRegistrationBean() { |                 new ServletListenerRegistrationBean<>(); | ||||||
|         final ServletListenerRegistrationBean<ServletContextListener> bean = |         bean.setListener(new RWTServletContextListener()); | ||||||
|                 new ServletListenerRegistrationBean<>(); |         return bean; | ||||||
|         bean.setListener(new RWTServletContextListener()); |     } | ||||||
|         return bean; | 
 | ||||||
|     } |     @Bean | ||||||
| 
 |     public ServletRegistrationBean<RWTServlet> servletRegistrationBean() { | ||||||
|     @Bean |         return new ServletRegistrationBean<>(new RWTServlet(), this.entrypoint + "/*"); | ||||||
|     public ServletRegistrationBean<RWTServlet> servletRegistrationBean() { |     } | ||||||
|         return new ServletRegistrationBean<>(new RWTServlet(), this.entrypoint + "/*"); | 
 | ||||||
|     } |     @Bean | ||||||
| 
 |     public MessageSource messageSource() { | ||||||
|     @Bean |         final ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource = | ||||||
|     public MessageSource messageSource() { |                 new ReloadableResourceBundleMessageSource(); | ||||||
|         final ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource = | 
 | ||||||
|                 new ReloadableResourceBundleMessageSource(); |         log.info(" +++ Register external messages resources from: {}", this.externalMessagesPath); | ||||||
| 
 | 
 | ||||||
|         log.info(" +++ Register external messages resources from: {}", this.externalMessagesPath); |         reloadableResourceBundleMessageSource.setBasenames( | ||||||
| 
 |                 this.externalMessagesPath, | ||||||
|         reloadableResourceBundleMessageSource.setBasenames( |                 "classpath:messages"); | ||||||
|                 this.externalMessagesPath, | 
 | ||||||
|                 "classpath:messages"); |         return reloadableResourceBundleMessageSource; | ||||||
| 
 |     } | ||||||
|         return reloadableResourceBundleMessageSource; | 
 | ||||||
|     } |     private static class RAPServletContextInitializer implements ServletContextInitializer { | ||||||
| 
 |         @Override | ||||||
|     private static class RAPServletContextInitializer implements ServletContextInitializer { |         public void onStartup(final ServletContext servletContext) { | ||||||
|         @Override |             servletContext.setInitParameter( | ||||||
|         public void onStartup(final ServletContext servletContext) throws ServletException { |                     "org.eclipse.rap.applicationConfiguration", | ||||||
|             servletContext.setInitParameter( |                     RAPConfiguration.class.getName()); | ||||||
|                     "org.eclipse.rap.applicationConfiguration", |         } | ||||||
|                     RAPConfiguration.class.getName()); |     } | ||||||
|         } | 
 | ||||||
|     } | } | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -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, | ||||||
|  |  | ||||||
|  | @ -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]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -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 */ | ||||||
|  |  | ||||||
|  | @ -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(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -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()); | } | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -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; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -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); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -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) { | ||||||
|  |  | ||||||
|  | @ -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(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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(); | } | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -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"); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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"); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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"); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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()); | 
 | ||||||
| 
 | } | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -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(); | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -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; |     } | ||||||
|         } | 
 | ||||||
|     } | } | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -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 + "]"; | } | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | @ -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); | ||||||
| 
 |     } | ||||||
| } | 
 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -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); | } | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -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); |             } | ||||||
|                 } |         } | ||||||
|             } | 
 | ||||||
|         } |     } | ||||||
| 
 | } | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -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(); | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|  |  | ||||||
|  | @ -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) { | ||||||
|  | @ -460,7 +459,7 @@ public final class ClientConnectionTable { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         void updateData(final TableItem tableItem) { |         void updateData(final TableItem tableItem) { | ||||||
|             tableItem.setText(0, getConnectionIdentifer()); |             tableItem.setText(0, getConnectionIdentifier()); | ||||||
|             tableItem.setText(1, getConnectionAddress()); |             tableItem.setText(1, getConnectionAddress()); | ||||||
|             tableItem.setText(2, getStatusName()); |             tableItem.setText(2, getStatusName()); | ||||||
|         } |         } | ||||||
|  | @ -533,7 +532,7 @@ public final class ClientConnectionTable { | ||||||
|         public int compareTo(final UpdatableTableItem other) { |         public int compareTo(final UpdatableTableItem other) { | ||||||
|             return Comparator.comparingInt(UpdatableTableItem::statusWeight) |             return Comparator.comparingInt(UpdatableTableItem::statusWeight) | ||||||
|                     .thenComparingInt(UpdatableTableItem::thresholdsWeight) |                     .thenComparingInt(UpdatableTableItem::thresholdsWeight) | ||||||
|                     .thenComparing(UpdatableTableItem::getConnectionIdentifer) |                     .thenComparing(UpdatableTableItem::getConnectionIdentifier) | ||||||
|                     .compare(this, other); |                     .compare(this, other); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -580,7 +579,7 @@ public final class ClientConnectionTable { | ||||||
|             return Constants.EMPTY_NOTE; |             return Constants.EMPTY_NOTE; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         String getConnectionIdentifer() { |         String getConnectionIdentifier() { | ||||||
|             if (this.connectionData != null && this.connectionData.clientConnection.userSessionId != null) { |             if (this.connectionData != null && this.connectionData.clientConnection.userSessionId != null) { | ||||||
|                 return this.connectionData.clientConnection.userSessionId; |                 return this.connectionData.clientConnection.userSessionId; | ||||||
|             } |             } | ||||||
|  | @ -608,10 +607,7 @@ public final class ClientConnectionTable { | ||||||
|                 final IndicatorData indicatorData = |                 final IndicatorData indicatorData = | ||||||
|                         ClientConnectionTable.this.indicatorMapping.get(indicatorValue.getType()); |                         ClientConnectionTable.this.indicatorMapping.get(indicatorValue.getType()); | ||||||
| 
 | 
 | ||||||
|                 if (indicatorData == null) { |                 if (indicatorData != null) { | ||||||
|                     log.error("No IndicatorData of type: {} found", indicatorValue.getType()); |  | ||||||
|                 } else { |  | ||||||
| 
 |  | ||||||
|                     final double value = indicatorValue.getValue(); |                     final double value = indicatorValue.getValue(); | ||||||
|                     final int indicatorWeight = IndicatorData.getWeight(indicatorData, value); |                     final int indicatorWeight = IndicatorData.getWeight(indicatorData, value); | ||||||
|                     if (this.indicatorWeights[indicatorData.index] != indicatorWeight) { |                     if (this.indicatorWeights[indicatorData.index] != indicatorWeight) { | ||||||
|  |  | ||||||
|  | @ -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; |         } | ||||||
|         } |     } | ||||||
|     } | 
 | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  | @ -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
											
										
									
								
							|  | @ -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); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|  |  | ||||||
|  | @ -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); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -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); |     } | ||||||
|         } | 
 | ||||||
|     } | } | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -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; |         } | ||||||
|         } |     } | ||||||
|     } | 
 | ||||||
| 
 | } | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -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(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -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; |     } | ||||||
|     } | 
 | ||||||
| 
 | } | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -123,7 +123,7 @@ public class WidgetFactory { | ||||||
|         private ImageData image = null; |         private ImageData image = null; | ||||||
|         private ImageData greyedImage = null; |         private ImageData greyedImage = null; | ||||||
| 
 | 
 | ||||||
|         private ImageIcon(final String fileName) { |         ImageIcon(final String fileName) { | ||||||
|             this.fileName = fileName; |             this.fileName = fileName; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -199,7 +199,7 @@ public class WidgetFactory { | ||||||
| 
 | 
 | ||||||
|         public final String key; |         public final String key; | ||||||
| 
 | 
 | ||||||
|         private CustomVariant(final String key) { |         CustomVariant(final String key) { | ||||||
|             this.key = key; |             this.key = key; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -268,7 +268,7 @@ public class WidgetFactory { | ||||||
|      * @param parent The parent Composite |      * @param parent The parent Composite | ||||||
|      * @return the scrolled Composite to add the form content */ |      * @return the scrolled Composite to add the form content */ | ||||||
|     public Composite createPopupScrollComposite(final Composite parent) { |     public Composite createPopupScrollComposite(final Composite parent) { | ||||||
|         final Composite grid = PageService.createManagedVScrolledComposite( |         return PageService.createManagedVScrolledComposite( | ||||||
|                 parent, |                 parent, | ||||||
|                 scrolledComposite -> { |                 scrolledComposite -> { | ||||||
|                     final Composite g = new Composite(scrolledComposite, SWT.NONE); |                     final Composite g = new Composite(scrolledComposite, SWT.NONE); | ||||||
|  | @ -277,7 +277,6 @@ public class WidgetFactory { | ||||||
|                     return g; |                     return g; | ||||||
|                 }, |                 }, | ||||||
|                 false); |                 false); | ||||||
|         return grid; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public Composite createWarningPanel(final Composite parent) { |     public Composite createWarningPanel(final Composite parent) { | ||||||
|  | @ -733,7 +732,7 @@ public class WidgetFactory { | ||||||
|                 new FileUploadSelection(parent, this.i18nSupport, readonly); |                 new FileUploadSelection(parent, this.i18nSupport, readonly); | ||||||
| 
 | 
 | ||||||
|         if (supportedFiles != null) { |         if (supportedFiles != null) { | ||||||
|             supportedFiles.forEach(ext -> fileUploadSelection.withSupportFor(ext)); |             supportedFiles.forEach(fileUploadSelection::withSupportFor); | ||||||
|         } |         } | ||||||
|         return fileUploadSelection; |         return fileUploadSelection; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,113 +1,106 @@ | ||||||
| /* | /* | ||||||
|  * 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.webservice; | package ch.ethz.seb.sebserver.webservice; | ||||||
| 
 | 
 | ||||||
| import java.net.InetAddress; | import java.net.InetAddress; | ||||||
| import java.net.UnknownHostException; | import java.net.UnknownHostException; | ||||||
| 
 | 
 | ||||||
| import javax.annotation.PreDestroy; | import javax.annotation.PreDestroy; | ||||||
| 
 | 
 | ||||||
| import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; | ||||||
| import org.springframework.boot.context.event.ApplicationReadyEvent; | import org.springframework.boot.context.event.ApplicationReadyEvent; | ||||||
| import org.springframework.context.ApplicationEventPublisher; | import org.springframework.context.ApplicationEventPublisher; | ||||||
| import org.springframework.context.ApplicationListener; | import org.springframework.context.ApplicationListener; | ||||||
| import org.springframework.context.annotation.Import; | import org.springframework.context.annotation.Import; | ||||||
| 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.SEBServerInit; | import ch.ethz.seb.sebserver.SEBServerInit; | ||||||
| import ch.ethz.seb.sebserver.SEBServerInitEvent; | import ch.ethz.seb.sebserver.SEBServerInitEvent; | ||||||
| import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | ||||||
| 
 | 
 | ||||||
| @Component | @Component | ||||||
| @WebServiceProfile | @WebServiceProfile | ||||||
| @Import(DataSourceAutoConfiguration.class) | @Import(DataSourceAutoConfiguration.class) | ||||||
| public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent> { | public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent> { | ||||||
| 
 | 
 | ||||||
|     private final SEBServerInit sebServerInit; |     private final SEBServerInit sebServerInit; | ||||||
|     private final Environment environment; |     private final Environment environment; | ||||||
|     private final WebserviceInfo webserviceInfo; |     private final WebserviceInfo webserviceInfo; | ||||||
|     private final AdminUserInitializer adminUserInitializer; |     private final AdminUserInitializer adminUserInitializer; | ||||||
|     private final ApplicationEventPublisher applicationEventPublisher; |     private final ApplicationEventPublisher applicationEventPublisher; | ||||||
| 
 | 
 | ||||||
|     protected WebserviceInit( |     protected WebserviceInit( | ||||||
|             final SEBServerInit sebServerInit, |             final SEBServerInit sebServerInit, | ||||||
|             final Environment environment, |             final Environment environment, | ||||||
|             final WebserviceInfo webserviceInfo, |             final WebserviceInfo webserviceInfo, | ||||||
|             final AdminUserInitializer adminUserInitializer, |             final AdminUserInitializer adminUserInitializer, | ||||||
|             final ApplicationEventPublisher applicationEventPublisher) { |             final ApplicationEventPublisher applicationEventPublisher) { | ||||||
| 
 | 
 | ||||||
|         this.sebServerInit = sebServerInit; |         this.sebServerInit = sebServerInit; | ||||||
|         this.environment = environment; |         this.environment = environment; | ||||||
|         this.webserviceInfo = webserviceInfo; |         this.webserviceInfo = webserviceInfo; | ||||||
|         this.adminUserInitializer = adminUserInitializer; |         this.adminUserInitializer = adminUserInitializer; | ||||||
|         this.applicationEventPublisher = applicationEventPublisher; |         this.applicationEventPublisher = applicationEventPublisher; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void onApplicationEvent(final ApplicationReadyEvent event) { |     public void onApplicationEvent(final ApplicationReadyEvent event) { | ||||||
| 
 | 
 | ||||||
|         this.sebServerInit.init(); |         this.sebServerInit.init(); | ||||||
| 
 | 
 | ||||||
|         SEBServerInit.INIT_LOGGER.info("---->  **** Webservice starting up... ****"); |         SEBServerInit.INIT_LOGGER.info("---->  **** Webservice starting up... ****"); | ||||||
| 
 | 
 | ||||||
|         SEBServerInit.INIT_LOGGER.info("----> "); |         SEBServerInit.INIT_LOGGER.info("----> "); | ||||||
|         SEBServerInit.INIT_LOGGER.info("----> Init Database with flyway..."); |         SEBServerInit.INIT_LOGGER.info("----> Intitialize Services..."); | ||||||
|         SEBServerInit.INIT_LOGGER.info("----> TODO "); |         SEBServerInit.INIT_LOGGER.info("----> "); | ||||||
| 
 | 
 | ||||||
|         // TODO integration of Flyway for database initialization and migration:  https://flywaydb.org |         this.applicationEventPublisher.publishEvent(new SEBServerInitEvent(this)); | ||||||
|         //      see also https://flywaydb.org/getstarted/firststeps/api | 
 | ||||||
| 
 |         SEBServerInit.INIT_LOGGER.info("----> "); | ||||||
|         SEBServerInit.INIT_LOGGER.info("----> "); |         SEBServerInit.INIT_LOGGER.info("----> **** Webservice successfully started up! **** "); | ||||||
|         SEBServerInit.INIT_LOGGER.info("----> Intitialize Services..."); |         SEBServerInit.INIT_LOGGER.info("---->"); | ||||||
|         SEBServerInit.INIT_LOGGER.info("----> "); |         SEBServerInit.INIT_LOGGER.info("----> *** Info:"); | ||||||
| 
 | 
 | ||||||
|         this.applicationEventPublisher.publishEvent(new SEBServerInitEvent(this)); |         try { | ||||||
| 
 |             SEBServerInit.INIT_LOGGER.info("----> Server address: {}", this.environment.getProperty("server.address")); | ||||||
|         SEBServerInit.INIT_LOGGER.info("----> "); |             SEBServerInit.INIT_LOGGER.info("----> Server port: {}", this.environment.getProperty("server.port")); | ||||||
|         SEBServerInit.INIT_LOGGER.info("----> **** Webservice successfully started up! **** "); |             SEBServerInit.INIT_LOGGER.info("---->"); | ||||||
|         SEBServerInit.INIT_LOGGER.info("---->"); |             SEBServerInit.INIT_LOGGER.info("----> Local-Host address: {}", InetAddress.getLocalHost().getHostAddress()); | ||||||
|         SEBServerInit.INIT_LOGGER.info("----> *** Info:"); |             SEBServerInit.INIT_LOGGER.info("----> Local-Host name: {}", InetAddress.getLocalHost().getHostName()); | ||||||
| 
 |             SEBServerInit.INIT_LOGGER.info("---->"); | ||||||
|         try { |             SEBServerInit.INIT_LOGGER.info("----> Remote-Host address: {}", | ||||||
|             SEBServerInit.INIT_LOGGER.info("----> Server address: {}", this.environment.getProperty("server.address")); |                     InetAddress.getLoopbackAddress().getHostAddress()); | ||||||
|             SEBServerInit.INIT_LOGGER.info("----> Server port: {}", this.environment.getProperty("server.port")); |             SEBServerInit.INIT_LOGGER.info("----> Remote-Host name: {}", | ||||||
|             SEBServerInit.INIT_LOGGER.info("---->"); |                     InetAddress.getLoopbackAddress().getHostName()); | ||||||
|             SEBServerInit.INIT_LOGGER.info("----> Local-Host address: {}", InetAddress.getLocalHost().getHostAddress()); |         } catch (final UnknownHostException e) { | ||||||
|             SEBServerInit.INIT_LOGGER.info("----> Local-Host name: {}", InetAddress.getLocalHost().getHostName()); |             SEBServerInit.INIT_LOGGER.error("Unknown Host: ", e); | ||||||
|             SEBServerInit.INIT_LOGGER.info("---->"); |         } | ||||||
|             SEBServerInit.INIT_LOGGER.info("----> Remote-Host address: {}", | 
 | ||||||
|                     InetAddress.getLoopbackAddress().getHostAddress()); |         SEBServerInit.INIT_LOGGER.info("---->"); | ||||||
|             SEBServerInit.INIT_LOGGER.info("----> Remote-Host name: {}", |         SEBServerInit.INIT_LOGGER.info("----> External-Host URL: {}", this.webserviceInfo.getExternalServerURL()); | ||||||
|                     InetAddress.getLoopbackAddress().getHostName()); |         SEBServerInit.INIT_LOGGER.info("----> LMS-External-Address-Alias: {}", | ||||||
|         } catch (final UnknownHostException e) { |                 this.webserviceInfo.getLmsExternalAddressAlias()); | ||||||
|             SEBServerInit.INIT_LOGGER.error("Unknown Host: ", e); |         SEBServerInit.INIT_LOGGER.info("---->"); | ||||||
|         } |         SEBServerInit.INIT_LOGGER.info("----> HTTP Scheme {}", this.webserviceInfo.getHttpScheme()); | ||||||
| 
 |         SEBServerInit.INIT_LOGGER.info("---->"); | ||||||
|         SEBServerInit.INIT_LOGGER.info("---->"); |         SEBServerInit.INIT_LOGGER.info("----> Property Override Test: {}", this.webserviceInfo.getTestProperty()); | ||||||
|         SEBServerInit.INIT_LOGGER.info("----> External-Host URL: {}", this.webserviceInfo.getExternalServerURL()); | 
 | ||||||
|         SEBServerInit.INIT_LOGGER.info("----> LMS-External-Address-Alias: {}", |         // Create an initial admin account if requested and not already in the data-base | ||||||
|                 this.webserviceInfo.getLmsExternalAddressAlias()); |         this.adminUserInitializer.initAdminAccount(); | ||||||
|         SEBServerInit.INIT_LOGGER.info("---->"); | 
 | ||||||
|         SEBServerInit.INIT_LOGGER.info("----> HTTP Scheme {}", this.webserviceInfo.getHttpScheme()); |     } | ||||||
|         SEBServerInit.INIT_LOGGER.info("---->"); | 
 | ||||||
|         SEBServerInit.INIT_LOGGER.info("----> Property Override Test: {}", this.webserviceInfo.getTestProperty()); |     @PreDestroy | ||||||
| 
 |     public void gracefulShutdown() { | ||||||
|         // Create an initial admin account if requested and not already in the data-base |         SEBServerInit.INIT_LOGGER.info("**** Gracefully Shutdown of SEB Server instance {} ****", | ||||||
|         this.adminUserInitializer.initAdminAccount(); |                 this.webserviceInfo.getHostAddress()); | ||||||
| 
 |     } | ||||||
|     } | 
 | ||||||
| 
 | } | ||||||
|     @PreDestroy |  | ||||||
|     public void gracefulShutdown() { |  | ||||||
|         SEBServerInit.INIT_LOGGER.info("**** Gracefully Shutdown of SEB Server instance {} ****", |  | ||||||
|                 this.webserviceInfo.getHostAddress()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -23,6 +23,7 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; | import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Result; | import ch.ethz.seb.sebserver.gbl.util.Result; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; | import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; | ||||||
|  | import org.joda.time.DateTimeZone; | ||||||
| 
 | 
 | ||||||
| /** Defines the LMS API access service interface with all functionality needed to access | /** Defines the LMS API access service interface with all functionality needed to access | ||||||
|  * a LMS API within a given LmsSetup configuration. |  * a LMS API within a given LmsSetup configuration. | ||||||
|  | @ -101,12 +102,12 @@ public interface LmsAPIService { | ||||||
|     static Predicate<QuizData> quizFilterPredicate(final FilterMap filterMap) { |     static Predicate<QuizData> quizFilterPredicate(final FilterMap filterMap) { | ||||||
|         final String name = filterMap.getQuizName(); |         final String name = filterMap.getQuizName(); | ||||||
|         final DateTime from = filterMap.getQuizFromTime(); |         final DateTime from = filterMap.getQuizFromTime(); | ||||||
|         //final DateTime now = DateTime.now(DateTimeZone.UTC); |  | ||||||
|         return q -> { |         return q -> { | ||||||
|             final boolean nameFilter = StringUtils.isBlank(name) || (q.name != null && q.name.contains(name)); |             final boolean nameFilter = StringUtils.isBlank(name) || (q.name != null && q.name.contains(name)); | ||||||
|             final boolean startTimeFilter = |             final boolean startTimeFilter = | ||||||
|                     (from == null) || (q.startTime != null && (q.startTime.isEqual(from) || q.startTime.isAfter(from))); |                     (from == null) || (q.startTime != null && (q.startTime.isEqual(from) || q.startTime.isAfter(from))); | ||||||
|             return nameFilter && startTimeFilter /* && endTimeFilter */; |             final boolean currentlyRunning = DateTime.now(DateTimeZone.UTC).isBefore(q.endTime); | ||||||
|  |             return nameFilter && (startTimeFilter || currentlyRunning) ; | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -60,10 +60,10 @@ public interface ClientConfigService { | ||||||
|             unless = "#result.hasError()") |             unless = "#result.hasError()") | ||||||
|     Result<ClientDetails> getClientConfigDetails(String clientName); |     Result<ClientDetails> getClientConfigDetails(String clientName); | ||||||
| 
 | 
 | ||||||
|     @CacheEvict( |     /** Internally used to check OAuth2 access for a active SebClientConfig. | ||||||
|             cacheNames = EXAM_CLIENT_DETAILS_CACHE, |      * | ||||||
|             allEntries = true) |      * @param config the SebClientConfig to check access | ||||||
|     @EventListener(BulkActionEvent.class) |      * @return true if the system was able to gain an access token for the client. False otherwise | ||||||
|     void flushClientConfigData(BulkActionEvent event); |      */ | ||||||
| 
 |     boolean checkAccess(SebClientConfig config); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -10,8 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl; | ||||||
| 
 | 
 | ||||||
| import ch.ethz.seb.sebserver.WebSecurityConfig; | import ch.ethz.seb.sebserver.WebSecurityConfig; | ||||||
| import ch.ethz.seb.sebserver.gbl.Constants; | import ch.ethz.seb.sebserver.gbl.Constants; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType; | import ch.ethz.seb.sebserver.gbl.api.API; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.EntityType; |  | ||||||
| import ch.ethz.seb.sebserver.gbl.model.EntityKey; | import ch.ethz.seb.sebserver.gbl.model.EntityKey; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.institution.Institution; | import ch.ethz.seb.sebserver.gbl.model.institution.Institution; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig; | import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig; | ||||||
|  | @ -19,8 +18,6 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Result; | import ch.ethz.seb.sebserver.gbl.util.Result; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Utils; | import ch.ethz.seb.sebserver.gbl.util.Utils; | ||||||
| import ch.ethz.seb.sebserver.webservice.WebserviceInfo; | import ch.ethz.seb.sebserver.webservice.WebserviceInfo; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction; |  | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkActionEvent; |  | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService; | import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials; | import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.InstitutionDAO; | import ch.ethz.seb.sebserver.webservice.servicelayer.dao.InstitutionDAO; | ||||||
|  | @ -38,12 +35,21 @@ import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| import org.springframework.beans.factory.annotation.Qualifier; | import org.springframework.beans.factory.annotation.Qualifier; | ||||||
| import org.springframework.context.annotation.Lazy; | import org.springframework.context.annotation.Lazy; | ||||||
|  | import org.springframework.http.HttpEntity; | ||||||
|  | import org.springframework.http.HttpHeaders; | ||||||
|  | import org.springframework.http.HttpMethod; | ||||||
|  | import org.springframework.http.HttpStatus; | ||||||
|  | import org.springframework.http.MediaType; | ||||||
|  | import org.springframework.http.ResponseEntity; | ||||||
| import org.springframework.security.crypto.password.PasswordEncoder; | import org.springframework.security.crypto.password.PasswordEncoder; | ||||||
| import org.springframework.security.oauth2.common.OAuth2AccessToken; | import org.springframework.security.oauth2.common.OAuth2AccessToken; | ||||||
| import org.springframework.security.oauth2.provider.ClientDetails; | import org.springframework.security.oauth2.provider.ClientDetails; | ||||||
| import org.springframework.security.oauth2.provider.client.BaseClientDetails; | import org.springframework.security.oauth2.provider.client.BaseClientDetails; | ||||||
| import org.springframework.security.oauth2.provider.token.TokenStore; | import org.springframework.security.oauth2.provider.token.TokenStore; | ||||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||||
|  | import org.springframework.util.LinkedMultiValueMap; | ||||||
|  | import org.springframework.util.MultiValueMap; | ||||||
|  | import org.springframework.web.client.RestTemplate; | ||||||
| 
 | 
 | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.io.InputStream; | import java.io.InputStream; | ||||||
|  | @ -51,6 +57,7 @@ import java.io.OutputStream; | ||||||
| import java.io.PipedInputStream; | import java.io.PipedInputStream; | ||||||
| import java.io.PipedOutputStream; | import java.io.PipedOutputStream; | ||||||
| import java.nio.charset.StandardCharsets; | import java.nio.charset.StandardCharsets; | ||||||
|  | import java.util.Base64; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
| import java.util.UUID; | import java.util.UUID; | ||||||
|  | @ -316,19 +323,48 @@ public class ClientConfigServiceImpl implements ClientConfigService { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void flushClientConfigData(final BulkActionEvent event) { |     public boolean checkAccess(SebClientConfig config) { | ||||||
|  |         if(!config.isActive()) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         try { |         try { | ||||||
|             final BulkAction bulkAction = event.getBulkAction(); |             RestTemplate restTemplate = new RestTemplate(); | ||||||
|  |             String externalServerURL = webserviceInfo.getExternalServerURL() + | ||||||
|  |                     API.OAUTH_TOKEN_ENDPOINT; | ||||||
| 
 | 
 | ||||||
|             if (bulkAction.type == BulkActionType.DEACTIVATE || |             MultiValueMap<String, String> headers = new LinkedMultiValueMap<>(); | ||||||
|                     bulkAction.type == BulkActionType.HARD_DELETE) { |             headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE); | ||||||
|  |             ClientCredentials credentials = sebClientConfigDAO | ||||||
|  |                     .getSebClientCredentials(config.getModelId()) | ||||||
|  |                     .getOrThrow(); | ||||||
|  |             CharSequence plainClientSecret = clientCredentialService.getPlainClientSecret(credentials); | ||||||
|  |             String basicAuth = credentials.clientId + | ||||||
|  |                     String.valueOf(Constants.COLON) + | ||||||
|  |                     plainClientSecret; | ||||||
|  |             String encoded = Base64.getEncoder() | ||||||
|  |                     .encodeToString(basicAuth.getBytes()); | ||||||
| 
 | 
 | ||||||
|                 bulkAction.extractKeys(EntityType.SEB_CLIENT_CONFIGURATION) |             headers.add(HttpHeaders.AUTHORIZATION, "Basic " + encoded); | ||||||
|                         .forEach(this::flushClientConfigData); |             HttpEntity<String> entity = new HttpEntity<>( | ||||||
|  |                     "grant_type=client_credentials&scope=read write", | ||||||
|  |                     headers); | ||||||
|  | 
 | ||||||
|  |             ResponseEntity<String> exchange = restTemplate.exchange( | ||||||
|  |                     externalServerURL, | ||||||
|  |                     HttpMethod.POST, | ||||||
|  |                     entity, | ||||||
|  |                     String.class); | ||||||
|  | 
 | ||||||
|  |             if (exchange.getStatusCode().value() == HttpStatus.OK.value()) { | ||||||
|  |                 return true; | ||||||
|  |             } else { | ||||||
|  |                 log.warn("Failed to check access SebClientConfig {} response: {}", config, exchange.getStatusCode()); | ||||||
|  |                 return false; | ||||||
|             } |             } | ||||||
| 
 |         } catch (Exception e) { | ||||||
|         } catch (final Exception e) { |             log.warn("Failed to check access for SebClientConfig: {} cause: {}", config, e.getMessage()); | ||||||
|             log.error("Unexpected error while trying to flush ClientConfig data ", e); |             return false; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,109 +1,121 @@ | ||||||
| /* | /* | ||||||
|  * 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.webservice.servicelayer.session.impl; | package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; | ||||||
| 
 | 
 | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Collection; | import java.util.Arrays; | ||||||
| import java.util.Collections; | import java.util.Collection; | ||||||
| import java.util.List; | import java.util.Collections; | ||||||
| 
 | import java.util.List; | ||||||
| import org.slf4j.Logger; | 
 | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.Logger; | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.slf4j.LoggerFactory; | ||||||
| import org.springframework.beans.factory.annotation.Value; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.context.ApplicationContext; | import org.springframework.beans.factory.annotation.Value; | ||||||
| import org.springframework.context.annotation.Lazy; | import org.springframework.context.ApplicationContext; | ||||||
| import org.springframework.stereotype.Component; | import org.springframework.context.annotation.Lazy; | ||||||
| 
 | import org.springframework.stereotype.Component; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; | 
 | ||||||
| import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; | import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; | import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; | ||||||
| import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO; | import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator; | import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO; | ||||||
| 
 | import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator; | ||||||
| @Lazy | 
 | ||||||
| @Component | @Lazy | ||||||
| @WebServiceProfile | @Component | ||||||
| public class ClientIndicatorFactory { | @WebServiceProfile | ||||||
| 
 | public class ClientIndicatorFactory { | ||||||
|     private static final Logger log = LoggerFactory.getLogger(ClientIndicatorFactory.class); | 
 | ||||||
| 
 |     private static final Logger log = LoggerFactory.getLogger(ClientIndicatorFactory.class); | ||||||
|     private final ApplicationContext applicationContext; | 
 | ||||||
|     private final IndicatorDAO indicatorDAO; |     private final ApplicationContext applicationContext; | ||||||
|     private final boolean enableCaching; |     private final IndicatorDAO indicatorDAO; | ||||||
| 
 |     private final boolean enableCaching; | ||||||
|     @Autowired | 
 | ||||||
|     public ClientIndicatorFactory( |     @Autowired | ||||||
|             final ApplicationContext applicationContext, |     public ClientIndicatorFactory( | ||||||
|             final IndicatorDAO indicatorDAO, |             final ApplicationContext applicationContext, | ||||||
|             @Value("${sebserver.webservice.api.exam.enable-indicator-cache:true}") final boolean enableCaching) { |             final IndicatorDAO indicatorDAO, | ||||||
| 
 |             @Value("${sebserver.webservice.api.exam.enable-indicator-cache:true}") final boolean enableCaching) { | ||||||
|         this.applicationContext = applicationContext; | 
 | ||||||
|         this.indicatorDAO = indicatorDAO; |         this.applicationContext = applicationContext; | ||||||
|         this.enableCaching = enableCaching; |         this.indicatorDAO = indicatorDAO; | ||||||
|     } |         this.enableCaching = enableCaching; | ||||||
| 
 |     } | ||||||
|     public List<ClientIndicator> createFor(final ClientConnection clientConnection) { | 
 | ||||||
|         final List<ClientIndicator> result = new ArrayList<>(); |     public List<ClientIndicator> createFor(final ClientConnection clientConnection) { | ||||||
| 
 |         final List<ClientIndicator> result = new ArrayList<>(); | ||||||
|         if (clientConnection.examId == null) { | 
 | ||||||
|             return result; |         if (clientConnection.examId == null) { | ||||||
|         } |             return result; | ||||||
| 
 |         } | ||||||
|         try { | 
 | ||||||
| 
 |         try { | ||||||
|             final Collection<Indicator> examIndicators = this.indicatorDAO | 
 | ||||||
|                     .allForExam(clientConnection.examId) |             final Collection<Indicator> examIndicators = this.indicatorDAO | ||||||
|                     .getOrThrow(); |                     .allForExam(clientConnection.examId) | ||||||
| 
 |                     .getOrThrow(); | ||||||
|             boolean pingIndicatorAvailable = false; | 
 | ||||||
| 
 |             boolean pingIndicatorAvailable = false; | ||||||
|             for (final Indicator indicatorDef : examIndicators) { | 
 | ||||||
|                 try { |             for (final Indicator indicatorDef : examIndicators) { | ||||||
| 
 |                 try { | ||||||
|                     final ClientIndicator indicator = this.applicationContext | 
 | ||||||
|                             .getBean(indicatorDef.type.name(), ClientIndicator.class); |                     final ClientIndicator indicator = this.applicationContext | ||||||
| 
 |                             .getBean(indicatorDef.type.name(), ClientIndicator.class); | ||||||
|                     if (!pingIndicatorAvailable) { | 
 | ||||||
|                         pingIndicatorAvailable = indicatorDef.type == IndicatorType.LAST_PING; |                     if (!pingIndicatorAvailable) { | ||||||
|                     } |                         pingIndicatorAvailable = indicatorDef.type == IndicatorType.LAST_PING; | ||||||
| 
 |                     } | ||||||
|                     indicator.init( | 
 | ||||||
|                             indicatorDef, |                     indicator.init( | ||||||
|                             clientConnection.id, |                             indicatorDef, | ||||||
|                             this.enableCaching); |                             clientConnection.id, | ||||||
| 
 |                             this.enableCaching); | ||||||
|                     result.add(indicator); | 
 | ||||||
|                 } catch (final Exception e) { |                     result.add(indicator); | ||||||
|                     log.warn("No Indicator with type: {} found as registered bean. Ignore this one.", indicatorDef.type, |                 } catch (final Exception e) { | ||||||
|                             e); |                     log.warn("No Indicator with type: {} found as registered bean. Ignore this one.", indicatorDef.type, | ||||||
|                 } |                             e); | ||||||
|             } |                 } | ||||||
| 
 |             } | ||||||
|             // If there is no ping interval indicator set from the exam, we add a hidden one | 
 | ||||||
|             // to at least create missing ping events and track missing state |             // If there is no ping interval indicator set from the exam, we add a hidden one | ||||||
|             if (!pingIndicatorAvailable) { |             // to at least create missing ping events and track missing state | ||||||
|                 final PingIntervalClientIndicator pingIndicator = this.applicationContext |             if (!pingIndicatorAvailable) { | ||||||
|                         .getBean(PingIntervalClientIndicator.class); |                 final PingIntervalClientIndicator pingIndicator = this.applicationContext | ||||||
|                 pingIndicator.hidden = true; |                         .getBean(PingIntervalClientIndicator.class); | ||||||
|                 result.add(pingIndicator); |                 pingIndicator.hidden = true; | ||||||
|             } |                 final Indicator indicator = new Indicator( | ||||||
| 
 |                         null, | ||||||
|         } catch (final RuntimeException e) { |                         clientConnection.examId, | ||||||
|             log.error("Failed to create ClientIndicator for ClientConnection: {}", clientConnection); |                         "hidden_ping_indicator", | ||||||
|             throw e; |                         IndicatorType.LAST_PING, | ||||||
|         } catch (final Exception e) { |                         "", | ||||||
|             log.error("Failed to create ClientIndicator for ClientConnection: {}", clientConnection); |                         Arrays.asList(new Indicator.Threshold(5000d, ""))); | ||||||
|         } |                 pingIndicator.init( | ||||||
| 
 |                         indicator, | ||||||
|         return Collections.unmodifiableList(result); |                         clientConnection.id, | ||||||
|     } |                         this.enableCaching); | ||||||
| 
 |                 result.add(pingIndicator); | ||||||
| } |             } | ||||||
|  | 
 | ||||||
|  |         } catch (final RuntimeException e) { | ||||||
|  |             log.error("Failed to create ClientIndicator for ClientConnection: {}", clientConnection); | ||||||
|  |             throw e; | ||||||
|  |         } catch (final Exception e) { | ||||||
|  |             log.error("Failed to create ClientIndicator for ClientConnection: {}", clientConnection); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return Collections.unmodifiableList(result); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -38,7 +38,6 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator { | ||||||
| 
 | 
 | ||||||
|     long pingErrorThreshold; |     long pingErrorThreshold; | ||||||
|     boolean missingPing = false; |     boolean missingPing = false; | ||||||
| 
 |  | ||||||
|     boolean hidden = false; |     boolean hidden = false; | ||||||
| 
 | 
 | ||||||
|     public PingIntervalClientIndicator(final ClientEventExtensionMapper clientEventExtensionMapper) { |     public PingIntervalClientIndicator(final ClientEventExtensionMapper clientEventExtensionMapper) { | ||||||
|  |  | ||||||
|  | @ -1,135 +1,141 @@ | ||||||
| /* | /* | ||||||
|  * 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.webservice.weblayer.api; | package ch.ethz.seb.sebserver.webservice.weblayer.api; | ||||||
| 
 | 
 | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| 
 | 
 | ||||||
| import org.mybatis.dynamic.sql.SqlTable; | import org.mybatis.dynamic.sql.SqlTable; | ||||||
| import org.springframework.http.MediaType; | import org.springframework.http.MediaType; | ||||||
| import org.springframework.util.MultiValueMap; | import org.springframework.util.MultiValueMap; | ||||||
| import org.springframework.web.bind.annotation.RequestMapping; | import org.springframework.web.bind.annotation.RequestMapping; | ||||||
| import org.springframework.web.bind.annotation.RequestMethod; | import org.springframework.web.bind.annotation.RequestMethod; | ||||||
| import org.springframework.web.bind.annotation.RequestParam; | import org.springframework.web.bind.annotation.RequestParam; | ||||||
| import org.springframework.web.bind.annotation.RestController; | import org.springframework.web.bind.annotation.RestController; | ||||||
| 
 | 
 | ||||||
| import ch.ethz.seb.sebserver.gbl.api.API; | import ch.ethz.seb.sebserver.gbl.api.API; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType; | import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.EntityType; | import ch.ethz.seb.sebserver.gbl.api.EntityType; | ||||||
| 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.EntityKey; | import ch.ethz.seb.sebserver.gbl.model.EntityKey; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.GrantEntity; | import ch.ethz.seb.sebserver.gbl.model.GrantEntity; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.Page; | import ch.ethz.seb.sebserver.gbl.model.Page; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; | import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent; | import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent; | ||||||
| import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | ||||||
| import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport; | import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; | import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; | import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PermissionDeniedException; | import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PermissionDeniedException; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService; | import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser; | import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService; | import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientEventDAO; | import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientEventDAO; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; | import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; | import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; | import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; | import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; | ||||||
| 
 | 
 | ||||||
| @WebServiceProfile | @WebServiceProfile | ||||||
| @RestController | @RestController | ||||||
| @RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.SEB_CLIENT_EVENT_ENDPOINT) | @RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.SEB_CLIENT_EVENT_ENDPOINT) | ||||||
| public class ClientEventController extends ReadonlyEntityController<ClientEvent, ClientEvent> { | public class ClientEventController extends ReadonlyEntityController<ClientEvent, ClientEvent> { | ||||||
| 
 | 
 | ||||||
|     private final ExamDAO examDAO; |     private final ExamDAO examDAO; | ||||||
|     private final ClientEventDAO clientEventDAO; |     private final ClientEventDAO clientEventDAO; | ||||||
| 
 | 
 | ||||||
|     protected ClientEventController( |     protected ClientEventController( | ||||||
|             final AuthorizationService authorization, |             final AuthorizationService authorization, | ||||||
|             final BulkActionService bulkActionService, |             final BulkActionService bulkActionService, | ||||||
|             final ClientEventDAO entityDAO, |             final ClientEventDAO entityDAO, | ||||||
|             final UserActivityLogDAO userActivityLogDAO, |             final UserActivityLogDAO userActivityLogDAO, | ||||||
|             final PaginationService paginationService, |             final PaginationService paginationService, | ||||||
|             final BeanValidationService beanValidationService, |             final BeanValidationService beanValidationService, | ||||||
|             final ExamDAO examDAO) { |             final ExamDAO examDAO) { | ||||||
| 
 | 
 | ||||||
|         super(authorization, |         super(authorization, | ||||||
|                 bulkActionService, |                 bulkActionService, | ||||||
|                 entityDAO, |                 entityDAO, | ||||||
|                 userActivityLogDAO, |                 userActivityLogDAO, | ||||||
|                 paginationService, |                 paginationService, | ||||||
|                 beanValidationService); |                 beanValidationService); | ||||||
| 
 | 
 | ||||||
|         this.examDAO = examDAO; |         this.examDAO = examDAO; | ||||||
|         this.clientEventDAO = entityDAO; |         this.clientEventDAO = entityDAO; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @RequestMapping( |     @RequestMapping( | ||||||
|             path = API.SEB_CLIENT_EVENT_SEARCH_PATH_SEGMENT, |             path = API.SEB_CLIENT_EVENT_SEARCH_PATH_SEGMENT, | ||||||
|             method = RequestMethod.GET, |             method = RequestMethod.GET, | ||||||
|             consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, |             consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, | ||||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) |             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||||
|     public Page<ExtendedClientEvent> getExtendedPage( |     public Page<ExtendedClientEvent> getExtendedPage( | ||||||
|             @RequestParam( |             @RequestParam( | ||||||
|                     name = API.PARAM_INSTITUTION_ID, |                     name = API.PARAM_INSTITUTION_ID, | ||||||
|                     required = true, |                     required = true, | ||||||
|                     defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, |                     defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, | ||||||
|             @RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber, |             @RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber, | ||||||
|             @RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize, |             @RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize, | ||||||
|             @RequestParam(name = Page.ATTR_SORT, required = false) final String sort, |             @RequestParam(name = Page.ATTR_SORT, required = false) final String sort, | ||||||
|             @RequestParam final MultiValueMap<String, String> allRequestParams) { |             @RequestParam final MultiValueMap<String, String> allRequestParams) { | ||||||
| 
 | 
 | ||||||
|         // at least current user must have base read access for specified entity type within its own institution |         // at least current user must have base read access for specified entity type within its own institution | ||||||
|         checkReadPrivilege(institutionId); |         checkReadPrivilege(institutionId); | ||||||
| 
 | 
 | ||||||
|         final FilterMap filterMap = new FilterMap(allRequestParams); |         final FilterMap filterMap = new FilterMap(allRequestParams); | ||||||
| 
 | 
 | ||||||
|         // if current user has no read access for specified entity type within other institution |         // if current user has no read access for specified entity type within other institution | ||||||
|         // then the current users institutionId is put as a SQL filter criteria attribute to extends query performance |         // then the current users institutionId is put as a SQL filter criteria attribute to extends query performance | ||||||
|         if (!this.authorization.hasGrant(PrivilegeType.READ, getGrantEntityType())) { |         if (!this.authorization.hasGrant(PrivilegeType.READ, getGrantEntityType())) { | ||||||
|             filterMap.putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId)); |             filterMap.putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId)); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return this.paginationService.getPage( |         try { | ||||||
|                 pageNumber, | 
 | ||||||
|                 pageSize, |             return this.paginationService.getPage( | ||||||
|                 sort, |                     pageNumber, | ||||||
|                 getSQLTableOfEntity().name(), |                     pageSize, | ||||||
|                 () -> this.clientEventDAO.allMatchingExtended(filterMap, this::hasReadAccess)) |                     sort, | ||||||
|                 .getOrThrow(); |                     getSQLTableOfEntity().name(), | ||||||
|     } |                     () -> this.clientEventDAO.allMatchingExtended(filterMap, this::hasReadAccess)) | ||||||
| 
 |                     .getOrThrow(); | ||||||
|     @Override |         } catch (Exception e) { | ||||||
|     public Collection<EntityKey> getDependencies(final String modelId, final BulkActionType bulkActionType) { |             e.printStackTrace(); | ||||||
|         throw new UnsupportedOperationException(); |             throw e; | ||||||
|     } |         } | ||||||
| 
 |     } | ||||||
|     @Override | 
 | ||||||
|     protected SqlTable getSQLTableOfEntity() { |     @Override | ||||||
|         return ClientEventRecordDynamicSqlSupport.clientEventRecord; |     public Collection<EntityKey> getDependencies(final String modelId, final BulkActionType bulkActionType) { | ||||||
|     } |         throw new UnsupportedOperationException(); | ||||||
| 
 |     } | ||||||
|     @Override | 
 | ||||||
|     protected GrantEntity toGrantEntity(final ClientEvent entity) { |     @Override | ||||||
|         return this.examDAO |     protected SqlTable getSQLTableOfEntity() { | ||||||
|                 .byClientConnection(entity.connectionId) |         return ClientEventRecordDynamicSqlSupport.clientEventRecord; | ||||||
|                 .getOrThrow(); |     } | ||||||
|     } | 
 | ||||||
| 
 |     @Override | ||||||
|     @Override |     protected GrantEntity toGrantEntity(final ClientEvent entity) { | ||||||
|     protected void checkReadPrivilege(final Long institutionId) { |         return this.examDAO | ||||||
|         final SEBServerUser currentUser = this.authorization.getUserService().getCurrentUser(); |                 .byClientConnection(entity.connectionId) | ||||||
|         if (currentUser.institutionId().longValue() != institutionId.longValue()) { |                 .get(); | ||||||
|             throw new PermissionDeniedException( |     } | ||||||
|                     EntityType.CLIENT_EVENT, | 
 | ||||||
|                     PrivilegeType.READ, |     @Override | ||||||
|                     currentUser.getUserInfo()); |     protected void checkReadPrivilege(final Long institutionId) { | ||||||
|         } |         final SEBServerUser currentUser = this.authorization.getUserService().getCurrentUser(); | ||||||
|     } |         if (currentUser.institutionId().longValue() != institutionId.longValue()) { | ||||||
| 
 |             throw new PermissionDeniedException( | ||||||
| } |                     EntityType.CLIENT_EVENT, | ||||||
|  |                     PrivilegeType.READ, | ||||||
|  |                     currentUser.getUserInfo()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -39,6 +39,7 @@ import org.springframework.web.bind.annotation.PathVariable; | ||||||
| import org.springframework.web.bind.annotation.RequestMapping; | import org.springframework.web.bind.annotation.RequestMapping; | ||||||
| import org.springframework.web.bind.annotation.RequestMethod; | import org.springframework.web.bind.annotation.RequestMethod; | ||||||
| import org.springframework.web.bind.annotation.RestController; | import org.springframework.web.bind.annotation.RestController; | ||||||
|  | import org.springframework.web.client.RestTemplate; | ||||||
| 
 | 
 | ||||||
| import javax.servlet.ServletOutputStream; | import javax.servlet.ServletOutputStream; | ||||||
| import javax.servlet.http.HttpServletResponse; | import javax.servlet.http.HttpServletResponse; | ||||||
|  | @ -144,6 +145,15 @@ public class SebClientConfigController extends ActivatableEntityController<SebCl | ||||||
|                 .map(this::checkPasswordMatch); |                 .map(this::checkPasswordMatch); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     protected Result<SebClientConfig> notifySaved(SebClientConfig entity) { | ||||||
|  |         if (entity.isActive()) { | ||||||
|  |             // try to get access token for SEB client | ||||||
|  |             sebClientConfigService.checkAccess(entity); | ||||||
|  |         } | ||||||
|  |         return super.notifySaved(entity); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private SebClientConfig checkPasswordMatch(final SebClientConfig entity) { |     private SebClientConfig checkPasswordMatch(final SebClientConfig entity) { | ||||||
|         Collection<APIMessage> errors = new ArrayList<>(); |         Collection<APIMessage> errors = new ArrayList<>(); | ||||||
|         if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.encryptSecretConfirm)) { |         if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.encryptSecretConfirm)) { | ||||||
|  |  | ||||||
|  | @ -1,58 +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.webservice.weblayer.oauth; | package ch.ethz.seb.sebserver.webservice.weblayer.oauth; | ||||||
| 
 | 
 | ||||||
| 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.dao.DuplicateKeyException; | import org.springframework.dao.DuplicateKeyException; | ||||||
| import org.springframework.security.core.AuthenticationException; | import org.springframework.security.core.AuthenticationException; | ||||||
| import org.springframework.security.oauth2.common.OAuth2AccessToken; | import org.springframework.security.oauth2.common.OAuth2AccessToken; | ||||||
| import org.springframework.security.oauth2.provider.OAuth2Authentication; | import org.springframework.security.oauth2.provider.OAuth2Authentication; | ||||||
| import org.springframework.security.oauth2.provider.token.DefaultTokenServices; | import org.springframework.security.oauth2.provider.token.DefaultTokenServices; | ||||||
| 
 | 
 | ||||||
| public class DefaultTokenServicesFallback extends DefaultTokenServices { | // TODO check if we can apply some caching here to get better performance for SEB client connection attempts | ||||||
| 
 | public class DefaultTokenServicesFallback extends DefaultTokenServices { | ||||||
|     private static final Logger log = LoggerFactory.getLogger(DefaultTokenServicesFallback.class); | 
 | ||||||
| 
 |     private static final Logger log = LoggerFactory.getLogger(DefaultTokenServicesFallback.class); | ||||||
|     @Override | 
 | ||||||
|     public OAuth2AccessToken createAccessToken(final OAuth2Authentication authentication) |     @Override | ||||||
|             throws AuthenticationException { |     public OAuth2AccessToken createAccessToken(final OAuth2Authentication authentication) | ||||||
| 
 |             throws AuthenticationException { | ||||||
|         try { | 
 | ||||||
|             return super.createAccessToken(authentication); |         try { | ||||||
|         } catch (final DuplicateKeyException e) { |             return super.createAccessToken(authentication); | ||||||
| 
 |         } catch (final DuplicateKeyException e) { | ||||||
|             log.info( | 
 | ||||||
|                     "Catched DuplicateKeyException, try to handle it by trying to get the already stored access token after waited some time"); |             log.warn( | ||||||
| 
 |                     "Caught DuplicateKeyException, try to handle it by trying to get the already stored access token after waited some time"); | ||||||
|             final String clientName = authentication.getName(); | 
 | ||||||
|             if (StringUtils.isNotBlank(clientName)) { |             final String clientName = authentication.getName(); | ||||||
| 
 |             if (StringUtils.isNotBlank(clientName)) { | ||||||
|                 // wait a second... | 
 | ||||||
|                 try { |                 // wait some time... | ||||||
|                     Thread.sleep(1000); |                 try { | ||||||
|                 } catch (final InterruptedException e1) { |                     Thread.sleep(500); | ||||||
|                     log.warn("Failed to sleep: {}", e1.getMessage()); |                 } catch (final InterruptedException e1) { | ||||||
|                 } |                     log.warn("Failed to sleep: {}", e1.getMessage()); | ||||||
| 
 |                 } | ||||||
|                 final OAuth2AccessToken accessToken = this.getAccessToken(authentication); | 
 | ||||||
|                 if (accessToken != null) { |                 final OAuth2AccessToken accessToken = this.getAccessToken(authentication); | ||||||
|                     log.info("Found original accees token for client: {} token: {}", clientName, accessToken); |                 if (accessToken != null) { | ||||||
|                     return accessToken; |                     log.debug("Found original access token for client: {} ", clientName); | ||||||
|                 } |                     return accessToken; | ||||||
|             } |                 } | ||||||
| 
 |             } | ||||||
|             // If no access token is available, propagate the original exception | 
 | ||||||
|             log.error("Unable the handle DuplicateKeyException properly", e); |             // If no access token is available, propagate the original exception | ||||||
|             throw e; |             log.error("Unable the handle DuplicateKeyException properly", e); | ||||||
|         } |             throw e; | ||||||
|     } |         } | ||||||
| 
 |     } | ||||||
| } | 
 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1,71 +1,71 @@ | ||||||
| server.address=localhost | server.address=localhost | ||||||
| server.port=8090 | server.port=8090 | ||||||
| 
 | 
 | ||||||
| logging.file=log/sebserver.log | logging.file=log/sebserver.log | ||||||
| 
 | 
 | ||||||
| # data source configuration | # data source configuration | ||||||
| spring.datasource.initialize=true | spring.datasource.initialize=true | ||||||
| spring.datasource.initialization-mode=always | spring.datasource.initialization-mode=always | ||||||
| spring.datasource.url=jdbc:mariadb://localhost:3306/SEBServer?createDatabaseIfNotExist=true&verifyServerCertificate=false&useSSL=false&requireSSL=false | spring.datasource.url=jdbc:mariadb://localhost:3306/SEBServer?createDatabaseIfNotExist=true&verifyServerCertificate=false&useSSL=false&requireSSL=false | ||||||
| spring.datasource.driver-class-name=org.mariadb.jdbc.Driver | spring.datasource.driver-class-name=org.mariadb.jdbc.Driver | ||||||
| spring.flyway.enabled=true | spring.flyway.enabled=true | ||||||
| spring.flyway.locations=classpath:config/sql/base,classpath:config/sql/dev | spring.flyway.locations=classpath:config/sql/base,classpath:config/sql/dev | ||||||
| spring.flyway.baselineOnMigrate=true | spring.flyway.baselineOnMigrate=true | ||||||
| spring.datasource.hikari.initializationFailTimeout=30000 | spring.datasource.hikari.initializationFailTimeout=30000 | ||||||
| spring.datasource.hikari.connectionTimeout=30000 | spring.datasource.hikari.connectionTimeout=30000 | ||||||
| spring.datasource.hikari.idleTimeout=600000 | spring.datasource.hikari.idleTimeout=600000 | ||||||
| spring.datasource.hikari.maxLifetime=1800000 | spring.datasource.hikari.maxLifetime=1800000 | ||||||
| 
 | 
 | ||||||
| sebserver.http.client.connect-timeout=15000 | sebserver.http.client.connect-timeout=15000 | ||||||
| sebserver.http.client.connection-request-timeout=10000 | sebserver.http.client.connection-request-timeout=10000 | ||||||
| sebserver.http.client.read-timeout=20000 | sebserver.http.client.read-timeout=20000 | ||||||
| 
 | 
 | ||||||
| # webservice configuration | # webservice configuration | ||||||
| sebserver.init.adminaccount.gen-on-init=false | sebserver.init.adminaccount.gen-on-init=false | ||||||
| sebserver.webservice.distributed=false | sebserver.webservice.distributed=false | ||||||
| sebserver.webservice.http.scheme=http | sebserver.webservice.http.scheme=http | ||||||
| sebserver.webservice.http.external.servername= | sebserver.webservice.http.external.servername= | ||||||
| sebserver.webservice.http.external.port= | sebserver.webservice.http.external.port=${server.port} | ||||||
| sebserver.webservice.http.redirect.gui=/gui | sebserver.webservice.http.redirect.gui=/gui | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| sebserver.webservice.api.admin.endpoint=/admin-api/v1 | sebserver.webservice.api.admin.endpoint=/admin-api/v1 | ||||||
| sebserver.webservice.api.admin.accessTokenValiditySeconds=3600 | sebserver.webservice.api.admin.accessTokenValiditySeconds=3600 | ||||||
| sebserver.webservice.api.admin.refreshTokenValiditySeconds=-1 | sebserver.webservice.api.admin.refreshTokenValiditySeconds=-1 | ||||||
| sebserver.webservice.api.exam.config.init.permittedProcesses=config/initialPermittedProcesses.xml | sebserver.webservice.api.exam.config.init.permittedProcesses=config/initialPermittedProcesses.xml | ||||||
| sebserver.webservice.api.exam.config.init.prohibitedProcesses=config/initialProhibitedProcesses.xml | sebserver.webservice.api.exam.config.init.prohibitedProcesses=config/initialProhibitedProcesses.xml | ||||||
| sebserver.webservice.api.exam.update-interval=1 * * * * * | sebserver.webservice.api.exam.update-interval=1 * * * * * | ||||||
| sebserver.webservice.api.exam.time-prefix=0 | sebserver.webservice.api.exam.time-prefix=0 | ||||||
| sebserver.webservice.api.exam.time-suffix=0 | sebserver.webservice.api.exam.time-suffix=0 | ||||||
| sebserver.webservice.api.exam.endpoint=/exam-api | sebserver.webservice.api.exam.endpoint=/exam-api | ||||||
| sebserver.webservice.api.exam.endpoint.discovery=${sebserver.webservice.api.exam.endpoint}/discovery | sebserver.webservice.api.exam.endpoint.discovery=${sebserver.webservice.api.exam.endpoint}/discovery | ||||||
| sebserver.webservice.api.exam.endpoint.v1=${sebserver.webservice.api.exam.endpoint}/v1 | sebserver.webservice.api.exam.endpoint.v1=${sebserver.webservice.api.exam.endpoint}/v1 | ||||||
| sebserver.webservice.api.exam.accessTokenValiditySeconds=3600 | sebserver.webservice.api.exam.accessTokenValiditySeconds=3600 | ||||||
| sebserver.webservice.api.exam.event-handling-strategy=ASYNC_BATCH_STORE_STRATEGY | sebserver.webservice.api.exam.event-handling-strategy=ASYNC_BATCH_STORE_STRATEGY | ||||||
| sebserver.webservice.api.exam.enable-indicator-cache=true | sebserver.webservice.api.exam.enable-indicator-cache=true | ||||||
| sebserver.webservice.api.pagination.maxPageSize=500 | sebserver.webservice.api.pagination.maxPageSize=500 | ||||||
| # comma separated list of known possible OpenEdX API access token request endpoints | # comma separated list of known possible OpenEdX API access token request endpoints | ||||||
| sebserver.webservice.lms.openedx.api.token.request.paths=/oauth2/access_token | sebserver.webservice.lms.openedx.api.token.request.paths=/oauth2/access_token | ||||||
| sebserver.webservice.lms.moodle.api.token.request.paths= | sebserver.webservice.lms.moodle.api.token.request.paths= | ||||||
| sebserver.webservice.lms.address.alias=lms.mockup.com=lms.address.alias | sebserver.webservice.lms.address.alias=lms.mockup.com=lms.address.alias | ||||||
| 
 | 
 | ||||||
| # NOTE: This is a temporary work-around for SEB Restriction API within Open edX SEB integration plugin to | # NOTE: This is a temporary work-around for SEB Restriction API within Open edX SEB integration plugin to | ||||||
| #       apply on load-balanced infrastructure or infrastructure that has several layers of cache. | #       apply on load-balanced infrastructure or infrastructure that has several layers of cache. | ||||||
| #       The reason for this is that the API (Open edX system) internally don't apply a resource-change that is | #       The reason for this is that the API (Open edX system) internally don't apply a resource-change that is | ||||||
| #       done within HTTP API call immediately from an outside perspective. | #       done within HTTP API call immediately from an outside perspective. | ||||||
| #       After a resource-change on the API is done, the system toggles between the old and the new resource | #       After a resource-change on the API is done, the system toggles between the old and the new resource | ||||||
| #       while constantly calling GET. This usually happens for about a minute or two then it stabilizes on the new resource | #       while constantly calling GET. This usually happens for about a minute or two then it stabilizes on the new resource | ||||||
| # | # | ||||||
| #       This may source on load-balancing or internally caching on Open edX side. | #       This may source on load-balancing or internally caching on Open edX side. | ||||||
| #       To mitigate this effect the SEB Server can be configured to apply a resource-change on the | #       To mitigate this effect the SEB Server can be configured to apply a resource-change on the | ||||||
| #       API several times in a row to flush as match caches and reach as match as possible server instances. | #       API several times in a row to flush as match caches and reach as match as possible server instances. | ||||||
| # | # | ||||||
| #       Since this is a brute-force method to mitigate the problem, this should only be a temporary | #       Since this is a brute-force method to mitigate the problem, this should only be a temporary | ||||||
| #       work-around until a better solution on Open edX SEB integration side has been found and applied. | #       work-around until a better solution on Open edX SEB integration side has been found and applied. | ||||||
| #sebserver.webservice.lms.openedx.seb.restriction.push-count=10 | #sebserver.webservice.lms.openedx.seb.restriction.push-count=10 | ||||||
| 
 | 
 | ||||||
| # actuator configuration | # actuator configuration | ||||||
| management.server.port=${server.port} | management.server.port=${server.port} | ||||||
| management.endpoints.web.base-path=/management | management.endpoints.web.base-path=/management | ||||||
| management.endpoints.web.exposure.include=logfile,loggers,jolokia | management.endpoints.web.exposure.include=logfile,loggers,jolokia | ||||||
| management.endpoints.web.path-mapping.jolokia=jmx | management.endpoints.web.path-mapping.jolokia=jmx | ||||||
|  | @ -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
											
										
									
								
							
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 anhefti
						anhefti