/* * Copyright (C) 2009 eXo Platform SAS. * * 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 org.exoplatform.services.jcr.impl.dataflow.persistent.infinispan; import org.exoplatform.commons.utils.SecurityHelper; import org.exoplatform.container.ExoContainer; import org.exoplatform.container.ExoContainerContext; import org.exoplatform.container.configuration.ConfigurationManager; import org.exoplatform.management.annotations.Managed; import org.exoplatform.management.annotations.ManagedDescription; import org.exoplatform.services.ispn.DistributedCacheManager; import org.exoplatform.services.jcr.access.AccessControlList; import org.exoplatform.services.jcr.config.CacheEntry; import org.exoplatform.services.jcr.config.RepositoryConfigurationException; import org.exoplatform.services.jcr.config.WorkspaceEntry; import org.exoplatform.services.jcr.dataflow.ItemState; import org.exoplatform.services.jcr.dataflow.ItemStateChangesLog; import org.exoplatform.services.jcr.dataflow.persistent.PersistedNodeData; import org.exoplatform.services.jcr.dataflow.persistent.PersistedPropertyData; import org.exoplatform.services.jcr.dataflow.persistent.WorkspaceStorageCache; import org.exoplatform.services.jcr.dataflow.persistent.WorkspaceStorageCacheListener; import org.exoplatform.services.jcr.datamodel.IllegalPathException; import org.exoplatform.services.jcr.datamodel.InternalQName; import org.exoplatform.services.jcr.datamodel.ItemData; import org.exoplatform.services.jcr.datamodel.ItemType; import org.exoplatform.services.jcr.datamodel.NodeData; import org.exoplatform.services.jcr.datamodel.NullItemData; import org.exoplatform.services.jcr.datamodel.NullNodeData; import org.exoplatform.services.jcr.datamodel.NullPropertyData; import org.exoplatform.services.jcr.datamodel.PropertyData; import org.exoplatform.services.jcr.datamodel.QPath; import org.exoplatform.services.jcr.datamodel.QPathEntry; import org.exoplatform.services.jcr.datamodel.ValueData; import org.exoplatform.services.jcr.impl.Constants; import org.exoplatform.services.jcr.impl.backup.BackupException; import org.exoplatform.services.jcr.impl.backup.Backupable; import org.exoplatform.services.jcr.impl.backup.DataRestore; import org.exoplatform.services.jcr.impl.backup.rdbms.DataRestoreContext; import org.exoplatform.services.jcr.impl.core.itemfilters.QPathEntryFilter; import org.exoplatform.services.jcr.impl.dataflow.ValueDataUtil; import org.exoplatform.services.jcr.impl.dataflow.persistent.SimplePersistedSize; import org.exoplatform.services.jcr.infinispan.AbstractMapper; import org.exoplatform.services.jcr.infinispan.CacheKey; import org.exoplatform.services.jcr.infinispan.ISPNCacheFactory; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.exoplatform.services.transaction.ActionNonTxAware; import org.exoplatform.services.transaction.TransactionService; import org.infinispan.Cache; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.Configuration; import org.infinispan.context.Flag; import org.infinispan.distexec.mapreduce.Collector; import org.infinispan.distexec.mapreduce.MapReduceTask; import org.infinispan.distexec.mapreduce.Reducer; import org.infinispan.lifecycle.ComponentStatus; import org.infinispan.notifications.Listener; import org.infinispan.notifications.cachelistener.annotation.CacheEntryModified; import org.infinispan.notifications.cachelistener.event.CacheEntryModifiedEvent; import org.picocontainer.Startable; import java.io.File; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.SystemException; import javax.transaction.TransactionManager; /** * Created by The eXo Platform SAS.<br> * * Cache based on Infinispan.<br> * * <ul> * <li>cache transparent: or item cached or not, we should not generate "not found" Exceptions </li> * </ul> * * This cache implementation stores items by UUID and parent UUID with QPathEntry. * Except this cache stores list of children UUID. * * <br> * Current state notes (subject of change): * <ul> * <li>cache implements WorkspaceStorageCache, without any stuff about references and locks</li> * <li>transaction style implemented via batches, do with JTA (i.e. via exo's TransactionService + JBoss TM)</li> * </ul> * * @author anatoliy.bazko@exoplatform.org Anatoliy Bazko * @version $Id: ISPNCacheWorkspaceStorageCache.java 3514 2010-11-22 16:14:36Z nzamosenchuk $ */ @SuppressWarnings("unchecked") public class ISPNCacheWorkspaceStorageCache implements WorkspaceStorageCache, Backupable, Startable { private static final Log LOG = ExoLogger//NOSONAR .getLogger("exo.jcr.component.core.ISPNCacheWorkspaceStorageCache");//NOSONAR /** * Name of the cache in case of the distributed cache */ private static final String CACHE_NAME = "JCRCache"; /** * This id will be the unique identifier of the workspace in case the * distributed mode is enabled as the cache will be then shared so we * need this id to prevent mixing data of different workspace. In case * the workspace is not distributed the value of this variable will be * null to avoid consuming more memory for nothing */ protected final String ownerId; private final boolean enabled; protected final BufferedISPNCache cache; private final GlobalOperationCaller caller; /** * The list of all the listeners */ private final List<WorkspaceStorageCacheListener> listeners = new CopyOnWriteArrayList<WorkspaceStorageCacheListener>(); private final CacheActionNonTxAware<Void, Void> commitTransaction = new CacheActionNonTxAware<Void, Void>() { @Override protected Void execute(Void arg) { cache.commitTransaction(); return null; } }; private final CacheActionNonTxAware<ItemData, String> getFromCacheById = new CacheActionNonTxAware<ItemData, String>() { @Override protected ItemData execute(String id) { return id == null ? null : (ItemData)cache.get(new CacheId(getOwnerId(), id)); } }; private final CacheActionNonTxAware<ItemData, String> getFromBufferedCacheById = new CacheActionNonTxAware<ItemData, String>() { @Override protected ItemData execute(String id) { return id == null ? null : (ItemData)cache.getFromBuffer(new CacheId(getOwnerId(), id)); } }; private final CacheActionNonTxAware<List<NodeData>, NodeData> getChildNodes = new CacheActionNonTxAware<List<NodeData>, NodeData>() { @Override protected List<NodeData> execute(NodeData parent) { // get list of children uuids final Set<String> set = (Set<String>)cache.get(new CacheNodesId(getOwnerId(), parent.getIdentifier())); if (set != null) { if (set instanceof FakeValueSet) { return null; } final List<NodeData> childs = new ArrayList<NodeData>(); for (String childId : set) { NodeData child = (NodeData)cache.get(new CacheId(getOwnerId(), childId)); if (child == null) { return null; } childs.add(child); } // order children by orderNumber, as HashSet returns children in other order Collections.sort(childs, new NodesOrderComparator<NodeData>()); return childs; } else { return null; } } }; private final CacheActionNonTxAware<ItemData, Object> getFromCacheByPath = new CacheActionNonTxAware<ItemData, Object>() { @Override protected ItemData execute(Object... args) { String parentIdentifier = (String)args[0]; QPathEntry name = (QPathEntry)args[1]; ItemType itemType = (ItemType)args[2]; String itemId = null; if (itemType == ItemType.UNKNOWN) { // Try as node first. itemId = (String)cache.get(new CacheQPath(getOwnerId(), parentIdentifier, name, ItemType.NODE)); if (itemId == null || itemId.equals(NullItemData.NULL_ID)) { // node with such a name is not found or marked as not-exist, so check the properties String propId = (String)cache.get(new CacheQPath(getOwnerId(), parentIdentifier, name, ItemType.PROPERTY)); if (propId != null) { itemId = propId; } } } else if (itemType == ItemType.NODE) { itemId = (String)cache.get(new CacheQPath(getOwnerId(), parentIdentifier, name, ItemType.NODE));; } else { itemId = (String)cache.get(new CacheQPath(getOwnerId(), parentIdentifier, name, ItemType.PROPERTY));; } if (itemId != null) { if (itemId.equals(NullItemData.NULL_ID)) { if (itemType == ItemType.UNKNOWN || itemType == ItemType.NODE) { return new NullNodeData(); } else { return new NullPropertyData(); } } else { return get(itemId); } } return null; } }; private final CacheActionNonTxAware<Integer, NodeData> getChildNodesCount = new CacheActionNonTxAware<Integer, NodeData>() { @Override protected Integer execute(NodeData parent) { Set<String> list = (Set<String>)cache.get(new CacheNodesId(getOwnerId(), parent.getIdentifier())); return list != null ? list.size() : -1; } }; private final CacheActionNonTxAware<List<PropertyData>, Object> getChildProps = new CacheActionNonTxAware<List<PropertyData>, Object>() { @Override protected List<PropertyData> execute(Object... args) { String parentId = (String)args[0]; boolean withValue = (Boolean)args[1]; // get list of children uuids final Set<String> set = (Set<String>)cache.get(new CachePropsId(getOwnerId(), parentId)); if (set != null) { final List<PropertyData> childs = new ArrayList<PropertyData>(); for (String childId : set) { PropertyData child = (PropertyData)cache.get(new CacheId(getOwnerId(), childId)); if (child == null) { return null; } if (withValue && child.getValues().size() <= 0) { return null; } childs.add(child); } return childs; } else { return null; } } }; private final CacheActionNonTxAware<List<PropertyData>, String> getReferencedProperties = new CacheActionNonTxAware<List<PropertyData>, String>() { @Override protected List<PropertyData> execute(String identifier) { // get list of children uuids final Set<String> set = (Set<String>)cache.get(new CacheRefsId(getOwnerId(), identifier)); if (set != null) { final List<PropertyData> props = new ArrayList<PropertyData>(); for (String childId : set) { PropertyData prop = (PropertyData)cache.get(new CacheId(getOwnerId(), childId)); if (prop == null || prop instanceof NullItemData) { return null; } // add property as many times as has referenced values List<ValueData> lData = prop.getValues(); for (int i = 0, length = lData.size(); i < length; i++) { ValueData vdata = lData.get(i); try { if (ValueDataUtil.getString(vdata).equals(identifier)) { props.add(prop); } } catch (IllegalStateException e) { // property was not added, force read from lower layer return null; } catch (RepositoryException e) { // property was not added, force read from lower layer return null; } } } return props; } else { return null; } } }; private final CacheActionNonTxAware<Long, Void> getSize = new CacheActionNonTxAware<Long, Void>() { @Override protected Long execute(Void arg) { return (long)caller.getCacheSize(); } }; /** * Node order comparator for getChildNodes(). */ class NodesOrderComparator<N extends NodeData> implements Comparator<NodeData> { /** * {@inheritDoc} */ public int compare(NodeData n1, NodeData n2) { return n1.getOrderNumber() - n2.getOrderNumber(); } } class ChildItemsIterator<T extends ItemData> implements Iterator<T> { private Iterator<String> childs; private Iterator<Entry<CacheKey, Object>> entries; private String parentId; private Class<? extends ItemData> type; private T next; ChildItemsIterator(String parentId, Class<? extends ItemData> type, CacheKey key) { Set<String> set = (Set<String>)cache.get(key); if (set != null) { this.childs = ((Set<String>)cache.get(key)).iterator(); } else { this.entries = cache.entrySet().iterator(); this.parentId = parentId; this.type = type; } fetchNext(); } protected void fetchNext() { if (childs != null) { // Case where all the children nodes have been loaded if (childs.hasNext()) { // traverse to the first existing or the end of children T n = null; do { n = (T)cache.get(new CacheId(getOwnerId(), childs.next())); } while (n == null && childs.hasNext()); next = n; } else { next = null; } } else { // Case where all the children nodes have not been loaded if (entries.hasNext()) { // traverse to the first children node or the end of the entry set T n = null; do { Entry<CacheKey, Object> entry = entries.next(); if (entry.getKey() instanceof CacheId && type.isInstance(entry.getValue())) { ItemData item = (ItemData)entry.getValue(); if (parentId.equals(item.getParentIdentifier())) { n = (T)item; } } } while (n == null && entries.hasNext()); next = n; } else { next = null; } } } public boolean hasNext() { return next != null; } public T next() { if (next == null) { throw new NoSuchElementException(); } final T current = next; fetchNext(); return current; } public void remove() { throw new IllegalArgumentException("Not implemented"); } } class ChildNodesIterator<N extends NodeData> extends ChildItemsIterator<N> { ChildNodesIterator(String parentId) { super(parentId, NodeData.class, new CacheNodesId(getOwnerId(), parentId)); } @Override public N next() { return super.next(); } } class ChildPropertiesIterator<P extends PropertyData> extends ChildItemsIterator<P> { ChildPropertiesIterator(String parentId) { super(parentId, PropertyData.class, new CachePropsId(getOwnerId(), parentId)); } @Override public P next() { return super.next(); } } /** * Cache constructor. * * @param wsConfig WorkspaceEntry workspace configuration * @param cfm The configuration manager * @throws RepositoryException if error of initialization * @throws RepositoryConfigurationException if error of configuration */ public ISPNCacheWorkspaceStorageCache(WorkspaceEntry wsConfig, ConfigurationManager cfm) throws RepositoryException, RepositoryConfigurationException { this(null, wsConfig, cfm, null, null); } /** * Cache constructor. * * @param ctx The {@link ExoContainerContext} that owns the current component * @param wsConfig WorkspaceEntry workspace configuration * @param cfm The configuration manager * @param ts TransactionService external transaction service * @throws RepositoryException if error of initialization * @throws RepositoryConfigurationException if error of configuration */ public ISPNCacheWorkspaceStorageCache(ExoContainerContext ctx, WorkspaceEntry wsConfig, ConfigurationManager cfm, TransactionService ts) throws RepositoryException, RepositoryConfigurationException { this(ctx, wsConfig, cfm, null, ts); } /** * Cache constructor. * * @param ctx The {@link ExoContainerContext} that owns the current component * @param wsConfig WorkspaceEntry workspace configuration * @param cfm The configuration manager * @param dcm The distributed cache manager * @throws RepositoryException if error of initialization * @throws RepositoryConfigurationException if error of configuration */ public ISPNCacheWorkspaceStorageCache(ExoContainerContext ctx, WorkspaceEntry wsConfig, ConfigurationManager cfm, DistributedCacheManager dcm) throws RepositoryException, RepositoryConfigurationException { this(ctx, wsConfig, cfm, dcm, null); } /** * Cache constructor. * * @param ctx The {@link ExoContainerContext} that owns the current component * @param wsConfig WorkspaceEntry workspace configuration * @param cfm The configuration manager * @param dcm The distributed cache manager * @param ts TransactionService external transaction service * @throws RepositoryException if error of initialization * @throws RepositoryConfigurationException if error of configuration */ public ISPNCacheWorkspaceStorageCache(ExoContainerContext ctx, WorkspaceEntry wsConfig, ConfigurationManager cfm, DistributedCacheManager dcm, TransactionService ts) throws RepositoryException, RepositoryConfigurationException { if (wsConfig.getCache() == null) { throw new RepositoryConfigurationException("Cache configuration not found"); } this.enabled = wsConfig.getCache().isEnabled(); // create cache using custom factory ISPNCacheFactory<CacheKey, Object> factory = new ISPNCacheFactory<CacheKey, Object>(cfm, ts == null ? null : ts.getTransactionManager()); // create parent Infinispan instance CacheEntry cacheEntry = wsConfig.getCache(); boolean useDistributedCache = cacheEntry.getParameterBoolean("use-distributed-cache", false); Cache<CacheKey, Object> parentCache; if (useDistributedCache) { // We expect a distributed cache if (dcm == null) { throw new IllegalArgumentException("The DistributedCacheManager has not been defined in the configuration," + " please configure it at root container level if you want to use a distributed cache."); } parentCache = dcm.getCache(CACHE_NAME); this.ownerId = ctx.getName(); if (LOG.isDebugEnabled()) { LOG.debug("The distributed cache has been enabled for the workspace whose unique id is " + ownerId); } } else { parentCache = factory.createCache("Data_" + wsConfig.getUniqueName(), cacheEntry); Configuration config = parentCache.getCacheConfiguration(); if (config.clustering().cacheMode() == CacheMode.DIST_SYNC || config.clustering().cacheMode() == CacheMode.DIST_ASYNC) { throw new IllegalArgumentException("Cache configuration not allowed, if you want to use the distributed " + "cache please enable the parameter 'use-distributed-cache' and configure the DistributedCacheManager."); } this.ownerId = null; } Boolean allowLocalChanges = useDistributedCache ? cacheEntry.getParameterBoolean("allow-local-changes", Boolean.TRUE) : Boolean.TRUE; this.cache = new BufferedISPNCache(parentCache, allowLocalChanges); if (useDistributedCache) { this.caller = new DistributedOperationCaller(); } else { this.caller = new GlobalOperationCaller(); cache.addListener(new CacheEventListener()); } this.cache.start(); } private boolean isDistributedMode() { return ownerId != null; } private String getOwnerId() { return ownerId; } /** * Return TransactionManager used by ISPN backing the JCR cache. * * @return TransactionManager */ public TransactionManager getTransactionManager() { return cache.getTransactionManager(); } /** * {@inheritDoc} */ public void put(ItemData item) { // There is different commit processing for NullNodeData and ordinary ItemData. if (item instanceof NullItemData) { putNullItem((NullItemData)item); return; } boolean inTransaction = cache.isTransactionActive(); try { if (!inTransaction) { cache.beginTransaction(); } cache.setLocal(true); if (item.isNode()) { putNode((NodeData)item, ModifyChildOption.NOT_MODIFY); } else { putProperty((PropertyData)item, ModifyChildOption.NOT_MODIFY); } } finally { cache.setLocal(false); if (!inTransaction) { dedicatedTxCommit(); } } } /** * {@inheritDoc} */ public void remove(ItemData item) { removeItem(item); } /** * {@inheritDoc} */ public void remove(String identifier, ItemData item) { boolean inTransaction = cache.isTransactionActive(); try { if (!inTransaction) { cache.beginTransaction(); } cache.setLocal(true); cache.remove(new CacheId(getOwnerId(), identifier), item); } finally { cache.setLocal(false); if (!inTransaction) { dedicatedTxCommit(); } } } /** * {@inheritDoc} */ public void onSaveItems(final ItemStateChangesLog itemStates) { // if something happen we will rollback changes boolean rollback = true; try { ItemState lastDelete = null; cache.beginTransaction(); Set<String> idsToSkip = null; List<ItemState> states = itemStates.getAllStates(); for (int i = 0, length = states.size(); i < length; i++) { ItemState state = states.get(i); if (state.isAdded()) { if (state.isPersisted()) { putItem(state.getData()); } } else if (state.isUpdated()) { if (state.isPersisted()) { // There was a problem with removing a list of samename siblings in on transaction, // so putItemInBufferedCache(..) and updateInBufferedCache(..) used instead put(..) and update (..) methods. ItemData prevItem = putItemInBufferedCache(state.getData()); if (state.isNode() && (prevItem != null || state.getOldPath() != null)) { // nodes reordered, if previous is null it's InvalidItemState case idsToSkip = updateInBuffer((NodeData)state.getData(), prevItem != null ? prevItem.getQPath() : state.getOldPath(), idsToSkip); if (i + 1 < length) { // We check if the next state is another update on a persisted node, because if so we can keep the skip list otherwise we can get // rid of it ItemState nextState = states.get(i + 1); if (!nextState.isUpdated() || !nextState.isNode() || !nextState.isPersisted()) { // No order before has been detected so we have no need to keep the list of ids idsToSkip = null; } } } } } else if (state.isDeleted()) { if (state.isPersisted()) { removeItem(state.getData()); } } else if (state.isRenamed()) { renameItem(state, lastDelete); } else if (state.isPathChanged()) { updateTreePath(state.getOldPath(), state.getData().getQPath(), (Set<String>)null); } else if (state.isMixinChanged()) { if (state.isPersisted()) { // update subtree ACLs updateMixin((NodeData)state.getData()); } } if (state.isDeleted()) { lastDelete = state; } } cache.commitTransaction(); rollback = false; } finally { if (rollback) { cache.rollbackTransaction(); } } } /** * {@inheritDoc} */ public void addChildNodesByPage(NodeData parent, List<NodeData> childs, int fromOrderNum) { boolean inTransaction = cache.isTransactionActive(); try { if (!inTransaction) { cache.beginTransaction(); } cache.setLocal(true); CacheNodesByPageId cacheId = new CacheNodesByPageId(getOwnerId(), parent.getIdentifier()); Map<Integer, Set<String>> pages = (Map<Integer, Set<String>>)cache.get(cacheId); if (pages == null) { pages = new HashMap<Integer, Set<String>>(); } Set<String> set = new HashSet<String>(); for (NodeData child : childs) { putNode(child, ModifyChildOption.NOT_MODIFY); set.add(child.getIdentifier()); } pages.put(fromOrderNum, set); cache.put(cacheId, pages); } finally { cache.setLocal(false); if (!inTransaction) { dedicatedTxCommit(); } } } /** * {@inheritDoc} */ public void addChildNodes(NodeData parent, List<NodeData> childs) { boolean inTransaction = cache.isTransactionActive(); try { if (!inTransaction) { cache.beginTransaction(); } cache.setLocal(true); Set<Object> set = new HashSet<Object>(); for (NodeData child : childs) { putNode(child, ModifyChildOption.NOT_MODIFY); set.add(child.getIdentifier()); } cache.putIfAbsent(new CacheNodesId(getOwnerId(), parent.getIdentifier()), set); } finally { cache.setLocal(false); if (!inTransaction) { dedicatedTxCommit(); } } } /** * {@inheritDoc} */ public void addChildNodes(NodeData parent, QPathEntryFilter pattern, List<NodeData> childs) { boolean inTransaction = cache.isTransactionActive(); try { if (!inTransaction) { cache.beginTransaction(); } cache.setLocal(true); Set<String> set = new HashSet<String>(); for (NodeData child : childs) { putNode(child, ModifyChildOption.NOT_MODIFY); set.add(child.getIdentifier()); } CachePatternNodesId cacheId = new CachePatternNodesId(getOwnerId(), parent.getIdentifier()); Map<QPathEntryFilter, Set<String>> patterns = (Map<QPathEntryFilter, Set<String>>)cache.get(cacheId); if (patterns == null) { patterns = new HashMap<QPathEntryFilter, Set<String>>(); } patterns.put(pattern, set); cache.put(cacheId, patterns); } finally { cache.setLocal(false); if (!inTransaction) { dedicatedTxCommit(); } } } /** * {@inheritDoc} */ public void addChildProperties(NodeData parent, List<PropertyData> childs) { boolean inTransaction = cache.isTransactionActive(); try { if (!inTransaction) { cache.beginTransaction(); } cache.setLocal(true); if (childs.size() > 0) { // add all new Set<Object> set = new HashSet<Object>(); for (PropertyData child : childs) { putProperty(child, ModifyChildOption.NOT_MODIFY); set.add(child.getIdentifier()); } cache.putIfAbsent(new CachePropsId(getOwnerId(), parent.getIdentifier()), set); } else { LOG.warn("Empty properties list cached " + (parent != null ? parent.getQPath().getAsString() : parent)); } } finally { cache.setLocal(false); if (!inTransaction) { dedicatedTxCommit(); } } } /** * {@inheritDoc} */ public void addChildProperties(NodeData parent, QPathEntryFilter pattern, List<PropertyData> childs) { boolean inTransaction = cache.isTransactionActive(); try { if (!inTransaction) { cache.beginTransaction(); } cache.setLocal(true); if (childs.size() > 0) { // add all new Set<String> set = new HashSet<String>(); for (PropertyData child : childs) { putProperty(child, ModifyChildOption.NOT_MODIFY); set.add(child.getIdentifier()); } CachePatternPropsId cacheId = new CachePatternPropsId(getOwnerId(), parent.getIdentifier()); Map<QPathEntryFilter, Set<String>> patterns = (Map<QPathEntryFilter, Set<String>>)cache.get(cacheId); if (patterns == null) { patterns = new HashMap<QPathEntryFilter, Set<String>>(); } patterns.put(pattern, set); cache.put(cacheId, patterns); } } finally { cache.setLocal(false); if (!inTransaction) { dedicatedTxCommit(); } } } /** * {@inheritDoc} */ public void addChildPropertiesList(NodeData parent, List<PropertyData> childProperties) { } /** * {@inheritDoc} */ public ItemData get(String parentIdentifier, QPathEntry name, ItemType itemType) { return getFromCacheByPath.run(parentIdentifier, name, itemType); } /** * {@inheritDoc} */ public ItemData get(String id) { return getFromCacheById.run(id); } /** * {@inheritDoc} */ public List<NodeData> getChildNodes(final NodeData parent) { return getChildNodes.run(parent); } /** * {@inheritDoc} */ public void addChildNodesCount(NodeData parent, int count) { boolean inTransaction = cache.isTransactionActive(); try { if (!inTransaction) { cache.beginTransaction(); } cache.setLocal(true); cache.putIfAbsent(new CacheNodesId(getOwnerId(), parent.getIdentifier()), new FakeValueSet(count)); } finally { cache.setLocal(false); if (!inTransaction) { dedicatedTxCommit(); } } } /** * {@inheritDoc} */ public List<NodeData> getChildNodesByPage(final NodeData parent, final int fromOrderNum) { // get list of children uuids final Map<Integer, Set<String>> pages = (Map<Integer, Set<String>>)cache.get(new CacheNodesByPageId(getOwnerId(), parent.getIdentifier())); if (pages == null) { return null; } Set<String> set = pages.get(fromOrderNum); if (set == null) { return null; } final List<NodeData> childs = new ArrayList<NodeData>(); for (String childId : set) { NodeData child = (NodeData)cache.get(new CacheId(getOwnerId(), childId)); if (child == null) { return null; } childs.add(child); } // order children by orderNumber, as HashSet returns children in other order Collections.sort(childs, new NodesOrderComparator<NodeData>()); return childs; } /** * {@inheritDoc} */ public List<NodeData> getChildNodes(final NodeData parent, final QPathEntryFilter pattern) { // get list of children uuids final Map<QPathEntryFilter, Set<String>> patterns = (Map<QPathEntryFilter, Set<String>>)cache.get(new CachePatternNodesId(getOwnerId(), parent.getIdentifier())); if (patterns == null) { return null; } Set<String> set = patterns.get(pattern); if (set == null) { return null; } final List<NodeData> childs = new ArrayList<NodeData>(); for (String childId : set) { NodeData child = (NodeData)cache.get(new CacheId(getOwnerId(), childId)); if (child == null) { return null; } childs.add(child); } // order children by orderNumber, as HashSet returns children in other order Collections.sort(childs, new NodesOrderComparator<NodeData>()); return childs; } /** * {@inheritDoc} */ public int getChildNodesCount(NodeData parent) { return getChildNodesCount.run(parent); } /** * {@inheritDoc} */ public List<PropertyData> getChildProperties(NodeData parent) { return getChildProps(parent.getIdentifier(), true); } public List<PropertyData> getChildProperties(NodeData parent, QPathEntryFilter pattern) { // get list of children uuids final Map<QPathEntryFilter, Set<String>> patterns = (Map<QPathEntryFilter, Set<String>>)cache.get(new CachePatternPropsId(getOwnerId(), parent.getIdentifier())); if (patterns == null) { return null; } Set<String> set = patterns.get(pattern); if (set == null) { return null; } final List<PropertyData> childs = new ArrayList<PropertyData>(); for (String childId : set) { PropertyData child = (PropertyData)cache.get(new CacheId(getOwnerId(), childId)); if (child == null) { return null; } if (child.getValues().size() <= 0) { return null; } childs.add(child); } return childs; } /** * {@inheritDoc} */ public List<PropertyData> listChildProperties(NodeData parent) { return getChildProps(parent.getIdentifier(), false); } /** * Internal get child properties. * * @param parentId String * @param withValue boolean, if true only "full" Propeties can be returned * @return List of PropertyData */ protected List<PropertyData> getChildProps(String parentId, boolean withValue) { return getChildProps.run(parentId, withValue); } /** * {@inheritDoc} */ public long getSize() { return getSize.run(); } /** * {@inheritDoc} */ public boolean isEnabled() { return enabled; } /** * {@inheritDoc} */ public boolean isPatternSupported() { return true; } /** * {@inheritDoc} */ public boolean isChildNodesByPageSupported() { return true; } /** * Internal put Item. * * @param item ItemData, new data to put in the cache * @return ItemData, previous data or null */ protected ItemData putItem(ItemData item) { if (item.isNode()) { return putNode((NodeData)item, ModifyChildOption.MODIFY); } else { return putProperty((PropertyData)item, ModifyChildOption.MODIFY); } } protected ItemData putItemInBufferedCache(ItemData item) { if (item.isNode()) { return putNodeInBufferedCache((NodeData)item, ModifyChildOption.MODIFY); } else { return putProperty((PropertyData)item, ModifyChildOption.MODIFY); } } /** * Internal put Node. * * @param node, NodeData, new data to put in the cache * @return NodeData, previous data or null */ protected ItemData putNode(NodeData node, ModifyChildOption modifyListsOfChild) { if (node.getParentIdentifier() != null) { if (modifyListsOfChild == ModifyChildOption.NOT_MODIFY) { cache.putIfAbsent(new CacheQPath(getOwnerId(), node.getParentIdentifier(), node.getQPath(), ItemType.NODE), node.getIdentifier()); } else { cache.put(new CacheQPath(getOwnerId(), node.getParentIdentifier(), node.getQPath(), ItemType.NODE), node.getIdentifier()); } // if MODIFY and List present OR FORCE_MODIFY, then write if (modifyListsOfChild != ModifyChildOption.NOT_MODIFY) { cache.addToPatternList(new CachePatternNodesId(getOwnerId(), node.getParentIdentifier()), node); cache.addToList(new CacheNodesId(getOwnerId(), node.getParentIdentifier()), node.getIdentifier(), modifyListsOfChild == ModifyChildOption.FORCE_MODIFY); cache.remove(new CacheNodesByPageId(getOwnerId(), node.getParentIdentifier())); } } if (modifyListsOfChild == ModifyChildOption.NOT_MODIFY) { return (ItemData)cache.putIfAbsent(new CacheId(getOwnerId(), node.getIdentifier()), node); } else { return (ItemData)cache.put(new CacheId(getOwnerId(), node.getIdentifier()), node, true); } } protected ItemData putNodeInBufferedCache(NodeData node, ModifyChildOption modifyListsOfChild) { if (node.getParentIdentifier() != null) { cache.put(new CacheQPath(getOwnerId(), node.getParentIdentifier(), node.getQPath(), ItemType.NODE), node.getIdentifier()); // if MODIFY and List present OR FORCE_MODIFY, then write if (modifyListsOfChild != ModifyChildOption.NOT_MODIFY) { cache.addToList(new CacheNodesId(getOwnerId(), node.getParentIdentifier()), node.getIdentifier(), modifyListsOfChild == ModifyChildOption.FORCE_MODIFY); } } // NullNodeData must never be returned inside internal cache operations. ItemData itemData = (ItemData)cache.putInBuffer(new CacheId(getOwnerId(), node.getIdentifier()), node); return (itemData instanceof NullItemData) ? null : itemData; } /** * Internal put NullNode. * * @param item, NullItemData, new data to put in the cache */ protected void putNullItem(NullItemData item) { boolean inTransaction = cache.isTransactionActive(); try { if (!inTransaction) { cache.beginTransaction(); } cache.setLocal(true); if (!item.getIdentifier().equals(NullItemData.NULL_ID)) { cache.putIfAbsent(new CacheId(getOwnerId(), item.getIdentifier()), item); } else if (item.getName() != null && item.getParentIdentifier() != null) { cache.putIfAbsent( new CacheQPath(getOwnerId(), item.getParentIdentifier(), item.getName(), ItemType.getItemType(item)), NullItemData.NULL_ID); } } finally { cache.setLocal(false); if (!inTransaction) { dedicatedTxCommit(); } } } /** * Internal put Property. * * @param prop, PropertyData, new data to put in the cache * @param modifyListsOfChild * @return PropertyData, previous data or null */ protected PropertyData putProperty(PropertyData prop, ModifyChildOption modifyListsOfChild) { // if MODIFY and List present OR FORCE_MODIFY, then write if (modifyListsOfChild != ModifyChildOption.NOT_MODIFY) { cache.addToPatternList(new CachePatternPropsId(getOwnerId(), prop.getParentIdentifier()), prop); cache.addToList(new CachePropsId(getOwnerId(), prop.getParentIdentifier()), prop.getIdentifier(), modifyListsOfChild == ModifyChildOption.FORCE_MODIFY); } if (modifyListsOfChild == ModifyChildOption.NOT_MODIFY) { cache.putIfAbsent( new CacheQPath(getOwnerId(), prop.getParentIdentifier(), prop.getQPath(), ItemType.PROPERTY), prop.getIdentifier()); } else { cache.put(new CacheQPath(getOwnerId(), prop.getParentIdentifier(), prop.getQPath(), ItemType.PROPERTY), prop.getIdentifier()); } // add referenced property if (modifyListsOfChild != ModifyChildOption.NOT_MODIFY && prop.getType() == PropertyType.REFERENCE) { List<ValueData> lData = prop.getValues(); for (int i = 0, length = lData.size(); i < length; i++) { ValueData vdata = lData.get(i); String nodeIdentifier = null; try { nodeIdentifier = ValueDataUtil.getString(vdata); } catch (IllegalStateException e) { if (LOG.isTraceEnabled()) { LOG.trace("An exception occurred: " + e.getMessage()); } } catch (RepositoryException e) { if (LOG.isTraceEnabled()) { LOG.trace("An exception occurred: " + e.getMessage()); } } cache.addToList(new CacheRefsId(getOwnerId(), nodeIdentifier), prop.getIdentifier(), modifyListsOfChild == ModifyChildOption.FORCE_MODIFY); } } // NullItemData must never be returned inside internal cache operations. ItemData returnedData; if (modifyListsOfChild == ModifyChildOption.NOT_MODIFY) { returnedData = (ItemData)cache.putIfAbsent(new CacheId(getOwnerId(), prop.getIdentifier()), prop); } else { returnedData = (ItemData)cache.put(new CacheId(getOwnerId(), prop.getIdentifier()), prop, true); } return (returnedData instanceof NullItemData) ? null : (PropertyData) returnedData; } protected void removeItem(ItemData item) { cache.remove(new CacheId(getOwnerId(), item.getIdentifier())); cache .remove(new CacheQPath(getOwnerId(), item.getParentIdentifier(), item.getQPath(), ItemType.getItemType(item))); if (item.isNode()) { if (item.getParentIdentifier() != null) { cache.removeFromPatternList(new CachePatternNodesId(getOwnerId(), item.getParentIdentifier()), item); cache.removeFromList(new CacheNodesId(getOwnerId(), item.getParentIdentifier()), item.getIdentifier()); cache.remove(new CacheNodesByPageId(getOwnerId(), item.getParentIdentifier())); } cache.remove(new CacheNodesId(getOwnerId(), item.getIdentifier())); cache.remove(new CachePropsId(getOwnerId(), item.getIdentifier())); cache.remove(new CacheNodesByPageId(getOwnerId(), item.getIdentifier())); cache.remove(new CachePatternNodesId(getOwnerId(), item.getIdentifier())); cache.remove(new CachePatternPropsId(getOwnerId(), item.getIdentifier())); cache.remove(new CacheRefsId(getOwnerId(), item.getIdentifier())); } else { cache.removeFromPatternList(new CachePatternPropsId(getOwnerId(), item.getParentIdentifier()), item); cache.removeFromList(new CachePropsId(getOwnerId(), item.getParentIdentifier()), item.getIdentifier()); } } /** * Update Node's mixin and ACL. * * @param node NodeData */ protected void updateMixin(NodeData node) { NodeData prevData = (NodeData)cache.put(new CacheId(getOwnerId(), node.getIdentifier()), node, true); // prevent update NullNodeData if (!(prevData instanceof NullNodeData)) { if (prevData != null) { // do update ACL if needed if (prevData.getACL() == null || !prevData.getACL().equals(node.getACL())) { updateChildsACL(node.getIdentifier(), node.getACL()); } } else if (LOG.isDebugEnabled()) { LOG.debug("Previous NodeData not found for mixin update " + node.getQPath().getAsString()); } } } /** * Update Node hierarchy in case of same-name siblings reorder. * Assumes the new (updated) nodes already put in the cache. Previous name of updated nodes will be calculated * and that node will be deleted (if has same id as the new node). Children paths will be updated to a new node path. * * @param node new node data * @param prevPath old path * @param idsToSkip set of ids to skip, this is needed to avoid modifying the path twice in case of an OrderBefore * @return the ids to skip for the next operation */ protected Set<String> updateInBuffer(final NodeData node, final QPath prevPath, Set<String> idsToSkip) { // I expect that NullNodeData will never update existing NodeData. CacheQPath prevKey = new CacheQPath(getOwnerId(), node.getParentIdentifier(), prevPath, ItemType.NODE); if (node.getIdentifier().equals(cache.getFromBuffer(prevKey))) { cache.remove(prevKey); } // update childs paths if index changed int nodeIndex = node.getQPath().getEntries()[node.getQPath().getEntries().length - 1].getIndex(); int prevNodeIndex = prevPath.getEntries()[prevPath.getEntries().length - 1].getIndex(); if (nodeIndex != prevNodeIndex) { // its a same name reordering return updateTreePath(prevPath, node.getQPath(), idsToSkip); } return null; } /** * Check all items in cache if it is a descendant of the previous root path, and if so update the path according * the new root path. * * @param prevRootPath previous root path * @param newRootPath new root path * @param idsToSkip set of ids to skip, this is needed to avoid modifying the path twice in case of an OrderBefore * @return the ids to skip for the next operation */ protected Set<String> updateTreePath(QPath prevRootPath, QPath newRootPath, Set<String> idsToSkip) { return caller.updateTreePath(prevRootPath, newRootPath, idsToSkip); } /** * Apply rename operation on cache. Parent node will be re-added into the cache since * parent or name might changing. For other children only item data will be replaced. */ protected void renameItem(final ItemState state, final ItemState lastDelete) { ItemData data = state.getData(); ItemData prevData = getFromBufferedCacheById.run(data.getIdentifier()); if (data.isNode()) { if (state.isPersisted()) { // it is state where name can be changed by rename operation, so re-add node removeItem(lastDelete.getData()); putItem(state.getData()); } else { // update item data with new name only cache.put(new CacheId(getOwnerId(), data.getIdentifier()), data, false); } } else { PropertyData prop = (PropertyData)data; if (prevData != null && !(prevData instanceof NullItemData)) { PropertyData newProp = new PersistedPropertyData(prop.getIdentifier(), prop.getQPath(), prop.getParentIdentifier(), prop.getPersistedVersion(), prop.getType(), prop.isMultiValued(), ((PropertyData)prevData).getValues(), new SimplePersistedSize( ((PersistedPropertyData)prevData).getPersistedSize())); // update item data with new name and old values only cache.put(new CacheId(getOwnerId(), newProp.getIdentifier()), newProp, false); } else { // remove item to avoid inconsistency in cluster mode since we have not old values cache.remove(new CacheId(getOwnerId(), data.getIdentifier())); } } } /** * Update child Nodes ACLs. * * @param parentId String - root node id of JCR subtree. * @param acl AccessControlList */ protected void updateChildsACL(String parentId, AccessControlList acl) { caller.updateChildsACL(parentId, acl); } public void beginTransaction() { cache.beginTransaction(); } public void commitTransaction() { cache.commitTransaction(); } public void rollbackTransaction() { cache.rollbackTransaction(); } /** * {@inheritDoc} */ public boolean isTXAware() { return true; } /** * <li>NOT_MODIFY - node(property) is not added to the parent's list * (no persistent changes performed, cache used as cache)</li> * <li>MODIFY - node(property) is added to the parent's list if parent in the cache * (new item is added to persistent, add to list if it is present)</li> * <li>FORCE_MODIFY - node(property) is added to the parent's list anyway (when list is read from DB, forcing write)</li> */ private enum ModifyChildOption { NOT_MODIFY, MODIFY, FORCE_MODIFY } /** * Allows to commit the cache changes in a dedicated XA Tx in order to avoid potential * deadlocks */ private void dedicatedTxCommit() { commitTransaction.run(); } /** * {@inheritDoc} */ public void addReferencedProperties(String identifier, List<PropertyData> refProperties) { boolean inTransaction = cache.isTransactionActive(); try { if (!inTransaction) { cache.beginTransaction(); } cache.setLocal(true); Set<Object> set = new HashSet<Object>(); for (PropertyData prop : refProperties) { putProperty(prop, ModifyChildOption.NOT_MODIFY); set.add(prop.getIdentifier()); } cache.putIfAbsent(new CacheRefsId(getOwnerId(), identifier), set); } finally { cache.setLocal(false); if (!inTransaction) { dedicatedTxCommit(); } } } /** * {@inheritDoc} */ public List<PropertyData> getReferencedProperties(String identifier) { return getReferencedProperties.run(identifier); } /** * {@inheritDoc} */ public void backup(File storageDir) throws BackupException { } /** * {@inheritDoc} */ @Managed @ManagedDescription("Remove all the existing items from the cache") public void clean() throws BackupException { LOG.info("Start to clean all the existing items from ISPN cache"); if (cache.getStatus() == ComponentStatus.RUNNING) { caller.clearCache(); } } /** * {@inheritDoc} */ public DataRestore getDataRestorer(DataRestoreContext context) throws BackupException { return new DataRestore() { /** * {@inheritDoc} */ public void clean() throws BackupException { LOG.info("Start to clean all the existing items from ISPN cache"); caller.clearCache(); } /** * {@inheritDoc} */ public void restore() throws BackupException { } /** * {@inheritDoc} */ public void commit() throws BackupException { } /** * {@inheritDoc} */ public void rollback() throws BackupException { } /** * {@inheritDoc} */ public void close() throws BackupException { } }; } /** * {@inheritDoc} */ public void addListener(WorkspaceStorageCacheListener listener) { if (isDistributedMode()) { throw new UnsupportedOperationException("The cache listeners are not supported by the " + "ISPNCacheWorkspaceStorageCache in case of the distributed mode"); } listeners.add(listener); } /** * {@inheritDoc} */ public void removeListener(WorkspaceStorageCacheListener listener) { if (isDistributedMode()) { throw new UnsupportedOperationException("The cache listeners are not supported by the " + "ISPNCacheWorkspaceStorageCache in case of the distributed mode"); } listeners.remove(listener); } /** * Called when a cache entry corresponding to the given node has item updated * @param data the item corresponding to the updated cache entry */ private void onCacheEntryUpdated(ItemData data) { if (data == null || data instanceof NullItemData) { return; } for (WorkspaceStorageCacheListener listener : listeners) { try { listener.onCacheEntryUpdated(data); } catch (RuntimeException e) //NOSONAR { LOG.warn("The method onCacheEntryUpdated fails for the listener " + listener.getClass(), e); } } } private static boolean updateTreePath(Cache<CacheKey, Object> cache, String ownerId, ItemData data, QPath prevRootPath, QPath newRootPath) { if (data == null) { return false; } // check is this descendant of prevRootPath QPath nodeQPath = data.getQPath(); if (nodeQPath != null && nodeQPath.isDescendantOf(prevRootPath)) { //make relative path QPathEntry[] relativePath = null; try { relativePath = nodeQPath.getRelPath(nodeQPath.getDepth() - prevRootPath.getDepth()); } catch (IllegalPathException e) { // Do nothing. Never happens. if (LOG.isTraceEnabled()) { LOG.trace("An exception occurred: " + e.getMessage()); } } if (relativePath == null) { LOG.error("Could not get the relative path of the node " + nodeQPath + " with " + (nodeQPath.getDepth() - prevRootPath.getDepth()) + " as relative degree"); return false; } // make new path - no matter node or property QPath newPath = QPath.makeChildPath(newRootPath, relativePath); if (data.isNode()) { // update node NodeData prevNode = (NodeData)data; PersistedNodeData newNode = new PersistedNodeData(prevNode.getIdentifier(), newPath, prevNode.getParentIdentifier(), prevNode.getPersistedVersion(), prevNode.getOrderNumber(), prevNode.getPrimaryTypeName(), prevNode.getMixinTypeNames(), prevNode.getACL()); // update this node cache.put(new CacheId(ownerId, newNode.getIdentifier()), newNode); } else { //update property PropertyData prevProp = (PropertyData)data; PersistedPropertyData newProp = new PersistedPropertyData(prevProp.getIdentifier(), newPath, prevProp.getParentIdentifier(), prevProp.getPersistedVersion(), prevProp.getType(), prevProp.isMultiValued(), prevProp.getValues(), new SimplePersistedSize(((PersistedPropertyData)prevProp).getPersistedSize())); // update this property cache.put(new CacheId(ownerId, newProp.getIdentifier()), newProp); } return true; } return false; } /** * Actions that are not supposed to be called within a transaction * * Created by The eXo Platform SAS * Author : Nicolas Filotto * nicolas.filotto@exoplatform.com * 21 janv. 2010 */ protected abstract class CacheActionNonTxAware<R, A> extends ActionNonTxAware<R, A, RuntimeException> { /** * {@inheritDoc} */ protected TransactionManager getTransactionManager() { return ISPNCacheWorkspaceStorageCache.this.getTransactionManager(); } } @SuppressWarnings("rawtypes") @Listener public class CacheEventListener { @CacheEntryModified public void cacheEntryModified(CacheEntryModifiedEvent evt) { if (!evt.isPre() && evt.getKey() instanceof CacheId) { final ItemData value = (ItemData)evt.getValue(); onCacheEntryUpdated(value); } } } /** * This class defines all the methods that could change between the replicated and the distributed mode. * By default it implements the methods for the local and replicated mode. * */ private class GlobalOperationCaller { protected int getCacheSize() { return cache.size(); } protected void clearCache() { cache.clear(); } /** * Update child Nodes ACLs. * * @param parentId String - root node id of JCR subtree. * @param acl AccessControlList */ protected void updateChildsACL(String parentId, AccessControlList acl) { loop : for (Iterator<NodeData> iter = new ChildNodesIterator<NodeData>(parentId); iter.hasNext();) { NodeData prevNode = iter.next(); // is ACL changes on this node (i.e. ACL inheritance brokes) boolean hasExoPrivilegeable = false; boolean hasExoOwneable = false; for (InternalQName mixin : prevNode.getMixinTypeNames()) { if (mixin.equals(Constants.EXO_PRIVILEGEABLE)) { hasExoPrivilegeable = true; if (hasExoOwneable) { continue loop; } } else if (mixin.equals(Constants.EXO_OWNEABLE)) { hasExoOwneable = true; if (hasExoPrivilegeable) { continue loop; } } } AccessControlList newAcl = null; if (hasExoOwneable) { newAcl = new AccessControlList(prevNode.getACL().getOwner(), acl.getPermissionEntries()); } else if (hasExoPrivilegeable) { newAcl = new AccessControlList(acl.getOwner(), prevNode.getACL().getPermissionEntries()); } if (newAcl != null) { if (newAcl.equals(prevNode.getACL())) { // No need to keep traversing the cache since the acl is the same continue loop; } acl = newAcl; } // recreate with new path for child Nodes only PersistedNodeData newNode = new PersistedNodeData(prevNode.getIdentifier(), prevNode.getQPath(), prevNode.getParentIdentifier(), prevNode.getPersistedVersion(), prevNode.getOrderNumber(), prevNode.getPrimaryTypeName(), prevNode.getMixinTypeNames(), acl); // update this node cache.put(new CacheId(getOwnerId(), newNode.getIdentifier()), newNode); // update childs recursive updateChildsACL(newNode.getIdentifier(), acl); } } /** * Check all items in cache if it is a descendant of the previous root path, and if so update the path according * the new root path. * * @param prevRootPath previous root path * @param newRootPath new root path * @param idsToSkip set of ids to skip, this is needed to avoid modifying the path twice in case of an OrderBefore * @return the ids to skip for the next operation */ protected Set<String> updateTreePath(QPath prevRootPath, QPath newRootPath, Set<String> idsToSkip) { Set<String> result = new HashSet<String>(); Map<CacheKey, Object> changes = cache.getLastChanges(); for (CacheKey key : changes.keySet()) { if (key instanceof CacheId) { ItemData data = (ItemData)changes.get(key); if (data != null && (idsToSkip == null || !idsToSkip.contains(data.getIdentifier())) && ISPNCacheWorkspaceStorageCache .updateTreePath(cache, getOwnerId(), data, prevRootPath, newRootPath)) { result.add(data.getIdentifier()); } } } // check all ITEMS in cache for (CacheKey key : cache.keySet()) { if (key instanceof CacheId && !changes.containsKey(key)) { ItemData data = (ItemData)cache.get(key); if (ISPNCacheWorkspaceStorageCache.updateTreePath(cache, getOwnerId(), data, prevRootPath, newRootPath)) { result.add(data.getIdentifier()); } } } return result; } } /** * This class implements all the global operations for the distributed mode * */ private class DistributedOperationCaller extends GlobalOperationCaller { /** * {@inheritDoc} */ @Override protected int getCacheSize() { Map<String, Integer> map = SecurityHelper.doPrivilegedAction(new PrivilegedAction<Map<String, Integer>>() { public Map<String, Integer> run() { MapReduceTask<CacheKey, Object, String, Integer> task = new MapReduceTask<CacheKey, Object, String, Integer>(cache); task.mappedWith(new GetSizeMapper(getOwnerId())).reducedWith(new GetSizeReducer<String>()); return task.execute(); } }); int sum = 0; for (Integer i : map.values()) { sum += i; } return sum; } /** * {@inheritDoc} */ @Override protected void clearCache() { SecurityHelper.doPrivilegedAction(new PrivilegedAction<Void>() { public Void run() { MapReduceTask<CacheKey, Object, Void, Void> task = new MapReduceTask<CacheKey, Object, Void, Void>(cache); task.mappedWith(new ClearCacheMapper(getOwnerId())).reducedWith(new IdentityReducer()); task.execute(); return null; } }); } /** * {@inheritDoc} */ @Override protected Set<String> updateTreePath(final QPath prevRootPath, final QPath newRootPath, Set<String> idsToSkip) { Set<String> result = new HashSet<String>(); final TransactionManager tm = getTransactionManager(); if (tm != null) { try { // Add the action out of the current transaction to avoid deadlocks tm.getTransaction().registerSynchronization(new Synchronization() { public void beforeCompletion() { } public void afterCompletion(int status) { if (status == Status.STATUS_COMMITTED) { try { // Since the tx is successfully committed we can call components non tx aware // The listeners will need to be executed outside the current tx so we suspend // the current tx we can face enlistment issues on product like ISPN tm.suspend(); _updateTreePath(prevRootPath, newRootPath); } catch (SystemException e) { LOG.warn("Cannot suspend the transaction", e); } } } }); return result; } catch (Exception e) //NOSONAR { if (LOG.isDebugEnabled()) { LOG.debug("Cannot register the synchronization to the current transaction in order to update" + " the path out of the transaction", e); } } } _updateTreePath(prevRootPath, newRootPath); return result; } private void _updateTreePath(final QPath prevRootPath, final QPath newRootPath) { SecurityHelper.doPrivilegedAction(new PrivilegedAction<Void>() { public Void run() { MapReduceTask<CacheKey, Object, Void, Void> task = new MapReduceTask<CacheKey, Object, Void, Void>(cache); task.mappedWith(new UpdateTreePathMapper(getOwnerId(), prevRootPath, newRootPath)).reducedWith( new IdentityReducer()); task.execute(); return null; } }); } /** * {@inheritDoc} */ @Override protected void updateChildsACL(String parentId, final AccessControlList acl) { ItemData parentItem = get(parentId); if (!(parentItem instanceof NodeData)) { return; } final QPath parentPath = ((NodeData)parentItem).getQPath(); SecurityHelper.doPrivilegedAction(new PrivilegedAction<Void>() { public Void run() { MapReduceTask<CacheKey, Object, Void, Void> task = new MapReduceTask<CacheKey, Object, Void, Void>(cache); task.mappedWith(new UpdateChildsACLMapper(getOwnerId(), parentPath, acl)).reducedWith( new IdentityReducer()); task.execute(); return null; } }); } } public static class GetSizeMapper extends AbstractMapper<String, Integer> { public GetSizeMapper() { } public GetSizeMapper(String ownerId) { super(ownerId); } /** * {@inheritDoc} */ @Override protected void _map(CacheKey key, Object value, Collector<String, Integer> collector) { collector.emit("total", Integer.valueOf(1)); } } public static class GetSizeReducer<K> implements Reducer<K, Integer> { /** * The serial version UID */ private static final long serialVersionUID = 7877781449514234007L; /** * @see org.infinispan.distexec.mapreduce.Reducer#reduce(java.lang.Object, java.util.Iterator) */ public Integer reduce(K reducedKey, Iterator<Integer> iter) { int sum = 0; while (iter.hasNext()) { Integer i = iter.next(); sum += i; } return sum; } } public static class ClearCacheMapper extends AbstractMapper<Void, Void> { public ClearCacheMapper() { } public ClearCacheMapper(String ownerId) { super(ownerId); } /** * {@inheritDoc} */ @Override protected void _map(CacheKey key, Object value, Collector<Void, Void> collector) { ExoContainer container = ExoContainerContext.getTopContainer(); if (container == null) { LOG.error("The top container could not be found"); return; } DistributedCacheManager dcm = (DistributedCacheManager)container.getComponentInstanceOfType(DistributedCacheManager.class); if (dcm == null) { LOG.error("The DistributedCacheManager could not be found at top container level, please configure it."); return; } Cache<CacheKey, Object> cache = dcm.getCache(CACHE_NAME); cache.getAdvancedCache().withFlags(Flag.SKIP_REMOTE_LOOKUP, Flag.FAIL_SILENTLY).remove(key); } } public static class IdentityReducer implements Reducer<Void, Void> { /** * The serial version UID */ private static final long serialVersionUID = -6193360351201912040L; /** * @see org.infinispan.distexec.mapreduce.Reducer#reduce(java.lang.Object, java.util.Iterator) */ public Void reduce(Void reducedKey, Iterator<Void> iter) { return null; } } public static class UpdateTreePathMapper extends AbstractMapper<Void, Void> { private QPath prevRootPath, newRootPath; public UpdateTreePathMapper() { } public UpdateTreePathMapper(String ownerId, QPath prevRootPath, QPath newRootPath) { super(ownerId); this.prevRootPath = prevRootPath; this.newRootPath = newRootPath; } /** * {@inheritDoc} */ @Override protected boolean isValid(CacheKey key) { return super.isValid(key) && key instanceof CacheId; } /** * {@inheritDoc} */ @Override public void writeExternal(ObjectOutput out) throws IOException { super.writeExternal(out); byte[] buf = prevRootPath.getAsString().getBytes(Constants.DEFAULT_ENCODING); out.writeInt(buf.length); out.write(buf); buf = newRootPath.getAsString().getBytes(Constants.DEFAULT_ENCODING); out.writeInt(buf.length); out.write(buf); } /** * {@inheritDoc} */ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { super.readExternal(in); byte[] buf; try { buf = new byte[in.readInt()]; in.readFully(buf); String sQPath = new String(buf, Constants.DEFAULT_ENCODING); prevRootPath = QPath.parse(sQPath); buf = new byte[in.readInt()]; in.readFully(buf); sQPath = new String(buf, Constants.DEFAULT_ENCODING); newRootPath = QPath.parse(sQPath); } catch (IllegalPathException e) { throw new IOException("Deserialization error. ", e); } } /** * {@inheritDoc} */ @Override protected void _map(CacheKey key, Object value, Collector<Void, Void> collector) { ExoContainer container = ExoContainerContext.getTopContainer(); if (container == null) { LOG.error("The top container could not be found"); return; } DistributedCacheManager dcm = (DistributedCacheManager)container.getComponentInstanceOfType(DistributedCacheManager.class); if (dcm == null) { LOG.error("The DistributedCacheManager could not be found at top container level, please configure it."); return; } Cache<CacheKey, Object> cache = dcm.getCache(CACHE_NAME); ISPNCacheWorkspaceStorageCache.updateTreePath(cache.getAdvancedCache().withFlags(Flag.SKIP_REMOTE_LOOKUP), ownerId, (ItemData)value, prevRootPath, newRootPath); } } public static class UpdateChildsACLMapper extends AbstractMapper<Void, Void> { private QPath parentPath; private AccessControlList acl; public UpdateChildsACLMapper() { } public UpdateChildsACLMapper(String ownerId, QPath parentPath, AccessControlList acl) { super(ownerId); this.parentPath = parentPath; this.acl = acl; } /** * {@inheritDoc} */ @Override protected boolean isValid(CacheKey key) { return super.isValid(key) && key instanceof CacheId; } /** * {@inheritDoc} */ @Override public void writeExternal(ObjectOutput out) throws IOException { super.writeExternal(out); byte[] buf = parentPath.getAsString().getBytes(Constants.DEFAULT_ENCODING); out.writeInt(buf.length); out.write(buf); out.writeBoolean(acl != null); if (acl != null) { acl.writeExternal(out); } } /** * {@inheritDoc} */ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { super.readExternal(in); byte[] buf; try { buf = new byte[in.readInt()]; in.readFully(buf); String sQPath = new String(buf, Constants.DEFAULT_ENCODING); parentPath = QPath.parse(sQPath); } catch (IllegalPathException e) { throw new IOException("Deserialization error. ", e); } if (in.readBoolean()) { this.acl = new AccessControlList(); acl.readExternal(in); } } /** * {@inheritDoc} */ @Override protected void _map(CacheKey key, Object value, Collector<Void, Void> collector) { if (!(value instanceof NodeData)) { return; } NodeData prevNode = (NodeData)value; // check is this descendant of parentPath QPath nodeQPath = prevNode.getQPath(); if (nodeQPath == null || !nodeQPath.isDescendantOf(parentPath)) { return; } // is ACL changes on this node (i.e. ACL inheritance brokes) boolean hasExoPrivilegeable = false; boolean hasExoOwneable = false; for (InternalQName mixin : prevNode.getMixinTypeNames()) { if (mixin.equals(Constants.EXO_PRIVILEGEABLE)) { hasExoPrivilegeable = true; if (hasExoOwneable) { return; } } else if (mixin.equals(Constants.EXO_OWNEABLE)) { hasExoOwneable = true; if (hasExoPrivilegeable) { return; } } } ExoContainer container = ExoContainerContext.getTopContainer(); if (container == null) { LOG.error("The top container could not be found"); return; } DistributedCacheManager dcm = (DistributedCacheManager)container.getComponentInstanceOfType(DistributedCacheManager.class); if (dcm == null) { LOG.error("The DistributedCacheManager could not be found at top container level, please configure it."); return; } Cache<CacheKey, Object> cache = dcm.getCache(CACHE_NAME); // we force the reloading cache.getAdvancedCache().withFlags(Flag.SKIP_REMOTE_LOOKUP, Flag.FAIL_SILENTLY).remove(key); } } /** * {@inheritDoc} */ public void start() { } /** * {@inheritDoc} */ public void stop() { cache.stop(); } public static class FakeValueSet extends HashSet<String> { /** * Serial Version UID */ private static final long serialVersionUID = 6163005084471981227L; public FakeValueSet() {} public FakeValueSet(int size) { for (int i = 0; i < size; i++) { // We put only fake ids to store the size and to force a reloading in case getChildNodes // is called add(Integer.toString(i)); } } } }