/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package com.celements.navigation.cmd;
import static com.celements.navigation.cmd.MenuItemsUtils.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.context.Execution;
import org.xwiki.model.reference.DocumentReference;
import com.celements.navigation.TreeNode;
import com.google.common.base.MoreObjects;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.web.Utils;
public class GetNotMappedMenuItemsForParentCommand {
private final static Logger LOGGER = LoggerFactory.getLogger(
GetNotMappedMenuItemsForParentCommand.class);
private final Map<String, Map<String, List<TreeNode>>> menuItems;
private int queryCount = 0;
private XWikiContext getContext() {
return (XWikiContext) Utils.getComponent(Execution.class).getContext().getProperty(
"xwikicontext");
}
public GetNotMappedMenuItemsForParentCommand() {
/*
* ConcurrentHashMap allows non synchronized reading and still prevents following
* situation: Thread B changes the HashMap and Thread A does not synchronize on read
* which may lead to Thread A reading an old version from its memory cache. Details
* see JSR133
*/
menuItems = new ConcurrentHashMap<String, Map<String, List<TreeNode>>>();
}
/**
* use for tests only!!!
*/
void injectMapForTests(String wikiName, Map<String, List<TreeNode>> myWikiTestMap) {
menuItems.put(wikiName, myWikiTestMap);
}
Map<String, List<TreeNode>> internalGetMenuItemsForWiki(String wikiName) {
return menuItems.get(wikiName);
}
/**
* @deprecated instead use getTreeNodesForParentKey(String)
*/
@Deprecated
public List<TreeNode> getTreeNodesForParentKey(String searchParentKey, XWikiContext context) {
return getTreeNodesForParentKey(searchParentKey);
}
public List<TreeNode> getTreeNodesForParentKey(String searchParentKey) {
String wikiName = getWikiName(searchParentKey);
LOGGER.trace("getNotMappedMenuItemsFromDatabase: for cacheKey [{}].", wikiName);
Map<String, List<TreeNode>> wikiMenu = internalGetMenuItemsForWiki(wikiName);
if (wikiMenu == null) {
wikiMenu = loadMenuForWiki(wikiName);
}
if (wikiMenu.containsKey(searchParentKey)) {
return wikiMenu.get(searchParentKey);
}
return Collections.emptyList();
}
synchronized Map<String, List<TreeNode>> loadMenuForWiki(String wikiName) {
if (!menuItems.containsKey(wikiName)) {
LOGGER.debug("loadMenuForWiki: loading for wikiName [{}].", wikiName);
Map<String, List<TreeNode>> wikiMenuItemsMap = new HashMap<String, List<TreeNode>>();
final MenuItemObjectPartNameGetter strategy = new MenuItemObjectPartNameGetter();
queryCount = queryCount + 1;
List<TreeNode> menu = null;
try {
String parentKey = "";
String oldParentKey = "";
int docCount = 0;
long start = System.currentTimeMillis();
List<Object[]> results = getFromDBForParentKey(wikiName);
long end = System.currentTimeMillis();
LOGGER.info("loadMenuForWiki: time for searchDocumentsNames: {} ", (end - start));
start = System.currentTimeMillis();
for (Object[] docData : results) {
docCount++;
oldParentKey = parentKey;
// doc.fullName, doc.space, doc.parent, pos.value
String fullName = MoreObjects.firstNonNull(docData[0], "").toString();
String spaceName = MoreObjects.firstNonNull(docData[1], "").toString();
String parentFN = MoreObjects.firstNonNull(docData[2], "").toString();
LOGGER.debug("got item from db: {}", fullName);
parentKey = getParentKey(wikiName, parentFN, spaceName);
if (!oldParentKey.equals(parentKey) || (menu == null)) {
if (menu != null) {
LOGGER.debug("put menu in cache for parent [{}]", oldParentKey);
wikiMenuItemsMap.put(oldParentKey, menu);
}
menu = getMenuCacheForParent(wikiMenuItemsMap, parentKey);
}
LOGGER.debug("put item [{}] in cache [{}]: ", fullName, parentKey);
if ((wikiName == null) || (fullName.split("\\.").length < 2) || wikiName.isEmpty()
|| spaceName.isEmpty() || ("".equals(fullName.split("\\.")[1]))) {
LOGGER.warn("loadMenuForWiki: skip [{}] because of null value!! " + "'{}', '{}', '{}'",
fullName, wikiName, spaceName, fullName.split("\\.")[1]);
} else {
DocumentReference docRef = new DocumentReference(wikiName, spaceName, fullName.split(
"\\.")[1]);
TreeNode treeNode = new TreeNode(docRef, resolveParentRef(parentFN),
(Integer) docData[3], strategy);
menu.add(treeNode);
}
}
LOGGER.info("loadMenuForWiki: '{}' documents found.", docCount);
if (menu != null) {
wikiMenuItemsMap.put(parentKey, menu);
}
for (String theParentKey : wikiMenuItemsMap.keySet()) {
List<TreeNode> theMenu = wikiMenuItemsMap.get(theParentKey);
wikiMenuItemsMap.put(theParentKey, Collections.unmodifiableList(theMenu));
}
end = System.currentTimeMillis();
LOGGER.info("loadMenuForWiki: time for building cache: {} ", (end - start));
LOGGER.info("loadMenuForWiki: cache size: {}", wikiMenuItemsMap.size());
menuItems.put(wikiName, Collections.unmodifiableMap(wikiMenuItemsMap));
} catch (XWikiException exp) {
LOGGER.error("loadMenuForWiki failed. ", exp);
}
}
return internalGetMenuItemsForWiki(wikiName);
}
List<Object[]> getFromDBForParentKey(String wikiName) throws XWikiException {
String databaseBefore = getContext().getDatabase();
try {
List<Object[]> results = executeSearch(wikiName);
return results;
} finally {
getContext().setDatabase(databaseBefore);
}
}
List<Object[]> executeSearch(String wikiName) throws XWikiException {
getContext().setDatabase(wikiName);
String hql = getHQL();
LOGGER.debug("Executing on db [{}] hql: {}", getContext().getDatabase(), hql);
List<Object[]> results = getContext().getWiki().getStore().search(hql, 0, 0, getContext());
return results;
}
String getHQL() {
/*
* select doc.XWD_FULLNAME from xwikidoc doc, xwikiobjects obj, xwikiintegers pos
* where obj.XWO_NAME=doc.XWD_FULLNAME and obj.XWO_CLASSNAME='Celements2.MenuItem' and
* obj.XWO_ID = pos.XWI_ID and pos.XWI_NAME = 'menu_position' and doc.XWD_TRANSLATION
* = 0 and doc.XWD_WEB <> 'Trash' order by doc.XWD_PARENT, pos.XWI_VALUE; -> executed
* in onecprod_customizing ->1061 rows in set (0.20 sec)
*/
return "select doc.fullName, doc.space, doc.parent, pos.value"
+ " from XWikiDocument as doc, BaseObject as obj, IntegerProperty as pos"
+ " where obj.name=doc.fullName" + " and obj.className='Celements2.MenuItem'"
+ " and obj.id = pos.id.id" + " and pos.id.name = 'menu_position'"
+ " and doc.translation = 0" + " and doc.space <> 'Trash'"
+ " order by doc.parent, pos.value";
}
String getWikiCacheKey(String parentKey) {
return getWikiName(parentKey);
}
private String getWikiName(String parentKey) {
String wikiName = getContext().getDatabase();
if (parentKey.contains(":")) {
wikiName = parentKey.split(":", 2)[0];
}
return wikiName;
}
String getParentKey(String wikiName, String parent, String menuSpace) {
String parentKey = "";
if (parent != null) {
parentKey = parent;
}
if (parentKey.indexOf('.') < 0) {
parentKey = menuSpace + "." + parentKey;
}
if (parentKey.indexOf(':') < 0) {
parentKey = wikiName + ":" + parentKey;
}
return parentKey;
}
/*
* (non-Javadoc)
*
* @see com.celements.web.utils.IWebUtils#queryCount()
*/
public int queryCount() {
return queryCount;
}
/*
* (non-Javadoc)
*
* @see com.celements.web.utils.IWebUtils#flushMenuItemCache(com.xpn.xwiki.XWikiContext)
*/
synchronized public void flushMenuItemCache(XWikiContext context) {
/*
* flushMenuItemCache must be synchronized to prevent following situation: 1) Thread A
* starts loadMenuForWiki for myWiki and gets current state from DB 2) Thread B
* changed a document and flushes menuItems for myWiki 3) Thread A stores already old
* version of myWiki in menuItems cache
*/
if (context != null) {
LOGGER.debug("Entered method flushMenuItemCache with context db [{}].",
context.getDatabase());
menuItems.remove(context.getDatabase());
} else {
LOGGER.warn("skip flushMenuItemCache for context == null].");
}
}
private List<TreeNode> getMenuCacheForParent(Map<String, List<TreeNode>> wikiMenuItemsMap,
String parentKey) {
if (wikiMenuItemsMap.containsKey(parentKey)) {
return wikiMenuItemsMap.get(parentKey);
} else {
return new ArrayList<TreeNode>(20);
}
}
}