Skip to content

Commit

Permalink
Improved: Add ‘WebAppCache’ (OFBIZ-10606)
Browse files Browse the repository at this point in the history
This improves the cohesion of the ‘ComponentConfig’ class by
extracting the webapps cache related methods into a separate
‘WebAppCache’ class.


git-svn-id: https://svn.apache.org/repos/asf/ofbiz/ofbiz-framework/trunk@1854429 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
mthl committed Feb 26, 2019
1 parent 96d1388 commit 4aa65a5
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,10 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.ofbiz.base.container.ContainerConfig;
import org.apache.ofbiz.base.container.ContainerConfig.Configuration;
Expand All @@ -55,12 +53,8 @@ public final class ComponentConfig {

public static final String module = ComponentConfig.class.getName();
public static final String OFBIZ_COMPONENT_XML_FILENAME = "ofbiz-component.xml";
/* Note: These Maps are not UtilCache instances because there is no strategy or implementation for reloading components.
* Also, we are using LinkedHashMap to maintain insertion order - which client code depends on. This means
* we will need to use synchronization code because there is no concurrent implementation of LinkedHashMap.
*/
// This map is not a UtilCache instance because there is no strategy or implementation for reloading components.
private static final ComponentConfigCache componentConfigCache = new ComponentConfigCache();
private static final Map<String, List<WebappInfo>> serverWebApps = new LinkedHashMap<>();

public static Boolean componentExists(String componentName) {
Assert.notEmpty("componentName", componentName);
Expand Down Expand Up @@ -187,55 +181,6 @@ public static List<WebappInfo> getAllWebappResourceInfos(String componentName) {
return webappInfos;
}

public static List<WebappInfo> getAppBarWebInfos(String serverName) {
return ComponentConfig.getAppBarWebInfos(serverName, null, null);
}

public static List<WebappInfo> getAppBarWebInfos(String serverName, Comparator<? super String> comp, String menuName) {
String serverWebAppsKey = serverName + menuName;
List<WebappInfo> webInfos = null;
synchronized (serverWebApps) {
webInfos = serverWebApps.get(serverWebAppsKey);
}
if (webInfos == null) {
Map<String, WebappInfo> tm = null;
// use a TreeMap to sort the components alpha by title
if (comp != null) {
tm = new TreeMap<>(comp);
} else {
tm = new TreeMap<>();
}
for (ComponentConfig cc : getAllComponents()) {
for (WebappInfo wInfo : cc.getWebappInfos()) {
String key = UtilValidate.isNotEmpty(wInfo.position) ? wInfo.position : wInfo.title;
if (serverName.equals(wInfo.server) && wInfo.getAppBarDisplay()) {
if (UtilValidate.isNotEmpty(menuName)) {
if (menuName.equals(wInfo.menuName)) {
tm.put(key, wInfo);
}
} else {
tm.put(key, wInfo);
}
} if (!wInfo.getAppBarDisplay() && UtilValidate.isEmpty(menuName)) {
tm.put(key, wInfo);
}
}
}
webInfos = new ArrayList<>(tm.size());
webInfos.addAll(tm.values());
webInfos = Collections.unmodifiableList(webInfos);
synchronized (serverWebApps) {
// We are only preventing concurrent modification, we are not guaranteeing a singleton.
serverWebApps.put(serverWebAppsKey, webInfos);
}
}
return webInfos;
}

public static List<WebappInfo> getAppBarWebInfos(String serverName, String menuName) {
return getAppBarWebInfos(serverName, null, menuName);
}

public static ComponentConfig getComponentConfig(String globalName) throws ComponentException {
// TODO: we need to look up the rootLocation from the container config, or this will blow up
return getComponentConfig(globalName, null);
Expand Down Expand Up @@ -314,21 +259,6 @@ public static WebappInfo getWebAppInfo(String serverName, String contextRoot) {
return info;
}

public static WebappInfo getWebappInfo(String serverName, String webAppName) {
WebappInfo webappInfo = null;
List<WebappInfo> webappsInfo = getAppBarWebInfos(serverName);
for(WebappInfo currApp : webappsInfo) {
String currWebAppName = currApp.getMountPoint().replace("/", "").replace("*", "");
if (webAppName.equals(currWebAppName)) {
webappInfo = currApp;
break;
}
}
return webappInfo;
}



public static boolean isFileResourceLoader(String componentName, String resourceLoaderName) throws ComponentException {
ComponentConfig cc = getComponentConfig(componentName);
return cc.isFileResourceLoader(resourceLoaderName);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.ofbiz.webapp;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Supplier;

import org.apache.ofbiz.base.component.ComponentConfig;
import org.apache.ofbiz.base.component.ComponentConfig.WebappInfo;
import org.apache.ofbiz.base.util.UtilValidate;

/**
* Cache for web applications information retrieved from
* {@linkplain ComponentConfig component configurations}.
* <p>
* This improves performance by avoiding to retrieve web applications from
* component configurations each time.
* <p>
* This is a cache which doesn't implement any invalidation mechanism. Once a
* web applications is defined, it is <b>memoized</b> because it is not meant
* to change while OFBiz is running.
*
* @see <a href="https://en.wikipedia.org/wiki/Memoization">Memoization</a>
*/
public class WebAppCache {
// Synchronized map storing web applications.
// The LinkedHashMap is used to maintain insertion order (which client code depends on).
// There is no concurrent implementation of LinkedHashMap, so we are using manual synchronization instead.
private final LinkedHashMap<String, List<WebappInfo>> serverWebApps;
// Source for retrieving components configurations.
private final Supplier<Collection<ComponentConfig>> ccs;

/**
* Constructs an empty web application cache.
*
* @param supplier the source from which components configurations
* are retrieved
*/
public WebAppCache(Supplier<Collection<ComponentConfig>>supplier) {
ccs = supplier;
serverWebApps = new LinkedHashMap<>();
}

/**
* Retrieves the web applications information that must be visible
* in the context of the server {@code serverName}.
*
* @param serverName the name of the server to match
* @return the corresponding web applications information
*/
public List<WebappInfo> getAppBarWebInfos(String serverName) {
return getAppBarWebInfos(serverName, null, null);
}

/**
* Retrieves the web applications information that must be visible inside
* the menu {@code menuName} in the context of the server {@code serverName}.
* <p>
* When an empty string or {@code null} is used for {@code menuName},
* all the web application information corresponding to {@code serverName} are matched.
*
* @param serverName the name of server to match
* @param menuName the name of the menu to match
* @return the corresponding web applications information
* @throws NullPointerException when {@code serverName} is {@code null}
*/
public List<WebappInfo> getAppBarWebInfos(String serverName, String menuName) {
return getAppBarWebInfos(serverName, null, menuName);
}

/**
* Retrieves the web applications information that must be visible inside
* the menu {@code menuName} in the context of the server {@code serverName}.
* <p>
* When an empty string or {@code null} is used for {@code menuName},
* all the web application information corresponding to {@code serverName} are matched.
*
* @param serverName the name of server to match
* @param comp the comparator used for ordering the results
* @param menuName the name of the menu to match
* @return the corresponding web applications information
* @throws NullPointerException when {@code serverName} is {@code null}
*/
private List<WebappInfo> getAppBarWebInfos(String serverName, Comparator<? super String> comp, String menuName) {
String serverWebAppsKey = serverName + menuName;
List<WebappInfo> webInfos = null;
synchronized (serverWebApps) {
webInfos = serverWebApps.get(serverWebAppsKey);
}
if (webInfos == null) {
Map<String, WebappInfo> tm = null;
// use a TreeMap to sort the components alpha by title
if (comp != null) {
tm = new TreeMap<>(comp);
} else {
tm = new TreeMap<>();
}
for (ComponentConfig cc : ccs.get()) {
for (WebappInfo wInfo : cc.getWebappInfos()) {
String key = UtilValidate.isNotEmpty(wInfo.position) ? wInfo.position : wInfo.title;
if (serverName.equals(wInfo.server) && wInfo.getAppBarDisplay()) {
if (UtilValidate.isNotEmpty(menuName)) {
if (menuName.equals(wInfo.menuName)) {
tm.put(key, wInfo);
}
} else {
tm.put(key, wInfo);
}
} if (!wInfo.getAppBarDisplay() && UtilValidate.isEmpty(menuName)) {
tm.put(key, wInfo);
}
}
}
webInfos = new ArrayList<>(tm.size());
webInfos.addAll(tm.values());
webInfos = Collections.unmodifiableList(webInfos);
synchronized (serverWebApps) {
// We are only preventing concurrent modification, we are not guaranteeing a singleton.
serverWebApps.put(serverWebAppsKey, webInfos);
}
}
return webInfos;
}

/**
* Retrieves the first web application information which mount point correspond to
* {@code webAppName} in the context of the server {@code serverName}.
*
* @param serverName the name of the server to match
* @param webAppName the name of the web application to match
* @return the corresponding web application information
* @throws NullPointerException when {@code serverName} or {@doc webAppName} are {@code null}
*/
public WebappInfo getWebappInfo(String serverName, String webAppName) {
WebappInfo webappInfo = null;
List<WebappInfo> webappsInfo = getAppBarWebInfos(serverName);
for(WebappInfo currApp : webappsInfo) {
String currWebAppName = currApp.getMountPoint().replace("/", "").replace("*", "");
if (webAppName.equals(currWebAppName)) {
webappInfo = currApp;
break;
}
}
return webappInfo;
}

// Instance of the cache shared by the loginWorker and Freemarker appbar rendering.
// TODO: Find a way to share this cache without relying on a global variable.
private static WebAppCache sharedCache = new WebAppCache(ComponentConfig::getAllComponents);

/**
* Provides access to a shared instance of the webapp cache.
*
* @return the shared webapp cache.
*/
public static WebAppCache getShared() {
return sharedCache;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
import org.apache.ofbiz.service.LocalDispatcher;
import org.apache.ofbiz.service.ModelService;
import org.apache.ofbiz.service.ServiceUtil;
import org.apache.ofbiz.webapp.WebAppCache;
import org.apache.ofbiz.webapp.WebAppUtil;
import org.apache.ofbiz.webapp.stats.VisitHandler;
import org.apache.ofbiz.widget.model.ThemeFactory;
Expand All @@ -97,6 +98,7 @@ public class LoginWorker {
public static final String securityProperties = "security.properties";

private static final String keyValue = UtilProperties.getPropertyValue(securityProperties, "login.secret_key_string");
private static final WebAppCache webapps = WebAppCache.getShared();

public static StringWrapper makeLoginUrl(PageContext pageContext) {
return makeLoginUrl(pageContext, "checkLogin");
Expand Down Expand Up @@ -922,17 +924,17 @@ public static String autoLoginSet(HttpServletRequest request, HttpServletRespons
Delegator delegator = (Delegator) request.getAttribute("delegator");
HttpSession session = request.getSession();
GenericValue userLogin = (GenericValue) session.getAttribute("userLogin");
ServletContext context = request.getServletContext();
String serverId = (String) request.getServletContext().getAttribute("_serverId");
String applicationName = UtilHttp.getApplicationName(request);
WebappInfo webappInfo = ComponentConfig.getWebappInfo((String) context.getAttribute("_serverId"), applicationName);
WebappInfo webappInfo = webapps.getWebappInfo(serverId, applicationName);

if (userLogin != null &&
((webappInfo != null && webappInfo.isAutologinCookieUsed())
|| webappInfo == null)) { // When using an empty mountpoint, ie using root as mountpoint. Beware: works only for 1 webapp!
Cookie autoLoginCookie = new Cookie(getAutoLoginCookieName(request), userLogin.getString("userLoginId"));
autoLoginCookie.setMaxAge(60 * 60 * 24 * 365);
autoLoginCookie.setDomain(EntityUtilProperties.getPropertyValue("url", "cookie.domain", delegator));
autoLoginCookie.setPath( applicationName.equals("root") ? "/" : request.getContextPath());
autoLoginCookie.setPath(applicationName.equals("root") ? "/" : request.getContextPath());
autoLoginCookie.setSecure(true);
autoLoginCookie.setHttpOnly(true);
response.addCookie(autoLoginCookie);
Expand Down Expand Up @@ -1368,7 +1370,7 @@ public static boolean hasBasePermission(GenericValue userLogin, HttpServletReque
* user is authorized to access
*/
public static Collection<ComponentConfig.WebappInfo> getAppBarWebInfos(Security security, GenericValue userLogin, String serverName, String menuName) {
Collection<ComponentConfig.WebappInfo> allInfos = ComponentConfig.getAppBarWebInfos(serverName, menuName);
Collection<ComponentConfig.WebappInfo> allInfos = webapps.getAppBarWebInfos(serverName, menuName);
Collection<ComponentConfig.WebappInfo> allowedInfos = new ArrayList<ComponentConfig.WebappInfo>(allInfos.size());
for (ComponentConfig.WebappInfo info : allInfos) {
if (hasApplicationPermission(info, security, userLogin)) {
Expand Down
4 changes: 2 additions & 2 deletions themes/rainbowstone/template/includes/HomeMenu.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ under the License.
<#if (externalLoginKey)?exists><#assign externalKeyParam = "?externalLoginKey=" + requestAttributes.externalLoginKey?if_exists></#if>
<#assign ofbizServerName = application.getAttribute("_serverId")?default("default-server")>
<#assign contextPath = request.getContextPath()>
<#assign displayApps = Static["org.apache.ofbiz.base.component.ComponentConfig"].getAppBarWebInfos(ofbizServerName, "main")>
<#assign displaySecondaryApps = Static["org.apache.ofbiz.base.component.ComponentConfig"].getAppBarWebInfos(ofbizServerName, "secondary")>
<#assign displayApps = Static["org.apache.ofbiz.webapp.WebAppCache"].getShared().getAppBarWebInfos(ofbizServerName, "main")>
<#assign displaySecondaryApps = Static["org.apache.ofbiz.webapp.WebAppCache"].getShared().getAppBarWebInfos(ofbizServerName, "secondary")>
<#assign avatarList = EntityQuery.use(delegator).from("PartyContent").where("partyId", person.partyId!, "partyContentTypeId", "LGOIMGURL").queryList()!>
<#if avatarList?has_content>
<#assign avatar = Static["org.apache.ofbiz.entity.util.EntityUtil"].getFirst(avatarList)>
Expand Down
4 changes: 2 additions & 2 deletions themes/rainbowstone/template/includes/TopAppBar.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ under the License.
<#if (externalLoginKey)?exists><#assign externalKeyParam = "?externalLoginKey=" + requestAttributes.externalLoginKey?if_exists></#if>
<#assign ofbizServerName = application.getAttribute("_serverId")?default("default-server")>
<#assign contextPath = request.getContextPath()>
<#assign displayApps = Static["org.apache.ofbiz.base.component.ComponentConfig"].getAppBarWebInfos(ofbizServerName, "main")>
<#assign displaySecondaryApps = Static["org.apache.ofbiz.base.component.ComponentConfig"].getAppBarWebInfos(ofbizServerName, "secondary")>
<#assign displayApps = Static["org.apache.ofbiz.webapp.WebAppCache"].getShared().getAppBarWebInfos(ofbizServerName, "main")>
<#assign displaySecondaryApps = Static["org.apache.ofbiz.webapp.WebAppCache"].getShared().getAppBarWebInfos(ofbizServerName, "secondary")>
<#if person?has_content>
<#assign avatarList = EntityQuery.use(delegator).from("PartyContent").where("partyId", person.partyId!, "partyContentTypeId", "LGOIMGURL").queryList()!>
<#if avatarList?has_content>
Expand Down

0 comments on commit 4aa65a5

Please sign in to comment.