Author: nzamosenchuk
Date: 2010-09-28 05:21:26 -0400 (Tue, 28 Sep 2010)
New Revision: 3196
Added:
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/datamodel/NullNodeData.java
Modified:
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/CacheableWorkspaceDataManager.java
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/LinkedWorkspaceStorageCacheImpl.java
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/jbosscache/BufferedJBossCache.java
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/jbosscache/JBossCacheWorkspaceStorageCache.java
jcr/trunk/exo.jcr.component.core/src/test/java/org/exoplatform/services/jcr/impl/dataflow/persistent/TestCacheableWorkspaceDataManager.java
Log:
EXOJCR-952 : committing changes related to EXOJCR-609 into the trunk (from former
branches/1.14.x)
Added:
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/datamodel/NullNodeData.java
===================================================================
---
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/datamodel/NullNodeData.java
(rev 0)
+++
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/datamodel/NullNodeData.java 2010-09-28
09:21:26 UTC (rev 3196)
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2010 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.datamodel;
+
+import org.exoplatform.services.jcr.access.AccessControlList;
+import org.exoplatform.services.jcr.dataflow.ItemDataVisitor;
+
+import javax.jcr.RepositoryException;
+
+/**
+ * This class is used to represent <code>null</code> value, it is designed to
be used
+ * into the cache to represent missing value.
+ *
+ * @author <a href="anatoliy.bazko(a)exoplatform.org">Anatoliy
Bazko</a>
+ * @version $Id: NullNodeData.java 111 2010-11-11 11:11:11Z tolusha $
+ */
+public class NullNodeData implements NodeData
+{
+
+ private final String id;
+
+ private final String parentId;
+
+ private final QPath path;
+
+ public NullNodeData(NodeData parentData, QPathEntry name)
+ {
+ this.parentId = parentData.getIdentifier();
+ this.path = QPath.makeChildPath(parentData.getQPath(), name);
+ this.id = parentId + "$" + name.asString();
+ }
+
+ public NullNodeData(String id)
+ {
+ this.parentId = null;
+ this.path = new QPath(new QPathEntry[]{new QPathEntry(null, null, 0)});
+ this.id = id;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AccessControlList getACL()
+ {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public InternalQName[] getMixinTypeNames()
+ {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getOrderNumber()
+ {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public InternalQName getPrimaryTypeName()
+ {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void accept(ItemDataVisitor visitor) throws RepositoryException
+ {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getIdentifier()
+ {
+ return id;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getParentIdentifier()
+ {
+ return parentId;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getPersistedVersion()
+ {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public QPath getQPath()
+ {
+ return path;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isNode()
+ {
+ return true;
+ }
+
+}
Property changes on:
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/datamodel/NullNodeData.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Modified:
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/CacheableWorkspaceDataManager.java
===================================================================
---
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/CacheableWorkspaceDataManager.java 2010-09-28
09:01:44 UTC (rev 3195)
+++
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/CacheableWorkspaceDataManager.java 2010-09-28
09:21:26 UTC (rev 3196)
@@ -23,6 +23,7 @@
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.NullNodeData;
import org.exoplatform.services.jcr.datamodel.PropertyData;
import org.exoplatform.services.jcr.datamodel.QPathEntry;
import org.exoplatform.services.jcr.datamodel.ValueData;
@@ -426,7 +427,7 @@
fixPropertyValues((PropertyData)data);
}
- return data;
+ return data instanceof NullNodeData ? null : data;
}
/**
@@ -435,10 +436,10 @@
@Override
public ItemData getItemData(String identifier) throws RepositoryException
{
- // 2. Try from cache
+ // 1. Try from cache
ItemData data = getCachedItemData(identifier);
- // 3. Try from container
+ // 2 Try from container
if (data == null)
{
final DataRequest request = new DataRequest(identifier);
@@ -468,7 +469,7 @@
fixPropertyValues((PropertyData)data);
}
- return data;
+ return data instanceof NullNodeData ? null : data;
}
/**
@@ -677,9 +678,9 @@
throws RepositoryException
{
ItemData data = super.getItemData(parentData, name, itemType);
- if (data != null && cache.isEnabled())
+ if (cache.isEnabled())
{
- cache.put(data);
+ cache.put(data == null ? new NullNodeData(parentData, name) : data);
}
return data;
}
@@ -694,9 +695,16 @@
protected ItemData getPersistedItemData(String identifier) throws RepositoryException
{
ItemData data = super.getItemData(identifier);
- if (data != null && cache.isEnabled())
+ if (cache.isEnabled())
{
- cache.put(data);
+ if (data != null)
+ {
+ cache.put(data);
+ }
+ else if (identifier != null)
+ {
+ cache.put(new NullNodeData(identifier));
+ }
}
return data;
}
Modified:
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/LinkedWorkspaceStorageCacheImpl.java
===================================================================
---
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/LinkedWorkspaceStorageCacheImpl.java 2010-09-28
09:01:44 UTC (rev 3195)
+++
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/LinkedWorkspaceStorageCacheImpl.java 2010-09-28
09:21:26 UTC (rev 3196)
@@ -27,6 +27,7 @@
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.NullNodeData;
import org.exoplatform.services.jcr.datamodel.PropertyData;
import org.exoplatform.services.jcr.datamodel.QPath;
import org.exoplatform.services.jcr.datamodel.QPathEntry;
@@ -257,7 +258,9 @@
return true;
}
else
+ {
return false;
+ }
}
}
@@ -455,7 +458,9 @@
{
ItemData item = ce.getValue().getItem();
if (item != null)
+ {
removeExpiredChilds(item);
+ }
citer.remove();
expiredCount++;
@@ -463,8 +468,10 @@
}
if (log.isDebugEnabled())
+ {
log.debug("Cleaner task done in " +
(System.currentTimeMillis() - start) + "ms. Size " + sizeBefore
+ " -> " + cache.size() + ", " + expiredCount
+ " processed.");
+ }
}
catch (ConcurrentModificationException e)
{
@@ -483,9 +490,13 @@
catch (Throwable e)
{
if (log.isDebugEnabled())
+ {
log.error("Cleaner task error " + e, e);
+ }
else
+ {
log.error("Cleaner task error " + e + ". Will try next
time.");
+ }
}
finally
{
@@ -494,8 +505,10 @@
}
else // skip if lock is used by another process
if (log.isDebugEnabled())
+ {
log.debug("Cleaner task skipped. Ceche in use by another process
["
+ String.valueOf(writeLock.getLockOwner()) + "]. Will try next
time.");
+ }
}
/**
@@ -511,7 +524,9 @@
if (propertiesCache.remove(item.getIdentifier()) != null)
{
if (log.isDebugEnabled())
+ {
log.debug(name + ", removeExpiredChilds() propertiesCache.remove
" + item.getIdentifier());
+ }
}
}
else
@@ -520,7 +535,9 @@
if (propertiesCache.remove(item.getParentIdentifier()) != null)
{
if (log.isDebugEnabled())
+ {
log.debug(name + ", removeExpiredChilds() propertiesCache.remove
" + item.getParentIdentifier());
+ }
}
}
}
@@ -575,7 +592,9 @@
}
else // skip if previous in progress
if (log.isDebugEnabled())
+ {
log.debug("Cleaner task skipped. Previous one still runs. Will try next
time.");
+ }
}
}
@@ -595,7 +614,9 @@
}
else // skip if previous in progress
if (log.isDebugEnabled())
+ {
log.debug("Statistic task skipped. Previous one still runs. Will try
next time.");
+ }
}
}
@@ -807,6 +828,7 @@
totalGetTime);
if (showStatistic)
+ {
try
{
double rel =
@@ -829,6 +851,7 @@
{
LOG.warn("Show statistic log.info error " + e);
}
+ }
this.statistic = st;
@@ -938,12 +961,20 @@
if (enabled && item != null)
{
+ if (item instanceof NullNodeData)
+ {
+ // skip null values
+ return;
+ }
+
writeLock.lock();
try
{
if (LOG.isDebugEnabled())
+ {
LOG.debug(name + ", put() " + item.getQPath().getAsString() +
" " + item.getIdentifier()
+ " -- " + item);
+ }
putItem(item);
@@ -971,17 +1002,23 @@
for (int ci = 0; ci < cachedParentChilds.size(); ci++)
{
if (index == ci)
+ {
newChilds.add(nodeData); // place in new position
+ }
else
+ {
newChilds.add(cachedParentChilds.get(ci)); // copy
+ }
}
nodesCache.put(item.getParentIdentifier(), newChilds); //
cache
// new
// list
if (LOG.isDebugEnabled())
+ {
LOG.debug(name + ", put() update child node "
+ nodeData.getIdentifier()
+ " order #" + orderNumber);
+ }
}
else
{
@@ -989,8 +1026,10 @@
cachedParentChilds.set(index, nodeData); // replace at
// current position
if (LOG.isDebugEnabled())
+ {
LOG.debug(name + ", put() update child node "
+ nodeData.getIdentifier()
+ " at index #" + index);
+ }
}
}
@@ -1000,13 +1039,17 @@
// add new to the end
List<NodeData> newChilds = new
ArrayList<NodeData>(cachedParentChilds.size() + 1);
for (int ci = 0; ci < cachedParentChilds.size(); ci++)
+ {
newChilds.add(cachedParentChilds.get(ci));
+ }
newChilds.add(nodeData); // add
nodesCache.put(item.getParentIdentifier(), newChilds); // cache
new list
if (LOG.isDebugEnabled())
+ {
LOG.debug(name + ", put() add child node " +
nodeData.getIdentifier());
+ }
}
}
}
@@ -1028,8 +1071,10 @@
// update already cached in list
cachedParentChilds.set(index, (PropertyData)item); // replace
at current position
if (LOG.isDebugEnabled())
+ {
LOG.debug(name + ", put() update child property
" + item.getIdentifier()
+ " at index #" + index);
+ }
}
else if (index == -1)
@@ -1037,18 +1082,24 @@
// add new
List<PropertyData> newChilds = new
ArrayList<PropertyData>(cachedParentChilds.size() + 1);
for (int ci = 0; ci < cachedParentChilds.size(); ci++)
+ {
newChilds.add(cachedParentChilds.get(ci));
+ }
newChilds.add((PropertyData)item);
propertiesCache.put(item.getParentIdentifier(), newChilds); //
cache new list
if (LOG.isDebugEnabled())
+ {
LOG.debug(name + ", put() add child property
" + item.getIdentifier());
+ }
}
}
}
else
+ {
// if it's a props list with empty values, remove cached list
propertiesCache.remove(item.getParentIdentifier());
+ }
}
}
}
@@ -1071,7 +1122,7 @@
{
if (enabled && parentData != null && childItems != null)
{ // TODO don't check parentData !=
- // null && childItems != null
+ // null && childItems != null
String logInfo = null;
if (LOG.isDebugEnabled())
@@ -1104,8 +1155,10 @@
for (ItemData p : childItems)
{
if (LOG.isDebugEnabled())
+ {
LOG.debug(name + ", addChildProperties() " +
p.getQPath().getAsString() + " "
+ p.getIdentifier() + " -- " + p);
+ }
putItem(p);
}
@@ -1122,7 +1175,9 @@
}
if (LOG.isDebugEnabled())
+ {
LOG.debug(name + ", addChildProperties() <<< " +
logInfo);
+ }
}
}
@@ -1133,7 +1188,7 @@
{
if (enabled && parentData != null && childItems != null)
{ // TODO don't check parentData !=
- // null && childItems != null
+ // null && childItems != null
String logInfo = null;
if (LOG.isDebugEnabled())
@@ -1173,7 +1228,9 @@
}
if (LOG.isDebugEnabled())
+ {
LOG.debug(name + ", addChildPropertiesList() <<< " +
logInfo);
+ }
}
}
@@ -1184,7 +1241,7 @@
{
if (enabled && parentData != null && childItems != null)
{ // TODO don't check parentData !=
- // null && childItems != null
+ // null && childItems != null
String logInfo = null;
if (LOG.isDebugEnabled())
@@ -1218,8 +1275,10 @@
for (ItemData n : childItems)
{
if (LOG.isDebugEnabled())
+ {
LOG.debug(name + ", addChildNodes() " +
n.getQPath().getAsString() + " " + n.getIdentifier()
+ " -- " + n);
+ }
putItem(n);
}
@@ -1236,7 +1295,9 @@
}
if (LOG.isDebugEnabled())
+ {
LOG.debug(name + ", addChildNodes() <<< " + logInfo);
+ }
}
}
@@ -1255,7 +1316,9 @@
if (enabled && item != null)
{
if (LOG.isDebugEnabled())
+ {
LOG.debug(name + ", remove() " + item.getQPath().getAsString() +
" " + item.getIdentifier());
+ }
writeLock.lock();
try
@@ -1274,7 +1337,9 @@
removeChildNode(item.getParentIdentifier(), itemId);
}
else
+ {
removeChildProperty(item.getParentIdentifier(), itemId);
+ }
}
catch (Exception e)
{
@@ -1308,8 +1373,10 @@
{
// check if wasn't removed
if (LOG.isDebugEnabled())
+ {
LOG.debug(name + ", getItem() " + identifier + " -->
"
+ (c != null ? c.getQPath().getAsString() + " parent:" +
c.getParentIdentifier() : "[null]"));
+ }
hits++;
return c;
@@ -1366,8 +1433,10 @@
if (v.getExpiredTime() > System.currentTimeMillis())
{
if (LOG.isDebugEnabled())
+ {
LOG.debug(name + ", getItem() " + (c != null ?
c.getQPath().getAsString() : "[null]") + " --> "
+ (c != null ? c.getIdentifier() + " parent:" +
c.getParentIdentifier() : "[null]"));
+ }
hits++;
return c;
@@ -1438,9 +1507,13 @@
}
if (cn != null)
+ {
hits++;
+ }
else
+ {
miss++;
+ }
return cn;
}
catch (Exception e)
@@ -1491,9 +1564,13 @@
}
if (cn != null)
+ {
hits++;
+ }
else
+ {
miss++;
+ }
return cn != null ? cn.size() : -1;
}
catch (Exception e)
@@ -1550,7 +1627,9 @@
return cp;
}
else
+ {
miss++;
+ }
}
catch (Exception e)
{
@@ -1599,9 +1678,13 @@
}
if (cp != null)
+ {
hits++;
+ }
else
+ {
miss++;
+ }
return cp;
}
catch (Exception e)
@@ -1662,7 +1745,8 @@
{
writeLock.unlock();
}
- LOG.info(name + " : set liveTime=" + liveTime + "ms. New value will
be applied to items cached from this moment.");
+ LOG
+ .info(name + " : set liveTime=" + liveTime + "ms. New value will
be applied to items cached from this moment.");
}
/**
@@ -1679,8 +1763,10 @@
final CacheValue v2 =
cache.remove(new CacheQPath(item.getParentIdentifier(), item.getQPath(),
ItemType.getItemType(item)));
if (v2 != null && !v2.getItem().getIdentifier().equals(itemId))
+ {
// same path but diff identifier node... phantom
removeItem(v2.getItem());
+ }
}
/**
@@ -1691,7 +1777,9 @@
protected void removeSiblings(final NodeData node)
{
if (node.getIdentifier().equals(Constants.ROOT_UUID))
+ {
return;
+ }
// remove child nodes of the item parent recursive
writeLock.lock();
@@ -1724,11 +1812,15 @@
}
}
else
+ {
citer.remove(); // remove empty C record
+ }
}
for (CacheId id : toRemove)
+ {
cache.remove(id);
+ }
toRemove.clear();
@@ -1748,7 +1840,9 @@
{
if (!enabled)
+ {
return;
+ }
ItemState prevState = null;
for (Iterator<ItemState> iter = changesLog.getAllStates().iterator();
iter.hasNext();)
@@ -1756,8 +1850,10 @@
ItemState state = iter.next();
ItemData item = state.getData();
if (LOG.isDebugEnabled())
+ {
LOG.debug(name + ", onSaveItems() " +
ItemState.nameFromValue(state.getState()) + " "
+ item.getQPath().getAsString() + " " + item.getIdentifier() +
" parent:" + item.getParentIdentifier());
+ }
try
{
@@ -1789,7 +1885,9 @@
// back from the persistence
if (prevState.isDeleted()
&&
prevState.getData().getParentIdentifier().equals(item.getParentIdentifier()))
+ {
removeSiblings((NodeData)item);
+ }
}
}
else if (item.getQPath().getName().equals(Constants.EXO_PERMISSIONS))
@@ -1885,7 +1983,9 @@
{
i.remove();
if (childProperties.size() <= 0)
+ {
propertiesCache.remove(parentIdentifier);
+ }
return cn;
}
}
Modified:
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/jbosscache/BufferedJBossCache.java
===================================================================
---
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/jbosscache/BufferedJBossCache.java 2010-09-28
09:01:44 UTC (rev 3195)
+++
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/jbosscache/BufferedJBossCache.java 2010-09-28
09:21:26 UTC (rev 3196)
@@ -163,6 +163,14 @@
this.local.set(local);
}
+ /**
+ * Returns current state.
+ */
+ public boolean isLocal()
+ {
+ return this.local.get();
+ }
+
public int getNumberOfNodes()
{
return ((CacheSPI<Serializable, Object>)parentCache).getNumberOfNodes();
Modified:
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/jbosscache/JBossCacheWorkspaceStorageCache.java
===================================================================
---
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/jbosscache/JBossCacheWorkspaceStorageCache.java 2010-09-28
09:01:44 UTC (rev 3195)
+++
jcr/trunk/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/jbosscache/JBossCacheWorkspaceStorageCache.java 2010-09-28
09:21:26 UTC (rev 3196)
@@ -1,1250 +1,1343 @@
-/*
- * 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.jbosscache;
-
-import org.exoplatform.container.configuration.ConfigurationManager;
-import org.exoplatform.services.jcr.access.AccessControlList;
-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.WorkspaceStorageCache;
-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.PropertyData;
-import org.exoplatform.services.jcr.datamodel.QPath;
-import org.exoplatform.services.jcr.datamodel.QPathEntry;
-import org.exoplatform.services.jcr.impl.Constants;
-import org.exoplatform.services.jcr.impl.dataflow.TransientNodeData;
-import org.exoplatform.services.jcr.impl.dataflow.TransientPropertyData;
-import org.exoplatform.services.jcr.jbosscache.ExoJBossCacheFactory;
-import org.exoplatform.services.jcr.jbosscache.ExoJBossCacheFactory.CacheType;
-import org.exoplatform.services.log.ExoLogger;
-import org.exoplatform.services.log.Log;
-import org.exoplatform.services.transaction.TransactionService;
-import org.jboss.cache.Cache;
-import org.jboss.cache.Fqn;
-import org.jboss.cache.Node;
-import org.jboss.cache.config.EvictionRegionConfig;
-import org.jboss.cache.eviction.ExpirationAlgorithmConfig;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Set;
-
-import javax.jcr.RepositoryException;
-import javax.transaction.TransactionManager;
-
-/**
- * Created by The eXo Platform SAS.<p/>
- *
- * Cache based on JBossCache.<p/>
- *
- * <ul>
- * <li>cache transparent: or item cached or not, we should not generate "not
found" Exceptions </li>
- * <li>cache consists of next resident nodes:
- * <ul>
- * <li>/$ITEMS - stores items by Id (i.e. /$ITEMS/itemId)</li>
- * <li>/$CHILD_NODES, /$CHILD_PROPS - stores items by parentId and name (i.e.
/$CHILD_NODES/parentId/childName.$ITEM_ID)</li>
- * <li>/$CHILD_NODES_LIST, /$CHILD_PROPS_LIST - stores child list by parentId
and child Id
- * (i.e. /$CHILD_NODES_LIST/parentId.lists = serialized
Set<Object>)</li>
- * </ul>
- * </li>
- * <li>all child properties/nodes lists should be evicted from parent at same time
- * i.e. for /$CHILD_NODES_LIST, /$CHILD_PROPS_LIST we need customized eviction
policy (EvictionActionPolicy) to evict
- * whole list on one of childs eviction
- * </li>
- * </ul>
- *
- * <p/>
- * Current state notes (subject of change):
- * <ul>
- * <li>cache implements WorkspaceStorageCache, without any stuff about references
and locks</li>
- * <li>transaction style implemented via JBC barches, do with JTA (i.e. via
exo's TransactionService + JBoss TM)</li>
- * <li>we need customized eviction policy (EvictionActionPolicy) for
/$CHILD_NODES_LIST, /$CHILD_PROPS_LIST</li>
- * </ul>
- *
- * @author <a href="mailto:peter.nedonosko@exoplatform.com">Peter
Nedonosko</a>
- * @version $Id: JBossCacheWorkspaceStorageCache.java 13869 2008-05-05 08:40:10Z
pnedonosko $
- */
-public class JBossCacheWorkspaceStorageCache implements WorkspaceStorageCache
-{
-
- private static final Log LOG =
ExoLogger.getLogger("exo.jcr.component.core.JBossCacheWorkspaceStorageCache");
-
- public static final String JBOSSCACHE_CONFIG = "jbosscache-configuration";
-
- public static final String JBOSSCACHE_EXPIRATION =
"jbosscache-expiration-time";
-
- /**
- * Indicate whether the JBoss Cache instance used can be shared with other caches
- */
- public static final String JBOSSCACHE_SHAREABLE = "jbosscache-shareable";
-
- public static final Boolean JBOSSCACHE_SHAREABLE_DEFAULT = Boolean.FALSE;
-
- public static final long JBOSSCACHE_EXPIRATION_DEFAULT = 900000; // 15 minutes
-
- public static final String ITEMS = "$ITEMS".intern();
-
- public static final String CHILD_NODES = "$CHILD_NODES".intern();
-
- public static final String CHILD_PROPS = "$CHILD_PROPS".intern();
-
- public static final String CHILD_NODES_LIST = "$CHILD_NODES_LIST".intern();
-
- public static final String CHILD_PROPS_LIST = "$CHILD_PROPS_LIST".intern();
-
- public static final String LOCKS = "$LOCKS".intern();
-
- public static final String ITEM_DATA = "$data".intern();
-
- public static final String ITEM_ID = "$id".intern();
-
- public static final String ITEM_LIST = "$lists".intern();
-
- protected final BufferedJBossCache cache;
-
- protected final Fqn<String> itemsRoot;
-
- protected final Fqn<String> childNodes;
-
- protected final Fqn<String> childProps;
-
- protected final Fqn<String> childNodesList;
-
- protected final Fqn<String> childPropsList;
-
- protected final Fqn<String> rootFqn;
-
- /**
- * 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>
- {
-
- final Iterator<Object> childs;
-
- final String parentId;
-
- final Fqn<String> root;
-
- T next;
-
- ChildItemsIterator(Fqn<String> root, String parentId)
- {
- this.parentId = parentId;
- this.root = root;
-
- Fqn<String> parentFqn = makeChildListFqn(root, parentId);
- Node<Serializable, Object> parent = cache.getNode(parentFqn);
- if (parent != null)
- {
- this.childs = cache.getChildrenNames(parentFqn).iterator();
- fetchNext();
- }
- else
- {
- this.childs = null;
- this.next = null;
- }
- }
-
- protected void fetchNext()
- {
- if (childs.hasNext())
- {
- // traverse to the first existing or the end of childs
- T n = null;
- do
- {
- String itemId = (String)cache.get(makeChildFqn(root, parentId,
(String)childs.next()), ITEM_ID);
- if (itemId != null)
- {
- n = (T)cache.get(makeItemFqn(itemId), ITEM_DATA);
- }
- }
- while (n == null && childs.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(childNodes, parentId);
- }
-
- @Override
- public N next()
- {
- return super.next();
- }
- }
-
- class ChildPropertiesIterator<P extends PropertyData> extends
ChildItemsIterator<P>
- {
-
- ChildPropertiesIterator(String parentId)
- {
- super(childProps, parentId);
- }
-
- @Override
- public P next()
- {
- return super.next();
- }
- }
-
- /**
- * Cache constructor with eXo TransactionService support.
- *
- * @param wsConfig WorkspaceEntry workspace config
- * @param transactionService TransactionService external transaction service
- * @throws RepositoryException if error of initialization
- * @throws RepositoryConfigurationException if error of configuration
- */
- public JBossCacheWorkspaceStorageCache(WorkspaceEntry wsConfig, TransactionService
transactionService,
- ConfigurationManager cfm) throws RepositoryException,
RepositoryConfigurationException
- {
- if (wsConfig.getCache() == null)
- {
- throw new RepositoryConfigurationException("Cache configuration not
found");
- }
-
- // create cache using custom factory
- ExoJBossCacheFactory<Serializable, Object> factory;
-
- if (transactionService != null)
- {
- factory = new ExoJBossCacheFactory<Serializable, Object>(cfm,
transactionService.getTransactionManager());
- }
- else
- {
- factory = new ExoJBossCacheFactory<Serializable, Object>(cfm);
- }
-
- // create parent JBossCache instance
- Cache<Serializable, Object> parentCache =
factory.createCache(wsConfig.getCache());
- // get all eviction configurations
- List<EvictionRegionConfig> evictionConfigurations =
- parentCache.getConfiguration().getEvictionConfig().getEvictionRegionConfigs();
- // append and default eviction configuration, since it is not present in region
configurations
-
evictionConfigurations.add(parentCache.getConfiguration().getEvictionConfig().getDefaultEvictionRegionConfig());
-
- boolean useExpiration = false;
- // looking over all eviction configurations till the end or till some expiration
algorithm subclass not found.
- for (EvictionRegionConfig evictionRegionConfig : evictionConfigurations)
- {
- if (evictionRegionConfig.getEvictionAlgorithmConfig() instanceof
ExpirationAlgorithmConfig)
- {
- // force set expiration key to default value in all Expiration configurations
(if any)
-
((ExpirationAlgorithmConfig)evictionRegionConfig.getEvictionAlgorithmConfig())
- .setExpirationKeyName(ExpirationAlgorithmConfig.EXPIRATION_KEY);
- useExpiration = true;
- }
- }
-
- if (useExpiration)
- {
- LOG.info("Using BufferedJBossCache compatible with Expiration
algorithm.");
- }
-
- this.rootFqn = Fqn.fromElements(wsConfig.getUniqueName());
- parentCache =
- ExoJBossCacheFactory.getUniqueInstance(CacheType.JCR_CACHE, rootFqn,
parentCache, wsConfig.getCache()
- .getParameterBoolean(JBOSSCACHE_SHAREABLE,
JBOSSCACHE_SHAREABLE_DEFAULT).booleanValue());
-
- // if expiration is used, set appropriate factory with with timeout set via
configuration (or default one 15minutes)
- this.cache =
- new BufferedJBossCache(parentCache, useExpiration,
wsConfig.getCache().getParameterTime(JBOSSCACHE_EXPIRATION,
- JBOSSCACHE_EXPIRATION_DEFAULT));
-
- this.itemsRoot = Fqn.fromRelativeElements(rootFqn, ITEMS);
- this.childNodes = Fqn.fromRelativeElements(rootFqn, CHILD_NODES);
- this.childProps = Fqn.fromRelativeElements(rootFqn, CHILD_PROPS);
- this.childNodesList = Fqn.fromRelativeElements(rootFqn, CHILD_NODES_LIST);
- this.childPropsList = Fqn.fromRelativeElements(rootFqn, CHILD_PROPS_LIST);
-
- this.cache.create();
- this.cache.start();
-
- createResidentNode(childNodes);
- createResidentNode(childNodesList);
- createResidentNode(childProps);
- createResidentNode(childPropsList);
- createResidentNode(itemsRoot);
- }
-
- /**
- * Cache constructor with JBossCache JTA transaction support.
- *
- * @param wsConfig WorkspaceEntry workspace config
- * @throws RepositoryException if error of initialization
- * @throws RepositoryConfigurationException if error of configuration
- */
- public JBossCacheWorkspaceStorageCache(WorkspaceEntry wsConfig, ConfigurationManager
cfm)
- throws RepositoryException, RepositoryConfigurationException
- {
- this(wsConfig, null, cfm);
- }
-
- /**
- * Checks if node with give FQN not exists and creates resident node.
- * @param fqn
- */
- protected void createResidentNode(Fqn fqn)
- {
- Node<Serializable, Object> cacheRoot = cache.getRoot();
- if (!cacheRoot.hasChild(fqn))
- {
- cache.getInvocationContext().getOptionOverrides().setCacheModeLocal(true);
- cacheRoot.addChild(fqn).setResident(true);
- }
- else
- {
- cache.getNode(fqn).setResident(true);
- }
-
- }
-
- protected static String readJBCConfig(final WorkspaceEntry wsConfig) throws
RepositoryConfigurationException
- {
- if (wsConfig.getCache() != null)
- {
- return wsConfig.getCache().getParameterValue(JBOSSCACHE_CONFIG);
- }
- else
- {
- throw new RepositoryConfigurationException("Cache configuration not
found");
- }
- }
-
- /**
- * Return TransactionManager used by JBossCache backing the JCR cache.
- *
- * @return TransactionManager
- */
- public TransactionManager getTransactionManager()
- {
- return cache.getTransactionManager();
- }
-
- /**
- * {@inheritDoc}
- */
- public void put(ItemData item)
- {
- 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)
- {
- cache.commitTransaction();
- }
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public void remove(ItemData item)
- {
- removeItem(item);
- }
-
- /**
- * {@inheritDoc}
- */
- public void onSaveItems(final ItemStateChangesLog itemStates)
- {
- // if something happen we will rollback changes
- boolean rollback = true;
- try
- {
- cache.beginTransaction();
- for (ItemState state : itemStates.getAllStates())
- {
- 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 (prevItem != null && state.isNode())
- {
- // nodes reordered, if previous is null it's InvalidItemState
case
- updateInBuffer((NodeData)state.getData(), (NodeData)prevItem);
- }
- }
- }
- else if (state.isDeleted())
- {
- removeItem(state.getData());
- }
- else if (state.isRenamed())
- {
- putItem(state.getData());
- }
- else if (state.isMixinChanged())
- {
- if (state.isPersisted())
- {
- // update subtree ACLs
- updateMixin((NodeData)state.getData());
- }
- }
- }
- cache.commitTransaction();
- rollback = false;
- }
- finally
- {
- if (rollback)
- {
- cache.rollbackTransaction();
- }
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public void addChildNodes(NodeData parent, List<NodeData> childs)
- {
- boolean inTransaction = cache.isTransactionActive();
- try
- {
-
- if (!inTransaction)
- {
- cache.beginTransaction();
- }
-
- cache.setLocal(true);
- // remove previous all (to be sure about consistency)
- cache.removeNode(makeChildListFqn(childNodesList, parent.getIdentifier()));
-
- if (childs.size() > 0)
- {
- Set<Object> set = new HashSet<Object>();
- for (NodeData child : childs)
- {
- putNode(child, ModifyChildOption.NOT_MODIFY);
- set.add(child.getIdentifier());
- }
- cache.put(makeChildListFqn(childNodesList, parent.getIdentifier()),
ITEM_LIST, set);
- }
- else
- {
- // cache fact of empty childs list
- cache.put(makeChildListFqn(childNodesList, parent.getIdentifier()),
ITEM_LIST, new HashSet<Object>());
- }
- }
- finally
- {
- cache.setLocal(false);
- if (!inTransaction)
- {
- cache.commitTransaction();
- }
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public void addChildProperties(NodeData parent, List<PropertyData> childs)
- {
- boolean inTransaction = cache.isTransactionActive();
- try
- {
- if (!inTransaction)
- {
- cache.beginTransaction();
- }
- cache.setLocal(true);
- // remove previous all (to be sure about consistency)
- cache.removeNode(makeChildListFqn(childPropsList, parent.getIdentifier()));
- 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.put(makeChildListFqn(childPropsList, parent.getIdentifier()),
ITEM_LIST, set);
-
- }
- else
- {
- LOG.warn("Empty properties list cached " + (parent != null ?
parent.getQPath().getAsString() : parent));
- }
- }
- finally
- {
- cache.setLocal(false);
- if (!inTransaction)
- {
- cache.commitTransaction();
- }
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public void addChildPropertiesList(NodeData parent, List<PropertyData>
childProperties)
- {
- // TODO not implemented, will force read from DB
- // try
- // {
- // cache.beginTransaction();
- // cache.setLocal(true);
- //
- // }
- // finally
- // {
- // cache.setLocal(false);
- // cache.commitTransaction();
- // }
- }
-
- /**
- * {@inheritDoc}
- */
- public ItemData get(String parentId, QPathEntry name)
- {
- return get(parentId, name, ItemType.UNKNOWN);
- }
-
- /**
- * {@inheritDoc}
- */
- public ItemData get(String parentId, QPathEntry name, ItemType itemType)
- {
- String itemId = null;
- if (itemType == ItemType.NODE || itemType == ItemType.UNKNOWN)
- {
- // try as node first
- itemId = (String)cache.get(makeChildFqn(childNodes, parentId, name), ITEM_ID);
- }
-
- if (itemType == ItemType.PROPERTY || itemType == ItemType.UNKNOWN && itemId
== null)
- {
- // try as property
- itemId = (String)cache.get(makeChildFqn(childProps, parentId, name), ITEM_ID);
- }
-
- if (itemId != null)
- {
- return get(itemId);
- }
-
- return null;
- }
-
- /**
- * {@inheritDoc}
- */
- public ItemData get(String id)
- {
- return (ItemData)cache.get(makeItemFqn(id), ITEM_DATA);
- }
-
- /**
- * {@inheritDoc}
- */
- public List<NodeData> getChildNodes(final NodeData parent)
- {
- // get list of children uuids
- final Set<Object> set =
- (Set<Object>)cache.get(makeChildListFqn(childNodesList,
parent.getIdentifier()), ITEM_LIST);
- if (set != null)
- {
- final List<NodeData> childs = new ArrayList<NodeData>();
-
- for (Object child : set)
- {
- NodeData node = (NodeData)cache.get(makeItemFqn((String)child), ITEM_DATA);
- if (node == null)
- {
- return null;
- }
-
- childs.add(node);
- }
-
- // order children by orderNumber, as HashSet returns children in other order
- Collections.sort(childs, new NodesOrderComparator<NodeData>());
-
- return childs;
- }
- else
- {
- return null;
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public int getChildNodesCount(NodeData parent)
- {
- // get list of children uuids
- final Set<Object> set =
- (Set<Object>)cache.get(makeChildListFqn(childNodesList,
parent.getIdentifier()), ITEM_LIST);
-
- return set != null ? set.size() : -1;
- }
-
- /**
- * {@inheritDoc}
- */
- public List<PropertyData> getChildProperties(NodeData parent)
- {
- return getChildProps(parent.getIdentifier(), true);
- }
-
- /**
- * {@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)
- {
- // get set of property uuids
- final Set<Object> set =
(Set<Object>)cache.get(makeChildListFqn(childPropsList, parentId), ITEM_LIST);
- if (set != null)
- {
- final List<PropertyData> childs = new ArrayList<PropertyData>();
-
- for (Object child : set)
- {
- PropertyData prop = (PropertyData)cache.get(makeItemFqn((String)child),
ITEM_DATA);
- if (prop == null)
- {
- return null;
- }
- if (withValue && prop.getValues().size() <= 0)
- {
- // don't return list of empty-valued props (but listChildProperties()
can)
- return null;
- }
- childs.add(prop);
- }
- return childs;
- }
- else
- {
- return null;
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public long getSize()
- {
- // Total number of JBC nodes in the cache - the total amount of resident nodes
- return numNodes(cache.getNode(rootFqn)) - 6;
- }
-
- /**
- * Evaluates the total amount of sub-nodes that the given node contains
- */
- private static long numNodes(Node<Serializable, Object> n)
- {
- long count = 1;// for n
- if (n != null)
- {
- for (Node<Serializable, Object> child : n.getChildren())
- {
- count += numNodes(child);
- }
- }
- return count;
- }
-
- /**
- * {@inheritDoc}
- */
- public boolean isEnabled()
- {
- return true;
- }
-
- // non-public members
-
- /**
- * Make Item absolute Fqn, i.e. /$ITEMS/itemID.
- *
- * @param itemId String
- * @return Fqn
- */
- protected Fqn<String> makeItemFqn(String itemId)
- {
- return Fqn.fromRelativeElements(itemsRoot, itemId);
- }
-
- /**
- * Make child Item absolute Fqn, i.e. /root/parentId/childName.
- *
- * @param root Fqn
- * @param parentId String
- * @param childName QPathEntry
- * @return Fqn
- */
- protected Fqn<String> makeChildFqn(Fqn<String> root, String parentId,
QPathEntry childName)
- {
- return Fqn.fromRelativeElements(root, parentId, childName.getAsString(true));
- }
-
- /**
- * Make child Item absolute Fqn, i.e. /root/parentId/childName.
- *
- * @param root Fqn
- * @param parentId String
- * @param childName String
- * @return Fqn
- */
- protected Fqn<String> makeChildFqn(Fqn<String> root, String parentId,
String childName)
- {
- return Fqn.fromRelativeElements(root, parentId, childName);
- }
-
- /**
- * Make child node parent absolute Fqn, i.e. /root/itemId.
- *
- * @param root Fqn
- * @param parentId String
- * @return Fqn
- */
- protected Fqn<String> makeChildListFqn(Fqn<String> root, String parentId)
- {
- return Fqn.fromRelativeElements(root, parentId);
- }
-
- /**
- * 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 not a root node
- if (node.getParentIdentifier() != null)
- {
- // add in CHILD_NODES
- cache.put(makeChildFqn(childNodes, node.getParentIdentifier(),
node.getQPath().getEntries()[node.getQPath()
- .getEntries().length - 1]), ITEM_ID, node.getIdentifier());
- // if MODIFY and List present OR FORCE_MODIFY, then write
- if ((modifyListsOfChild == ModifyChildOption.MODIFY &&
cache.getNode(makeChildListFqn(childNodesList, node
- .getParentIdentifier())) != null)
- || modifyListsOfChild == ModifyChildOption.FORCE_MODIFY)
- {
- cache.addToList(makeChildListFqn(childNodesList, node.getParentIdentifier()),
ITEM_LIST, node
- .getIdentifier());
- }
-
- }
- // add in ITEMS
- return (ItemData)cache.put(makeItemFqn(node.getIdentifier()), ITEM_DATA, node);
- }
-
- protected ItemData putNodeInBufferedCache(NodeData node, ModifyChildOption
modifyListsOfChild)
- {
- // if not a root node
- if (node.getParentIdentifier() != null)
- {
- // add in CHILD_NODES
- cache.put(makeChildFqn(childNodes, node.getParentIdentifier(),
node.getQPath().getEntries()[node.getQPath()
- .getEntries().length - 1]), ITEM_ID, node.getIdentifier());
- // if MODIFY and List present OR FORCE_MODIFY, then write
- if ((modifyListsOfChild == ModifyChildOption.MODIFY &&
cache.getNode(makeChildListFqn(childNodesList, node
- .getParentIdentifier())) != null)
- || modifyListsOfChild == ModifyChildOption.FORCE_MODIFY)
- {
- cache.addToList(makeChildListFqn(childNodesList, node.getParentIdentifier()),
ITEM_LIST, node
- .getIdentifier());
- }
- }
- // add in ITEMS
- return (ItemData)cache.putInBuffer(makeItemFqn(node.getIdentifier()), ITEM_DATA,
node);
- }
-
- /**
- * Internal put Property.
- *
- * @param node, PropertyData, new data to put in the cache
- * @return PropertyData, previous data or null
- */
- protected PropertyData putProperty(PropertyData prop, ModifyChildOption
modifyListsOfChild)
- {
- // add in CHILD_PROPS
- cache.put(makeChildFqn(childProps, prop.getParentIdentifier(),
prop.getQPath().getEntries()[prop.getQPath()
- .getEntries().length - 1]), ITEM_ID, prop.getIdentifier());
- // if MODIFY and List present OR FORCE_MODIFY, then write
- if ((modifyListsOfChild == ModifyChildOption.MODIFY &&
cache.getNode(makeChildListFqn(childPropsList, prop
- .getParentIdentifier())) != null)
- || modifyListsOfChild == ModifyChildOption.FORCE_MODIFY)
- {
- cache.addToList(makeChildListFqn(childPropsList, prop.getParentIdentifier()),
ITEM_LIST, prop.getIdentifier());
- }
- // add in ITEMS
- return (PropertyData)cache.put(makeItemFqn(prop.getIdentifier()), ITEM_DATA,
prop);
- }
-
- protected void removeItem(ItemData item)
- {
- if (item.isNode())
- {
- if (item.getParentIdentifier() != null)
- {
- // if not a root node
-
- // remove from CHILD_NODES of parent
- cache.removeNode(makeChildFqn(childNodes, item.getParentIdentifier(),
item.getQPath().getEntries()[item
- .getQPath().getEntries().length - 1]));
-
- // remove from CHILD_NODES_LIST of parent
- cache.removeFromList(makeChildListFqn(childNodesList,
item.getParentIdentifier()), ITEM_LIST, item
- .getIdentifier());
-
- // remove from CHILD_NODES as parent
- cache.removeNode(makeChildListFqn(childNodes, item.getIdentifier()));
-
- // remove from CHILD_NODES_LIST as parent
- cache.removeNode(makeChildListFqn(childNodesList, item.getIdentifier()));
-
- // remove from CHILD_PROPS as parent
- cache.removeNode(makeChildListFqn(childProps, item.getIdentifier()));
-
- // remove from CHILD_PROPS_LIST as parent
- cache.removeNode(makeChildListFqn(childPropsList, item.getIdentifier()));
- }
- }
- else
- {
- // remove from CHILD_PROPS
- cache.removeNode(makeChildFqn(childProps, item.getParentIdentifier(),
item.getQPath().getEntries()[item
- .getQPath().getEntries().length - 1]));
-
- // remove from CHILD_PROPS_LIST
- cache.removeFromList(makeChildListFqn(childPropsList,
item.getParentIdentifier()), ITEM_LIST, item
- .getIdentifier());
- }
- // remove from ITEMS
- cache.removeNode(makeItemFqn(item.getIdentifier()));
- }
-
- /**
- * Update Node's mixin and ACL.
- *
- * @param node NodeData
- */
- protected void updateMixin(NodeData node)
- {
- NodeData prevData = (NodeData)cache.put(makeItemFqn(node.getIdentifier()),
ITEM_DATA, node);
- 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 hierachy in case of same-name siblings reorder.
- * Assumes the new (updated) nodes already putted 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). Childs paths will
be updated to a new node path.
- *
- * @param node NodeData
- * @param prevNode NodeData
- */
- protected void update(final NodeData node, final NodeData prevNode)
- {
- // get previously cached NodeData and using its name remove child on the parent
- Fqn<String> prevFqn =
- makeChildFqn(childNodes, node.getParentIdentifier(),
prevNode.getQPath().getEntries()[prevNode.getQPath()
- .getEntries().length - 1]);
- if (node.getIdentifier().equals(cache.get(prevFqn, ITEM_ID)))
- {
- // it's same-name siblings re-ordering, delete previous child
- if (!cache.removeNode(prevFqn) && LOG.isDebugEnabled())
- {
- LOG.debug("Node not extists as a child but update asked " +
node.getQPath().getAsString());
- }
- }
-
- // update childs paths if index changed
- int nodeIndex = node.getQPath().getEntries()[node.getQPath().getEntries().length -
1].getIndex();
- int prevNodeIndex =
prevNode.getQPath().getEntries()[prevNode.getQPath().getEntries().length - 1].getIndex();
- if (nodeIndex != prevNodeIndex)
- {
- updateTreePath(node.getIdentifier(), node.getQPath(), null); // don't change
ACL, it's same parent
- }
- }
-
- /**
- * This method duplicate update method, except using getFromBuffer inside.
- *
- * @param node NodeData
- * @param prevNode NodeData
- */
- protected void updateInBuffer(final NodeData node, final NodeData prevNode)
- {
- // get previously cached NodeData and using its name remove child on the parent
- Fqn<String> prevFqn =
- makeChildFqn(childNodes, node.getParentIdentifier(),
prevNode.getQPath().getEntries()[prevNode.getQPath()
- .getEntries().length - 1]);
- if (node.getIdentifier().equals(cache.getFromBuffer(prevFqn, ITEM_ID)))
- {
- // it's same-name siblings re-ordering, delete previous child
- if (!cache.removeNode(prevFqn) && LOG.isDebugEnabled())
- {
- LOG.debug("Node not extists as a child but update asked " +
node.getQPath().getAsString());
- }
- }
-
- // update childs paths if index changed
- int nodeIndex = node.getQPath().getEntries()[node.getQPath().getEntries().length -
1].getIndex();
- int prevNodeIndex =
prevNode.getQPath().getEntries()[prevNode.getQPath().getEntries().length - 1].getIndex();
- if (nodeIndex != prevNodeIndex)
- {
- // its a samename reordering
- updateTreePath(prevNode.getQPath(), node.getQPath(), null); // don't change
ACL, it's same parent
- }
- }
-
- /**
- * Check all items in cache - is it descendant of prevRootPath, and update path
according newRootPath.
- *
- * @param prevRootPath
- * @param newRootPath
- * @param acl
- */
- protected void updateTreePath(final QPath prevRootPath, final QPath newRootPath, final
AccessControlList acl)
- {
- boolean inheritACL = acl != null;
-
- // check all ITEMS in cache
- Node<Serializable, Object> items = cache.getNode(itemsRoot);
- Set<Object> childrenNames = items.getChildrenNames();
- Iterator<Object> namesIt = childrenNames.iterator();
-
- while (namesIt.hasNext())
- {
- String id = (String)namesIt.next();
- ItemData data = (ItemData)cache.get(makeItemFqn(id), ITEM_DATA);
-
- // check is this descendant of prevRootPath
- QPath nodeQPath = data.getQPath();
- if (nodeQPath.isDescendantOf(prevRootPath))
- {
-
- //make relative path
- QPathEntry[] relativePath = null;
- try
- {
- relativePath = nodeQPath.getRelPath(nodeQPath.getDepth() -
prevRootPath.getDepth());
- }
- catch (IllegalPathException e)
- {
- // Do nothing. Never happens.
- }
-
- // make new path - no matter node or property
- QPath newPath = QPath.makeChildPath(newRootPath, relativePath);
-
- if (data.isNode())
- {
- // update node
-
- NodeData prevNode = (NodeData)data;
-
- TransientNodeData newNode =
- new TransientNodeData(newPath, prevNode.getIdentifier(),
prevNode.getPersistedVersion(), prevNode
- .getPrimaryTypeName(), prevNode.getMixinTypeNames(),
prevNode.getOrderNumber(), prevNode
- .getParentIdentifier(), inheritACL ? acl : prevNode.getACL()); //
TODO check ACL
- // update this node
- cache.put(makeItemFqn(newNode.getIdentifier()), ITEM_DATA, newNode);
- }
- else
- {
- //update property
-
- PropertyData prevProp = (PropertyData)data;
-
- if (inheritACL
- &&
(prevProp.getQPath().getName().equals(Constants.EXO_PERMISSIONS) ||
prevProp.getQPath().getName()
- .equals(Constants.EXO_OWNER)))
- {
- inheritACL = false;
- }
-
- TransientPropertyData newProp =
- new TransientPropertyData(newPath, prevProp.getIdentifier(),
prevProp.getPersistedVersion(), prevProp
- .getType(), prevProp.getParentIdentifier(),
prevProp.isMultiValued(), prevProp.getValues());
- cache.put(makeItemFqn(newProp.getIdentifier()), ITEM_DATA, newProp);
- }
- }
- }
- }
-
- /**
- * Update Nodes tree with new path.
- *
- * @param parentId String - root node id of JCR subtree.
- * @param rootPath QPath
- * @param acl AccessControlList
- */
- protected void updateTreePath(final String parentId, final QPath rootPath, final
AccessControlList acl)
- {
- boolean inheritACL = acl != null;
-
- // update properties
- for (Iterator<PropertyData> iter = new
ChildPropertiesIterator<PropertyData>(parentId); iter.hasNext();)
- {
- PropertyData prevProp = iter.next();
-
- if (inheritACL
- && (prevProp.getQPath().getName().equals(Constants.EXO_PERMISSIONS)
|| prevProp.getQPath().getName()
- .equals(Constants.EXO_OWNER)))
- {
- inheritACL = false;
- }
- // recreate with new path for child Props only
- QPath newPath =
- QPath
- .makeChildPath(rootPath,
prevProp.getQPath().getEntries()[prevProp.getQPath().getEntries().length - 1]);
- TransientPropertyData newProp =
- new TransientPropertyData(newPath, prevProp.getIdentifier(),
prevProp.getPersistedVersion(), prevProp
- .getType(), prevProp.getParentIdentifier(), prevProp.isMultiValued(),
prevProp.getValues());
- cache.put(makeItemFqn(newProp.getIdentifier()), ITEM_DATA, newProp);
- }
-
- // update child nodes
- for (Iterator<NodeData> iter = new
ChildNodesIterator<NodeData>(parentId); iter.hasNext();)
- {
- NodeData prevNode = iter.next();
- // recreate with new path for child Nodes only
- QPath newPath =
- QPath
- .makeChildPath(rootPath,
prevNode.getQPath().getEntries()[prevNode.getQPath().getEntries().length - 1]);
- TransientNodeData newNode =
- new TransientNodeData(newPath, prevNode.getIdentifier(),
prevNode.getPersistedVersion(), prevNode
- .getPrimaryTypeName(), prevNode.getMixinTypeNames(),
prevNode.getOrderNumber(), prevNode
- .getParentIdentifier(), inheritACL ? acl : prevNode.getACL()); // TODO
check ACL
- // update this node
- cache.put(makeItemFqn(newNode.getIdentifier()), ITEM_DATA, newNode);
- // update childs recursive
- updateTreePath(newNode.getIdentifier(), newNode.getQPath(), inheritACL ? acl :
null);
- }
- }
-
- /**
- * Update child Nodes ACLs.
- *
- * @param parentId String - root node id of JCR subtree.
- * @param acl AccessControlList
- */
- protected void updateChildsACL(final String parentId, final AccessControlList acl)
- {
- 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)
- for (InternalQName mixin : prevNode.getMixinTypeNames())
- {
- if (mixin.equals(Constants.EXO_PRIVILEGEABLE) ||
mixin.equals(Constants.EXO_OWNEABLE))
- {
- continue;
- }
- }
- // recreate with new path for child Nodes only
- TransientNodeData newNode =
- new TransientNodeData(prevNode.getQPath(), prevNode.getIdentifier(),
prevNode.getPersistedVersion(),
- prevNode.getPrimaryTypeName(), prevNode.getMixinTypeNames(),
prevNode.getOrderNumber(), prevNode
- .getParentIdentifier(), acl);
- // update this node
- cache.put(makeItemFqn(newNode.getIdentifier()), ITEM_DATA, newNode);
- // update childs recursive
- updateChildsACL(newNode.getIdentifier(), 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
- }
-
-}
+/*
+ * 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.jbosscache;
+
+import org.exoplatform.container.configuration.ConfigurationManager;
+import org.exoplatform.services.jcr.access.AccessControlList;
+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.WorkspaceStorageCache;
+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.NullNodeData;
+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.impl.Constants;
+import org.exoplatform.services.jcr.impl.dataflow.TransientNodeData;
+import org.exoplatform.services.jcr.impl.dataflow.TransientPropertyData;
+import org.exoplatform.services.jcr.jbosscache.ExoJBossCacheFactory;
+import org.exoplatform.services.jcr.jbosscache.ExoJBossCacheFactory.CacheType;
+import org.exoplatform.services.log.ExoLogger;
+import org.exoplatform.services.log.Log;
+import org.exoplatform.services.transaction.TransactionService;
+import org.jboss.cache.Cache;
+import org.jboss.cache.Fqn;
+import org.jboss.cache.Node;
+import org.jboss.cache.config.EvictionRegionConfig;
+import org.jboss.cache.eviction.ExpirationAlgorithmConfig;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.transaction.TransactionManager;
+
+/**
+ * Created by The eXo Platform SAS.<p/>
+ *
+ * Cache based on JBossCache.<p/>
+ *
+ * <ul>
+ * <li>cache transparent: or item cached or not, we should not generate "not
found" Exceptions </li>
+ * <li>cache consists of next resident nodes:
+ * <ul>
+ * <li>/$ITEMS - stores items by Id (i.e. /$ITEMS/itemId)</li>
+ * <li>/$CHILD_NODES, /$CHILD_PROPS - stores items by parentId and name (i.e.
/$CHILD_NODES/parentId/childName.$ITEM_ID)</li>
+ * <li>/$CHILD_NODES_LIST, /$CHILD_PROPS_LIST - stores child list by parentId
and child Id
+ * (i.e. /$CHILD_NODES_LIST/parentId.lists = serialized
Set<Object>)</li>
+ * </ul>
+ * </li>
+ * <li>all child properties/nodes lists should be evicted from parent at same time
+ * i.e. for /$CHILD_NODES_LIST, /$CHILD_PROPS_LIST we need customized eviction
policy (EvictionActionPolicy) to evict
+ * whole list on one of childs eviction
+ * </li>
+ * </ul>
+ *
+ * <p/>
+ * Current state notes (subject of change):
+ * <ul>
+ * <li>cache implements WorkspaceStorageCache, without any stuff about references
and locks</li>
+ * <li>transaction style implemented via JBC barches, do with JTA (i.e. via
exo's TransactionService + JBoss TM)</li>
+ * <li>we need customized eviction policy (EvictionActionPolicy) for
/$CHILD_NODES_LIST, /$CHILD_PROPS_LIST</li>
+ * </ul>
+ *
+ * @author <a href="mailto:peter.nedonosko@exoplatform.com">Peter
Nedonosko</a>
+ * @version $Id: JBossCacheWorkspaceStorageCache.java 13869 2008-05-05 08:40:10Z
pnedonosko $
+ */
+public class JBossCacheWorkspaceStorageCache implements WorkspaceStorageCache
+{
+
+ private static final Log LOG =
ExoLogger.getLogger("exo.jcr.component.core.JBossCacheWorkspaceStorageCache");
+
+ public static final String JBOSSCACHE_CONFIG = "jbosscache-configuration";
+
+ public static final String JBOSSCACHE_EXPIRATION =
"jbosscache-expiration-time";
+
+ /**
+ * Indicate whether the JBoss Cache instance used can be shared with other caches
+ */
+ public static final String JBOSSCACHE_SHAREABLE = "jbosscache-shareable";
+
+ public static final Boolean JBOSSCACHE_SHAREABLE_DEFAULT = Boolean.FALSE;
+
+ public static final long JBOSSCACHE_EXPIRATION_DEFAULT = 900000; // 15 minutes
+
+ public static final String ITEMS = "$ITEMS".intern();
+
+ public static final String NULL_ITEMS = "$NULL_ITEMS".intern();
+
+ public static final String CHILD_NODES = "$CHILD_NODES".intern();
+
+ public static final String CHILD_PROPS = "$CHILD_PROPS".intern();
+
+ public static final String CHILD_NODES_LIST = "$CHILD_NODES_LIST".intern();
+
+ public static final String CHILD_PROPS_LIST = "$CHILD_PROPS_LIST".intern();
+
+ public static final String LOCKS = "$LOCKS".intern();
+
+ public static final String ITEM_DATA = "$data".intern();
+
+ public static final String ITEM_ID = "$id".intern();
+
+ public static final String ITEM_LIST = "$lists".intern();
+
+ protected final BufferedJBossCache cache;
+
+ protected final Fqn<String> itemsRoot;
+
+ protected final Fqn<String> nullItemsRoot;
+
+ protected final Fqn<String> childNodes;
+
+ protected final Fqn<String> childProps;
+
+ protected final Fqn<String> childNodesList;
+
+ protected final Fqn<String> childPropsList;
+
+ protected final Fqn<String> rootFqn;
+
+ /**
+ * 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>
+ {
+
+ final Iterator<Object> childs;
+
+ final String parentId;
+
+ final Fqn<String> root;
+
+ T next;
+
+ ChildItemsIterator(Fqn<String> root, String parentId)
+ {
+ this.parentId = parentId;
+ this.root = root;
+
+ Fqn<String> parentFqn = makeChildListFqn(root, parentId);
+ Node<Serializable, Object> parent = cache.getNode(parentFqn);
+ if (parent != null)
+ {
+ this.childs = cache.getChildrenNames(parentFqn).iterator();
+ fetchNext();
+ }
+ else
+ {
+ this.childs = null;
+ this.next = null;
+ }
+ }
+
+ protected void fetchNext()
+ {
+ if (childs.hasNext())
+ {
+ // traverse to the first existing or the end of childs
+ T n = null;
+ do
+ {
+ String itemId = (String)cache.get(makeChildFqn(root, parentId,
(String)childs.next()), ITEM_ID);
+ if (itemId != null)
+ {
+ n = (T)cache.get(makeItemFqn(itemId), ITEM_DATA);
+ }
+ }
+ while (n == null && childs.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(childNodes, parentId);
+ }
+
+ @Override
+ public N next()
+ {
+ return super.next();
+ }
+ }
+
+ class ChildPropertiesIterator<P extends PropertyData> extends
ChildItemsIterator<P>
+ {
+
+ ChildPropertiesIterator(String parentId)
+ {
+ super(childProps, parentId);
+ }
+
+ @Override
+ public P next()
+ {
+ return super.next();
+ }
+ }
+
+ /**
+ * Cache constructor with eXo TransactionService support.
+ *
+ * @param wsConfig WorkspaceEntry workspace config
+ * @param transactionService TransactionService external transaction service
+ * @throws RepositoryException if error of initialization
+ * @throws RepositoryConfigurationException if error of configuration
+ */
+ public JBossCacheWorkspaceStorageCache(WorkspaceEntry wsConfig, TransactionService
transactionService,
+ ConfigurationManager cfm) throws RepositoryException,
RepositoryConfigurationException
+ {
+ if (wsConfig.getCache() == null)
+ {
+ throw new RepositoryConfigurationException("Cache configuration not
found");
+ }
+
+ // create cache using custom factory
+ ExoJBossCacheFactory<Serializable, Object> factory;
+
+ if (transactionService != null)
+ {
+ factory = new ExoJBossCacheFactory<Serializable, Object>(cfm,
transactionService.getTransactionManager());
+ }
+ else
+ {
+ factory = new ExoJBossCacheFactory<Serializable, Object>(cfm);
+ }
+
+ // create parent JBossCache instance
+ Cache<Serializable, Object> parentCache =
factory.createCache(wsConfig.getCache());
+ // get all eviction configurations
+ List<EvictionRegionConfig> evictionConfigurations =
+ parentCache.getConfiguration().getEvictionConfig().getEvictionRegionConfigs();
+ // append and default eviction configuration, since it is not present in region
configurations
+
evictionConfigurations.add(parentCache.getConfiguration().getEvictionConfig().getDefaultEvictionRegionConfig());
+
+ boolean useExpiration = false;
+ // looking over all eviction configurations till the end or till some expiration
algorithm subclass not found.
+ for (EvictionRegionConfig evictionRegionConfig : evictionConfigurations)
+ {
+ if (evictionRegionConfig.getEvictionAlgorithmConfig() instanceof
ExpirationAlgorithmConfig)
+ {
+ // force set expiration key to default value in all Expiration configurations
(if any)
+
((ExpirationAlgorithmConfig)evictionRegionConfig.getEvictionAlgorithmConfig())
+ .setExpirationKeyName(ExpirationAlgorithmConfig.EXPIRATION_KEY);
+ useExpiration = true;
+ }
+ }
+
+ if (useExpiration)
+ {
+ LOG.info("Using BufferedJBossCache compatible with Expiration
algorithm.");
+ }
+
+ this.rootFqn = Fqn.fromElements(wsConfig.getUniqueName());
+ parentCache =
+ ExoJBossCacheFactory.getUniqueInstance(CacheType.JCR_CACHE, rootFqn,
parentCache, wsConfig.getCache()
+ .getParameterBoolean(JBOSSCACHE_SHAREABLE,
JBOSSCACHE_SHAREABLE_DEFAULT).booleanValue());
+
+ // if expiration is used, set appropriate factory with with timeout set via
configuration (or default one 15minutes)
+ this.cache =
+ new BufferedJBossCache(parentCache, useExpiration,
wsConfig.getCache().getParameterTime(JBOSSCACHE_EXPIRATION,
+ JBOSSCACHE_EXPIRATION_DEFAULT));
+
+ this.itemsRoot = Fqn.fromRelativeElements(rootFqn, ITEMS);
+ this.nullItemsRoot = Fqn.fromElements(NULL_ITEMS);
+ this.childNodes = Fqn.fromRelativeElements(rootFqn, CHILD_NODES);
+ this.childProps = Fqn.fromRelativeElements(rootFqn, CHILD_PROPS);
+ this.childNodesList = Fqn.fromRelativeElements(rootFqn, CHILD_NODES_LIST);
+ this.childPropsList = Fqn.fromRelativeElements(rootFqn, CHILD_PROPS_LIST);
+
+ this.cache.create();
+ this.cache.start();
+
+ createResidentNode(childNodes);
+ createResidentNode(childNodesList);
+ createResidentNode(childProps);
+ createResidentNode(childPropsList);
+ createResidentNode(itemsRoot);
+ createResidentNode(nullItemsRoot);
+ }
+
+ /**
+ * Cache constructor with JBossCache JTA transaction support.
+ *
+ * @param wsConfig WorkspaceEntry workspace config
+ * @throws RepositoryException if error of initialization
+ * @throws RepositoryConfigurationException if error of configuration
+ */
+ public JBossCacheWorkspaceStorageCache(WorkspaceEntry wsConfig, ConfigurationManager
cfm)
+ throws RepositoryException, RepositoryConfigurationException
+ {
+ this(wsConfig, null, cfm);
+ }
+
+ /**
+ * Checks if node with give FQN not exists and creates resident node.
+ * @param fqn
+ */
+ protected void createResidentNode(Fqn fqn)
+ {
+ Node<Serializable, Object> cacheRoot = cache.getRoot();
+ if (!cacheRoot.hasChild(fqn))
+ {
+ cache.getInvocationContext().getOptionOverrides().setCacheModeLocal(true);
+ cacheRoot.addChild(fqn).setResident(true);
+ }
+ else
+ {
+ cache.getNode(fqn).setResident(true);
+ }
+
+ }
+
+ protected static String readJBCConfig(final WorkspaceEntry wsConfig) throws
RepositoryConfigurationException
+ {
+ if (wsConfig.getCache() != null)
+ {
+ return wsConfig.getCache().getParameterValue(JBOSSCACHE_CONFIG);
+ }
+ else
+ {
+ throw new RepositoryConfigurationException("Cache configuration not
found");
+ }
+ }
+
+ /**
+ * Return TransactionManager used by JBossCache backing the JCR cache.
+ *
+ * @return TransactionManager
+ */
+ public TransactionManager getTransactionManager()
+ {
+ return cache.getTransactionManager();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void put(ItemData item)
+ {
+ boolean inTransaction = cache.isTransactionActive();
+ try
+ {
+ if (!inTransaction)
+ {
+ cache.beginTransaction();
+ }
+ cache.setLocal(true);
+
+ if (item instanceof NullNodeData)
+ {
+ putNullNode((NullNodeData)item);
+ }
+ else
+ {
+ if (item.isNode())
+ {
+ putNode((NodeData)item, ModifyChildOption.NOT_MODIFY);
+ }
+ else
+ {
+ putProperty((PropertyData)item, ModifyChildOption.NOT_MODIFY);
+ }
+ }
+ }
+ finally
+ {
+ cache.setLocal(false);
+ if (!inTransaction)
+ {
+ cache.commitTransaction();
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void remove(ItemData item)
+ {
+ removeItem(item);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onSaveItems(final ItemStateChangesLog itemStates)
+ {
+ // if something happen we will rollback changes
+ boolean rollback = true;
+ try
+ {
+ cache.beginTransaction();
+ for (ItemState state : itemStates.getAllStates())
+ {
+ 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 (prevItem != null && state.isNode())
+ {
+ // nodes reordered, if previous is null it's InvalidItemState
case
+ updateInBuffer((NodeData)state.getData(), (NodeData)prevItem);
+ }
+ }
+ }
+ else if (state.isDeleted())
+ {
+ removeItem(state.getData());
+ }
+ else if (state.isRenamed())
+ {
+ putItem(state.getData());
+ }
+ else if (state.isMixinChanged())
+ {
+ if (state.isPersisted())
+ {
+ // update subtree ACLs
+ updateMixin((NodeData)state.getData());
+ }
+ }
+ }
+ cache.commitTransaction();
+ rollback = false;
+ }
+ finally
+ {
+ if (rollback)
+ {
+ cache.rollbackTransaction();
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void addChildNodes(NodeData parent, List<NodeData> childs)
+ {
+ boolean inTransaction = cache.isTransactionActive();
+ try
+ {
+
+ if (!inTransaction)
+ {
+ cache.beginTransaction();
+ }
+
+ cache.setLocal(true);
+ // remove previous all (to be sure about consistency)
+ cache.removeNode(makeChildListFqn(childNodesList, parent.getIdentifier()));
+
+ if (childs.size() > 0)
+ {
+ Set<Object> set = new HashSet<Object>();
+ for (NodeData child : childs)
+ {
+ putNode(child, ModifyChildOption.NOT_MODIFY);
+ set.add(child.getIdentifier());
+ }
+ cache.put(makeChildListFqn(childNodesList, parent.getIdentifier()),
ITEM_LIST, set);
+ }
+ else
+ {
+ // cache fact of empty childs list
+ cache.put(makeChildListFqn(childNodesList, parent.getIdentifier()),
ITEM_LIST, new HashSet<Object>());
+ }
+ }
+ finally
+ {
+ cache.setLocal(false);
+ if (!inTransaction)
+ {
+ cache.commitTransaction();
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void addChildProperties(NodeData parent, List<PropertyData> childs)
+ {
+ boolean inTransaction = cache.isTransactionActive();
+ try
+ {
+ if (!inTransaction)
+ {
+ cache.beginTransaction();
+ }
+ cache.setLocal(true);
+ // remove previous all (to be sure about consistency)
+ cache.removeNode(makeChildListFqn(childPropsList, parent.getIdentifier()));
+ 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.put(makeChildListFqn(childPropsList, parent.getIdentifier()),
ITEM_LIST, set);
+
+ }
+ else
+ {
+ LOG.warn("Empty properties list cached " + (parent != null ?
parent.getQPath().getAsString() : parent));
+ }
+ }
+ finally
+ {
+ cache.setLocal(false);
+ if (!inTransaction)
+ {
+ cache.commitTransaction();
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void addChildPropertiesList(NodeData parent, List<PropertyData>
childProperties)
+ {
+ // TODO not implemented, will force read from DB
+ // try
+ // {
+ // cache.beginTransaction();
+ // cache.setLocal(true);
+ //
+ // }
+ // finally
+ // {
+ // cache.setLocal(false);
+ // cache.commitTransaction();
+ // }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ItemData get(String parentId, QPathEntry name)
+ {
+ return get(parentId, name, ItemType.UNKNOWN);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ItemData get(String parentId, QPathEntry name, ItemType itemType)
+ {
+ String itemId = null;
+ if (itemType == ItemType.NODE || itemType == ItemType.UNKNOWN)
+ {
+ // try as node first
+ itemId = (String)cache.get(makeChildFqn(childNodes, parentId, name), ITEM_ID);
+ }
+
+ if (itemType == ItemType.PROPERTY || itemType == ItemType.UNKNOWN && itemId
== null)
+ {
+ // try as property
+ itemId = (String)cache.get(makeChildFqn(childProps, parentId, name), ITEM_ID);
+ }
+
+ if (itemId != null)
+ {
+ return getFromCacheById(itemId);
+ }
+
+ return (ItemData)cache.get(makeNullItemFqn(parentId + "$" +
name.getAsString(true)), ITEM_DATA);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ItemData get(String id)
+ {
+ ItemData data = getFromCacheById(id);
+
+ return data != null ? data : (ItemData)cache.get(makeNullItemFqn(id), ITEM_DATA);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List<NodeData> getChildNodes(final NodeData parent)
+ {
+ // get list of children uuids
+ final Set<Object> set =
+ (Set<Object>)cache.get(makeChildListFqn(childNodesList,
parent.getIdentifier()), ITEM_LIST);
+ if (set != null)
+ {
+ final List<NodeData> childs = new ArrayList<NodeData>();
+
+ for (Object child : set)
+ {
+ NodeData node = (NodeData)cache.get(makeItemFqn((String)child), ITEM_DATA);
+ if (node == null)
+ {
+ return null;
+ }
+
+ childs.add(node);
+ }
+
+ // order children by orderNumber, as HashSet returns children in other order
+ Collections.sort(childs, new NodesOrderComparator<NodeData>());
+
+ return childs;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getChildNodesCount(NodeData parent)
+ {
+ // get list of children uuids
+ final Set<Object> set =
+ (Set<Object>)cache.get(makeChildListFqn(childNodesList,
parent.getIdentifier()), ITEM_LIST);
+
+ return set != null ? set.size() : -1;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List<PropertyData> getChildProperties(NodeData parent)
+ {
+ return getChildProps(parent.getIdentifier(), true);
+ }
+
+ /**
+ * {@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)
+ {
+ // get set of property uuids
+ final Set<Object> set =
(Set<Object>)cache.get(makeChildListFqn(childPropsList, parentId), ITEM_LIST);
+ if (set != null)
+ {
+ final List<PropertyData> childs = new ArrayList<PropertyData>();
+
+ for (Object child : set)
+ {
+ PropertyData prop = (PropertyData)cache.get(makeItemFqn((String)child),
ITEM_DATA);
+ if (prop == null)
+ {
+ return null;
+ }
+ if (withValue && prop.getValues().size() <= 0)
+ {
+ // don't return list of empty-valued props (but listChildProperties()
can)
+ return null;
+ }
+ childs.add(prop);
+ }
+ return childs;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public long getSize()
+ {
+ // Total number of JBC nodes in the cache - the total amount of resident nodes
+ return numNodes(cache.getNode(rootFqn)) - 6;
+ }
+
+ /**
+ * Evaluates the total amount of sub-nodes that the given node contains
+ */
+ private static long numNodes(Node<Serializable, Object> n)
+ {
+ long count = 1;// for n
+ if (n != null)
+ {
+ for (Node<Serializable, Object> child : n.getChildren())
+ {
+ count += numNodes(child);
+ }
+ }
+ return count;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isEnabled()
+ {
+ return true;
+ }
+
+ // non-public members
+
+ /**
+ * Make Item absolute Fqn, i.e. /$ITEMS/itemID.
+ *
+ * @param itemId String
+ * @return Fqn
+ */
+ protected Fqn<String> makeItemFqn(String itemId)
+ {
+ return Fqn.fromRelativeElements(itemsRoot, itemId);
+ }
+
+ /**
+ * Make Item absolute Fqn, i.e. /$NULL_ITEMS/itemID.
+ *
+ * @param itemId String
+ * @return Fqn
+ */
+ protected Fqn<String> makeNullItemFqn(String itemId)
+ {
+ return Fqn.fromRelativeElements(nullItemsRoot, itemId);
+ }
+
+ /**
+ * Make child Item absolute Fqn, i.e. /root/parentId/childName.
+ *
+ * @param root Fqn
+ * @param parentId String
+ * @param childName QPathEntry
+ * @return Fqn
+ */
+ protected Fqn<String> makeChildFqn(Fqn<String> root, String parentId,
QPathEntry childName)
+ {
+ return Fqn.fromRelativeElements(root, parentId, childName.getAsString(true));
+ }
+
+ /**
+ * Make child Item absolute Fqn, i.e. /root/parentId/childName.
+ *
+ * @param root Fqn
+ * @param parentId String
+ * @param childName String
+ * @return Fqn
+ */
+ protected Fqn<String> makeChildFqn(Fqn<String> root, String parentId,
String childName)
+ {
+ return Fqn.fromRelativeElements(root, parentId, childName);
+ }
+
+ /**
+ * Make child node parent absolute Fqn, i.e. /root/itemId.
+ *
+ * @param root Fqn
+ * @param parentId String
+ * @return Fqn
+ */
+ protected Fqn<String> makeChildListFqn(Fqn<String> root, String parentId)
+ {
+ return Fqn.fromRelativeElements(root, parentId);
+ }
+
+ /**
+ * Gets item data from cache by item identifier.
+ */
+ protected ItemData getFromCacheById(String id)
+ {
+ return (ItemData)cache.get(makeItemFqn(id), ITEM_DATA);
+ }
+
+ /**
+ * 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)
+ {
+
+ // remove possible NullNodeData from cache
+ boolean local = cache.isLocal();
+ cache.setLocal(false);
+
+ removeNullNode(node);
+
+ cache.setLocal(local);
+
+ // if not a root node
+ if (node.getParentIdentifier() != null)
+ {
+ // add in CHILD_NODES
+ cache.put(makeChildFqn(childNodes, node.getParentIdentifier(),
node.getQPath().getEntries()[node.getQPath()
+ .getEntries().length - 1]), ITEM_ID, node.getIdentifier());
+ // if MODIFY and List present OR FORCE_MODIFY, then write
+ if ((modifyListsOfChild == ModifyChildOption.MODIFY &&
cache.getNode(makeChildListFqn(childNodesList, node
+ .getParentIdentifier())) != null)
+ || modifyListsOfChild == ModifyChildOption.FORCE_MODIFY)
+ {
+ cache.addToList(makeChildListFqn(childNodesList, node.getParentIdentifier()),
ITEM_LIST, node
+ .getIdentifier());
+ }
+
+ }
+ // add in ITEMS
+ return (ItemData)cache.put(makeItemFqn(node.getIdentifier()), ITEM_DATA, node);
+ }
+
+ /**
+ * Internal put NullNode.
+ *
+ * @param node, NodeData, new data to put in the cache
+ * @return ItemData, previous data or null
+ */
+ protected ItemData putNullNode(NullNodeData node)
+ {
+ return (ItemData)cache.put(makeNullItemFqn(node.getIdentifier()), ITEM_DATA,
node);
+ }
+
+ /**
+ * Removes NullNode from cache.
+ *
+ * @param item
+ * that possible has corresponding NullNode in cache
+ *
+ */
+ protected void removeNullNode(ItemData item)
+ {
+ Fqn<String> fqn = makeNullItemFqn(item.getIdentifier());
+ if ((NullNodeData)cache.get(fqn, ITEM_DATA) != null)
+ {
+ cache.removeNode(fqn);
+ }
+
+ fqn =
+ makeNullItemFqn(item.getParentIdentifier() + "$"
+ + item.getQPath().getEntries()[item.getQPath().getEntries().length -
1].getAsString(true));
+ if (cache.get(fqn, ITEM_DATA) != null)
+ {
+ cache.removeNode(fqn);
+ }
+ }
+
+ protected ItemData putNodeInBufferedCache(NodeData node, ModifyChildOption
modifyListsOfChild)
+ {
+ // if not a root node
+ if (node.getParentIdentifier() != null)
+ {
+ // add in CHILD_NODES
+ cache.put(makeChildFqn(childNodes, node.getParentIdentifier(),
node.getQPath().getEntries()[node.getQPath()
+ .getEntries().length - 1]), ITEM_ID, node.getIdentifier());
+ // if MODIFY and List present OR FORCE_MODIFY, then write
+ if ((modifyListsOfChild == ModifyChildOption.MODIFY &&
cache.getNode(makeChildListFqn(childNodesList, node
+ .getParentIdentifier())) != null)
+ || modifyListsOfChild == ModifyChildOption.FORCE_MODIFY)
+ {
+ cache.addToList(makeChildListFqn(childNodesList, node.getParentIdentifier()),
ITEM_LIST, node
+ .getIdentifier());
+ }
+ }
+ // add in ITEMS
+ return (ItemData)cache.putInBuffer(makeItemFqn(node.getIdentifier()), ITEM_DATA,
node);
+ }
+
+ /**
+ * Internal put Property.
+ *
+ * @param node, PropertyData, new data to put in the cache
+ * @return PropertyData, previous data or null
+ */
+ protected PropertyData putProperty(PropertyData prop, ModifyChildOption
modifyListsOfChild)
+ {
+
+ // remove possible NullNodeData from cache
+ boolean local = cache.isLocal();
+ cache.setLocal(false);
+
+ removeNullNode(prop);
+
+ cache.setLocal(local);
+
+ // add in CHILD_PROPS
+ cache.put(makeChildFqn(childProps, prop.getParentIdentifier(),
prop.getQPath().getEntries()[prop.getQPath()
+ .getEntries().length - 1]), ITEM_ID, prop.getIdentifier());
+ // if MODIFY and List present OR FORCE_MODIFY, then write
+ if ((modifyListsOfChild == ModifyChildOption.MODIFY &&
cache.getNode(makeChildListFqn(childPropsList, prop
+ .getParentIdentifier())) != null)
+ || modifyListsOfChild == ModifyChildOption.FORCE_MODIFY)
+ {
+ cache.addToList(makeChildListFqn(childPropsList, prop.getParentIdentifier()),
ITEM_LIST, prop.getIdentifier());
+ }
+
+ ItemData result =
+ get(prop.getParentIdentifier(),
prop.getQPath().getEntries()[prop.getQPath().getEntries().length - 1]);
+
+ // add in ITEMS
+ return (PropertyData)cache.put(makeItemFqn(prop.getIdentifier()), ITEM_DATA,
prop);
+ }
+
+ protected void removeItem(ItemData item)
+ {
+ if (item.isNode())
+ {
+ if (item.getParentIdentifier() != null)
+ {
+ // if not a root node
+
+ // remove from CHILD_NODES of parent
+ cache.removeNode(makeChildFqn(childNodes, item.getParentIdentifier(),
item.getQPath().getEntries()[item
+ .getQPath().getEntries().length - 1]));
+
+ // remove from CHILD_NODES_LIST of parent
+ cache.removeFromList(makeChildListFqn(childNodesList,
item.getParentIdentifier()), ITEM_LIST, item
+ .getIdentifier());
+
+ // remove from CHILD_NODES as parent
+ cache.removeNode(makeChildListFqn(childNodes, item.getIdentifier()));
+
+ // remove from CHILD_NODES_LIST as parent
+ cache.removeNode(makeChildListFqn(childNodesList, item.getIdentifier()));
+
+ // remove from CHILD_PROPS as parent
+ cache.removeNode(makeChildListFqn(childProps, item.getIdentifier()));
+
+ // remove from CHILD_PROPS_LIST as parent
+ cache.removeNode(makeChildListFqn(childPropsList, item.getIdentifier()));
+ }
+ }
+ else
+ {
+ // remove from CHILD_PROPS
+ cache.removeNode(makeChildFqn(childProps, item.getParentIdentifier(),
item.getQPath().getEntries()[item
+ .getQPath().getEntries().length - 1]));
+
+ // remove from CHILD_PROPS_LIST
+ cache.removeFromList(makeChildListFqn(childPropsList,
item.getParentIdentifier()), ITEM_LIST, item
+ .getIdentifier());
+ }
+ // remove from ITEMS
+ cache.removeNode(makeItemFqn(item.getIdentifier()));
+ }
+
+ /**
+ * Update Node's mixin and ACL.
+ *
+ * @param node NodeData
+ */
+ protected void updateMixin(NodeData node)
+ {
+ NodeData prevData = (NodeData)cache.put(makeItemFqn(node.getIdentifier()),
ITEM_DATA, node);
+ 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 hierachy in case of same-name siblings reorder.
+ * Assumes the new (updated) nodes already putted 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). Childs paths will
be updated to a new node path.
+ *
+ * @param node NodeData
+ * @param prevNode NodeData
+ */
+ protected void update(final NodeData node, final NodeData prevNode)
+ {
+ // get previously cached NodeData and using its name remove child on the parent
+ Fqn<String> prevFqn =
+ makeChildFqn(childNodes, node.getParentIdentifier(),
prevNode.getQPath().getEntries()[prevNode.getQPath()
+ .getEntries().length - 1]);
+ if (node.getIdentifier().equals(cache.get(prevFqn, ITEM_ID)))
+ {
+ // it's same-name siblings re-ordering, delete previous child
+ if (!cache.removeNode(prevFqn) && LOG.isDebugEnabled())
+ {
+ LOG.debug("Node not extists as a child but update asked " +
node.getQPath().getAsString());
+ }
+ }
+
+ // update childs paths if index changed
+ int nodeIndex = node.getQPath().getEntries()[node.getQPath().getEntries().length -
1].getIndex();
+ int prevNodeIndex =
prevNode.getQPath().getEntries()[prevNode.getQPath().getEntries().length - 1].getIndex();
+ if (nodeIndex != prevNodeIndex)
+ {
+ updateTreePath(node.getIdentifier(), node.getQPath(), null); // don't change
ACL, it's same parent
+ }
+ }
+
+ /**
+ * This method duplicate update method, except using getFromBuffer inside.
+ *
+ * @param node NodeData
+ * @param prevNode NodeData
+ */
+ protected void updateInBuffer(final NodeData node, final NodeData prevNode)
+ {
+ // get previously cached NodeData and using its name remove child on the parent
+ Fqn<String> prevFqn =
+ makeChildFqn(childNodes, node.getParentIdentifier(),
prevNode.getQPath().getEntries()[prevNode.getQPath()
+ .getEntries().length - 1]);
+ if (node.getIdentifier().equals(cache.getFromBuffer(prevFqn, ITEM_ID)))
+ {
+ // it's same-name siblings re-ordering, delete previous child
+ if (!cache.removeNode(prevFqn) && LOG.isDebugEnabled())
+ {
+ LOG.debug("Node not extists as a child but update asked " +
node.getQPath().getAsString());
+ }
+ }
+
+ // update childs paths if index changed
+ int nodeIndex = node.getQPath().getEntries()[node.getQPath().getEntries().length -
1].getIndex();
+ int prevNodeIndex =
prevNode.getQPath().getEntries()[prevNode.getQPath().getEntries().length - 1].getIndex();
+ if (nodeIndex != prevNodeIndex)
+ {
+ // its a samename reordering
+ updateTreePath(prevNode.getQPath(), node.getQPath(), null); // don't change
ACL, it's same parent
+ }
+ }
+
+ /**
+ * Check all items in cache - is it descendant of prevRootPath, and update path
according newRootPath.
+ *
+ * @param prevRootPath
+ * @param newRootPath
+ * @param acl
+ */
+ protected void updateTreePath(final QPath prevRootPath, final QPath newRootPath, final
AccessControlList acl)
+ {
+ boolean inheritACL = acl != null;
+
+ // check all ITEMS in cache
+ Node<Serializable, Object> items = cache.getNode(itemsRoot);
+ Set<Object> childrenNames = items.getChildrenNames();
+ Iterator<Object> namesIt = childrenNames.iterator();
+
+ while (namesIt.hasNext())
+ {
+ String id = (String)namesIt.next();
+ ItemData data = (ItemData)cache.get(makeItemFqn(id), ITEM_DATA);
+
+ // check is this descendant of prevRootPath
+ QPath nodeQPath = data.getQPath();
+ if (nodeQPath.isDescendantOf(prevRootPath))
+ {
+
+ //make relative path
+ QPathEntry[] relativePath = null;
+ try
+ {
+ relativePath = nodeQPath.getRelPath(nodeQPath.getDepth() -
prevRootPath.getDepth());
+ }
+ catch (IllegalPathException e)
+ {
+ // Do nothing. Never happens.
+ }
+
+ // make new path - no matter node or property
+ QPath newPath = QPath.makeChildPath(newRootPath, relativePath);
+
+ if (data.isNode())
+ {
+ // update node
+
+ NodeData prevNode = (NodeData)data;
+
+ TransientNodeData newNode =
+ new TransientNodeData(newPath, prevNode.getIdentifier(),
prevNode.getPersistedVersion(), prevNode
+ .getPrimaryTypeName(), prevNode.getMixinTypeNames(),
prevNode.getOrderNumber(), prevNode
+ .getParentIdentifier(), inheritACL ? acl : prevNode.getACL()); //
TODO check ACL
+ // update this node
+ cache.put(makeItemFqn(newNode.getIdentifier()), ITEM_DATA, newNode);
+ }
+ else
+ {
+ //update property
+
+ PropertyData prevProp = (PropertyData)data;
+
+ if (inheritACL
+ &&
(prevProp.getQPath().getName().equals(Constants.EXO_PERMISSIONS) ||
prevProp.getQPath().getName()
+ .equals(Constants.EXO_OWNER)))
+ {
+ inheritACL = false;
+ }
+
+ TransientPropertyData newProp =
+ new TransientPropertyData(newPath, prevProp.getIdentifier(),
prevProp.getPersistedVersion(), prevProp
+ .getType(), prevProp.getParentIdentifier(),
prevProp.isMultiValued(), prevProp.getValues());
+ cache.put(makeItemFqn(newProp.getIdentifier()), ITEM_DATA, newProp);
+ }
+ }
+ }
+ }
+
+ /**
+ * Update Nodes tree with new path.
+ *
+ * @param parentId String - root node id of JCR subtree.
+ * @param rootPath QPath
+ * @param acl AccessControlList
+ */
+ protected void updateTreePath(final String parentId, final QPath rootPath, final
AccessControlList acl)
+ {
+ boolean inheritACL = acl != null;
+
+ // update properties
+ for (Iterator<PropertyData> iter = new
ChildPropertiesIterator<PropertyData>(parentId); iter.hasNext();)
+ {
+ PropertyData prevProp = iter.next();
+
+ if (inheritACL
+ && (prevProp.getQPath().getName().equals(Constants.EXO_PERMISSIONS)
|| prevProp.getQPath().getName()
+ .equals(Constants.EXO_OWNER)))
+ {
+ inheritACL = false;
+ }
+ // recreate with new path for child Props only
+ QPath newPath =
+ QPath
+ .makeChildPath(rootPath,
prevProp.getQPath().getEntries()[prevProp.getQPath().getEntries().length - 1]);
+ TransientPropertyData newProp =
+ new TransientPropertyData(newPath, prevProp.getIdentifier(),
prevProp.getPersistedVersion(), prevProp
+ .getType(), prevProp.getParentIdentifier(), prevProp.isMultiValued(),
prevProp.getValues());
+ cache.put(makeItemFqn(newProp.getIdentifier()), ITEM_DATA, newProp);
+ }
+
+ // update child nodes
+ for (Iterator<NodeData> iter = new
ChildNodesIterator<NodeData>(parentId); iter.hasNext();)
+ {
+ NodeData prevNode = iter.next();
+ // recreate with new path for child Nodes only
+ QPath newPath =
+ QPath
+ .makeChildPath(rootPath,
prevNode.getQPath().getEntries()[prevNode.getQPath().getEntries().length - 1]);
+ TransientNodeData newNode =
+ new TransientNodeData(newPath, prevNode.getIdentifier(),
prevNode.getPersistedVersion(), prevNode
+ .getPrimaryTypeName(), prevNode.getMixinTypeNames(),
prevNode.getOrderNumber(), prevNode
+ .getParentIdentifier(), inheritACL ? acl : prevNode.getACL()); // TODO
check ACL
+ // update this node
+ cache.put(makeItemFqn(newNode.getIdentifier()), ITEM_DATA, newNode);
+ // update childs recursive
+ updateTreePath(newNode.getIdentifier(), newNode.getQPath(), inheritACL ? acl :
null);
+ }
+ }
+
+ /**
+ * Update child Nodes ACLs.
+ *
+ * @param parentId String - root node id of JCR subtree.
+ * @param acl AccessControlList
+ */
+ protected void updateChildsACL(final String parentId, final AccessControlList acl)
+ {
+ 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)
+ for (InternalQName mixin : prevNode.getMixinTypeNames())
+ {
+ if (mixin.equals(Constants.EXO_PRIVILEGEABLE) ||
mixin.equals(Constants.EXO_OWNEABLE))
+ {
+ continue;
+ }
+ }
+ // recreate with new path for child Nodes only
+ TransientNodeData newNode =
+ new TransientNodeData(prevNode.getQPath(), prevNode.getIdentifier(),
prevNode.getPersistedVersion(),
+ prevNode.getPrimaryTypeName(), prevNode.getMixinTypeNames(),
prevNode.getOrderNumber(), prevNode
+ .getParentIdentifier(), acl);
+ // update this node
+ cache.put(makeItemFqn(newNode.getIdentifier()), ITEM_DATA, newNode);
+ // update childs recursive
+ updateChildsACL(newNode.getIdentifier(), 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
+ }
+
+}
Modified:
jcr/trunk/exo.jcr.component.core/src/test/java/org/exoplatform/services/jcr/impl/dataflow/persistent/TestCacheableWorkspaceDataManager.java
===================================================================
---
jcr/trunk/exo.jcr.component.core/src/test/java/org/exoplatform/services/jcr/impl/dataflow/persistent/TestCacheableWorkspaceDataManager.java 2010-09-28
09:01:44 UTC (rev 3195)
+++
jcr/trunk/exo.jcr.component.core/src/test/java/org/exoplatform/services/jcr/impl/dataflow/persistent/TestCacheableWorkspaceDataManager.java 2010-09-28
09:21:26 UTC (rev 3196)
@@ -25,7 +25,9 @@
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.NullNodeData;
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.storage.SystemDataContainerHolder;
@@ -143,7 +145,8 @@
public void testGetItemDataByNodeDataNQPathEntry() throws Exception
{
- final NodeData nodeData = new PersistedNodeData("getItemData", null,
null, 0, 1, null, null, null);
+ final NodeData nodeData =
+ new PersistedNodeData("getItemData", new QPath(new QPathEntry[]{}),
null, 0, 1, null, null, null);
assertEquals(0, con.getItemDataByNodeDataNQPathEntryCalls.get());
MyTask task = new MyTask()
{