details: https://code.openbravo.com/erp/devel/pi/rev/2c0b8265b9b1 changeset: 32514:2c0b8265b9b1 user: Asier Lostalé <asier.lostale <at> openbravo.com> date: Fri Jul 28 11:04:38 2017 +0200 summary: fixed 36359: can get stale cached WS user context
When invoking WS, user context is cached in order to prevent its (heavy) creation on every request. This cache was not properly invalidated. Changes to improve this invalidation: 1. Purge cache before trying to retrieve elements, not afterwards. 2. Cache elements already had a valid period of 30 minutes, but this period was restarted everytime the element was used. Now the period starts when the context is cached and it never gets reset. 3. In addition to this expiration time, ADUser.updated was checked when purging cache, and if it was modified since it was cached, it was removed. This implementation had some problems, that are now solved: * ADUser is not the only (nor even the main) entity that when modified can change permissions in cached contexts, therefore modifications in any other entity (ie. Organization) were not triggering cache invalidataion. A new EventObserver taking care of all entities that participate in the permissions definition has been created to invalidate cache. * For every WS request ADUser.updated for every cached context was checked querying DB, adding some performance overhead (specially when many contexts where already cached). As now invalidation occurs in memory, it is not necessary to query DB anymore. This mechanism provides a fully accurate cache when working in a single node. In case of clustered environments, it is possible that caches kept in a node different than the one modifying permissions to continue returning stale contexts, but, as per topic 2, this is restricted to, at most, 30 minutes. details: https://code.openbravo.com/erp/devel/pi/rev/93c5e55ef8fb changeset: 32515:93c5e55ef8fb user: Asier Lostalé <asier.lostale <at> openbravo.com> date: Thu Jul 27 08:38:09 2017 +0200 summary: [wsah] refactor to split in methods details: https://code.openbravo.com/erp/devel/pi/rev/87a29aed13a1 changeset: 32516:87a29aed13a1 user: Asier Lostalé <asier.lostale <at> openbravo.com> date: Thu Jul 27 08:52:27 2017 +0200 summary: [wsah] added some log details: https://code.openbravo.com/erp/devel/pi/rev/a3e04cd55526 changeset: 32517:a3e04cd55526 user: Asier Lostalé <asier.lostale <at> openbravo.com> date: Thu Jul 27 09:41:55 2017 +0200 summary: [wsah] added adcs.getWindow details: https://code.openbravo.com/erp/devel/pi/rev/dd2f17bee637 changeset: 32518:dd2f17bee637 user: Asier Lostalé <asier.lostale <at> openbravo.com> date: Thu Jul 27 09:42:22 2017 +0200 summary: [adcs] get window from adcs, don't iterate over all roles to get current one details: https://code.openbravo.com/erp/devel/pi/rev/8325bece5e17 changeset: 32519:8325bece5e17 user: Asier Lostalé <asier.lostale <at> openbravo.com> date: Thu Jul 27 10:11:15 2017 +0200 summary: [wsah] prevent more queries + dal initialization using adcs details: https://code.openbravo.com/erp/devel/pi/rev/ffd6bed38f34 changeset: 32520:ffd6bed38f34 user: Asier Lostalé <asier.lostale <at> openbravo.com> date: Thu Jul 27 10:40:46 2017 +0200 summary: [wsah] minor code clean up details: https://code.openbravo.com/erp/devel/pi/rev/3ec690db55fe changeset: 32521:3ec690db55fe user: Asier Lostalé <asier.lostale <at> openbravo.com> date: Mon Jul 31 09:00:49 2017 +0200 summary: fixed bug 36172: WindowSettingsActionHandler underperformance Several changes in WindowSettingsActionHandler performance which improve both processing time and heap memory consumption: - Use ADCS to query window/tab definition in order to prevent DB queries - Optimized DAL queries so that only relevant records are queries from DB and loaded in memory diffstat: modules/org.openbravo.client.application/src/org/openbravo/client/application/WindowSettingsActionHandler.java | 401 +++++---- modules/org.openbravo.client.application/src/org/openbravo/client/application/window/ApplicationDictionaryCachedStructures.java | 39 +- src/org/openbravo/service/UserCtxCacheObserver.java | 66 + src/org/openbravo/service/web/UserContextCache.java | 54 +- 4 files changed, 352 insertions(+), 208 deletions(-) diffs (truncated from 752 to 300 lines): diff -r c5f112181318 -r 3ec690db55fe modules/org.openbravo.client.application/src/org/openbravo/client/application/WindowSettingsActionHandler.java --- a/modules/org.openbravo.client.application/src/org/openbravo/client/application/WindowSettingsActionHandler.java Thu Jul 20 17:54:57 2017 -0400 +++ b/modules/org.openbravo.client.application/src/org/openbravo/client/application/WindowSettingsActionHandler.java Mon Jul 31 09:00:49 2017 +0200 @@ -28,15 +28,17 @@ import javax.enterprise.inject.Any; import javax.enterprise.inject.Instance; import javax.inject.Inject; +import javax.servlet.ServletException; -import org.apache.log4j.Logger; import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; import org.openbravo.base.exception.OBException; import org.openbravo.base.model.Entity; import org.openbravo.base.model.ModelProvider; import org.openbravo.base.model.Property; import org.openbravo.client.application.personalization.PersonalizationHandler; +import org.openbravo.client.application.window.ApplicationDictionaryCachedStructures; import org.openbravo.client.kernel.BaseActionHandler; import org.openbravo.client.kernel.KernelUtils; import org.openbravo.client.kernel.StaticResourceComponent; @@ -47,11 +49,12 @@ import org.openbravo.erpCommon.utility.PropertyException; import org.openbravo.model.ad.access.FieldAccess; import org.openbravo.model.ad.access.TabAccess; -import org.openbravo.model.ad.access.WindowAccess; import org.openbravo.model.ad.ui.Field; import org.openbravo.model.ad.ui.Tab; import org.openbravo.model.ad.ui.Window; import org.openbravo.service.db.DalConnectionProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Computes different settings which may be user/role specific for a certain window. @@ -61,206 +64,254 @@ */ @ApplicationScoped public class WindowSettingsActionHandler extends BaseActionHandler { - private static final Logger log4j = Logger.getLogger(WindowSettingsActionHandler.class); + private static final Logger log = LoggerFactory.getLogger(WindowSettingsActionHandler.class); public static final String EXTRA_CALLBACKS = "extraCallbacks"; @Inject private PersonalizationHandler personalizationHandler; @Inject + private ApplicationDictionaryCachedStructures adcs; + + @Inject @Any private Instance<ExtraWindowSettingsInjector> extraSettings; + @Override protected JSONObject execute(Map<String, Object> parameters, String data) { + long t = System.currentTimeMillis(); + final String windowId = (String) parameters.get("windowId"); try { OBContext.setAdminMode(); - final String windowId = (String) parameters.get("windowId"); - final Window window = OBDal.getInstance().get(Window.class, windowId); - final String roleId = OBContext.getOBContext().getRole().getId(); - final DalConnectionProvider dalConnectionProvider = new DalConnectionProvider(false); - final JSONObject jsonUIPattern = new JSONObject(); - final String windowType = window.getWindowType(); - for (Tab tab : window.getADTabList()) { - final boolean readOnlyAccess = org.openbravo.erpCommon.utility.WindowAccessData - .hasReadOnlyAccess(dalConnectionProvider, roleId, tab.getId()); - String uiPattern = readOnlyAccess ? "RO" : tab.getUIPattern(); - // window should be read only when is assigned with a table defined as a view - if (!"RO".equals(uiPattern) && ("T".equals(windowType) || "M".equals(windowType)) - && tab.getTable().isView()) { - log4j.warn("Tab \"" + tab.getName() - + "\" is set to read only because is assigned with a table defined as a view."); - uiPattern = "RO"; - } - jsonUIPattern.put(tab.getId(), uiPattern); - } + final Window window = adcs.getWindow(windowId); + final JSONObject json = new JSONObject(); - json.put("uiPattern", jsonUIPattern); - final String autoSaveStr = Preferences.getPreferenceValue("Autosave", false, OBContext - .getOBContext().getCurrentClient(), OBContext.getOBContext().getCurrentOrganization(), - OBContext.getOBContext().getUser(), OBContext.getOBContext().getRole(), window); - json.put("autoSave", Preferences.YES.equals(autoSaveStr)); + json.put("uiPattern", getUIPattern(window)); + json.put("autoSave", getBooleanPreference(window, "Autosave", true)); try { json.put("personalization", personalizationHandler.getPersonalizationForWindow(window)); - } catch (Throwable t) { + } catch (Throwable e) { // be robust about errors in the personalization settings - log4j.error("Error for window " + window, t); + log.error("Error for window: " + window + " - role: " + OBContext.getOBContext().getRole(), + e); } - final String showConfirmationStr = Preferences.getPreferenceValue("ShowConfirmationDefault", - false, OBContext.getOBContext().getCurrentClient(), OBContext.getOBContext() - .getCurrentOrganization(), OBContext.getOBContext().getUser(), OBContext - .getOBContext().getRole(), window); - json.put("showAutoSaveConfirmation", Preferences.YES.equals(showConfirmationStr)); - - // Field Level Roles - final JSONArray tabs = new JSONArray(); - json.put("tabs", tabs); - for (WindowAccess winAccess : window.getADWindowAccessList()) { - if (winAccess.isActive() && winAccess.getRole().getId().equals(roleId)) { - for (TabAccess tabAccess : winAccess.getADTabAccessList()) { - if (tabAccess.isActive()) { - boolean tabEditable = tabAccess.isEditableField(); - final Entity entity = ModelProvider.getInstance().getEntityByTableId( - tabAccess.getTab().getTable().getId()); - final JSONObject jTab = new JSONObject(); - tabs.put(jTab); - jTab.put("tabId", tabAccess.getTab().getId()); - jTab.put("updatable", tabEditable); - final JSONObject jFields = new JSONObject(); - jTab.put("fields", jFields); - final Set<String> fields = new TreeSet<String>(); - for (Field field : tabAccess.getTab().getADFieldList()) { - if (!field.isReadOnly() && !field.isShownInStatusBar() - && field.getColumn().isUpdatable()) { - final Property property = KernelUtils.getProperty(entity, field); - if (property != null) { - fields.add(property.getName()); - } - } - } - for (FieldAccess fieldAccess : tabAccess.getADFieldAccessList()) { - if (fieldAccess.isActive()) { - final Property property = KernelUtils.getProperty(entity, fieldAccess.getField()); - if (property != null) { - final String name = KernelUtils.getProperty(entity, fieldAccess.getField()) - .getName(); - if (fields.contains(name)) { - jFields.put(name, fieldAccess.isEditableField()); - fields.remove(name); - } - } - } - } - for (String name : fields) { - jFields.put(name, tabEditable); - } - } - } - } - } - - // processes can be secured in 3 ways: - // - Secured preference is set: explicit grant is required - // - Process is marked as requiresExplicitAccessPermission: explicit grant is required - // - None of the above: permission is inherited from window - boolean securedProcess = false; - try { - securedProcess = Preferences.YES.equals(Preferences.getPreferenceValue("SecuredProcess", - true, OBContext.getOBContext().getCurrentClient(), OBContext.getOBContext() - .getCurrentOrganization(), OBContext.getOBContext().getUser(), OBContext - .getOBContext().getRole(), window)); - } catch (PropertyException e) { - // do nothing, property is not set so securedProcess is false - } - - String restrictedProcessesQry = " as f where tab.window = :window and tab.active = true and ("; - - restrictedProcessesQry += "(column.oBUIAPPProcess is not null"; - if (!securedProcess) { - // not secured, restrict only those that require explicit permission - // subquery required to prevent inner join due to compound path check - // (process.requiresExplicitAccessPermission) - restrictedProcessesQry += " and exists (select 1 from OBUIAPP_Process p where p = f.column.oBUIAPPProcess and requiresExplicitAccessPermission = true) "; - } - restrictedProcessesQry += " and not exists (select 1 from " // - + " OBUIAPP_Process_Access a" - + " where a.obuiappProcess = f.column.oBUIAPPProcess" - + " and a.role.id = :role and a.active=true))"// - - + " or (column.process is not null "; - - if (!securedProcess) { - // not secured, restrict only those that require explicit permission - // subquery required to prevent inner join due to compound path check - // (process.requiresExplicitAccessPermission) - restrictedProcessesQry += " and exists (select 1 from ADProcess p where p = f.column.process and requiresExplicitAccessPermission = true) "; - } - - restrictedProcessesQry += " and not exists (select 1 from ADProcessAccess a where a.process = f.column.process and " - + " a.role.id = :role and a.active=true))"; - - restrictedProcessesQry += ") order by f.tab"; - - OBQuery<Field> q = OBDal.getInstance().createQuery(Field.class, restrictedProcessesQry); - q.setNamedParameter("window", window); - q.setNamedParameter("role", OBContext.getOBContext().getRole().getId()); - - final JSONArray processes = new JSONArray(); - json.put("notAccessibleProcesses", processes); - Tab tab = null; - JSONObject t; - JSONArray ps = null; - for (Field f : q.list()) { - if (tab == null || !tab.getId().equals(f.getTab().getId())) { - t = new JSONObject(); - tab = f.getTab(); - ps = new JSONArray(); - t.put("tabId", tab.getId()); - t.put("processes", ps); - processes.put(t); - } - ps.put(KernelUtils.getProperty(f).getName()); - } - - JSONObject extraSettingsJson = new JSONObject(); - json.put("extraSettings", extraSettingsJson); - JSONArray extraCallbacks = new JSONArray(); - - // Add the extraSettings injected - for (ExtraWindowSettingsInjector nextSetting : extraSettings) { - Map<String, Object> settingsToAdd = nextSetting.doAddSetting(parameters, json); - for (Entry<String, Object> setting : settingsToAdd.entrySet()) { - String settingKey = setting.getKey(); - Object settingValue = setting.getValue(); - if (settingKey.equals(EXTRA_CALLBACKS)) { - if (settingValue instanceof List) { - for (Object callbackExtra : (List<?>) settingValue) { - if (callbackExtra instanceof String) { - extraCallbacks.put(callbackExtra); - } else { - log4j.warn("You are trying to set a wrong instance of extraCallbacks"); - } - } - } else if (settingValue instanceof String) { - extraCallbacks.put(settingValue); - } else { - log4j.warn("You are trying to set a wrong instance of extraCallbacks"); - } - } else { - extraSettingsJson.put(settingKey, settingValue); - } - } - } - json.put("extraCallbacks", extraCallbacks); + json.put("showAutoSaveConfirmation", + getBooleanPreference(window, "ShowConfirmationDefault", false)); + json.put("tabs", getFieldLevelRoles(window)); + json.put("notAccessibleProcesses", getNotAccessibleProcesses(window)); + setExtraSettings(parameters, json); return json; } catch (Exception e) { + log.error("Error for window: " + windowId + " - role: " + OBContext.getOBContext().getRole(), + e); throw new OBException(e); } finally { OBContext.restorePreviousMode(); // clear anything we have in session as there's no change to make faster flush OBDal.getInstance().getSession().clear(); + log.debug("window: {} - role: {} - took: {}ms", new Object[] { windowId, + OBContext.getOBContext().getRole(), System.currentTimeMillis() - t }); } } + + private JSONObject getUIPattern(Window window) throws ServletException, JSONException { + final String roleId = OBContext.getOBContext().getRole().getId(); + final JSONObject jsonUIPattern = new JSONObject(); + final String windowType = window.getWindowType(); + DalConnectionProvider cp = new DalConnectionProvider(false); + for (Tab tab : window.getADTabList()) { + final boolean readOnlyAccess = org.openbravo.erpCommon.utility.WindowAccessData + .hasReadOnlyAccess(cp, roleId, tab.getId()); + String uiPattern = readOnlyAccess ? "RO" : tab.getUIPattern(); + // window should be read only when is assigned with a table defined as a view + if (!"RO".equals(uiPattern) && ("T".equals(windowType) || "M".equals(windowType)) + && tab.getTable().isView()) { + log.warn("Tab {} is set to read only because is assigned with a table defined as a view.", + tab); + uiPattern = "RO"; + } + jsonUIPattern.put(tab.getId(), uiPattern); + } + return jsonUIPattern; + } + + private boolean getBooleanPreference(Window window, String preference, boolean defaultValue) { + try { + String prefValue = Preferences.getPreferenceValue(preference, false, OBContext.getOBContext() + .getCurrentClient(), OBContext.getOBContext().getCurrentOrganization(), OBContext + .getOBContext().getUser(), OBContext.getOBContext().getRole(), window); + return Preferences.YES.equals(prefValue); + } catch (PropertyException ignore) { + return defaultValue; + } + } + + private JSONArray getFieldLevelRoles(Window window) throws JSONException { + final String roleId = OBContext.getOBContext().getRole().getId(); ------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, Slashdot.org! http://sdm.link/slashdot _______________________________________________ Openbravo-commits mailing list Openbravo-commits@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/openbravo-commits