From 1867b07664ac25efa78e52302570617ae210e7b0 Mon Sep 17 00:00:00 2001 From: Teo Sarca Date: Wed, 5 Apr 2017 15:27:49 +0300 Subject: [PATCH 1/5] #286 UserSession language endpoint shall always work with JSON values --- .../ui/web/debug/DebugRestController.java | 11 ++++++++ .../de/metas/ui/web/session/UserSession.java | 5 ++++ .../session/UserSessionRestController.java | 25 ++++++++----------- .../ui/web/session/json/JSONUserSession.java | 8 ++++-- 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/main/java/de/metas/ui/web/debug/DebugRestController.java b/src/main/java/de/metas/ui/web/debug/DebugRestController.java index f7f586254..260441dd9 100644 --- a/src/main/java/de/metas/ui/web/debug/DebugRestController.java +++ b/src/main/java/de/metas/ui/web/debug/DebugRestController.java @@ -27,6 +27,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -365,4 +366,14 @@ public void setLoggerLevel( } } + @PutMapping("/language") + public String setAD_Language(@RequestBody final String adLanguage) + { + final String adLanguageOld = userSession.setAD_Language(adLanguage); + final String adLanguageNew = userSession.getAD_Language(); + logResourceValueChanged("AD_Language", adLanguageNew, adLanguageOld); + + return adLanguageNew; + } + } diff --git a/src/main/java/de/metas/ui/web/session/UserSession.java b/src/main/java/de/metas/ui/web/session/UserSession.java index b84c8114c..d334bad11 100644 --- a/src/main/java/de/metas/ui/web/session/UserSession.java +++ b/src/main/java/de/metas/ui/web/session/UserSession.java @@ -284,6 +284,11 @@ public String getAD_Language() { return Env.getAD_Language(getCtx()); } + + public Language getLanguage() + { + return Env.getLanguage(getCtx()); + } public Locale getLocale() { diff --git a/src/main/java/de/metas/ui/web/session/UserSessionRestController.java b/src/main/java/de/metas/ui/web/session/UserSessionRestController.java index 34f785ca2..9c9ac1377 100644 --- a/src/main/java/de/metas/ui/web/session/UserSessionRestController.java +++ b/src/main/java/de/metas/ui/web/session/UserSessionRestController.java @@ -1,5 +1,6 @@ package de.metas.ui.web.session; +import org.compiere.util.Language; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -9,6 +10,7 @@ import de.metas.ui.web.config.WebConfig; import de.metas.ui.web.session.json.JSONUserSession; +import de.metas.ui.web.window.datatypes.json.JSONLookupValue; /* * #%L @@ -41,13 +43,6 @@ public class UserSessionRestController @Autowired private UserSession userSession; - private static final void logResourceValueChanged(final String name, final Object value, final Object valueOld) - { - System.out.println("*********************************************************************************************"); - System.out.println("Changed " + name + " " + valueOld + " -> " + value); - System.out.println("*********************************************************************************************"); - } - @GetMapping public JSONUserSession getAll() { @@ -55,18 +50,18 @@ public JSONUserSession getAll() } @PutMapping("/language") - public String setAD_Language(@RequestBody final String adLanguage) + public JSONLookupValue setLanguage(@RequestBody final JSONLookupValue value) { - final String adLanguageOld = userSession.setAD_Language(adLanguage); - final String adLanguageNew = userSession.getAD_Language(); - logResourceValueChanged("AD_Language", adLanguageNew, adLanguageOld); - - return adLanguageNew; + final String adLanguage = value.getKey(); + userSession.setAD_Language(adLanguage); + + return getLanguage(); } @GetMapping("/language") - public String getAD_Language() + public JSONLookupValue getLanguage() { - return userSession.getAD_Language(); + final Language language = userSession.getLanguage(); + return JSONLookupValue.of(language.getAD_Language(), language.getName()); } } diff --git a/src/main/java/de/metas/ui/web/session/json/JSONUserSession.java b/src/main/java/de/metas/ui/web/session/json/JSONUserSession.java index 15c174a93..8c782e09e 100644 --- a/src/main/java/de/metas/ui/web/session/json/JSONUserSession.java +++ b/src/main/java/de/metas/ui/web/session/json/JSONUserSession.java @@ -1,5 +1,7 @@ package de.metas.ui.web.session.json; +import org.compiere.util.Language; + import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; import com.fasterxml.jackson.annotation.JsonInclude; @@ -7,6 +9,7 @@ import de.metas.ui.web.session.UserSession; import de.metas.ui.web.window.datatypes.json.JSONDate; +import de.metas.ui.web.window.datatypes.json.JSONLookupValue; /* * #%L @@ -50,7 +53,7 @@ public static final JSONUserSession of(final UserSession userSession) private final String rolename; @JsonProperty("language") - private final String language; + private final JSONLookupValue language; @JsonProperty("timeZone") private final String timeZone; @@ -71,7 +74,8 @@ private JSONUserSession(final UserSession userSession) rolename = null; } - language = userSession.getAD_Language(); + final Language language = userSession.getLanguage(); + this.language = JSONLookupValue.of(language.getAD_Language(), language.getName()); timeZone = JSONDate.getCurrentTimeZoneAsJson(); } From 38a2718f504061456e89f6e01ed5b85dad1e017d Mon Sep 17 00:00:00 2001 From: Teo Sarca Date: Thu, 6 Apr 2017 02:18:02 +0300 Subject: [PATCH 2/5] #287 refactor UserSession * introduced UserSessionData session scoped bean where we will store the UserSession's data * UserSession is conceptually a service now --- .../metas/ui/web/WebRestApiApplication.java | 4 +- .../ui/web/debug/DebugRestController.java | 34 +-- .../web/session/InternalUserSessionData.java | 193 +++++++++++++ .../de/metas/ui/web/session/UserSession.java | 256 +++++++----------- .../session/WebRestApiContextProvider.java | 5 + .../window/datatypes/json/JSONOptions.java | 8 +- 6 files changed, 319 insertions(+), 181 deletions(-) create mode 100644 src/main/java/de/metas/ui/web/session/InternalUserSessionData.java diff --git a/src/main/java/de/metas/ui/web/WebRestApiApplication.java b/src/main/java/de/metas/ui/web/WebRestApiApplication.java index 22c94da0f..3ffde31a6 100644 --- a/src/main/java/de/metas/ui/web/WebRestApiApplication.java +++ b/src/main/java/de/metas/ui/web/WebRestApiApplication.java @@ -109,9 +109,9 @@ public static boolean isProfileActive(final String profile) private ApplicationContext applicationContext; @Bean - public Adempiere adempiere() + public Adempiere adempiere(final WebRestApiContextProvider webuiContextProvider) { - Env.setContextProvider(new WebRestApiContextProvider()); + Env.setContextProvider(webuiContextProvider); InterfaceWrapperHelper.registerHelper(new DocumentInterfaceWrapperHelper()); diff --git a/src/main/java/de/metas/ui/web/debug/DebugRestController.java b/src/main/java/de/metas/ui/web/debug/DebugRestController.java index 260441dd9..76518f349 100644 --- a/src/main/java/de/metas/ui/web/debug/DebugRestController.java +++ b/src/main/java/de/metas/ui/web/debug/DebugRestController.java @@ -5,9 +5,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.stream.Stream; import org.adempiere.ad.dao.IQueryStatisticsLogger; import org.adempiere.util.Check; @@ -55,7 +52,6 @@ import de.metas.ui.web.view.IDocumentViewsRepository; import de.metas.ui.web.view.json.JSONDocumentViewResult; import de.metas.ui.web.window.WindowConstants; -import de.metas.ui.web.window.datatypes.json.JSONOptions; import de.metas.ui.web.window.model.DocumentCollection; import de.metas.ui.web.window.model.lookup.LookupDataSourceFactory; import de.metas.ui.web.window.model.sql.SqlDocumentsRepository; @@ -137,23 +133,25 @@ private static final void logResourceValueChanged(final String name, final Objec @RequestMapping(value = "/showColumnNamesForCaption", method = RequestMethod.PUT) public void setShowColumnNamesForCaption(@RequestBody final String showColumnNamesForCaptionStr) { - final boolean showColumnNamesForCaption = DisplayType.toBoolean(showColumnNamesForCaptionStr); - final Object showColumnNamesForCaptionOldObj = userSession.setProperty(JSONOptions.SESSION_ATTR_ShowColumnNamesForCaption, showColumnNamesForCaption); - logResourceValueChanged("showColumnNamesForCaption", showColumnNamesForCaption, showColumnNamesForCaptionOldObj); + final boolean showColumnNamesForCaption_Old = userSession.isShowColumnNamesForCaption(); + userSession.setShowColumnNamesForCaption(DisplayType.toBoolean(showColumnNamesForCaptionStr)); + final boolean showColumnNamesForCaption_New = userSession.isShowColumnNamesForCaption(); + logResourceValueChanged("showColumnNamesForCaption", showColumnNamesForCaption_New, showColumnNamesForCaption_Old); } - @RequestMapping(value = "/disableDeprecatedRestAPI", method = RequestMethod.PUT) - public void setDisableDeprecatedRestAPI(@RequestBody final String disableDeprecatedRestAPIStr) + @RequestMapping(value = "/allowDeprecatedRestAPI", method = RequestMethod.PUT) + public void setAllowDeprecatedRestAPI(@RequestBody final String allowDeprecatedRestAPI) { - final boolean disableDeprecatedRestAPI = DisplayType.toBoolean(disableDeprecatedRestAPIStr); - final Object disableDeprecatedRestAPIOldObj = userSession.setProperty(UserSession.PARAM_DisableDeprecatedRestAPI, disableDeprecatedRestAPI); - logResourceValueChanged(UserSession.PARAM_DisableDeprecatedRestAPI, disableDeprecatedRestAPI, disableDeprecatedRestAPIOldObj); + final boolean allowDeprecatedRestAPI_Old = userSession.isAllowDeprecatedRestAPI(); + userSession.setAllowDeprecatedRestAPI(DisplayType.toBoolean(allowDeprecatedRestAPI)); + final boolean allowDeprecatedRestAPI_New = userSession.isAllowDeprecatedRestAPI(); + logResourceValueChanged("Allow Deprecated REST API", allowDeprecatedRestAPI_New, allowDeprecatedRestAPI_Old); } @RequestMapping(value = "/disableDeprecatedRestAPI", method = RequestMethod.GET) - public boolean isDisableDeprecatedRestAPI() + public boolean isAllowDeprecatedRestAPI() { - return userSession.getPropertyAsBoolean(UserSession.PARAM_DisableDeprecatedRestAPI, false); + return userSession.isAllowDeprecatedRestAPI(); } @RequestMapping(value = "/traceSqlQueries", method = RequestMethod.GET) @@ -316,14 +314,6 @@ public String getLoggerName() { return loggerName; } - - public static final LoggingModule forLoggerName(final String loggerName) - { - return Stream.of(values()) - .filter(value -> Objects.equals(value.loggerName, loggerName)) - .findFirst() - .orElseThrow(() -> new NoSuchElementException(loggerName)); - } } @GetMapping("/logger/_setLevel/{level}") diff --git a/src/main/java/de/metas/ui/web/session/InternalUserSessionData.java b/src/main/java/de/metas/ui/web/session/InternalUserSessionData.java new file mode 100644 index 000000000..a79f67222 --- /dev/null +++ b/src/main/java/de/metas/ui/web/session/InternalUserSessionData.java @@ -0,0 +1,193 @@ +package de.metas.ui.web.session; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Locale; +import java.util.Properties; + +import org.compiere.util.Env; +import org.compiere.util.Language; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.SessionScope; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import com.google.common.base.MoreObjects; + +import de.metas.ui.web.base.session.UserPreference; + +/* + * #%L + * metasfresh-webui-api + * %% + * Copyright (C) 2017 metas GmbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +/** + * Internal {@link UserSession} data. + * + * NOTE: it's here and not inside UserSession class because it seems spring could not discover it + * + * @author metas-dev + * + */ +@Component +@Primary +@SessionScope(proxyMode = ScopedProxyMode.TARGET_CLASS) +@lombok.Data +/* package */ class InternalUserSessionData implements Serializable, InitializingBean +{ + private static final long serialVersionUID = 4046535476486036184L; + + // + // Actual session data + // NOTE: make sure none of those fields are "final" because this will prevent deserialization + private String sessionId = null; + private UserPreference userPreference = null; + private boolean loggedIn = false; + private Locale locale = null; + + // + // Defaults + @Value("${metasfresh.webui.debug.showColumnNamesForCaption:false}") + private boolean defaultShowColumnNamesForCaption; + private boolean showColumnNamesForCaption; + // + @Value("${metasfresh.webui.debug.allowDeprecatedRestAPI:false}") + private boolean defaultAllowDeprecatedRestAPI; + private boolean allowDeprecatedRestAPI; + + // + public InternalUserSessionData() + { + final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + sessionId = requestAttributes.getSessionId(); + + userPreference = new UserPreference(); + loggedIn = false; + + // + // Set initial language + try + { + final Locale locale = LocaleContextHolder.getLocale(); + final Language language = Language.getLanguage(locale); + verifyLanguageAndSet(language); + } + catch (final Exception e) + { + UserSession.logger.warn("Failed setting the language, but moving on", e); + } + + UserSession.logger.trace("User session created: {}", this); + } + + @Override + public void afterPropertiesSet() throws Exception + { + // + // Set initial properties + setShowColumnNamesForCaption(defaultShowColumnNamesForCaption); + setAllowDeprecatedRestAPI(defaultAllowDeprecatedRestAPI); + } + + @Override + public String toString() + { + return MoreObjects.toStringHelper(this) + .omitNullValues() + .add("sessionId", sessionId) + .add("loggedIn", loggedIn) + .add("locale", locale) + .add("userPreferences", userPreference) + .toString(); + } + + private void writeObject(final java.io.ObjectOutputStream out) throws IOException + { + out.defaultWriteObject(); + + UserSession.logger.trace("User session serialized: {}", this); + } + + private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException + { + in.defaultReadObject(); + + UserSession.logger.trace("User session deserialized: {}", this); + } + + Properties getCtx() + { + return Env.getCtx(); + } + + public int getAD_Client_ID() + { + return Env.getAD_Client_ID(getCtx()); + } + + public int getAD_User_ID() + { + return Env.getAD_User_ID(getCtx()); + } + + public String getUserName() + { + return Env.getContext(getCtx(), Env.CTXNAME_AD_User_Name); + } + + public String getRoleName() + { + return Env.getContext(getCtx(), Env.CTXNAME_AD_Role_Name); + } + + String getAdLanguage() + { + return Env.getContext(getCtx(), Env.CTXNAME_AD_Language); + } + + Language getLanguage() + { + return Env.getLanguage(getCtx()); + } + + String verifyLanguageAndSet(final Language lang) + { + final Properties ctx = getCtx(); + final String adLanguageOld = Env.getContext(ctx, Env.CTXNAME_AD_Language); + + // + // Check the language (and update it if needed) + Env.verifyLanguage(lang); + + // + // Actual update + final String adLanguageNew = lang.getAD_Language(); + Env.setContext(ctx, Env.CTXNAME_AD_Language, adLanguageNew); + this.locale = lang.getLocale(); + UserSession.logger.info("Changed AD_Language: {} -> {}, {}", adLanguageOld, adLanguageNew, lang); + + return adLanguageOld; + } +} diff --git a/src/main/java/de/metas/ui/web/session/UserSession.java b/src/main/java/de/metas/ui/web/session/UserSession.java index d334bad11..7ffe6472a 100644 --- a/src/main/java/de/metas/ui/web/session/UserSession.java +++ b/src/main/java/de/metas/ui/web/session/UserSession.java @@ -1,11 +1,8 @@ package de.metas.ui.web.session; -import java.io.IOException; -import java.io.Serializable; import java.util.Locale; -import java.util.Map; +import java.util.Objects; import java.util.Properties; -import java.util.concurrent.ConcurrentHashMap; import org.adempiere.ad.security.IUserRolePermissions; import org.adempiere.ad.security.UserRolePermissionsKey; @@ -16,23 +13,17 @@ import org.compiere.util.Evaluatees; import org.compiere.util.Language; import org.slf4j.Logger; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Scope; -import org.springframework.context.annotation.ScopedProxyMode; -import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.stereotype.Component; -import org.springframework.web.context.request.RequestAttributes; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; import org.springframework.web.context.request.RequestContextHolder; -import com.google.common.base.MoreObjects; - import de.metas.logging.LogManager; import de.metas.ui.web.base.session.UserPreference; import de.metas.ui.web.exceptions.DeprecatedRestAPINotAllowedException; import de.metas.ui.web.login.exceptions.AlreadyLoggedInException; import de.metas.ui.web.login.exceptions.NotLoggedInException; -import de.metas.ui.web.window.datatypes.json.JSONOptions; +import lombok.NonNull; /* * #%L @@ -47,24 +38,28 @@ * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public - * License along with this program. If not, see + * License along with this program. If not, see * . * #L% */ -@Component -@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) -public class UserSession implements InitializingBean, Serializable +/** + * User Session service + * + * @author metas-dev + */ +@Service +public class UserSession { - private static final long serialVersionUID = -1478300848154458479L; - /** * Gets current {@link UserSession} if any * + * NOTE: please use this method only if there is no other way to get the {@link UserSession} + * * @return {@link UserSession} or null */ public static UserSession getCurrentOrNull() @@ -78,20 +73,28 @@ public static UserSession getCurrentOrNull() } // - UserSession userSession = _userSession; + UserSession userSession = _staticUserSession; if (userSession == null) { synchronized (UserSession.class) { - if (_userSession == null) + if (_staticUserSession == null) { - userSession = _userSession = Adempiere.getSpringApplicationContext().getBean(UserSession.class); + userSession = _staticUserSession = Adempiere.getSpringApplicationContext().getBean(UserSession.class); } } } return userSession; } - + + /** + * Gets current {@link UserSession}. + * + * NOTE: please use this method only if there is no other way to get the {@link UserSession} + * + * @return user session; never returns null + * @throws NotLoggedInException + */ public static UserSession getCurrent() throws NotLoggedInException { final UserSession userSession = getCurrentOrNull(); @@ -102,88 +105,26 @@ public static UserSession getCurrent() throws NotLoggedInException return userSession; } - private static final transient Logger logger = LogManager.getLogger(UserSession.class); + // services + static final transient Logger logger = LogManager.getLogger(UserSession.class); + private final transient ApplicationEventPublisher eventPublisher; - private static UserSession _userSession = null; + private static UserSession _staticUserSession = null; - // NOTE: make sure none of those fields are "final" because this will prevent deserialization - private String sessionId = null; - private UserPreference userPreference; - private boolean loggedIn; - private Locale locale; + @Autowired + private InternalUserSessionData data; // session scoped - private final Map properties = new ConcurrentHashMap<>(); - - @Value("${metasfresh.webui.debug.showColumnNamesForCaption:false}") - private boolean default_showColumnNamesForCaption; - - public static final String PARAM_DisableDeprecatedRestAPI = "metasfresh.webui.debug.DisableDeprecatedRestAPI"; - @Value("${" + PARAM_DisableDeprecatedRestAPI + ":true}") - private boolean default_disableDeprecatedRestAPI; - - public UserSession() + @Autowired + public UserSession(final ApplicationEventPublisher eventPublisher) { super(); - final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); - sessionId = requestAttributes.getSessionId(); - - userPreference = new UserPreference(); - loggedIn = false; - // - // Set initial language - try - { - final Locale locale = LocaleContextHolder.getLocale(); - final Language language = Language.getLanguage(locale); - setLanguage(language); - } - catch (final Exception e) - { - logger.warn("Failed setting the language, but moving on", e); - } - - logger.trace("User session created: {}", this); - } - - @Override - public String toString() - { - return MoreObjects.toStringHelper(this) - .omitNullValues() - .add("sessionId", sessionId) - .add("loggedIn", loggedIn) - .add("locale", locale) - .add("userPreferences", userPreference) - .toString(); - } - - @Override - public void afterPropertiesSet() throws Exception - { - // - // Set initial properties - properties.put(JSONOptions.SESSION_ATTR_ShowColumnNamesForCaption, default_showColumnNamesForCaption); - properties.put(PARAM_DisableDeprecatedRestAPI, default_disableDeprecatedRestAPI); - } - - private void writeObject(final java.io.ObjectOutputStream out) throws IOException - { - out.defaultWriteObject(); - - logger.trace("User session serialized: {}", this); - } - - private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException - { - in.defaultReadObject(); - - logger.trace("User session deserialized: {}", this); + this.eventPublisher = eventPublisher; } public String getSessionId() { - return sessionId; + return data.getSessionId(); } /** @@ -192,48 +133,49 @@ public String getSessionId() */ public Properties getCtx() { - return Env.getCtx(); + return data.getCtx(); } public Evaluatee toEvaluatee() { - return Evaluatees.ofCtx(getCtx()); + return Evaluatees.ofCtx(data.getCtx()); } public UserPreference getUserPreference() { - return userPreference; + return data.getUserPreference(); } public boolean isLoggedIn() { - return loggedIn; + return data.isLoggedIn(); } public void setLoggedIn(final boolean loggedIn) { - if (this.loggedIn == loggedIn) + final boolean currentlyLoggedIn = data.isLoggedIn(); + if (currentlyLoggedIn == loggedIn) { return; } if (loggedIn) { - this.loggedIn = true; - userPreference.loadPreference(getCtx()); - logger.trace("User session logged in: {}", this); + data.setLoggedIn(true); + data.getUserPreference().loadPreference(data.getCtx()); + logger.trace("User session logged in: {}", data); } else { - this.loggedIn = false; - userPreference = new UserPreference(); - logger.trace("User session logged out: {}", this); + data.setLoggedIn(false); + data.setUserPreference(new UserPreference()); + logger.trace("User session logged out: {}", data); } } public void assertLoggedIn() { - if (!isLoggedIn()) + if (!data.isLoggedIn()) { throw new NotLoggedInException(); } @@ -241,13 +183,17 @@ public void assertLoggedIn() public void assertNotLoggedIn() { - if (isLoggedIn()) + if (data.isLoggedIn()) { throw new AlreadyLoggedInException(); } } /** + * Sets user preferred language. + * + * Fires {@link LanguagedChangedEvent}. + * * @param adLanguage * @return old AD_Language */ @@ -255,99 +201,105 @@ public String setAD_Language(final String adLanguage) { Check.assumeNotEmpty(adLanguage, "adLanguage is not empty"); final Language lang = Language.getLanguage(adLanguage); - return setLanguage(lang); - } - - private String setLanguage(final Language lang) - { - final Properties ctx = getCtx(); - Check.assumeNotNull(ctx, "Parameter ctx is not null"); - - final String adLanguageOld = Env.getContext(ctx, Env.CTXNAME_AD_Language); - - Env.verifyLanguage(lang); - final String adLanguageNew = lang.getAD_Language(); - - Env.setContext(ctx, Env.CTXNAME_AD_Language, adLanguageNew); - locale = lang.getLocale(); + final String adLanguageOld = data.verifyLanguageAndSet(lang); + final String adLanguageNew = data.getAdLanguage(); logger.info("Changed AD_Language: {} -> {}, {}", adLanguageOld, adLanguageNew, lang); + // Fire event + if (!Objects.equals(adLanguageOld, adLanguageNew)) + { + eventPublisher.publishEvent(new LanguagedChangedEvent(adLanguageNew, getAD_User_ID())); + } + return adLanguageOld; } public int getAD_Client_ID() { - return Env.getAD_Client_ID(getCtx()); + return data.getAD_Client_ID(); } public String getAD_Language() { - return Env.getAD_Language(getCtx()); + return data.getAdLanguage(); } - + public Language getLanguage() { - return Env.getLanguage(getCtx()); + return data.getLanguage(); } public Locale getLocale() { - return locale; + return data.getLocale(); } public int getAD_User_ID() { - return Env.getAD_User_ID(getCtx()); + return data.getAD_User_ID(); } public String getUserName() { - return Env.getContext(getCtx(), Env.CTXNAME_AD_User_Name); + return data.getUserName(); } - + public String getRoleName() { - return Env.getContext(getCtx(), Env.CTXNAME_AD_Role_Name); + return data.getRoleName(); } public UserRolePermissionsKey getUserRolePermissionsKey() { // TODO: cache the permissions key - return UserRolePermissionsKey.of(getCtx()); + return UserRolePermissionsKey.of(data.getCtx()); } public IUserRolePermissions getUserRolePermissions() { - return Env.getUserRolePermissions(getCtx()); + return Env.getUserRolePermissions(data.getCtx()); } - public Object setProperty(final String name, final T value) + public void assertDeprecatedRestAPIAllowed() { - return properties.put(name, value); + if (!data.isAllowDeprecatedRestAPI()) + { + throw new DeprecatedRestAPINotAllowedException(); + } } - public T getProperty(final String name) + public boolean isAllowDeprecatedRestAPI() { - final Object valueObj = properties.get(name); + return data.isAllowDeprecatedRestAPI(); + } - @SuppressWarnings("unchecked") - final T valueConv = (T)valueObj; + public void setAllowDeprecatedRestAPI(final boolean allowDeprecatedRestAPI) + { + data.setAllowDeprecatedRestAPI(allowDeprecatedRestAPI); + } - return valueConv; + public boolean isShowColumnNamesForCaption() + { + return data.isShowColumnNamesForCaption(); } - public boolean getPropertyAsBoolean(final String name, final boolean defaultValue) + public void setShowColumnNamesForCaption(final boolean showColumnNamesForCaption) { - final Boolean value = getProperty(name); - return value != null ? value : defaultValue; + data.setShowColumnNamesForCaption(showColumnNamesForCaption); } - public void assertDeprecatedRestAPIAllowed() + /** + * Event fired when the user language was changed. + * Usually it is user triggered. + * + * @author metas-dev + * + */ + @lombok.Value + public static class LanguagedChangedEvent { - final boolean disableDeprecatedRestAI = getPropertyAsBoolean(PARAM_DisableDeprecatedRestAPI, false); - if (disableDeprecatedRestAI) - { - throw new DeprecatedRestAPINotAllowedException(); - } + @NonNull + private final String adLanguage; + private final int adUserId; } } diff --git a/src/main/java/de/metas/ui/web/session/WebRestApiContextProvider.java b/src/main/java/de/metas/ui/web/session/WebRestApiContextProvider.java index 6e6f24fc8..28159c78a 100644 --- a/src/main/java/de/metas/ui/web/session/WebRestApiContextProvider.java +++ b/src/main/java/de/metas/ui/web/session/WebRestApiContextProvider.java @@ -10,10 +10,13 @@ import org.adempiere.util.lang.NullAutoCloseable; import org.compiere.util.Env; import org.slf4j.Logger; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import de.metas.logging.LogManager; +import de.metas.ui.web.WebRestApiApplication; /* * #%L @@ -38,6 +41,8 @@ */ @SuppressWarnings("serial") +@Component +@Profile(WebRestApiApplication.PROFILE_Webui) public final class WebRestApiContextProvider implements ContextProvider, Serializable { private static final Logger logger = LogManager.getLogger(WebRestApiContextProvider.class); diff --git a/src/main/java/de/metas/ui/web/window/datatypes/json/JSONOptions.java b/src/main/java/de/metas/ui/web/window/datatypes/json/JSONOptions.java index cb6d6f86b..481dc6906 100644 --- a/src/main/java/de/metas/ui/web/window/datatypes/json/JSONOptions.java +++ b/src/main/java/de/metas/ui/web/window/datatypes/json/JSONOptions.java @@ -58,8 +58,6 @@ public static final JSONOptions of(final UserSession userSession) public static final String DEBUG_ATTRNAME = "json-options"; - public static final String SESSION_ATTR_ShowColumnNamesForCaption = JSONOptions.class.getName() + ".ShowColumnNamesForCaption"; - private final String adLanguage; private final boolean showAdvancedFields; private final String dataFieldsListStr; @@ -231,7 +229,7 @@ private JSONOptions(final Builder builder) adLanguage = builder.getAD_Language(); showAdvancedFields = builder.showAdvancedFields; dataFieldsListStr = Strings.emptyToNull(builder.dataFieldsListStr); - debugShowColumnNamesForCaption = builder.getPropertyAsBoolean(SESSION_ATTR_ShowColumnNamesForCaption, false); + debugShowColumnNamesForCaption = builder.isShowColumnNamesForCaption(false); newRecordDescriptorsProvider = builder.getNewRecordDescriptorsProvider(); } @@ -381,11 +379,11 @@ public Builder setDataFieldsList(final String dataFieldsListStr) return this; } - private boolean getPropertyAsBoolean(final String propertyName, final boolean defaultValue) + private boolean isShowColumnNamesForCaption(final boolean defaultValue) { if(_userSession != null) { - return _userSession.getPropertyAsBoolean(propertyName, defaultValue); + return _userSession.isShowColumnNamesForCaption(); } return defaultValue; From 22f3b227f339f3746ddc60d6b1b7686b55fb2590 Mon Sep 17 00:00:00 2001 From: Teo Sarca Date: Thu, 6 Apr 2017 02:18:29 +0300 Subject: [PATCH 3/5] #287 when user language is changed update notifications's queue language --- .../notification/UserNotificationsQueue.java | 9 ++++++- .../UserNotificationsService.java | 24 ++++++++++++++----- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/main/java/de/metas/ui/web/notification/UserNotificationsQueue.java b/src/main/java/de/metas/ui/web/notification/UserNotificationsQueue.java index 4dadec688..9197a8244 100644 --- a/src/main/java/de/metas/ui/web/notification/UserNotificationsQueue.java +++ b/src/main/java/de/metas/ui/web/notification/UserNotificationsQueue.java @@ -12,6 +12,7 @@ import org.springframework.messaging.simp.SimpMessagingTemplate; import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import de.metas.logging.LogManager; @@ -46,7 +47,7 @@ public class UserNotificationsQueue private static final Logger logger = LogManager.getLogger(UserNotificationsQueue.class); private final int adUserId; - private final String adLanguage; + private String adLanguage; private final SimpMessagingTemplate websocketMessagingTemplate; private final String websocketEndpoint; @@ -199,4 +200,10 @@ public int getUnreadCount() return unreadCount.get(); } + public void setLanguage(final String adLanguage) + { + Preconditions.checkNotNull(adLanguage, "language"); + this.adLanguage = adLanguage; + } + } diff --git a/src/main/java/de/metas/ui/web/notification/UserNotificationsService.java b/src/main/java/de/metas/ui/web/notification/UserNotificationsService.java index e29f70bf8..a8a41fadb 100644 --- a/src/main/java/de/metas/ui/web/notification/UserNotificationsService.java +++ b/src/main/java/de/metas/ui/web/notification/UserNotificationsService.java @@ -6,6 +6,7 @@ import org.adempiere.util.Services; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Service; @@ -13,6 +14,7 @@ import de.metas.event.IEventBus; import de.metas.event.IEventBusFactory; import de.metas.logging.LogManager; +import de.metas.ui.web.session.UserSession.LanguagedChangedEvent; /* * #%L @@ -27,11 +29,11 @@ * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public - * License along with this program. If not, see + * License along with this program. If not, see * . * #L% */ @@ -48,6 +50,16 @@ public class UserNotificationsService private final AtomicBoolean subscribedToEventBus = new AtomicBoolean(false); + @EventListener + private void onUserLanguageChanged(final LanguagedChangedEvent event) + { + final UserNotificationsQueue notificationsQueue = adUserId2notifications.get(event.getAdUserId()); + if(notificationsQueue != null) + { + notificationsQueue.setLanguage(event.getAdLanguage()); + } + } + private void subscribeToEventTopicsIfNeeded() { if (!subscribedToEventBus.getAndSet(true)) @@ -63,7 +75,7 @@ private void subscribeToEventTopicsIfNeeded() public synchronized void enableForSession(final String sessionId, final int adUserId, final String adLanguage) { logger.trace("Enabling for sessionId={}, adUserId={}, adLanguage={}", sessionId, adUserId, adLanguage); - + final UserNotificationsQueue notificationsQueue = adUserId2notifications.computeIfAbsent(adUserId, theSessionId -> new UserNotificationsQueue(adUserId, adLanguage, websocketMessagingTemplate)); notificationsQueue.addActiveSessionId(sessionId); @@ -99,7 +111,7 @@ public UserNotificationsList getNotifications(final int adUserId, final int limi private void forwardEventToNotificationsQueues(final IEventBus eventBus, final Event event) { logger.trace("Got event from {}: {}", eventBus, event); - + final UserNotification notification = UserNotification.of(event); if (event.isAllRecipients()) { @@ -112,12 +124,12 @@ private void forwardEventToNotificationsQueues(final IEventBus eventBus, final E for (final int recipientUserId : event.getRecipientUserIds()) { final UserNotificationsQueue notificationsQueue = adUserId2notifications.get(recipientUserId); - if(notificationsQueue == null) + if (notificationsQueue == null) { logger.trace("No notification queue was found for recipientUserId={}", recipientUserId); continue; } - + notificationsQueue.addNotification(notification.copy()); } } From 1aff2294146b86756e74f84fb9bd572e8147ad1c Mon Sep 17 00:00:00 2001 From: Teo Sarca Date: Thu, 6 Apr 2017 13:56:26 +0300 Subject: [PATCH 4/5] #288 JSONDocument serialize the includedTabsInfo as map --- .../de/metas/ui/web/window/datatypes/json/JSONDocument.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/metas/ui/web/window/datatypes/json/JSONDocument.java b/src/main/java/de/metas/ui/web/window/datatypes/json/JSONDocument.java index a5847e8a7..5b27c59b8 100644 --- a/src/main/java/de/metas/ui/web/window/datatypes/json/JSONDocument.java +++ b/src/main/java/de/metas/ui/web/window/datatypes/json/JSONDocument.java @@ -308,9 +308,10 @@ public static JSONDocument ofDocumentView(final IDocumentView documentView) @JsonSerialize(using = JsonMapAsValuesListSerializer.class) private Map fieldsByName; + /** {@link JSONIncludedTabInfo}s indexed by tabId */ @JsonProperty("includedTabsInfo") @JsonInclude(JsonInclude.Include.NON_EMPTY) - @JsonSerialize(using = JsonMapAsValuesListSerializer.class) + // @JsonSerialize(using = JsonMapAsValuesListSerializer.class) // serialize as Map (see #288) private Map includedTabsInfo; @JsonProperty("includedDocuments") From a181e7b59e079019c6676ff65987ebe3cd420c3f Mon Sep 17 00:00:00 2001 From: Teo Sarca Date: Thu, 6 Apr 2017 17:46:56 +0300 Subject: [PATCH 5/5] #289 New/Delete buttons missing when a document was initially loaded solution: update the included details status after loading the document --- src/main/java/de/metas/ui/web/window/model/Document.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/de/metas/ui/web/window/model/Document.java b/src/main/java/de/metas/ui/web/window/model/Document.java index 5ab18e6e6..216d269f5 100644 --- a/src/main/java/de/metas/ui/web/window/model/Document.java +++ b/src/main/java/de/metas/ui/web/window/model/Document.java @@ -1841,6 +1841,7 @@ public Document build() // // Update document's valid status document.checkAndGetValidStatus(OnValidStatusChanged.DO_NOTHING); + document.updateIncludedDetailsStatus(); // // Update document's save status