[jboss-cvs] JBossAS SVN: r86553 - in trunk: cluster/src/etc and 14 other directories.

jboss-cvs-commits at lists.jboss.org jboss-cvs-commits at lists.jboss.org
Tue Mar 31 18:10:16 EDT 2009


Author: bstansberry at jboss.com
Date: 2009-03-31 18:10:16 -0400 (Tue, 31 Mar 2009)
New Revision: 86553

Added:
   trunk/cluster/src/etc/farm-deployment-jboss-beans.xml
   trunk/cluster/src/main/org/jboss/profileservice/
   trunk/cluster/src/main/org/jboss/profileservice/cluster/
   trunk/cluster/src/main/org/jboss/profileservice/cluster/repository/
   trunk/cluster/src/main/org/jboss/profileservice/cluster/repository/DefaultRepositoryClusteringHandler.java
   trunk/cluster/src/main/org/jboss/profileservice/cluster/repository/DefaultSynchronizationPolicy.java
   trunk/system/src/main/org/jboss/system/server/profileservice/StaticClusteredProfileFactory.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/ClusteredDeploymentRepository.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/ClusteredDeploymentRepositoryFactory.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/ImmutableClusteredDeploymentRepository.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/RepositoryClusteringHandler.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/AbstractContentMetadataPersister.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/AbstractLocalContentManager.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/ContentMetadataPersister.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/JAXBRepositoryContentMetadataPersister.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/LocalContentManager.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/LocalContentManagerFactory.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/AbstractLocalContentChangeAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/DirectoryTimestampUpdateAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileBasedSynchronizationActionContext.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileReadAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileUtil.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileWriteAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FilesystemLocalContentManager.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FilesystemLocalContentManagerFactory.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/InitiateRmdirAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/MkDirAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/RemoveFileAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/AbstractSortedMetadataContainer.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/ClusteredProfileSourceMetaData.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/HotDeploymentClusteredProfileSourceMetaData.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/Identifiable.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/ImmutableClusteredProfileSourceMetaData.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/RepositoryContentMetadata.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/RepositoryItemMetadata.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/RepositoryRootMetadata.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractContentMetadataMutatorAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractContentModificationGenerator.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractSynchronizationAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractSynchronizationPolicy.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/ByteChunk.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/ContentModification.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/ImmutableSynchronizationPolicy.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/InconsistentRepositoryStructureException.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/LocalContentModificationGenerator.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/NoOpRepositorySynchronizationWriteAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/NoOpSynchronizationAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/RemoteContentModificationGenerator.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/RemoteRemovalAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/RemovalMetadataInsertionAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SimpleSynchronizationRemoteAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/StreamReadAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationActionContext.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationId.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationInitiationAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationPolicy.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationReadAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationRemoteAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationWriteAction.java
   trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/TwoPhaseCommitAction.java
   trunk/system/src/tests/org/jboss/test/server/profileservice/clustered/
   trunk/system/src/tests/org/jboss/test/server/profileservice/clustered/test/
   trunk/system/src/tests/org/jboss/test/server/profileservice/clustered/test/JAXBRepositoryContentMetadataPersisterUnitTestCase.java
   trunk/system/src/tests/org/jboss/test/server/profileservice/clustered/test/MockSynchronizationPolicy.java
   trunk/system/src/tests/org/jboss/test/server/profileservice/clustered/test/RemoteContentModificationGeneratorUnitTestCase.java
Modified:
   trunk/cluster/.classpath
   trunk/cluster/pom.xml
Log:
[JBAS-5552] ClusteredDeploymentRepository

Modified: trunk/cluster/.classpath
===================================================================
--- trunk/cluster/.classpath	2009-03-31 22:07:34 UTC (rev 86552)
+++ trunk/cluster/.classpath	2009-03-31 22:10:16 UTC (rev 86553)
@@ -2,7 +2,9 @@
 <classpath>
 	<classpathentry excluding="org/jbossmx/" kind="src" path="src/main"/>
 	<classpathentry kind="src" path="src/examples"/>
+	<classpathentry kind="src" path="src/tests"/>
 	<classpathentry exported="true" kind="src" path="/server"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/system"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
 	<classpathentry exported="true" kind="lib" path="/thirdparty/jgroups/lib/jgroups.jar" sourcepath="/JGroups/src"/>
 	<classpathentry exported="true" kind="lib" path="/thirdparty/apache-logging/lib/commons-logging.jar"/>
@@ -15,6 +17,6 @@
 	<classpathentry kind="lib" path="/thirdparty/jboss/common-logging-spi/lib/jboss-logging-spi.jar"/>
 	<classpathentry kind="lib" path="/thirdparty/jboss/common-core/lib/jboss-common-core.jar" sourcepath="/thirdparty/jboss/common-core/lib/jboss-common-core-sources.jar"/>
 	<classpathentry kind="lib" path="/thirdparty/jboss/microcontainer/lib/jboss-dependency.jar" sourcepath="/thirdparty/jboss/microcontainer/lib/jboss-dependency-sources.jar"/>
-	<classpathentry kind="lib" path="/thirdparty/jboss/jboss-bootstrap/lib/jboss-bootstrap.jar"/>
+	<classpathentry kind="lib" path="/thirdparty/jboss/jboss-bootstrap/lib/jboss-bootstrap.jar" sourcepath="/thirdparty/jboss/jboss-bootstrap/lib/jboss-bootstrap-sources.jar"/>
 	<classpathentry kind="output" path="output/eclipse-classes"/>
 </classpath>

Modified: trunk/cluster/pom.xml
===================================================================
--- trunk/cluster/pom.xml	2009-03-31 22:07:34 UTC (rev 86552)
+++ trunk/cluster/pom.xml	2009-03-31 22:10:16 UTC (rev 86553)
@@ -241,6 +241,11 @@
     <dependency>
       <groupId>org.jboss.remoting</groupId>
       <artifactId>jboss-remoting</artifactId>
+    </dependency>   
+
+    <dependency>
+      <groupId>org.jboss.integration</groupId>
+      <artifactId>jboss-profileservice-spi</artifactId>
     </dependency>
     
   </dependencies>

Added: trunk/cluster/src/etc/farm-deployment-jboss-beans.xml
===================================================================
--- trunk/cluster/src/etc/farm-deployment-jboss-beans.xml	                        (rev 0)
+++ trunk/cluster/src/etc/farm-deployment-jboss-beans.xml	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<deployment xmlns="urn:jboss:bean-deployer:2.0">
+   
+   <bean name="FarmProfileRepositoryClusteringHandler"
+         class="org.jboss.profileservice.cluster.repository.DefaultRepositoryClusteringHandler">
+         
+      <property name="partition"><inject bean="HAPartition"/></property>
+      <property name="profileName">farm</property>
+      <property name="synchronizationPolicy"><inject bean="FarmProfileSynchronizationPolicy"/></property>
+   </bean>
+   
+   <bean name="FarmProfileSynchronizationPolicy" 
+         class="org.jboss.profileservice.cluster.repository.DefaultSynchronizationPolicy"/>
+
+</deployment>


Property changes on: trunk/cluster/src/etc/farm-deployment-jboss-beans.xml
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/cluster/src/main/org/jboss/profileservice/cluster/repository/DefaultRepositoryClusteringHandler.java
===================================================================
--- trunk/cluster/src/main/org/jboss/profileservice/cluster/repository/DefaultRepositoryClusteringHandler.java	                        (rev 0)
+++ trunk/cluster/src/main/org/jboss/profileservice/cluster/repository/DefaultRepositoryClusteringHandler.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,1301 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.profileservice.cluster.repository;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.jboss.ha.framework.interfaces.ClusterNode;
+import org.jboss.ha.framework.interfaces.DistributedReplicantManager;
+import org.jboss.ha.framework.interfaces.HAPartition;
+import org.jboss.ha.framework.interfaces.DistributedReplicantManager.ReplicantListener;
+import org.jboss.ha.framework.server.lock.LocalAndClusterLockManager;
+import org.jboss.ha.framework.server.lock.TimeoutException;
+import org.jboss.kernel.spi.dependency.KernelControllerContext;
+import org.jboss.kernel.spi.dependency.KernelControllerContextAware;
+import org.jboss.logging.Logger;
+import org.jboss.profileservice.spi.ProfileKey;
+import org.jboss.system.server.profileservice.repository.clustered.RepositoryClusteringHandler;
+import org.jboss.system.server.profileservice.repository.clustered.local.LocalContentManager;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryItemMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ByteChunk;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ContentModification;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ImmutableSynchronizationPolicy;
+import org.jboss.system.server.profileservice.repository.clustered.sync.InconsistentRepositoryStructureException;
+import org.jboss.system.server.profileservice.repository.clustered.sync.LocalContentModificationGenerator;
+import org.jboss.system.server.profileservice.repository.clustered.sync.RemoteContentModificationGenerator;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationAction;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationId;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationInitiationAction;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationPolicy;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationReadAction;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationRemoteAction;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationWriteAction;
+import org.jboss.virtual.VirtualFile;
+
+/**
+ * {@link RepositoryClusteringHandler} implementation that uses {@link HAPartition}.
+ * 
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class DefaultRepositoryClusteringHandler 
+   implements RepositoryClusteringHandler, KernelControllerContextAware
+{
+   private static final Logger log = Logger.getLogger(DefaultRepositoryClusteringHandler.class);
+   
+   private static final Class<?>[] JOIN_SYNCHRONIZE_TYPES = 
+      new Class[]{RepositoryContentMetadata.class, RepositoryContentMetadata.class};
+   
+   private static final Class<?>[] MERGE_SYNCHRONIZE_TYPES = 
+      new Class[]{RepositoryContentMetadata.class};
+   
+   private static final Class<?>[] INITIATE_SYNCHRONIZATION_TYPES = 
+      new Class[]{SynchronizationId.class, List.class};
+   
+   private static final Class<?>[] TX_TYPES = new Class[]{SynchronizationId.class};
+   
+   private static final Class<?>[] EXECUTE_MOD_TYPES = 
+      new Class[]{SynchronizationId.class, RepositoryItemMetadata.class, boolean.class};
+   
+   private static final Class<?>[] PULL_BYTES_TYPES = 
+      new Class[]{SynchronizationId.class, RepositoryItemMetadata.class};
+   
+   private static final Class<?>[] PUSH_BYTES_TYPES = 
+      new Class[]{SynchronizationId.class, RepositoryItemMetadata.class, ByteChunk.class};
+   
+   public static final long DEFAULT_TIMEOUT = 60000;
+   
+   private String serviceHAName;
+   private HAPartition partition;
+   private String profileDomain;
+   private String profileServer;
+   private String profileName;
+   private boolean immutable;
+   private LocalAndClusterLockManager lockSupport;
+   private SynchronizationPolicy synchronizationPolicy;
+   private LocalContentManager<?> contentManager;
+   private RpcTarget rpcTarget = new RpcTarget();
+   private boolean inSync = false;
+   private long lockTimeout = DEFAULT_TIMEOUT;
+   private long methodCallTimeout = DEFAULT_TIMEOUT;
+   private volatile ActiveSynchronization activeSynchronization;
+   private final DRMListener drmListener = new DRMListener();
+   private List<ClusterNode> serviceView;
+   private boolean initialized;
+   
+
+   public HAPartition getPartition()
+   {
+      return partition;
+   }
+
+   public void setPartition(HAPartition partition)
+   {
+      checkUnitialized();
+      this.partition = partition;
+   }
+
+   public String getProfileDomain()
+   {
+      return profileDomain;
+   }
+
+   public void setProfileDomain(String profileDomain)
+   {
+      checkUnitialized();
+      this.profileDomain = profileDomain;
+   }
+
+   public String getProfileServer()
+   {
+      return profileServer;
+   }
+
+   public void setProfileServer(String profileServer)
+   {
+      checkUnitialized();
+      this.profileServer = profileServer;
+   }
+
+   public String getProfileName()
+   {
+      return profileName;
+   }
+
+   public void setProfileName(String profileName)
+   {
+      checkUnitialized();
+      this.profileName = profileName;
+   }
+   
+   /**
+    * Gets the name under which we register ourself with the HAPartition.
+    * 
+    * @return the name
+    */
+   public String getServiceHAName()
+   {
+      return serviceHAName;
+   }
+
+   /**
+    * Sets the name under which we register ourself with the HAPartition.
+    * 
+    * @param serviceHAName the name
+    */
+   public void setServiceHAName(String serviceHAName)
+   {
+      checkUnitialized();
+      this.serviceHAName = serviceHAName;
+   }
+
+   public SynchronizationPolicy getSynchronizationPolicy()
+   {
+      return synchronizationPolicy;
+   }
+
+   public void setSynchronizationPolicy(SynchronizationPolicy synchronizationPolicy)
+   {
+      checkUnitialized();
+      if (!immutable)
+      {
+         this.synchronizationPolicy = synchronizationPolicy;
+      }
+   }   
+   
+   public boolean isImmutable()
+   {
+      return immutable;
+   }
+
+   public void setImmutable(boolean immutable)
+   {
+      checkUnitialized();
+      if (immutable && !this.immutable)
+      {
+         setSynchronizationPolicy(new ImmutableSynchronizationPolicy());
+      }
+      this.immutable = immutable;
+   }
+
+   public long getLockTimeout()
+   {
+      return lockTimeout;
+   }
+
+   public void setLockTimeout(long lockTimeout)
+   {
+      this.lockTimeout = lockTimeout;
+   }
+
+   public long getMethodCallTimeout()
+   {
+      return methodCallTimeout;
+   }
+
+   public void setMethodCallTimeout(long methodCallTimeout)
+   {
+      this.methodCallTimeout = methodCallTimeout;
+   }
+   
+   
+   // --------------------------------------------  RepositoryClusteringHandler
+
+   public void initialize(LocalContentManager<?> persister) throws Exception
+   {
+      if (persister == null)
+      {
+         throw new IllegalArgumentException("Null persister");
+      }
+      
+      if (this.partition == null)
+      {
+         throw new IllegalStateException("Null partition; must inject an " +
+         		"HAPartition before invoking initialize");
+      }
+      if (this.serviceHAName == null)
+      {
+         throw new IllegalStateException("Null serviceHAName; must inject a " +
+         		"serviceHAName before invoking initialize");
+      }
+      if (this.synchronizationPolicy == null)
+      {
+         throw new IllegalStateException("Null synchronizationPolicy; must " +
+         		"inject a RepositorySynchronizationPolicy before invoking initialize");
+      }
+      
+      this.contentManager = persister;
+      
+      String lockServiceName = this.serviceHAName + "-ClusterLock";
+      this.lockSupport = new LocalAndClusterLockManager(lockServiceName, partition);
+      
+      this.lockSupport.start();
+      
+      this.partition.registerRPCHandler(getServiceHAName(), rpcTarget);
+      
+      DistributedReplicantManager drm = this.partition.getDistributedReplicantManager();
+      drm.add(getServiceHAName(), this.partition.getClusterNode());
+      this.serviceView = drm.lookupReplicantsNodes(getServiceHAName());
+      drm.registerListener(getServiceHAName(), drmListener);
+      
+      this.initialized = true;
+   }
+   
+   public void shutdown() throws Exception
+   {
+      DistributedReplicantManager drm = this.partition.getDistributedReplicantManager();
+      drm.unregisterListener(getServiceHAName(), drmListener);
+      drm.remove(getServiceHAName());
+      this.partition.unregisterRPCHandler(getServiceHAName(), rpcTarget);
+      this.lockSupport.stop();
+      
+      this.contentManager = null;
+      
+      this.initialized = false;
+   }
+
+   public String getPartitionName()
+   {
+      return this.partition == null ? null : this.partition.getPartitionName();
+   }
+
+   public String getLocalNodeName()
+   {
+      ClusterNode localNode = this.partition == null ? null : this.partition.getClusterNode();
+      return localNode == null ? null : localNode.getName();
+   }
+
+   public ProfileKey getProfileKey()
+   {
+      return new ProfileKey(profileDomain, profileServer, profileName);
+   }
+
+   public RepositoryContentMetadata synchronizeContent(boolean pullFromCluster) 
+         throws InconsistentRepositoryStructureException, IOException
+   {
+      List<ContentModification> modifications = null;
+      RepositoryContentMetadata localCurrentContent = this.contentManager.getCurrentContentMetadata();
+            
+      if (pullFromCluster)
+      {
+         // Normal join case
+         modifications = getModificationsFromCluster(localCurrentContent);
+      }
+      else if (!inSync)
+      {
+         // Merge case
+         modifications = getModificationsFromCluster(null);         
+      }
+      else
+      {
+         // Periodic scan case
+         modifications = getLocalModifications(localCurrentContent);
+      }
+      
+      if (modifications != null)
+      {
+         installModifications(modifications, localCurrentContent);
+      }
+      else
+      {
+         // No one else out there. Install the localCurrentContent as official.
+         this.contentManager.installCurrentContentMetadata();
+      }
+      
+      RepositoryContentMetadata result = this.contentManager.getOfficialContentMetadata();
+      inSync = true;
+      return result;      
+   }
+
+   public VirtualFile addDeploymentContent(String vfsPath, InputStream contentIS) throws IOException
+   {
+      RepositoryItemMetadata item = this.contentManager.getItemForAddition(vfsPath);
+      RepositoryContentMetadata updated = this.contentManager.getContentMetadataForAdd(item, contentIS);
+      RepositoryContentMetadata official = this.contentManager.getOfficialContentMetadata();
+      LocalContentModificationGenerator generator = new LocalContentModificationGenerator();
+      List<ContentModification> modifications;
+      try
+      {
+         modifications = generator.getModificationList(official, updated);
+      }
+      catch (InconsistentRepositoryStructureException e)
+      {
+         throw new IllegalStateException("Incompatible structure change", e);
+      }
+      
+      installModifications(modifications, updated);
+      
+      return this.contentManager.getVirtualFileForItem(item);
+   }
+
+   public void removeDeploymentContent(VirtualFile vf) throws Exception
+   {
+      RepositoryContentMetadata updated = this.contentManager.getContentMetadataForRemove(vf);
+      RepositoryContentMetadata official = this.contentManager.getOfficialContentMetadata();
+      LocalContentModificationGenerator generator = new LocalContentModificationGenerator();
+      List<ContentModification> modifications;
+      try
+      {
+         modifications = generator.getModificationList(official, updated);
+      }
+      catch (InconsistentRepositoryStructureException e)
+      {
+         throw new IllegalStateException("Incompatible structure change", e);
+      }
+      
+      installModifications(modifications, updated);
+   }
+
+   public boolean lockGlobally()
+   {
+      try
+      {
+         lockSupport.lockGlobally(getServiceHAName(), lockTimeout);
+         return true;
+      }
+      catch (TimeoutException e)
+      {
+         log.info("Unable to acquire global lock: " + e.getLocalizedMessage());
+      }
+      catch (InterruptedException e)
+      {
+         log.info("Interrupted while obtaining global lock: " + e.getLocalizedMessage());
+         Thread.currentThread().interrupt();         
+      }
+      return false;
+   }
+
+   public boolean lockLocally()
+   {
+      try
+      {
+         lockSupport.lockLocally(getServiceHAName(), lockTimeout);
+         return true;
+      }
+      catch (TimeoutException e)
+      {
+         log.info("Unable to acquire local lock: " + e.getLocalizedMessage());
+      }
+      catch (InterruptedException e)
+      {
+         log.info("Interrupted while obtaining global lock: " + e.getLocalizedMessage());
+         Thread.currentThread().interrupt(); 
+      }
+      return false;
+   }
+
+   public void unlockGlobally()
+   {
+      lockSupport.unlockGlobally(getServiceHAName());      
+   }
+
+   public void unlockLocally()
+   {
+      lockSupport.unlockLocally(getServiceHAName());
+   }
+   
+   // -------------------------------------------- KernelControllerContextAware
+
+   /**
+    * Registers the context name as the {@link #setServiceHAName(String) serviceHAName}
+    * if it is not already set.
+    * 
+    * {@inheritDoc}
+    */
+   public void setKernelControllerContext(KernelControllerContext context) throws Exception
+   {
+      if (context != null && serviceHAName == null)
+      {
+         setServiceHAName(context.getName().toString());
+      }      
+   }
+
+   /**
+    * This implementation is a no-op.
+    * 
+    * {@inheritDoc}
+    */
+   public void unsetKernelControllerContext(KernelControllerContext context) throws Exception
+   {
+      // no-op
+   }
+   
+   // --------------------------------------------------------------  Protected
+   
+   /**
+    * For benefit of unit tests, exposes the object we register with the HAPartition.
+    * Not intended for use outside of unit tests.
+    * 
+    * TODO see if we can eliminate this by having the unit tests capture
+    * the object registered with the HAPartition.
+    */
+   protected RpcTarget getRpcTarget()
+   {
+      return rpcTarget;
+   }
+   
+   /**
+    * For benefit of unit tests, exposes the object we register with the DRM
+    * as a {@link ReplicantListener}. Not intended for use outside of unit tests.
+    * 
+    * TODO see if we can eliminate this by having the unit tests capture
+    * the object registered with the DRM.
+    */
+   protected DRMListener getDRMListener()
+   {
+      return drmListener;
+   }
+   
+   // ----------------------------------------------------------------  Private
+
+   private List<ContentModification> getModificationsFromCluster(RepositoryContentMetadata localCurrentContent) 
+         throws IOException, InconsistentRepositoryStructureException
+   {
+      List<ContentModification> modifications = null;
+      
+      RepositoryContentMetadata localBaseMetadata = this.contentManager.getOfficialContentMetadata();
+      if (localBaseMetadata == null)
+      {
+         localBaseMetadata = this.contentManager.createEmptyContentMetadata();
+      }
+      
+      List<ClusterNode> nodes = this.partition.getDistributedReplicantManager().lookupReplicantsNodes(getServiceHAName());
+      for (ClusterNode node : nodes)
+      {
+         if (node.equals(this.partition.getClusterNode()))
+         {
+            continue;
+         }
+         try
+         {
+            Object rsp = null;
+            String methodName = null;
+            if (localCurrentContent != null)
+            {
+               methodName = "joinSynchronizeContent";
+               Object[] args = { localBaseMetadata, localCurrentContent };
+               rsp = this.partition.callMethodOnNode(getServiceHAName(), methodName, args, JOIN_SYNCHRONIZE_TYPES, methodCallTimeout, node);
+            }
+            else
+            {
+               methodName = "mergeSynchronizeContent";
+               Object[] args = { localBaseMetadata };
+               rsp = this.partition.callMethodOnNode(getServiceHAName(), methodName, args, MERGE_SYNCHRONIZE_TYPES, methodCallTimeout, node);
+               
+            }
+            if (rsp instanceof NotSynchronizedException)
+            {
+               throw (NotSynchronizedException) rsp;
+            }
+            else if (rsp instanceof List)
+            {
+               @SuppressWarnings("unchecked")
+               List<ContentModification> mods = (List<ContentModification>) rsp;
+               modifications = mods;
+               break;
+            }
+            else
+            {
+               log.warn("Unknown response to " + methodName + ": " + rsp);
+            }
+            
+         }
+         catch (NotSynchronizedException ignored)
+         {
+            log.debug("Cannot synchronize with " + node + " as it itself is not yet synchronized");
+         }
+         catch (InconsistentRepositoryStructureException e)
+         {
+            throw e;
+         }
+         catch (Throwable t)
+         {
+            rethrowAsUnchecked(t);
+         }
+      }
+      return modifications;
+   }
+
+   private List<ContentModification> getLocalModifications(RepositoryContentMetadata localCurrentContent)
+   {
+      RepositoryContentMetadata official = this.contentManager.getOfficialContentMetadata();
+      LocalContentModificationGenerator generator = new LocalContentModificationGenerator();
+      try
+      {
+         return generator.getModificationList(official, localCurrentContent);
+      }
+      catch (InconsistentRepositoryStructureException e)
+      {
+         throw new IllegalStateException("Incompatible structure change", e);
+      }
+   }
+
+   private void installModifications(List<ContentModification> modifications, RepositoryContentMetadata toInstall)
+   {
+      SynchronizationId<ClusterNode> id = 
+         new SynchronizationId<ClusterNode>(this.partition.getClusterNode());
+      
+      boolean committed = false;
+      try
+      {
+         // First, inform the cluster of the synchronization, let them
+         // establish their local modifications
+         
+         Object[] args = {id, modifications};
+         List<?> rsps = this.partition.callMethodOnCluster(getServiceHAName(), 
+               "initiateSynchronization", args, INITIATE_SYNCHRONIZATION_TYPES, true);
+         for (Object rsp : rsps)
+         {
+            if (rsp instanceof NotSynchronizedException)
+            {
+               continue;
+            }
+            else if (rsp instanceof Exception)
+            {
+               throw (Exception) rsp;
+            }
+         }
+         
+         // Do it ourself
+         handleInitiateSynchronization(id, modifications, toInstall);
+         
+         // Next, cycle through the actions
+         executeSynchronizationActions();
+                  
+         // Tell the cluster to "prepare" all actions; e.g. move temp files
+         // into the real area, do deletes etc
+         
+         rsps = this.partition.callMethodOnCluster(getServiceHAName(), "prepare", new Object[]{id}, TX_TYPES, true);         
+         boolean prepareOK = true;
+         for (Object rsp : rsps)
+         {
+            if (rsp instanceof NotSynchronizedException)
+            {
+               continue;
+            }
+            else if (rsp instanceof Exception)
+            {
+               throw (Exception) rsp;
+            }
+            else if (Boolean.TRUE.equals(rsp) == false)
+            {
+               prepareOK = false;
+               break;
+            }
+         }         
+         
+         if (prepareOK)
+         {
+            // Do it ourself
+            if (handlePrepare(id))
+            {
+               // Tell cluster to finalize
+               this.partition.callMethodOnCluster(getServiceHAName(), "commit", new Object[]{id}, TX_TYPES, true);
+               handleCommit(id);
+               committed = true;
+            }
+         }
+      }
+      catch (Exception e)
+      {
+         rethrowAsUnchecked(e);
+      }
+      finally
+      {
+         if (!committed)
+         {
+            // Roll back
+            try
+            {
+               this.partition.callMethodOnCluster(getServiceHAName(), "rollback", new Object[]{id}, TX_TYPES, true);
+            }
+            catch (Exception e)
+            {
+               log.error("Failed to roll back synchronization " + id + " on remote nodes", e);
+            }
+            finally
+            {
+               handleRollback(id);
+            }
+         }
+      }
+   }
+   
+   private void executeSynchronizationActions() throws Exception
+   {      
+      ActiveSynchronization active = activeSynchronization;
+      if (active == null)
+      {
+         throw new IllegalStateException("No active synchronization");
+      }
+
+      SynchronizationId<?> id = active.getId();
+      synchronized (active)
+      {
+         for (SynchronizationAction<?> action : active.getActions())
+         {
+            if (!active.isAlive())
+            {
+               throw new RuntimeException("Synchronization " + id + " terminated");
+            }
+            
+            if (action instanceof SynchronizationReadAction)
+            {
+               executePush(id, (SynchronizationReadAction<?>) action);
+            }
+            else if (action instanceof SynchronizationWriteAction)
+            {
+               executePull(id, (SynchronizationWriteAction<?>) action);
+            }
+            else if (action instanceof SynchronizationRemoteAction)
+            {
+               executeRemoteAction(id, (SynchronizationRemoteAction<?>) action);
+            }
+            else
+            {
+               action.complete();
+            }
+         }
+      }
+   }
+
+   private void executePull(SynchronizationId<?> id, SynchronizationWriteAction<?> action) throws Exception
+   {
+      List<ClusterNode> peers = this.partition.getDistributedReplicantManager().lookupReplicantsNodes(getServiceHAName());
+      peers.remove(this.partition.getClusterNode());
+      
+      boolean ok = false;
+      Exception lastCaught = null;
+      for (ClusterNode peer : peers)
+      {
+         try
+         {
+            if (executePullFromPeer(id, action, peer))
+            {
+               ok = true;
+               break;
+            }
+         }
+         catch (Exception e)
+         {
+            lastCaught = e;
+         }
+         // Reset the action for the next go round
+         action.cancel();
+      }
+      
+      if (!ok)
+      {
+         if (lastCaught != null)
+         {
+            throw lastCaught;
+         }
+         else
+         {
+            throw new RuntimeException("No node able to provide item " + 
+                  action.getRepositoryContentModification().getItem());
+         }
+      }
+      
+      action.complete();
+   }
+   
+   private boolean executePullFromPeer(SynchronizationId<?> id, SynchronizationWriteAction<?> action, ClusterNode node) throws Exception
+   {
+      int lastRead = 0;
+      while (lastRead > -1)
+      {
+         Object[] args = new Object[]{ id, action.getRepositoryContentModification().getItem()};
+         Object rsp = null;
+         try
+         {
+            rsp = this.partition.callMethodOnNode(getServiceHAName(), "pullBytes", args, PULL_BYTES_TYPES, methodCallTimeout, node);
+         }
+         catch (Throwable t)
+         {
+            rethrowAsException(t);
+         }
+         
+         if (rsp instanceof ByteChunk)
+         {
+            ByteChunk chunk = (ByteChunk) rsp;
+            lastRead = chunk.getByteCount();
+            if (lastRead > -1)
+            {
+               action.writeBytes(chunk);
+            }
+         }   
+         else if (rsp instanceof NotSynchronizedException)
+         {
+            return false;
+         }
+         else if (rsp instanceof Throwable)
+         {
+            rethrowAsException((Throwable) rsp);
+         } 
+         else if (rsp == null)
+         {
+            return false;
+         }
+         else
+         {
+            throw new IllegalStateException("Unknown response " + rsp);
+         }
+      }
+      
+      return true;
+   }
+
+   private void executePush(SynchronizationId<?> id, SynchronizationReadAction<?> action) throws Exception
+   {
+      int lastRead = 0;
+      while (lastRead > -1)
+      {
+         try
+         {
+            ByteChunk chunk = action.getNextBytes();
+         
+            Object[] args = new Object[]{ id, action.getRepositoryContentModification().getItem(), chunk};
+            List<?> rsps = this.partition.callMethodOnCluster(getServiceHAName(), "pushBytes", args, PUSH_BYTES_TYPES, true);
+            for (Object rsp : rsps)
+            {
+               if (rsp instanceof NotSynchronizedException)
+               {
+                  continue;
+               }
+               else if (rsp instanceof Throwable)
+               {
+                  rethrowAsException((Throwable) rsp);
+               }
+            }
+            lastRead = chunk.getByteCount();
+         }
+         catch (Exception e)
+         {
+            action.cancel();
+            throw e;
+         }
+      }
+      action.complete();
+   }
+
+   private void executeRemoteAction(SynchronizationId<?> id, 
+         SynchronizationRemoteAction<?> action) throws Exception
+   {
+      Object[] args = new Object[]{ id, action.getRepositoryContentModification().getItem(), action.isInitiation()};
+      List<?> rsps = null;
+      try
+      {
+         rsps = this.partition.callMethodOnCluster(getServiceHAName(), "executeModification", args, EXECUTE_MOD_TYPES, true);
+      }
+      catch (Exception e)
+      {
+         action.cancel();
+         throw e;
+      }
+      
+      action.complete(); // We sent the command, so it is complete even if we fail below
+      
+      for (Object rsp : rsps)
+      {
+         if (rsp instanceof NotSynchronizedException)
+         {
+            continue;
+         }
+         else if (rsp instanceof Throwable)
+         {
+            rethrowAsException((Throwable) rsp);
+         }
+      }
+   }
+   
+   private void checkUnitialized()
+   {
+      if (initialized)
+      {
+         throw new IllegalStateException("Cannot reconfigure an initialized " + 
+               getClass().getSimpleName());
+      }
+   }
+
+   private void handleInitiateSynchronization(SynchronizationId<ClusterNode> id, List<ContentModification> modifications, RepositoryContentMetadata toInstall)
+   {
+      boolean localLed = id.getOriginator().equals(partition.getClusterNode());
+      List<? extends SynchronizationAction<?>> actions = 
+         contentManager.initiateSynchronization(id, modifications, toInstall, localLed);
+      
+      activeSynchronization = new ActiveSynchronization(id, actions);
+   }
+
+   private boolean handlePrepare(SynchronizationId<ClusterNode> id) throws NotSynchronizedException
+   {
+      ActiveSynchronization active = activeSynchronization;
+      if (active != null)
+      {
+         synchronized (active)
+         {
+            active.validate(id);
+            
+            return active.prepare();
+         }
+      }
+      else
+      {
+         throw new NotSynchronizedException();
+      }
+   }
+
+   private void handleCommit(SynchronizationId<ClusterNode> id)
+   {
+      ActiveSynchronization active = activeSynchronization;
+      if (active != null)
+      {
+         synchronized (active)
+         {
+            active.validate(id);
+            
+            try
+            {
+               active.commit();
+            }
+            finally
+            {
+               activeSynchronization = null;
+            }
+         }
+      }
+   }
+
+   private void handleRollback(SynchronizationId<ClusterNode> id)
+   {
+      ActiveSynchronization active = activeSynchronization;
+      if (active != null)
+      {
+         synchronized (active)
+         {
+            active.validate(id);
+            
+            try
+            {
+               active.rollback();
+            }
+            finally
+            {
+               activeSynchronization = null;
+            }
+         }
+      }
+   }
+
+   private static void rethrowAsException(Throwable t) throws Exception
+   {
+      if (t == null)
+         return;
+      if (t instanceof Exception)
+      {
+         throw (Exception) t;
+      }
+      else if (t instanceof Error)
+      {
+         throw (Error) t;
+      }
+      else
+      {
+         throw new RuntimeException(t);
+      }
+   }
+
+   private static void rethrowAsUnchecked(Throwable t)
+   {
+      if (t == null)
+         return;
+      if (t instanceof RuntimeException)
+      {
+         throw (RuntimeException) t;
+      }
+      else if (t instanceof Error)
+      {
+         throw (Error) t;
+      }
+      else
+      {
+         throw new RuntimeException(t);
+      }
+   }
+   
+   // ----------------------------------------------------------------  Classes
+   
+   public class RpcTarget
+   {      
+      public List<ContentModification> joinSynchronizeContent(
+                              RepositoryContentMetadata remoteBaseContent,
+                              RepositoryContentMetadata remoteCurrentContent)
+                              throws NotSynchronizedException, InconsistentRepositoryStructureException
+      {
+         if (!inSync)
+         {
+            throw new NotSynchronizedException();
+         }
+         RemoteContentModificationGenerator generator = 
+            new RemoteContentModificationGenerator(synchronizationPolicy, remoteBaseContent);
+         return generator.getModificationList(contentManager.getOfficialContentMetadata(), remoteCurrentContent);
+      }      
+      
+      public List<ContentModification> mergeSynchronizeContent(
+            RepositoryContentMetadata remoteCurrentContent)
+            throws NotSynchronizedException, InconsistentRepositoryStructureException
+      {
+         if (!inSync)
+         {
+            throw new NotSynchronizedException();
+         }
+         
+         RemoteContentModificationGenerator generator = 
+         new RemoteContentModificationGenerator(synchronizationPolicy);
+         return generator.getModificationList(contentManager.getOfficialContentMetadata(), remoteCurrentContent);
+      }
+      
+      public void initiateSynchronization(SynchronizationId<ClusterNode> id,
+         List<ContentModification> modifications)
+            throws NotSynchronizedException
+      {
+         if (!inSync)
+         {
+            throw new NotSynchronizedException();         
+         }
+         
+         // Create the metadata we'll install when we are done
+         RepositoryContentMetadata toInstall = new RepositoryContentMetadata(contentManager.getOfficialContentMetadata());
+         
+         handleInitiateSynchronization(id, modifications, toInstall);
+      }      
+      
+      public void executeModification(SynchronizationId<ClusterNode> id, 
+                                      RepositoryItemMetadata item,
+                                      boolean initiation)
+            throws NotSynchronizedException
+      {
+         if (!inSync)
+         {
+            throw new NotSynchronizedException();
+         }
+         
+         ActiveSynchronization active = activeSynchronization;
+         if (active != null)
+         {
+            synchronized (active)
+            {
+               active.validate(id);
+               
+               SynchronizationAction<?> action = 
+                  (initiation ? active.getInitiationAction(item) : active.getMiscAction(item));
+               if (action != null)
+               {
+                  action.complete();
+               }
+               else
+               {
+                  throw new IllegalStateException("No action for " + item);
+               }
+            }
+         }
+         else
+         {
+            throw new NotSynchronizedException();
+         }
+      }     
+      
+      public void pushBytes(SynchronizationId<ClusterNode> id, 
+                            RepositoryItemMetadata item,
+                            ByteChunk chunk)
+            throws NotSynchronizedException, IOException
+      {
+         if (!inSync)
+         {
+            throw new NotSynchronizedException();
+         }
+         
+         ActiveSynchronization active = activeSynchronization;
+         if (active != null)
+         {
+            synchronized (active)
+            {
+               active.validate(id);
+               
+               SynchronizationWriteAction<?> action = active.getWriteAction(item);
+               if (action != null)
+               {
+                  if (chunk.getByteCount() < 0)
+                  {
+                     action.complete();
+                  }
+                  else
+                  {
+                     action.writeBytes(chunk);
+                  }
+               }
+               else
+               {
+                  throw new IllegalStateException("No action for " + item);
+               }
+            }
+         }
+         else
+         {
+            throw new NotSynchronizedException();
+         }
+      }     
+      
+      public ByteChunk pullBytes(SynchronizationId<ClusterNode> id, 
+                            RepositoryItemMetadata item)
+            throws NotSynchronizedException, IOException
+      {
+         if (!inSync)
+         {
+            throw new NotSynchronizedException();
+         }
+         
+         ActiveSynchronization active = activeSynchronization;
+         if (active != null)
+         {
+            synchronized (active)
+            {
+               active.validate(id);
+               
+               SynchronizationReadAction<?> action = active.getReadAction(item);
+               if (action != null)
+               {
+                  return action.getNextBytes();
+               }
+               else
+               {
+                  throw new IllegalStateException("No action for " + item);
+               }
+            }
+         }
+         else
+         {
+            throw new NotSynchronizedException();
+         }
+      }
+      
+      public boolean prepare(SynchronizationId<ClusterNode> id)
+            throws NotSynchronizedException
+      {
+         if (!inSync)
+         {
+            throw new NotSynchronizedException();
+         }
+         
+         return handlePrepare(id);
+      }      
+      
+      public void commit(SynchronizationId<ClusterNode> id)
+      {
+         if (inSync)
+         {
+            handleCommit(id);
+         }
+      }      
+      
+      public void rollback(SynchronizationId<ClusterNode> id)
+            throws NotSynchronizedException
+      {
+         if (inSync)
+         {
+            handleRollback(id);
+         }
+      }
+   }
+   
+   public class DRMListener implements ReplicantListener
+   {
+      @SuppressWarnings("unchecked")
+      public void replicantsChanged(String key, List newReplicants, int newReplicantsViewId, boolean merge)
+      {
+         List<ClusterNode> oldView = serviceView;
+         serviceView = newReplicants;
+         
+         // Abort any ongoing synchronization that if the originator is
+         // out of the view
+         ActiveSynchronization active = activeSynchronization;
+         if (active != null)
+         {
+            synchronized (active)
+            {
+               if (serviceView.contains(active.getId().getOriginator()) == false)
+               {
+                  contentManager.rollbackSynchronization(active.getId());
+                  activeSynchronization = null;
+               }
+            }
+         }
+         
+         if (merge)
+         {
+            ClusterNode master = (ClusterNode) (newReplicants.size() > 0 ? newReplicants.get(0) : null);
+            inSync = (master != null && oldView.contains(master));
+         }
+      }      
+   }
+   
+   private static class NotSynchronizedException extends Exception
+   {
+      /** The serialVersionUID */
+      private static final long serialVersionUID = -923676063561479453L;      
+   }
+   
+   private class ActiveSynchronization
+   {
+      private final SynchronizationId<ClusterNode> id;
+      private final List<? extends SynchronizationAction<?>> localActions;
+      private final Map<RepositoryItemMetadata, SynchronizationAction<?>> miscActionsByItem = 
+         new HashMap<RepositoryItemMetadata, SynchronizationAction<?>>();
+      private final Map<RepositoryItemMetadata, SynchronizationReadAction<?>> readActionsByItem = 
+         new HashMap<RepositoryItemMetadata, SynchronizationReadAction<?>>();
+      private final Map<RepositoryItemMetadata, SynchronizationWriteAction<?>> writeActionsByItem = 
+         new HashMap<RepositoryItemMetadata, SynchronizationWriteAction<?>>();
+      private final Map<RepositoryItemMetadata, SynchronizationInitiationAction<?>> initiationActionsByItem = 
+         new HashMap<RepositoryItemMetadata, SynchronizationInitiationAction<?>>();
+      private volatile boolean alive = true;
+      
+      private ActiveSynchronization(SynchronizationId<ClusterNode> id, List<? extends SynchronizationAction<?>> localActions)
+      {
+         if (id == null)
+         {
+            throw new IllegalArgumentException("Null id");
+         }
+         if (localActions == null)
+         {
+            throw new IllegalArgumentException("Null localActions");
+         }
+         this.id = id;
+         this.localActions = localActions;
+         for (SynchronizationAction<?> action : localActions)
+         {
+            RepositoryItemMetadata item = action.getRepositoryContentModification().getItem();
+            if (action instanceof SynchronizationInitiationAction<?>)
+            {
+               initiationActionsByItem.put(item, (SynchronizationInitiationAction<?>) action);
+            }
+            else if (action instanceof SynchronizationReadAction<?>)
+            {
+               readActionsByItem.put(item, (SynchronizationReadAction<?>) action);
+            }
+            else if (action instanceof SynchronizationWriteAction<?>)
+            {
+               writeActionsByItem.put(item, (SynchronizationWriteAction<?>) action);
+            }
+            else
+            {
+               miscActionsByItem.put(item, action);
+            }
+         }
+      }
+      
+      public SynchronizationId<ClusterNode> getId()
+      {
+         return id;
+      }
+      
+      public List<? extends SynchronizationAction<?>> getActions()
+      {
+         return localActions;
+      }
+      
+      public void validate(SynchronizationId<ClusterNode> id)
+      {
+         if (this.id.equals(id) == false)
+         {
+            throw new IllegalStateException("Invalid id " + id + 
+                  " another synchronization " + this.getId() + 
+                  " is in progress");            
+         }
+      }
+      
+      public SynchronizationReadAction<?> getReadAction(RepositoryItemMetadata item)
+      {
+         return readActionsByItem.get(item);
+      }
+      
+      public SynchronizationWriteAction<?> getWriteAction(RepositoryItemMetadata item)
+      {
+         return writeActionsByItem.get(item);
+      }
+      
+      public SynchronizationInitiationAction<?> getInitiationAction(RepositoryItemMetadata item)
+      {
+         return initiationActionsByItem.get(item);
+      }
+      
+      public SynchronizationAction<?> getMiscAction(RepositoryItemMetadata item)
+      {
+         return miscActionsByItem.get(item);
+      }
+      
+      public boolean isAlive()
+      {
+         return alive;
+      }
+      
+      public boolean prepare()
+      {
+         if (alive)
+         {
+            return contentManager.prepareSynchronization(id);
+         }
+         return false;
+      }
+      
+      public void rollback()
+      {
+         if (alive)
+         {
+            alive = false;
+            synchronized (this)
+            {
+               contentManager.rollbackSynchronization(id);
+            }
+         }
+      }
+      
+      public void commit()
+      {
+         if (alive)
+         {
+            synchronized (this)
+            {
+               contentManager.commitSynchronization(id);
+            }
+            alive = false;
+         }
+      }
+   }
+
+}


Property changes on: trunk/cluster/src/main/org/jboss/profileservice/cluster/repository/DefaultRepositoryClusteringHandler.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/cluster/src/main/org/jboss/profileservice/cluster/repository/DefaultSynchronizationPolicy.java
===================================================================
--- trunk/cluster/src/main/org/jboss/profileservice/cluster/repository/DefaultSynchronizationPolicy.java	                        (rev 0)
+++ trunk/cluster/src/main/org/jboss/profileservice/cluster/repository/DefaultSynchronizationPolicy.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,151 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.profileservice.cluster.repository;
+
+import org.jboss.ha.timestamp.TimestampDiscrepancy;
+import org.jboss.ha.timestamp.TimestampDiscrepancyService;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryItemMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.sync.AbstractSynchronizationPolicy;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationPolicy;
+
+/**
+ * Default implementation of {@link SynchronizationPolicy}.
+ * 
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ *
+ */
+public class DefaultSynchronizationPolicy extends AbstractSynchronizationPolicy
+{
+   private TimestampDiscrepancyService timestampService; 
+   
+   public TimestampDiscrepancyService getTimestampService()
+   {
+      return timestampService;
+   }
+
+   public void setTimestampService(TimestampDiscrepancyService timestampService)
+   {
+      this.timestampService = timestampService;
+   }
+   
+   
+   // ----------------------------------------------------  Protected Overrides 
+
+   /**
+    * Always accepts merge additions; accepts join additions if the 
+    * time-discrepancy adjusted timestamp of <code>toAdd</code> is later than
+    * the current time minus {@link #getRemovalTrackingTime() the maximum
+    * period we track item removals}. The idea of the latter being that if
+    * a previous version of <code>toAdd</code> was removed more recently than 
+    * that then we should have a record of its removal.
+    */
+   protected boolean acceptAddition(RepositoryItemMetadata toAdd, RepositoryItemMetadata joinersPrevious,
+         boolean merge)
+   {
+      if (merge)
+      {
+         return true;
+      }
+      else
+      {
+         TimestampDiscrepancy addDiscrepancy = timestampService.getTimestampDiscrepancy(toAdd.getOriginatingNode(), false);         
+         long adjustedTimestamp = addDiscrepancy.getMinLocalTimestamp(toAdd.getTimestamp());
+         return adjustedTimestamp > System.currentTimeMillis() - getRemovalTrackingTime();
+      }
+   }
+
+   /**
+    * Accepts the reincarnation if the time-discrepancy adjusted timestamp of
+    * <code>reincarnation</code> is at least as recent as the time-discrepancy 
+    * adjusted timestamp of the <code>current</code> item.
+    */
+   protected boolean acceptReincarnation(RepositoryItemMetadata reincarnation, RepositoryItemMetadata current,
+         boolean merge)
+   {
+      TimestampDiscrepancy addDiscrepancy = timestampService.getTimestampDiscrepancy(reincarnation.getOriginatingNode(), false); 
+      TimestampDiscrepancy deadDiscrepancy = timestampService.getTimestampDiscrepancy(current.getOriginatingNode(), false); 
+      return isChangeMoreRecent(reincarnation, current, addDiscrepancy, deadDiscrepancy, false);
+   }
+
+   /**
+    * Rejects removal if <code>sendersView</code> is <code>null</code>, else
+    * accepts the removal if the time-discrepancy adjusted timestamp of
+    * <code>sendersView</code> is at least as recent as the time-discrepancy 
+    * adjusted timestamp of the <code>current</code> item.
+    */
+   protected boolean acceptRemoval(RepositoryItemMetadata current, RepositoryItemMetadata sendersView,
+         boolean merge)
+   {
+      if (sendersView == null)
+      {
+         return false;
+      }
+
+      TimestampDiscrepancy senderTimestampDiscrepancy = timestampService.getTimestampDiscrepancy(sendersView.getOriginatingNode(), false); 
+      TimestampDiscrepancy currentTimestampDiscrepancy = timestampService.getTimestampDiscrepancy(current.getOriginatingNode(), false);
+      
+      return isChangeMoreRecent(sendersView, current, senderTimestampDiscrepancy, currentTimestampDiscrepancy, true);
+   }
+
+   /**
+    * Accepts the removal if the time-discrepancy adjusted timestamp of
+    * <code>update</code> is at least as recent as the time-discrepancy 
+    * adjusted timestamp of the <code>current</code> item.
+    */
+   protected boolean acceptUpdate(RepositoryItemMetadata update, RepositoryItemMetadata current,
+         boolean merge)
+   {
+      TimestampDiscrepancy updateDiscrepancy = timestampService.getTimestampDiscrepancy(update.getOriginatingNode(), false); 
+      TimestampDiscrepancy currentTimestampDiscrepancy = timestampService.getTimestampDiscrepancy(current.getOriginatingNode(), false);
+      
+      return isChangeMoreRecent(update, current, updateDiscrepancy, currentTimestampDiscrepancy, false);
+   }
+   
+   // ----------------------------------------------------------------  Private  
+
+   private static boolean isChangeMoreRecent(RepositoryItemMetadata toChange, RepositoryItemMetadata current,
+         TimestampDiscrepancy senderTimestampDiscrepancy, TimestampDiscrepancy currentTimestampDiscrepancy, boolean equalAllowed)
+   {
+      if (currentTimestampDiscrepancy == null)
+      {
+         // Just have to hope for the best
+         currentTimestampDiscrepancy = TimestampDiscrepancy.NO_DISCREPANCY;
+      }
+      
+      long senderTime, currentTime;
+      if (toChange.getOriginatingNode().equals(current.getOriginatingNode()))
+      {
+         senderTime = toChange.getTimestamp();
+         currentTime = current.getTimestamp();
+      }
+      else
+      {
+         senderTime = senderTimestampDiscrepancy.getMinLocalTimestamp(toChange.getTimestamp());
+         currentTime = currentTimestampDiscrepancy.getMaxLocalTimestamp(current.getTimestamp());         
+      }
+      return equalAllowed ? senderTime > currentTime :senderTime > currentTime;
+   }
+
+}


Property changes on: trunk/cluster/src/main/org/jboss/profileservice/cluster/repository/DefaultSynchronizationPolicy.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/StaticClusteredProfileFactory.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/StaticClusteredProfileFactory.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/StaticClusteredProfileFactory.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,228 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice;
+
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jboss.profileservice.spi.ProfileKey;
+import org.jboss.profileservice.spi.metadata.ProfileMetaData;
+import org.jboss.profileservice.spi.metadata.ProfileSourceMetaData;
+import org.jboss.profileservice.spi.metadata.SubProfileMetaData;
+import org.jboss.system.server.profile.repository.metadata.BasicProfileMetaData;
+import org.jboss.system.server.profile.repository.metadata.BasicSubProfileMetaData;
+import org.jboss.system.server.profileservice.repository.StaticProfileFactory;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.ClusteredProfileSourceMetaData;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.HotDeploymentClusteredProfileSourceMetaData;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.ImmutableClusteredProfileSourceMetaData;
+
+/**
+ * Expands upon the StaticProfileFactory to include a subprofiles
+ * for farmed content.
+ * 
+ * @author Brian Stansberry
+ */
+public class StaticClusteredProfileFactory extends StaticProfileFactory
+{
+   /** The bootstrap profile name. */
+   private static final String BOOTSTRAP_NAME = "bootstrap";
+   
+   /** The deployers profile name. */
+   private static final String DEPLOYERS_NAME = "deployers";
+   
+   /** The deployers profile name. */
+   private static final String APPLICATIONS_NAME = "applications";
+
+   /** The deploy-hasingleton profile name. */
+   private static final String HASINGLETON_NAME = "deploy-hasingleton";
+   
+   /** The farm profile name. */
+   private static final String FARM_NAME = "farm";
+   
+   /** The hasingleton uris. */
+   private List<URI> hasingletonURIs;
+   
+   /** The farm uris. */
+   private List<URI> farmURIs;
+   
+//   private String partitionName;
+
+   public List<URI> getHASingletonURIs()
+   {
+      return hasingletonURIs;
+   }
+
+   public void setHASingletonURIs(List<URI> hasingletonURIs)
+   {
+      this.hasingletonURIs = hasingletonURIs;
+   }
+
+   public List<URI> getFarmURIs()
+   {
+      return farmURIs;
+   }
+
+   public void setFarmURIs(List<URI> farmURIs)
+   {
+      this.farmURIs = farmURIs;
+   }
+
+//   public String getPartitionName()
+//   {
+//      return partitionName;
+//   }
+//
+//   public void setPartitionName(String partitionName)
+//   {
+//      this.partitionName = partitionName;
+//   }
+//
+//   @Override
+//   public void create() throws Exception
+//   {
+//      super.create();
+//      
+//      if (this.farmURIs != null || this.hasingletonURIs != null)
+//      {
+//         if (this.partitionName == null)
+//         {
+//            throw new IllegalStateException("Null partition name.");
+//         }
+//      }
+//   }
+
+   /**
+    * Create the legacy profiles, based on the injected uris. 
+    * 
+    * @param rootKey the key for the root profile.
+    * @throws Exception
+    */
+   @Override
+   protected void createProfileMetaData(ProfileKey rootKey, URL url) throws Exception
+   {     
+      if(rootKey == null)
+         throw new IllegalArgumentException("Null root profile key.");
+      
+      // Create bootstrap profile meta data
+      ProfileKey bootstrapKey = new ProfileKey(BOOTSTRAP_NAME);
+      ProfileMetaData bootstrap = createProfileMetaData(
+            BOOTSTRAP_NAME, false, new URI[] { getBootstrapURI() }, new String[0]);
+      addProfile(bootstrapKey, bootstrap);
+      
+      // Create deployers profile meta data
+      ProfileKey deployersKey = new ProfileKey(DEPLOYERS_NAME);
+      ProfileMetaData deployers = createProfileMetaData(
+            DEPLOYERS_NAME, false, new URI[] { getDeployersURI() }, new String[] { BOOTSTRAP_NAME });
+      addProfile(deployersKey, deployers);
+
+      // Create applications profile;
+      ProfileKey applicationsKey = new ProfileKey(APPLICATIONS_NAME);
+      URI[] deployURIs = getApplicationURIs().toArray(new URI[getApplicationURIs().size()]);
+      String[] applicationSubProfiles = new String[] { BOOTSTRAP_NAME, DEPLOYERS_NAME };
+      ProfileMetaData applications = createProfileMetaData(
+            APPLICATIONS_NAME, true, deployURIs, applicationSubProfiles);
+      addProfile(applicationsKey, applications);
+      
+      ProfileMetaData farm = null;
+      if (getFarmURIs() != null)
+      {
+         ProfileKey farmKey = new ProfileKey(FARM_NAME);
+         URI[] farmURIs = getFarmURIs().toArray(new URI[getFarmURIs().size()]);
+         String[] farmSubProfiles = new String[] { APPLICATIONS_NAME };
+         farm = createClusteredProfileMetaData(
+               FARM_NAME, true, farmURIs, farmSubProfiles);
+         addProfile(farmKey, farm);         
+      }
+      
+      String[] rootSubProfiles = farm == null ? new String[]{APPLICATIONS_NAME} 
+                                              : new String[] { FARM_NAME };
+      ProfileMetaData root = createProfileMetaData(
+            rootKey.getName(), true, new URI[0], rootSubProfiles);
+      addProfile(rootKey, root);
+      
+      if (getHASingletonURIs() != null)
+      {
+         ProfileKey hasingletonKey = new ProfileKey(HASINGLETON_NAME);
+         URI[] hasingletonURIs = getHASingletonURIs().toArray(new URI[getHASingletonURIs().size()]);
+         // Note HASingleton can't depend on others or it will get undeployed
+         // prematurely
+         String[] hasingletonSubProfiles = new String[0];
+         ProfileMetaData hasingletons = createProfileMetaData(
+               HASINGLETON_NAME, true, hasingletonURIs, hasingletonSubProfiles);
+         addProfile(hasingletonKey, hasingletons);         
+      }
+   }
+
+   private ProfileMetaData createClusteredProfileMetaData(String name, boolean hotDeployment, URI[] uris, String[] subProfiles)
+   {
+      // Create profile
+      BasicProfileMetaData metaData = new BasicProfileMetaData();
+      metaData.setName(name);
+      // Create profile sources
+      ProfileSourceMetaData source = createClusteredSource(uris, hotDeployment);
+      metaData.setSource(source);
+      
+      List<SubProfileMetaData> profileList = new ArrayList<SubProfileMetaData>();
+      for(String subProfile : subProfiles)
+      {
+         BasicSubProfileMetaData md = new BasicSubProfileMetaData();
+         md.setName(subProfile);
+         profileList.add(md);
+      }
+      metaData.setSubprofiles(profileList);
+      
+      return metaData;
+   }
+   
+   /**
+    * Create a profile repository source meta data.
+    * 
+    * @param uris the uris for the repository
+    * @param hotDeployment to create a hotDeployment profile
+    * 
+    * @return the profile source meta data.
+    */
+   protected ProfileSourceMetaData createClusteredSource(URI[] uris, boolean hotDeployment)
+   {
+      ClusteredProfileSourceMetaData source = null;
+      if(hotDeployment)
+      {
+         source = new HotDeploymentClusteredProfileSourceMetaData();
+      }
+      else
+      {
+         source = new ImmutableClusteredProfileSourceMetaData();
+      }
+      
+//      source.setPartitionName(getPartitionName());
+      
+      List<String> sources = new ArrayList<String>();
+      for(URI uri : uris)
+         sources.add(uri.toString());
+      source.setSources(sources);
+      return source;
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/StaticClusteredProfileFactory.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/ClusteredDeploymentRepository.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/ClusteredDeploymentRepository.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/ClusteredDeploymentRepository.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,814 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.jboss.deployers.vfs.spi.structure.modified.StructureModificationChecker;
+import org.jboss.profileservice.spi.DeploymentContentFlags;
+import org.jboss.profileservice.spi.ModificationInfo;
+import org.jboss.profileservice.spi.ProfileDeployment;
+import org.jboss.profileservice.spi.ProfileKey;
+import org.jboss.profileservice.spi.ModificationInfo.ModifyStatus;
+import org.jboss.system.server.profileservice.repository.AbstractDeploymentRepository;
+import org.jboss.system.server.profileservice.repository.clustered.local.LocalContentManager;
+import org.jboss.system.server.profileservice.repository.clustered.local.LocalContentManagerFactory;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.virtual.VirtualFile;
+
+/**
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class ClusteredDeploymentRepository 
+   extends AbstractDeploymentRepository
+{
+   private enum LoadStatus { UNLOADED, LOADING, LOADED };
+   
+   /** Handles interaction with the cluster */
+   private RepositoryClusteringHandler clusteringHandler;
+   /** Handles storing our RepositoryContentMetadata */
+//   private final ContentMetadataPersister persister;
+   /** Our current view of our content */
+//   private RepositoryContentMetadata contentMetadata;
+   /** URIs of the VFS roots we monitor, keyed by their name */
+   private final Map<String, URI> namedURIMap;
+   
+   private final Map<ProfileKey, RepositoryClusteringHandler> clusteringHandlers;
+   private final Set<LocalContentManagerFactory<?>> localContentManagerFactories ;
+   private LocalContentManager<?> localContentManager;
+   
+//   private final Map<Object, OutputStream> tempStreamMap = new ConcurrentHashMap<Object, OutputStream>();
+   /** 
+    * Whether we are loaded. Mutations of this field must occur with
+    * at least a local lock.
+    */
+   private volatile LoadStatus loadStatus = LoadStatus.UNLOADED;
+   
+   /** The structure modification checker */
+   private StructureModificationChecker checker;
+   private boolean created;
+      
+   /**
+    * Create a new ClusteredDeploymentRepository.
+    * 
+    * @param key
+    * @param uris
+    * @throws IOException 
+    */
+   public ClusteredDeploymentRepository(ProfileKey key, URI[] uris, 
+         Map<ProfileKey, RepositoryClusteringHandler> clusteringHandlers, 
+         Set<LocalContentManagerFactory<?>> persisterFactories) 
+         throws IOException
+   {
+      super(key, uris);
+      
+      if (clusteringHandlers == null)
+      {
+         throw new IllegalArgumentException("Null clusteringHandlers");
+      }
+      
+      if (persisterFactories == null)
+      {
+         throw new IllegalArgumentException("Null persisterFactories");
+      }
+      this.namedURIMap = getNamedURIMap(uris);
+      this.clusteringHandlers = clusteringHandlers;
+      this.localContentManagerFactories = persisterFactories;
+   }
+   
+   // -------------------------------------------------------------- Properties
+
+   public String getPartitionName()
+   {
+      // Prefer to return the value from our handler
+      return this.clusteringHandler == null ? null 
+                                            : this.clusteringHandler.getPartitionName();
+   }
+
+   public RepositoryClusteringHandler getClusteringHandler()
+   {
+      return clusteringHandler;
+   }
+   
+   public synchronized boolean registerClusteringHandler(RepositoryClusteringHandler handler)
+   {
+      if (handler == null)
+      {
+         throw new IllegalArgumentException("handler is null");
+      }
+      
+      boolean update = this.getProfileKey().equals(handler.getProfileKey());
+      if (update)
+      {
+         this.clusteringHandler = handler;
+      }
+      return update;
+   }
+   
+   public synchronized boolean unregisterClusteringHandler(RepositoryClusteringHandler handler)
+   {
+      if (handler == null)
+      {
+         throw new IllegalArgumentException("handler is null");
+      }
+      
+      boolean update = this.getProfileKey().equals(handler.getProfileKey());
+      
+      if (update)
+      {
+         this.clusteringHandler = null;
+      }
+      
+      return update;
+   }
+
+   /**
+    * Get the structure modified checker.
+    *
+    * @return the checker
+    */
+   protected StructureModificationChecker getChecker()
+   {
+      if (checker == null)
+         throw new IllegalArgumentException("Checker must be set");
+
+      return checker;
+   }
+
+   /**
+    * Set the checker.
+    *
+    * @param checker the checker
+    */
+   public void setChecker(StructureModificationChecker checker)
+   {
+      this.checker = checker;
+   }
+
+   // ---------------------------------------------------- DeploymentRepository
+
+   /**
+    * Extends superclass to check whether our configured URIs actually exist,
+    * if so validating that a clustering handler has been injected, throwing
+    * an <code>IllegalStateException</code> if not.
+    * 
+    * {@inheritDoc}
+    */
+   @Override
+   public void create() throws Exception
+   {
+      super.create();
+      
+      // See if our URIs actually exist. If not we don't care
+      // if we are missing required dependencies
+      boolean needToFunction = false;      
+      for (URI uri : this.getRepositoryURIs())
+      {
+         if (getCachedVirtualFile(uri).exists())
+         {
+            needToFunction = true;
+            break;
+         }
+      }
+      
+      if (needToFunction)
+      {  
+         this.clusteringHandler = this.clusteringHandlers.get(getProfileKey());
+         
+         if (this.clusteringHandler == null)
+         {
+            throw new IllegalStateException("Must register RepositoryClusteringHandler " +
+            		"before calling create()");
+         }
+         
+         for (LocalContentManagerFactory<?> factory : localContentManagerFactories)
+         {
+            if (factory.accepts(namedURIMap.values()))
+            {
+               this.localContentManager = factory.getRepositoryContentPersister(namedURIMap, getProfileKey(), this.clusteringHandler.getLocalNodeName());
+               break;
+            }
+         }
+         
+         if (this.localContentManager == null)
+         {
+            throw new IllegalStateException("No registered RepositoryContentPersisterFactory " +
+            		"is capable of handling URIs " + namedURIMap.values() + 
+            		" -- registeredFactories are " + localContentManagerFactories);
+         }
+      }
+      
+      this.created = true;
+   }
+
+   public void load() throws Exception
+   {      
+      if (!created)
+      {
+         create();
+      }
+      
+      if (this.clusteringHandler == null)
+      {
+         // We would have failed in create() if we actually had any valid URIs
+         // configured. So this means there are no valid URIs and we
+         // have nothing to do.
+         return;
+      }     
+      
+      this.clusteringHandler.initialize(this.localContentManager);
+      
+      // Take control of the cluster
+      if (!this.clusteringHandler.lockGlobally())
+      {
+         throw new RuntimeException("Cannot acquire global lock");
+      }
+      try
+      {         
+         if (this.loadStatus != LoadStatus.UNLOADED)
+         {
+            log.warn("load() called when repository status is " + this.loadStatus);
+            return;
+         }
+         
+         this.loadStatus = LoadStatus.LOADING;
+         
+         // Bring our content in line with the cluster 
+         clusteringHandler.synchronizeContent(true);
+         
+         // Load applications
+         for(URI uri : getRepositoryURIs())
+         {
+            VirtualFile root = getCachedVirtualFile(uri);
+            loadApplications(root);
+         }
+         
+         updateLastModfied();
+         
+         this.loadStatus = LoadStatus.LOADED;
+      }
+      catch (Throwable t)
+      {
+         // Revert back to unloaded status
+         this.loadStatus = LoadStatus.UNLOADED;
+         
+//         this.contentMetadata = null;
+         if (t instanceof Exception)
+         {
+            throw (Exception) t;
+         }
+         else if (t instanceof Error)
+         {
+            throw (Error) t;
+         }
+         else
+         {
+            UnknownError unk = new UnknownError("Caught unknown Throwable");
+            unk.initCause(t);
+            throw unk;
+         }
+      }
+      finally
+      {
+         this.clusteringHandler.unlockGlobally();
+         
+         if (this.loadStatus != LoadStatus.LOADED)
+         {
+            this.clusteringHandler.shutdown();
+         }
+      }
+   }
+   
+   public String addDeploymentContent(String vfsPath, InputStream contentIS) throws IOException
+   {
+      if (this.clusteringHandler == null)
+      {
+         throw new IllegalStateException("Must register RepositoryClusteringHandler before invoking addDeploymentContent()");
+      }
+      
+      String repositoryName = null;
+      
+      // Take control of the cluster
+      if (!this.clusteringHandler.lockGlobally())
+      {
+         throw new RuntimeException("Cannot acquire global lock");
+      }
+      try
+      {      
+         VirtualFile contentVF = this.clusteringHandler.addDeploymentContent(vfsPath, contentIS);
+
+         try
+         {
+            contentVF = getCachedVirtualFile(contentVF.toURI());
+            repositoryName = contentVF.toURI().toString();
+         }
+         catch(URISyntaxException e)
+         {
+            throw new RuntimeException(e); // This should not happen anyway
+         }
+
+         // Lock the content
+         lockDeploymentContent(vfsPath);
+      }
+      finally
+      {
+         this.clusteringHandler.unlockGlobally();
+      }
+      
+      return repositoryName;
+   }
+
+   public ProfileDeployment removeDeployment(String vfsPath) throws Exception
+   {
+      ProfileDeployment deployment = getDeployment(vfsPath);
+      VirtualFile root = deployment.getRoot();
+      this.clusteringHandler.removeDeploymentContent(root);
+      return super.removeDeployment(deployment.getName());
+   }
+
+   public Collection<ModificationInfo> getModifiedDeployments() throws Exception
+   {
+      if (this.clusteringHandler == null)
+      {
+         return Collections.emptyList();
+      }
+      
+      if (this.loadStatus != LoadStatus.LOADED)
+      {
+         log.debug("Ignoring getModifiedDeployments() call as status is " + this.loadStatus);
+      }
+      
+      // Only acquire a local lock until we know whether we actually 
+      // have a modification
+      RepositoryContentMetadata baseContent = null;
+      RepositoryContentMetadata latestContent = null;
+      boolean unmodified = false;
+      if (!this.clusteringHandler.lockLocally())
+      {
+         // Don't throw an exception; just log error and indicate no mods
+         log.error("getModifiedDeployments(): Cannot acquire local lock");
+         return Collections.emptySet();
+      }
+      try
+      {
+         baseContent = this.localContentManager.getOfficialContentMetadata();
+         latestContent = this.localContentManager.getCurrentContentMetadata();         
+         unmodified = latestContent.equals(baseContent);
+      }
+      finally
+      {
+         this.clusteringHandler.unlockLocally();
+      }
+      
+      Collection<ModificationInfo> result = null;
+      if (unmodified)
+      {
+         // Done
+         result = Collections.emptySet();
+      }
+      else
+      {
+         // Something was modified, so take control of the cluster and
+         // bring the cluster in line with our current state
+         
+         if (!this.clusteringHandler.lockGlobally())
+         {
+            // Don't throw an exception; just log error and indicate no mods
+            log.error("getModifiedDeployments(): Cannot acquire global lock");
+            return Collections.emptySet();
+         }
+         try
+         {      
+            // Tell the clustering handler to synchronize, but without
+            // pulling anything to cluster -- just push our changes
+            latestContent = this.clusteringHandler.synchronizeContent(false);
+            
+            return createModificationInfo();
+         }
+         finally
+         {
+            this.clusteringHandler.unlockGlobally();
+         }
+      }
+      
+      return result;
+      
+   }
+   
+   public void unload()
+   {      
+      if (this.clusteringHandler == null)
+      {
+         // We would have failed in create() if we actually had any valid URIs
+         // configured. So this means there are no valid URIs and we
+         // have nothing to do.
+         // Just call the superclass method to let it do whatever cleanup
+         // it wants.
+         super.unload();
+         return;
+      }
+      
+      if (this.loadStatus != LoadStatus.UNLOADED)
+      {
+         boolean locked = this.clusteringHandler.lockLocally();
+         try
+         {
+            if (!locked)
+            {
+               log.warn("remove(): failed to acquire local lock");
+            }
+            this.loadStatus = LoadStatus.UNLOADED;
+//            this.contentMetadata = null;
+                        
+            super.unload();
+            
+         }
+         finally
+         {
+            if (locked)
+            {
+               this.clusteringHandler.unlockLocally();
+            }
+            
+            try
+            {
+               this.clusteringHandler.shutdown();
+            }
+            catch (Exception e)
+            {
+               log.error("Caught exception shutting down RepositoryClusteringHandler", e);
+            }
+         }
+      }      
+   }
+
+   public void remove() throws Exception
+   {      
+      this.clusteringHandler = null;
+      this.localContentManager = null; 
+      this.created = false;
+   }
+
+   // --------------------------------------  Legacy RepositoryContentPersister
+
+   
+//   public TemporaryOutputStreamHandback getTemporaryOutputStream() throws IOException
+//   {
+//      if (this.loadStatus != LoadStatus.UNLOADED)
+//      {
+//         File tmp = createTempFile();
+//         tmp.deleteOnExit();
+//         OutputStream os = new FileOutputStream(tmp);
+//         this.tempStreamMap.put(tmp, os);
+//         return new TemporaryOutputStreamHandback(tmp, os);
+//      }
+//      else
+//      {
+//         // We don't accept changes from the cluster when we are unloaded.
+//         // Satisfy the contract with an object that does nothing
+//         return new TemporaryOutputStreamHandback(LoadStatus.UNLOADED, new NullOutputStream());
+//      }
+//   }
+//   
+//   public void destroyTemporaryOutputStream(TemporaryOutputStreamHandback handback)
+//   {
+//      if (handback == null)
+//      {
+//         throw new IllegalArgumentException("handback is null");
+//      }
+//      
+//      FileUtil.safeCloseStream(handback.getOutputStream(), handback.getHandback());
+//
+//      Object key = handback.getHandback();
+//      if (key instanceof File)
+//      {
+//         try
+//         {
+//            ((File) key).delete();
+//         }
+//         catch (Exception e)
+//         {
+//            log.trace("Failed to delete temporary file " + key, e);
+//         }
+//      }
+//   }
+//
+//   public void installRepositoryContentItem(TemporaryOutputStreamHandback handback, 
+//         String repositoryRoot, List<String> path, long timestamp) throws IOException
+//   {
+//      if (handback == null)
+//      {
+//         throw new IllegalArgumentException("handback is null");
+//      }
+//      
+//      Object key = handback.getHandback();
+//      File tmpFile = null;
+//      if (key instanceof File)
+//      {
+//         tmpFile = (File) key;
+//      }
+//      else if (key == LoadStatus.UNLOADED)
+//      {
+//         // We weren't ready to deal with this request so we returned
+//         // a fake. Ignore this call.
+//         log.trace("Current status is " + LoadStatus.UNLOADED + " so ignoring" +
+//                   " request to add/update " + path + " in " + repositoryRoot);
+//         return;
+//      }
+//      else
+//      {
+//         throw new IllegalArgumentException("Unknown handback type " + handback);
+//      }
+//      
+//      OutputStream ourOS = tempStreamMap.remove(key);
+//      if (ourOS == null)
+//      {
+//         // Tilt! But to be tidy close the stream
+//         FileUtil.safeCloseStream(handback.getOutputStream(), key);
+//         throw new IllegalStateException("Unknown handback " + key);
+//      }
+//      
+//      try
+//      { 
+//         FileUtil.safeCloseStream(ourOS, key);
+//         
+//         URI rootURI = namedURIMap.get(repositoryRoot);
+//         if (rootURI == null)
+//         {
+//            throw new IllegalArgumentException("Unknown root " + repositoryRoot);
+//         }
+//         
+//         File toReplace = new File(rootURI);
+//         for (String element : path)
+//         {
+//            toReplace = new File(toReplace, element);
+//         }
+//         if (toReplace.exists())
+//         {
+//            toReplace.delete();
+//         }
+//         
+//         FileUtil.localMove(tmpFile, toReplace );
+//         toReplace.setLastModified(timestamp);
+//         
+//         RepositoryItemMetadata itemMD = new RepositoryItemMetadata();
+//         itemMD.setRelativePathElements(path);
+//         itemMD.setTimestamp(timestamp);
+//         
+//         RepositoryRootMetadata rootMD = contentMetadata.getRepositoryRootMetadata(repositoryRoot);
+//         rootMD.addItemMetadata(itemMD);
+//         
+//         try
+//         {
+//            this.persister.store(getPartitionName(), this.contentMetadata);
+//         }
+//         catch (Exception e)
+//         {
+//            log.error("Exception peristent contentMetadata", e);
+//         }         
+//      }
+//      finally
+//      {
+//         if (tmpFile.exists())
+//         {
+//            if(!tmpFile.delete())
+//            {
+//               log.info("Could not delete file "+ tmpFile);
+//            }
+//         }
+//      }
+//      
+//   }
+//
+//   public void removeRepositoryContentItem(String repositoryRoot, List<String> path)
+//   {
+//      if (this.loadStatus == LoadStatus.UNLOADED)
+//      {
+//         // We don't accept changes from the cluster when we aren't loaded
+//         log.trace("Current status is " + LoadStatus.UNLOADED + " so ignoring" +
+//               " request to remove " + path + " from " + repositoryRoot);
+//         return;
+//      }
+//      
+//      URI rootURI = namedURIMap.get(repositoryRoot);
+//      if (rootURI == null)
+//      {
+//         throw new IllegalArgumentException("Unknown root " + repositoryRoot);
+//      }
+//      
+//      File file = new File(rootURI);
+//      for (String element : path)
+//      {
+//         file = new File(file, element);
+//      }
+//      if (file.exists())
+//      {
+//         file.delete();
+//      }
+//      
+//      RepositoryRootMetadata rootMD = contentMetadata.getRepositoryRootMetadata(repositoryRoot);
+//      if (rootMD != null)
+//      {
+//         if (rootMD.removeItemMetadata(path))
+//         {
+//            try
+//            {
+//               this.persister.store(getPartitionName(), this.contentMetadata);
+//            }
+//            catch (Exception e)
+//            {
+//               log.error("Exception peristing contentMetadata", e);
+//            }
+//         }
+//      }
+//   }
+
+   // ----------------------------------------------------------------- Private
+   
+   // BES -- retained for now in case I decide to scope the RepositoryClusteringHandler
+   // to a partition rather than to a ProfileKey
+   
+//   private boolean coordinateClusteringHandler(RepositoryClusteringHandler handler, 
+//                                               String partitionName)
+//   {
+//      boolean updated = false;
+//      
+//      if (this.clusteringHandler != null)
+//      {
+//         // We've had a handler injected, so this must be setting the 
+//         // partition name property. Check for consistency.
+//         if (getPartitionName().equals(partitionName) == false)
+//         {
+//            throw new IllegalStateException("Cannot set partition name to " + 
+//                  partitionName + " it is already set to " + getPartitionName());
+//         }
+//         
+//         if (this.clusteringHandler != handler)
+//         {
+//            log.debug("Updating handler for partition " + partitionName);
+//            this.clusteringHandler = handler;
+//            updated = true;
+//         }
+//      }
+//      else if (handler == null)
+//      {
+//         // No handler set and none passed in. Must be a call to set partitionName.
+//         // Use a null-safe equals check to see if it's an update.
+//         boolean same = (partitionName == this.partitionName 
+//               || (partitionName != null && partitionName.equals(this.partitionName)));
+//         if (!same)
+//         {
+//            this.partitionName = partitionName;
+//            updated = true;
+//         }
+//      }
+//      else
+//      {
+//         // It's an attempt to inject a handler. See if it matches our
+//         // required partition name
+//         String handlerPartition = handler.getPartitionName();
+//         if (this.partitionName == null || this.partitionName.equals(handlerPartition))
+//         {
+//            this.clusteringHandler = handler;
+//            updated = true;
+//         }
+//         else
+//         {
+//            log.debug("Ignoring injected handler for partition " + 
+//                  handlerPartition + "as we are configured for partition " + 
+//                  this.partitionName);
+//         }
+//      }
+//      
+//      return updated;      
+//   }
+
+   private Map<String, URI> getNamedURIMap(URI[] uris) throws IOException
+   {
+      Map<String, URI> map = new HashMap<String, URI>();
+      if (uris != null)
+      {
+         for (URI uri : uris)
+         {
+            VirtualFile vf = getCachedVirtualFile(uri);
+            map.put(vf.getName(), uri);
+         }
+      }
+      
+      return map;
+   }
+   
+   /**
+    * FIXME This was cut-and-pasted from MutableDeploymentRepository. Turn
+    * the guts of it into a static utility method somewhere.
+    * 
+    * @return
+    * @throws Exception
+    */
+   private Collection<ModificationInfo> createModificationInfo() throws Exception
+   {
+      ArrayList<ModificationInfo> modified = new ArrayList<ModificationInfo>();
+      Collection<ProfileDeployment> apps = getDeployments();
+      boolean trace = log.isTraceEnabled();
+      if (trace)
+         log.trace("Checking applications for modifications");
+      if (apps != null)
+      {
+         Iterator<ProfileDeployment> iter = apps.iterator();
+         int ignoreFlags = DeploymentContentFlags.LOCKED | DeploymentContentFlags.DISABLED;
+         while (iter.hasNext())
+         {
+            ProfileDeployment ctx = iter.next();
+            VirtualFile root = ctx.getRoot();
+            String pathName = root.getPathName();
+            // Ignore locked or disabled applications
+            if (this.hasDeploymentContentFlags(pathName, ignoreFlags))
+            {
+               if (trace)
+                  log.trace("Ignoring locked application: " + root);
+               continue;
+            }
+            // Check for removal
+            if (root.exists() == false)
+            {
+               long rootLastModified = root.getLastModified();
+               ModificationInfo info = new ModificationInfo(ctx, rootLastModified, ModifyStatus.REMOVED);
+               modified.add(info);
+               iter.remove();
+               // Remove last modified cache
+               getChecker().removeStructureRoot(root);
+               if (trace)
+                  log.trace(pathName + " was removed");
+            }
+            // Check for modification
+            else if (getChecker().hasStructureBeenModified(root) || hasDeploymentContentFlags(pathName, DeploymentContentFlags.MODIFIED))
+            {
+               long rootLastModified = root.getLastModified();
+               if (trace)
+                  log.trace(pathName + " was modified: " + rootLastModified);
+               // Create the modification info
+               ModificationInfo info = new ModificationInfo(ctx, rootLastModified, ModifyStatus.MODIFIED);
+               modified.add(info);
+            }
+         }
+         // Now check for additions
+         for (URI applicationDir : getRepositoryURIs())
+         {
+            VirtualFile deployDir = getCachedVirtualFile(applicationDir);
+            ArrayList<VirtualFile> added = new ArrayList<VirtualFile>();
+            addedDeployments(added, deployDir);
+            for (VirtualFile vf : added)
+            {
+               if (this.hasDeploymentContentFlags(vf.getPathName(), ignoreFlags))
+               {
+                  if (trace)
+                     log.trace("Ignoring locked application: " + vf);
+                  continue;
+               }
+               ProfileDeployment ctx = createDeployment(vf);
+               ModificationInfo info = new ModificationInfo(ctx, vf.getLastModified(), ModifyStatus.ADDED);
+               modified.add(info);
+               addDeployment(ctx.getName(), ctx);
+               getChecker().addStructureRoot(vf);
+            }
+         }
+      }
+
+      if (modified.size() > 0)
+         updateLastModfied();
+      return modified;
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/ClusteredDeploymentRepository.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/ClusteredDeploymentRepositoryFactory.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/ClusteredDeploymentRepositoryFactory.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/ClusteredDeploymentRepositoryFactory.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,238 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.jboss.deployers.vfs.spi.structure.modified.StructureModificationChecker;
+import org.jboss.profileservice.spi.DeploymentRepository;
+import org.jboss.profileservice.spi.DeploymentRepositoryFactory;
+import org.jboss.profileservice.spi.ProfileKey;
+import org.jboss.profileservice.spi.metadata.ProfileSourceMetaData;
+import org.jboss.system.server.profileservice.repository.clustered.local.LocalContentManagerFactory;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.ClusteredProfileSourceMetaData;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.HotDeploymentClusteredProfileSourceMetaData;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.ImmutableClusteredProfileSourceMetaData;
+import org.jboss.virtual.VirtualFileFilter;
+
+/**
+ * Factory for clustered deployment repositories.
+ * 
+ * @author Brian Stansberry
+ */
+public class ClusteredDeploymentRepositoryFactory implements DeploymentRepositoryFactory
+{   
+   /** The mutable type. */
+   public final static String MUTABLE_TYPE = HotDeploymentClusteredProfileSourceMetaData.class.getName();
+   
+   /** The immutable type. */
+   public final static String IMMUTABLE_TYPE = ImmutableClusteredProfileSourceMetaData.class.getName();
+  
+   /** The repository types. */
+   private static final List<String> types = Arrays.asList(new String[]{ MUTABLE_TYPE, IMMUTABLE_TYPE });
+   
+   private String defaultPartitionName;
+   
+   /** The deployment filter. */
+   private VirtualFileFilter deploymentFilter;
+
+   /** The structure modification checker */
+   private StructureModificationChecker checker;
+   
+   /** Factories for LocalContentManagers */
+   private final Set<LocalContentManagerFactory<?>> localContentManagerFactories = new HashSet<LocalContentManagerFactory<?>>();
+   
+   private final Map<ProfileKey, RepositoryClusteringHandler> clusteringHandlers = new HashMap<ProfileKey, RepositoryClusteringHandler>();
+   
+   // -------------------------------------------------------------- Properties
+
+   public String getDefaultPartitionName()
+   {
+      if (defaultPartitionName == null)
+      {
+         defaultPartitionName = new PrivilegedAction<String>()
+         {
+            public String run()
+            {
+               return System.getProperty("jboss.partition.name", "DefaultPartition");
+            }
+            
+         }.run();
+      }
+      return defaultPartitionName;
+   }
+
+   public void setDefaultPartitionName(String defaultPartitionName)
+   {
+      this.defaultPartitionName = defaultPartitionName;
+   }
+   
+   public VirtualFileFilter getDeploymentFilter()
+   {
+      return deploymentFilter;
+   }
+   
+   public void setDeploymentFilter(VirtualFileFilter deploymentFilter)
+   {
+      this.deploymentFilter = deploymentFilter;
+   }
+
+   public StructureModificationChecker getChecker()
+   {
+      return checker;
+   }
+
+   public void setChecker(StructureModificationChecker checker)
+   {
+      this.checker = checker;
+   }
+   
+   
+   // -------------------------------------------- Install/Uninstall Callbacks
+
+   
+   public void addRepositoryClusteringHandler(RepositoryClusteringHandler handler)
+   {
+      if (handler != null)
+      {
+         ProfileKey key = handler.getProfileKey();
+         synchronized (clusteringHandlers)
+         {
+            clusteringHandlers.put(key, handler);
+         }
+      }      
+   }
+   
+   public void removeRepositoryClusteringHandler(RepositoryClusteringHandler handler)
+   {
+      if (handler != null)
+      {
+         ProfileKey key = handler.getProfileKey();
+         synchronized (clusteringHandlers)
+         {
+            clusteringHandlers.remove(key);
+         }
+      }  
+   }
+   
+   public void addLocalContentManagerFactory(LocalContentManagerFactory<?> factory)
+   {
+      synchronized (localContentManagerFactories)
+      {
+         localContentManagerFactories.add(factory);
+      }
+   }
+   
+   public void removeLocalContentManagerFactory(LocalContentManagerFactory<?> factory)
+   {
+      synchronized (localContentManagerFactories)
+      {
+         localContentManagerFactories.remove(factory);
+      }
+   }
+   
+   // --------------------------------------------- DeploymentRepositoryFactory
+   
+   
+   public DeploymentRepository createDeploymentRepository(ProfileKey key, ProfileSourceMetaData metaData)
+         throws Exception
+   {
+      if(key == null)
+         throw new IllegalArgumentException("Null profile key.");
+      if(metaData == null)
+         throw new IllegalArgumentException("Null metaData");
+      
+      if ((metaData instanceof ClusteredProfileSourceMetaData) == false)
+      {
+         throw new IllegalArgumentException("Incompatible metadata type " + 
+               metaData.getClass().getName() + " -- a " + 
+               ClusteredProfileSourceMetaData.class.getSimpleName() + " is required");
+      }
+      
+      // Sanity check
+      String repositoryType = metaData.getType();
+      if(repositoryType == null)
+         throw new IllegalArgumentException("Null repository type.");
+      if(types.contains(repositoryType) == false)
+         throw new IllegalArgumentException("Cannot handle type: " + repositoryType);
+      
+      ClusteredProfileSourceMetaData clusteredMD = (ClusteredProfileSourceMetaData) metaData;
+      if (clusteredMD.getPartitionName() == null)
+      {
+         clusteredMD.setPartitionName(getDefaultPartitionName());
+      }
+      
+      URI[] uris = createUris(metaData);
+      Map<ProfileKey, RepositoryClusteringHandler> handlers = Collections.unmodifiableMap(this.clusteringHandlers);
+      Set<LocalContentManagerFactory<?>> persisters = Collections.unmodifiableSet(this.localContentManagerFactories);
+      
+      boolean immutable = clusteredMD instanceof ImmutableClusteredProfileSourceMetaData; 
+      if (immutable)
+      {
+         ImmutableClusteredDeploymentRepository repository = new ImmutableClusteredDeploymentRepository(key, uris, handlers, persisters);
+         // Manually inject beans :)
+         repository.setDeploymentFilter(deploymentFilter);
+         repository.setChecker(checker);
+         
+         return repository;
+      }
+      else
+      {
+         ClusteredDeploymentRepository repository = new ClusteredDeploymentRepository(key, uris, handlers, persisters);
+         // Manually inject beans :)
+         repository.setDeploymentFilter(deploymentFilter);
+         repository.setChecker(checker);
+         
+         return repository;
+      }
+   }
+
+   public String[] getTypes()
+   {
+      return types.toArray(new String[types.size()]); 
+   }
+   
+   // ----------------------------------------------------------------- Private
+   
+   private static URI[] createUris(ProfileSourceMetaData metaData) throws URISyntaxException
+   {
+      List<URI> uris = new ArrayList<URI>();
+      for(String source : metaData.getSources())
+      {
+         URI uri = new URI(source);
+         uris.add(uri);
+      }
+      return uris.toArray(new URI[uris.size()]);
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/ClusteredDeploymentRepositoryFactory.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/ImmutableClusteredDeploymentRepository.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/ImmutableClusteredDeploymentRepository.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/ImmutableClusteredDeploymentRepository.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,87 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.jboss.profileservice.spi.ModificationInfo;
+import org.jboss.profileservice.spi.ProfileKey;
+import org.jboss.system.server.profileservice.repository.clustered.local.LocalContentManagerFactory;
+
+/**
+ * @author Brian Stansberry
+ *
+ */
+public class ImmutableClusteredDeploymentRepository extends ClusteredDeploymentRepository
+{
+   
+   /**
+    * Create a new ImmutableClusteredDeploymentRepository.
+    * 
+    * @param key
+    * @param uris
+    */
+   public ImmutableClusteredDeploymentRepository(ProfileKey key, URI[] uris, 
+         Map<ProfileKey, RepositoryClusteringHandler> clusteringHandlers, 
+         Set<LocalContentManagerFactory<?>> persisterFactories)
+        throws IOException
+   {
+      super(key, uris, clusteringHandlers, persisterFactories);
+   }
+   
+   public Collection<ModificationInfo> getModifiedDeployments() throws Exception
+   {
+      return Collections.emptySet();
+   }
+
+   public String addDeploymentContent(String vfsPath, InputStream contentIS) throws IOException
+   {
+      throw new IllegalStateException("Cannot add content to an immutable repository.");
+   }
+
+   public void remove() throws Exception
+   {
+      throw new IllegalStateException("Cannot remove immutable repository.");
+   }
+
+   @Override
+   public synchronized boolean registerClusteringHandler(RepositoryClusteringHandler handler)
+   {
+      boolean update = super.registerClusteringHandler(handler);
+      if (update)
+      {
+         RepositoryClusteringHandler ours = getClusteringHandler();
+         ours.setImmutable(true);
+      }
+      return update;
+   }
+   
+   
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/ImmutableClusteredDeploymentRepository.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/RepositoryClusteringHandler.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/RepositoryClusteringHandler.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/RepositoryClusteringHandler.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,57 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.jboss.profileservice.spi.ProfileKey;
+import org.jboss.system.server.profileservice.repository.clustered.local.LocalContentManager;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.sync.InconsistentRepositoryStructureException;
+import org.jboss.virtual.VirtualFile;
+
+/**
+ * @author Brian Stansberry
+ *
+ */
+public interface RepositoryClusteringHandler
+{
+   ProfileKey getProfileKey();
+   String getPartitionName();
+   String getLocalNodeName();
+   
+   boolean isImmutable();
+   void setImmutable(boolean immutable);
+   
+   void initialize(LocalContentManager<?> persister) throws Exception;
+   void shutdown() throws Exception;
+   boolean lockGlobally();
+   boolean lockLocally();
+   RepositoryContentMetadata synchronizeContent(boolean pullFromCluster) 
+         throws InconsistentRepositoryStructureException, IOException;
+   VirtualFile addDeploymentContent(String vfsPath, InputStream contentIS) throws IOException;
+   void removeDeploymentContent(VirtualFile vf) throws Exception;
+   void unlockGlobally();
+   void unlockLocally();
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/RepositoryClusteringHandler.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/AbstractContentMetadataPersister.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/AbstractContentMetadataPersister.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/AbstractContentMetadataPersister.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,116 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.local;
+
+import java.io.File;
+import java.io.NotSerializableException;
+
+import org.jboss.logging.Logger;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+
+/**
+ * @author Brian Stansberry
+ *
+ */
+public abstract class AbstractContentMetadataPersister
+      implements ContentMetadataPersister
+{ 
+   private static final Logger log = Logger.getLogger(AbstractContentMetadataPersister.class); 
+   
+   private final File contentMetadataDir;
+   
+   public AbstractContentMetadataPersister(File dir)
+   {
+      if(dir == null)
+         throw new IllegalArgumentException("Null store dir.");
+      this.contentMetadataDir = dir;
+   }
+
+   public RepositoryContentMetadata load(String baseName)
+   {
+      File attachmentsStore = getMetadataPath(baseName);
+      if( attachmentsStore.exists() == false )
+      {
+         return null;
+      }
+
+      try
+      {
+         return loadMetadata(attachmentsStore);
+      }
+      catch (RuntimeException e)
+      {
+         throw e;
+      }
+      catch (Exception e)
+      {
+         throw new RuntimeException(e);
+      }
+   }
+
+   public void store(String baseName, RepositoryContentMetadata metadata)
+   {
+      File attachmentsStore = getMetadataPath(baseName);
+      File attachmentsParent = attachmentsStore.getParentFile();
+      if( attachmentsParent.exists() == false )
+      {
+         if( attachmentsParent.mkdirs() == false )
+            throw new RuntimeException("Failed to create attachmentsParent: "+attachmentsParent.getAbsolutePath());
+      }
+
+      if( metadata != null )
+      {
+         try
+         {
+            saveMetadata(attachmentsStore, metadata);
+         }
+         catch (RuntimeException e)
+         {
+            throw e;
+         }
+         catch(NotSerializableException e)
+         {
+            // Log what is in the attachments
+            StringBuilder tmp = new StringBuilder("Save failed with NSE, attachments contents: ");
+            tmp.append(metadata).append(" to: ").append(attachmentsStore);
+            log.error(tmp.toString());
+            throw new RuntimeException(e);
+         }
+         catch (Exception e)
+         {
+            throw new RuntimeException(e);
+         }
+      }
+   }
+   
+   public abstract File getMetadataPath(String baseName);
+
+   protected abstract RepositoryContentMetadata loadMetadata(File attachmentsStore) throws Exception;
+   
+   protected abstract void saveMetadata(File metadataStore, RepositoryContentMetadata metadata) throws Exception;
+   
+   protected File getContentMetadataDir()
+   {
+      return contentMetadataDir;
+   }
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/AbstractContentMetadataPersister.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/AbstractLocalContentManager.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/AbstractLocalContentManager.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/AbstractLocalContentManager.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,656 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.local;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.jboss.logging.Logger;
+import org.jboss.profileservice.spi.ProfileKey;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryItemMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryRootMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ContentModification;
+import org.jboss.system.server.profileservice.repository.clustered.sync.NoOpSynchronizationAction;
+import org.jboss.system.server.profileservice.repository.clustered.sync.RemovalMetadataInsertionAction;
+import org.jboss.system.server.profileservice.repository.clustered.sync.StreamReadAction;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationAction;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationActionContext;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationId;
+import org.jboss.system.server.profileservice.repository.clustered.sync.TwoPhaseCommitAction;
+import org.jboss.virtual.VFS;
+import org.jboss.virtual.VirtualFile;
+
+/**
+ *
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public abstract class AbstractLocalContentManager<T extends SynchronizationActionContext> implements LocalContentManager<T>
+{
+   private final Logger log = Logger.getLogger(getClass());
+   
+   private RepositoryContentMetadata officialContentMetadata;
+   private RepositoryContentMetadata currentContentMetadata;
+   private final Map<String, URI> namedURIMap;
+   private final Map<String, VirtualFile> vfCache = new ConcurrentHashMap<String, VirtualFile>();
+   private final ProfileKey profileKey;
+   private final String storeName;
+   private final String localNodeName;
+   private final ContentMetadataPersister contentMetadataPersister;
+   private List<TwoPhaseCommitAction<T>> currentSynchronizationActions;
+   private T currentSynchronizationActionContext;
+   private Map<RepositoryItemMetadata, InputStream> pendingStreams = new ConcurrentHashMap<RepositoryItemMetadata, InputStream>();
+
+   
+   protected static String createStoreName(ProfileKey key)
+   {
+      // Normal case
+      if (ProfileKey.DEFAULT.equals(key.getDomain()) 
+            && ProfileKey.DEFAULT.equals(key.getServer()))
+      {
+         return key.getName();
+      }
+      
+      StringBuilder sb = new StringBuilder();
+      if (ProfileKey.DEFAULT.equals(key.getDomain()) == false)
+      {
+         sb.append(key.getDomain());
+         sb.append('-');
+      }
+      if (ProfileKey.DEFAULT.equals(key.getServer()) == false)
+      {
+         sb.append(key.getServer());
+         sb.append('-');
+      }
+      sb.append(key.getName());
+      return sb.toString();
+   }
+   
+   protected AbstractLocalContentManager(Map<String, URI> namedURIMap,
+         ProfileKey profileKey, String localNodeName, 
+         ContentMetadataPersister contentMetadataPersister)
+   {
+      if (namedURIMap == null)
+      {
+         throw new IllegalArgumentException("Null namedURIMap");
+      }
+      if (profileKey == null)
+      {
+         throw new IllegalArgumentException("Null profileKey");
+      }
+      if (localNodeName == null)
+      {
+         throw new IllegalArgumentException("Null localNodeName");
+      }
+      if (contentMetadataPersister == null)
+      {
+         throw new IllegalArgumentException("Null contentMetadataPersister");
+      }
+      
+      this.namedURIMap = namedURIMap;
+      this.profileKey = profileKey;
+      this.storeName = createStoreName(profileKey);
+      this.localNodeName = localNodeName;
+      this.contentMetadataPersister = contentMetadataPersister;
+      this.officialContentMetadata = this.contentMetadataPersister.load(this.storeName);
+   }
+   
+   // -------------------------------------------------------------  Properties
+
+   public String getLocalNodeName()
+   {
+      return localNodeName;
+   }
+   
+   public String getStoreName()
+   {
+      return storeName;
+   }
+   
+   // ----------------------------------------------------  LocalContentManager
+
+   public RepositoryContentMetadata getOfficialContentMetadata()
+   {
+      return officialContentMetadata;
+   }
+
+   public RepositoryContentMetadata createEmptyContentMetadata()
+   {
+      RepositoryContentMetadata md = new RepositoryContentMetadata(profileKey);
+      List<RepositoryRootMetadata> roots = new ArrayList<RepositoryRootMetadata>();
+      for (Map.Entry<String, URI> entry : namedURIMap.entrySet())
+      {
+         RepositoryRootMetadata rmd = new RepositoryRootMetadata();
+         rmd.setName(entry.getKey());
+         roots.add(rmd);
+      }
+      md.setRepositories(roots);
+      return md;      
+   }
+
+   public RepositoryContentMetadata getCurrentContentMetadata() throws IOException
+   {
+      synchronized (this)
+      {
+         if (this.currentContentMetadata == null)
+         {
+            RepositoryContentMetadata md = new RepositoryContentMetadata(profileKey);
+            List<RepositoryRootMetadata> roots = new ArrayList<RepositoryRootMetadata>();
+            RepositoryContentMetadata official = getOfficialContentMetadata();
+            for (Map.Entry<String, URI> entry : namedURIMap.entrySet())
+            {
+               RepositoryRootMetadata rmd = new RepositoryRootMetadata();
+               rmd.setName(entry.getKey());
+               RepositoryRootMetadata existingRmd = official == null ? null : official.getRepositoryRootMetadata(entry.getKey());
+               
+               VirtualFile root = getCachedVirtualFile(entry.getValue());         
+               if (isDirectory(root))
+               {
+                  for(VirtualFile child: root.getChildren())
+                  {
+                     createItemMetadataFromScan(rmd, existingRmd, child, root);
+                  }
+               }
+               else
+               {
+                  // The root is itself an item. Treat it as a "child" of
+                  // itself with no relative path
+                  createItemMetadataFromScan(rmd, existingRmd, root, root);
+               }
+               
+               roots.add(rmd);
+            }
+            md.setRepositories(roots);
+            
+            // Retain any existing "removed item" metadata -- but only if
+            // it wasn't re-added!!
+            RepositoryContentMetadata existing = getOfficialContentMetadata();
+            if (existing != null)
+            {
+               for (RepositoryRootMetadata existingRoot : existing.getRepositories())
+               {
+                  RepositoryRootMetadata rmd = md.getRepositoryRootMetadata(existingRoot.getName());
+                  if (rmd != null)
+                  {
+                     for (RepositoryItemMetadata existingItem : existingRoot.getContent())
+                     {
+                        if (existingItem.isRemoved() // but check for re-add 
+                              && rmd.getItemMetadata(existingItem.getRelativePathElements()) == null)
+                        {
+                           rmd.addItemMetadata(new RepositoryItemMetadata(existingItem));
+                        }
+                     }
+                  }
+               }
+            }
+            
+            this.currentContentMetadata = md;
+         }
+         return this.currentContentMetadata;
+      }
+   }
+
+   public List<? extends SynchronizationAction<T>> initiateSynchronization(SynchronizationId<?> id,
+         List<ContentModification> modifications, RepositoryContentMetadata toInstall, boolean localLed)
+   {
+      if (id == null)
+      {
+         throw new IllegalArgumentException("Null id");
+      }
+      if (modifications == null)
+      {
+         throw new IllegalArgumentException("Null modifications");
+      }
+      if (toInstall == null)
+      {
+         throw new IllegalArgumentException("Null toInstall");
+      }
+      
+      synchronized (this)
+      {
+         if (currentSynchronizationActionContext != null)
+         {
+            throw new IllegalStateException("Synchronization " + currentSynchronizationActionContext.getId() + 
+                  " is already in progress");
+         }
+         
+         this.currentSynchronizationActionContext = createSynchronizationActionContext(id, toInstall);
+      }
+
+      List<TwoPhaseCommitAction<T>> actions = new ArrayList<TwoPhaseCommitAction<T>>();
+      for (ContentModification mod : modifications)
+      {
+         TwoPhaseCommitAction<T> action = null;
+         switch (mod.getType())
+         {
+            case PULL_FROM_CLUSTER:
+               action = createPullFromClusterAction(mod, localLed);
+               break;
+            case PUSH_TO_CLUSTER:
+               InputStream stream = pendingStreams.remove(mod.getItem());
+               if (stream == null)
+               {
+                  action = createPushToClusterAction(mod, localLed);
+               }
+               else
+               {
+                  action = createPushStreamToClusterAction(mod, stream);
+               }
+               break;
+            case REMOVE_FROM_CLUSTER:
+               action = createRemoveFromClusterAction(mod, localLed);
+               break;
+            case REMOVE_TO_CLUSTER:
+               action = createRemoveToClusterAction(mod, localLed);
+               break;
+            case PREPARE_RMDIR_FROM_CLUSTER:
+               action = createPrepareRmdirFromClusterAction(mod, localLed);
+               break;
+            case PREPARE_RMDIR_TO_CLUSTER:
+               action = createPrepareRmdirToClusterAction(mod, localLed);
+               break;
+            case DIR_TIMESTAMP_MISMATCH:
+               action = createDirectoryTimestampMismatchAction(mod, localLed);
+               break;
+            case MKDIR_FROM_CLUSTER:
+               action = createMkdirFromClusterAction(mod, localLed);
+               break;
+            case MKDIR_TO_CLUSTER:
+               action = createMkdirToClusterAction(mod, localLed);
+               break;
+            case REMOVAL_METADATA_FROM_CLUSTER:
+               action = createRemovalMetadataAction(mod, localLed);
+               break;
+            default:
+               throw new IllegalStateException("Unknown " + ContentModification.Type.class.getSimpleName() + " " + mod.getType());
+         }
+         actions.add(action);         
+      }
+      
+      this.currentSynchronizationActions = actions;
+      return Collections.unmodifiableList(actions);
+   }
+
+   public boolean prepareSynchronization(SynchronizationId<?> id)
+   {
+      validateSynchronization(id);
+      for (TwoPhaseCommitAction<T> action : this.currentSynchronizationActions)
+      {
+         if (action.prepare() == false)
+         {
+            return false;
+         }
+      }
+      return true;
+   }
+   
+   public void commitSynchronization(SynchronizationId<?> id)
+   {
+      validateSynchronization(id);
+      for (TwoPhaseCommitAction<T> action : this.currentSynchronizationActions)
+      {
+         action.commit();
+      }
+      updateContentMetadata(this.currentSynchronizationActionContext.getInProgressMetadata());
+      synchronized (this)
+      {
+         this.currentSynchronizationActions = null;
+         this.currentSynchronizationActionContext = null;
+      }
+   }
+
+   public void rollbackSynchronization(SynchronizationId<?> id)
+   {
+      validateSynchronization(id);
+      
+      for (TwoPhaseCommitAction<T> action : this.currentSynchronizationActions)
+      {
+         action.rollback();
+      }
+      synchronized (this)
+      {
+         this.currentSynchronizationActionContext = null;
+         this.currentSynchronizationActions = null;
+      }
+   }
+   
+   public void installCurrentContentMetadata()
+   {
+      synchronized (this)
+      {
+         if (this.currentContentMetadata == null)
+         {
+            throw new IllegalStateException("No currentContentMetadata");
+         }
+         if (this.currentSynchronizationActionContext != null)
+         {
+            throw new IllegalStateException("Cannot install currentContentMetadata; " +
+            		"cluster synchronization " + this.currentSynchronizationActionContext.getId() + 
+            		" is in progress");
+         }
+         
+         updateContentMetadata(this.currentContentMetadata);
+      }      
+   }
+
+   public RepositoryItemMetadata getItemForAddition(String vfsPath) throws IOException
+   {
+      RepositoryItemMetadata item = new RepositoryItemMetadata();
+      item.setRelativePath(vfsPath);
+      List<String> pathElements = item.getRelativePathElements();
+      String rootName = null;
+      for (RepositoryRootMetadata rmd : getOfficialContentMetadata().getRepositories())
+      {
+         if (rmd.getItemMetadata(pathElements) != null)
+         {
+            // Exact match to existing item -- done
+            rootName = rmd.getName();
+            break;
+         }
+         else if (rootName == null)
+         {
+            // Use the first root that can accept children
+            URI rootURI = namedURIMap.get(rootName);
+            VirtualFile vf = getCachedVirtualFile(rootURI);
+            if (isDirectory(vf))
+            {
+               rootName = rmd.getName();
+               break;               
+            }
+         }
+      }
+      
+      if (rootName == null)
+      {
+         throw new IllegalStateException("No roots can accept children");
+      }
+      
+      item.setRootName(rootName);
+      return item;
+   }
+
+   public RepositoryContentMetadata getContentMetadataForAdd(RepositoryItemMetadata toAdd, InputStream contentIS) throws IOException
+   {
+      RepositoryContentMetadata result = new RepositoryContentMetadata(getOfficialContentMetadata());
+      RepositoryRootMetadata rmd = result.getRepositoryRootMetadata(toAdd.getRootName());
+      if (rmd == null)
+      {
+         throw new IllegalArgumentException("Unknown root name " + toAdd.getRootName());
+      }
+      RepositoryItemMetadata remove = rmd.getItemMetadata(toAdd.getRelativePathElements());
+      if (remove.isDirectory())
+      {
+         for (RepositoryItemMetadata rim : rmd.getContent())
+         {
+            if (rim.isChildOf(remove))
+            {
+               rmd.removeItemMetadata(rim.getRelativePathElements());
+            }
+         }
+      }
+      rmd.addItemMetadata(toAdd);
+      pendingStreams.put(toAdd, contentIS);      
+      return result;
+   }
+
+   public VirtualFile getVirtualFileForItem(RepositoryItemMetadata item) throws IOException
+   {
+      URI uri = namedURIMap.get(item.getRootName());
+      VirtualFile vf = getCachedVirtualFile(uri);
+      List<String> path = item.getRelativePathElements();
+      for (String element : path)
+      {
+         vf = vf.getChild(element);
+         if (vf == null)
+         {
+            throw new IllegalStateException("No child " + element + " under " + vf);
+         }
+      }
+      return vf;
+   }
+   
+   public RepositoryContentMetadata getContentMetadataForRemove(VirtualFile vf) throws IOException
+   {
+      List<String> path = null;
+      RepositoryRootMetadata root = null;
+      RepositoryContentMetadata cmd = new RepositoryContentMetadata(getOfficialContentMetadata());
+      for (RepositoryRootMetadata rmd : cmd.getRepositories())
+      {
+         URI uri = namedURIMap.get(rmd.getName());
+         VirtualFile vfRoot = getCachedVirtualFile(uri);
+         try
+         {
+            path = getRelativePath(vf, vfRoot);
+            root = rmd;
+            break;
+         }
+         catch (IllegalStateException ise)
+         {
+            // vf wasn't a child; ignore and move on to next root
+         }
+      }
+      
+      if (root == null)
+      {
+         throw new IllegalArgumentException(vf + " is not a child of any known roots");
+      }
+      
+      RepositoryItemMetadata remove = root.getItemMetadata(path);
+      if (isDirectory(vf))
+      {
+         for (RepositoryItemMetadata rim : root.getContent())
+         {
+            if (rim.isChildOf(remove))
+            {
+               root.removeItemMetadata(rim.getRelativePathElements());
+            }
+         }
+         
+      }
+      root.removeItemMetadata(path);
+      return cmd;
+   }  
+   
+   
+   // --------------------------------------------------------------  Protected
+
+
+   protected abstract T createSynchronizationActionContext(SynchronizationId<?> id, RepositoryContentMetadata toUpdate);
+   
+   protected abstract TwoPhaseCommitAction<T> createPullFromClusterAction(ContentModification mod, boolean localLed);
+   
+   protected abstract TwoPhaseCommitAction<T> createPushToClusterAction(ContentModification mod, boolean localLed);
+   
+   protected abstract TwoPhaseCommitAction<T> createRemoveFromClusterAction(ContentModification mod, boolean localLed);
+   
+   protected abstract TwoPhaseCommitAction<T> createRemoveToClusterAction(ContentModification mod, boolean localLed);
+
+   protected abstract TwoPhaseCommitAction<T> createPrepareRmdirToClusterAction(ContentModification mod, boolean localLed);
+
+
+   protected abstract TwoPhaseCommitAction<T> createMkdirToClusterAction(ContentModification mod,
+         boolean localLed);
+
+   protected abstract TwoPhaseCommitAction<T> createMkdirFromClusterAction(
+         ContentModification mod, boolean localLed);
+
+   protected abstract TwoPhaseCommitAction<T> createDirectoryTimestampMismatchAction(
+         ContentModification mod, boolean localLed);
+
+   protected abstract TwoPhaseCommitAction<T> createPrepareRmdirFromClusterAction(ContentModification mod, boolean localLed);
+
+   protected T getSynchronizationActionContext()
+   {
+      return currentSynchronizationActionContext;
+   }
+   
+   protected VirtualFile getCachedVirtualFile(URI uri) throws IOException 
+   {
+      VirtualFile vf = this.vfCache.get(uri.toString());
+      if(vf == null)
+      {
+         vf = VFS.getRoot(uri);
+         this.vfCache.put(uri.toString(), vf);
+      }
+      return vf;
+   }
+   
+   protected URI getRootURIForModification(ContentModification mod)
+   {
+      return namedURIMap.get(mod.getRootName());
+   }
+   
+   public void clearCurrentContentMetadata()
+   {
+      this.currentContentMetadata = null;
+   }
+   
+   // --------------------------------------------------------------  Private
+   
+   private TwoPhaseCommitAction<T> createRemovalMetadataAction(ContentModification mod,
+                                                            boolean localLed)
+   {
+      if (localLed)
+      {
+         return new RemovalMetadataInsertionAction<T>(getSynchronizationActionContext(), mod);
+      }
+      else
+      {
+         return new NoOpSynchronizationAction<T>(getSynchronizationActionContext(), mod);
+      }
+   }
+   
+   private void updateContentMetadata(RepositoryContentMetadata newOfficial)
+   {
+      if (newOfficial.equals(this.officialContentMetadata) == false)
+      {
+         try
+         {
+            this.contentMetadataPersister.store(this.storeName, newOfficial);
+         }
+         catch (Exception e)
+         {
+            log.error("Caught exception persisting " + RepositoryContentMetadata.class.getSimpleName(), e);
+         }
+         this.officialContentMetadata = newOfficial;
+      }
+      this.currentContentMetadata = null;
+   }
+   
+   private void createItemMetadataFromScan(RepositoryRootMetadata rmd, 
+                                           RepositoryRootMetadata existingRMD, 
+                                           VirtualFile file, VirtualFile root) 
+        throws IOException
+   {      
+      boolean directory = isDirectory(file);
+      long timestamp = file.getLastModified();
+      
+      List<String> pathElements = getRelativePath(file, root);
+      RepositoryItemMetadata existing = existingRMD == null ? null : existingRMD.getItemMetadata(pathElements);
+      
+      // If there's an existing item, assume for now it's unchanged and keep existing originator
+      String originator = existing == null ? this.localNodeName : existing.getOriginatingNode();      
+      RepositoryItemMetadata md = new RepositoryItemMetadata(pathElements, timestamp, originator, directory, false);
+      if (md.equals(existing) == false)
+      {
+         // above if test failing means this is a new item or
+         // timestamp, removed or directory status has changed
+         // In any case, this node is now the originator
+         md.setOriginatingNode(this.localNodeName);
+      }
+      
+      rmd.addItemMetadata(md);
+      
+      if (directory)
+      {
+         for(VirtualFile child: file.getChildren())
+         {
+            createItemMetadataFromScan(rmd, existingRMD, child, root);
+         }
+      }
+   }
+
+   private TwoPhaseCommitAction<T> createPushStreamToClusterAction(ContentModification mod, InputStream stream)
+   {
+      return new StreamReadAction<T>(stream, getSynchronizationActionContext(), mod);
+   }
+
+   private void validateSynchronization(SynchronizationId<?> id)
+   {
+      if (id == null)
+      {
+         throw new IllegalArgumentException("Null id");
+      }
+      
+      if (this.currentSynchronizationActionContext == null)
+      {
+         throw new IllegalStateException("No active synchronization");
+      }
+      
+      SynchronizationId<?> ours = this.currentSynchronizationActionContext.getId();
+      if (id.equals(ours) == false)
+      {
+         throw new IllegalStateException(id + " does not match the current synchronization " + ours);
+      }
+   }
+   
+   private static boolean isDirectory(VirtualFile file) throws IOException
+   {
+      return (!file.isLeaf() && !file.isArchive());
+   }
+   
+   private static List<String> getRelativePath(VirtualFile file, VirtualFile root)
+      throws IOException
+   {
+      List<String> reversed = new ArrayList<String>();
+      VirtualFile now = file;
+      while(now != null && now.equals(root) == false)
+      {
+         reversed.add(now.getName());
+         now = now.getParent();
+      }
+      
+      if (now == null)
+      {
+         throw new IllegalArgumentException(file + " is not a child of " + root);
+      }
+      
+      List<String> forward = new ArrayList<String>(reversed.size());
+      for (int i = reversed.size() - 1; i > -1; i--)
+      {
+         forward.add(reversed.get(i));
+      }
+      
+      return forward;
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/AbstractLocalContentManager.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/ContentMetadataPersister.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/ContentMetadataPersister.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/ContentMetadataPersister.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,40 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.local;
+
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+
+/**
+ * Object responsible for maintaining a persistent copy of a node's
+ * {@link RepositoryContentMetadata}.
+ * 
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public interface ContentMetadataPersister
+{   
+   RepositoryContentMetadata load(String storeName);
+
+   void store(String storeName, RepositoryContentMetadata metadata);
+
+}
\ No newline at end of file


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/ContentMetadataPersister.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/JAXBRepositoryContentMetadataPersister.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/JAXBRepositoryContentMetadataPersister.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/JAXBRepositoryContentMetadataPersister.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,103 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.local;
+
+import java.io.File;
+import java.net.URI;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Marshaller;
+
+import org.jboss.bootstrap.spi.Server;
+import org.jboss.logging.Logger;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.xb.binding.Unmarshaller;
+import org.jboss.xb.binding.UnmarshallerFactory;
+import org.jboss.xb.binding.sunday.unmarshalling.DefaultSchemaResolver;
+
+/**
+ * RepositoryContentPersister that uses JAXB to store the content metadata as XML.
+ * 
+ * @author Brian Stansberry
+ */
+public class JAXBRepositoryContentMetadataPersister extends AbstractContentMetadataPersister
+{   
+   /** The logger */
+   private static final Logger log = Logger.getLogger(JAXBRepositoryContentMetadataPersister.class);
+   
+   /** The attachment suffix. */
+   private static final String METADATA_SUFFIX = "-repository-contents.xml";
+
+   /** The default schema resolver. */
+   private static final DefaultSchemaResolver resolver = new DefaultSchemaResolver();
+   
+   static
+   {
+      resolver.addClassBindingForLocation("repository-content", RepositoryContentMetadata.class);
+   }
+   
+   public JAXBRepositoryContentMetadataPersister(Server server)
+   {
+      this(server.getConfig().getServerDataDir());
+   }
+   
+   public JAXBRepositoryContentMetadataPersister(URI uri)
+   {
+      this(new File(uri));
+   }
+   
+   public JAXBRepositoryContentMetadataPersister(File dir)
+   {
+      super(dir);
+   }
+
+   @Override
+   public File getMetadataPath(String baseName)
+   {
+      final String vfsPath = baseName + METADATA_SUFFIX;
+      return new File(getContentMetadataDir(), vfsPath);
+   }
+
+   @Override
+   protected RepositoryContentMetadata loadMetadata(File metadataStore) throws Exception
+   {
+      Unmarshaller unmarshaller = UnmarshallerFactory.newInstance().newUnmarshaller();
+      return (RepositoryContentMetadata) unmarshaller.unmarshal(metadataStore.toURL().openStream(), resolver);
+   }
+
+   @Override
+   protected void saveMetadata(File metadataStore, RepositoryContentMetadata metadata) throws Exception
+   {
+      if (log.isTraceEnabled())
+      {
+         log.trace("saveMetadata, metadataStore="+metadataStore+ ", metadata="+metadata);
+      }
+      JAXBContext ctx = JAXBContext.newInstance(metadata.getClass());
+      Marshaller marshaller = ctx.createMarshaller();
+      marshaller.setProperty("jaxb.formatted.output", Boolean.TRUE);
+      marshaller.marshal(metadata, metadataStore);
+   }
+   
+   
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/JAXBRepositoryContentMetadataPersister.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/LocalContentManager.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/LocalContentManager.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/LocalContentManager.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,171 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.local;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import org.jboss.system.server.profileservice.repository.clustered.ClusteredDeploymentRepository;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryItemMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryRootMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.sync.AbstractContentModificationGenerator;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ContentModification;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationAction;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationActionContext;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationId;
+import org.jboss.virtual.VirtualFile;
+
+/**
+ * Object responsible for the local persistence operations associated with a
+ * {@link ClusteredDeploymentRepository}.
+ * 
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public interface LocalContentManager<T extends SynchronizationActionContext>
+{
+   
+   /**
+    * Gets the "official" record of the contents of the persistent store.
+    * This is the version persisted to disk following synchronization
+    * of changes with the cluster. 
+    * 
+    * @return
+    */
+   RepositoryContentMetadata getOfficialContentMetadata();
+   
+   /**
+    * Scan the persistent store for the current content metadata. This
+    * is not the "official" metadata that has been persisted, as any
+    * changes between it and the {@link #getOfficialContentMetadata() official version}
+    * have not been synchronized with the cluster.
+    */
+   RepositoryContentMetadata getCurrentContentMetadata() throws IOException;
+   
+   /**
+    * Initiate a process of synchronizing this node's persistent store with
+    * the rest of the cluster
+    * 
+    * @param id a unique id for this cluster synchronization process
+    * @param modifications the overall list of modifications that will occur during
+    *                      this process
+    * @param toInstall TODO
+    * @param localLed <code>true</code> if this node is driving the synchronization,
+    *                 <code>false</code> if another node is
+    * @return list of {@link SynchronizationAction} each of which
+    *         can be executed by the caller to fulfill a portion of this node's role in
+    *         the overall cluster synchronization
+    *         
+    * @throws IllegalStateException if another synchronization process has
+    *                               been initiated and not yet completed
+    */
+   List<? extends SynchronizationAction<T>> initiateSynchronization(SynchronizationId<?> id, 
+         List<ContentModification> modifications, RepositoryContentMetadata toInstall, boolean localLed);
+   
+   /**
+    * Execute the prepare phase of the two phase commit process for the cluster 
+    * synchronization that has been 
+    * {@link #initiateSynchronization(SynchronizationId, List, RepsitoryContentMetadata, boolean) initialized}.
+    * 
+    * @param id id of the synchronization. Cannot be <code>null</code>
+    * 
+    * @throws IllegalStateException if <code>id</code> is not equal to the
+    *  id of an uncompleted synchronization started via 
+    *  {@link #initiateSynchronization(SynchronizationId, List, RepsitoryContentMetadata, boolean)}
+    */
+   boolean prepareSynchronization(SynchronizationId<?> id);
+   
+   /**
+    * Complete the two-phase commit process for the cluster synchronization that has been 
+    * {@link #prepareSynchronization(SynchronizationId) prepared}. 
+    * 
+    * @param id id of the synchronization. Cannot be <code>null</code>
+    * 
+    * @throws IllegalStateException if <code>id</code> is not equal to the
+    *  id of an uncompleted synchronization started via 
+    *  {@link #initiateSynchronization(SynchronizationId, List, RepsitoryContentMetadata, boolean)}
+    */
+   void commitSynchronization(SynchronizationId<?> id);
+   
+   /**
+    * Roll back the cluster synchronization. 
+    * 
+    * @param id id of the synchronization. Cannot be <code>null</code>
+    * 
+    * @throws IllegalStateException if <code>id</code> is not equal to the
+    *  id of an uncompleted synchronization started via 
+    *  {@link #initiateSynchronization(SynchronizationId, List, RepsitoryContentMetadata, boolean)}
+    */
+   void rollbackSynchronization(SynchronizationId<?> id);
+   
+   /**
+    * Creates a new {@link RepositoryContentMetadata} with a child
+    * {@link RepositoryRootMetadata} for each of this persister's URIs,
+    * but no {@link RepositoryItemMetadata}s under those roots. When a node that
+    * is starting for the first time does not have a persisted set of 
+    * content metadata, this method should be used to create an object that can 
+    * be used as a base to 
+    * {@link AbstractContentModificationGenerator#getModificationList(RepositoryContentMetadata, RepositoryContentMetadata) generate a set of modifications}
+    * needed to synchronize the node with the cluster.
+    * 
+    * @return a {@link RepositoryContentMetadata} with no grandchildren.
+    */
+   RepositoryContentMetadata createEmptyContentMetadata();
+   
+   /**
+    * Install the result from the latest call to {@link #getCurrentContentMetadata()}
+    * as the "official" content metadata. Intended for use during node startup
+    * when the node discovers it is the only member of the cluster, and thus
+    * that it's "current" content metadata is "official".
+    * 
+    * @throws IllegalStateException if {@link #initiateSynchronization(SynchronizationId, List, RepsitoryContentMetadata, boolean)
+    *  a cluster synchronization has been initiated} since the call to {@link #getCurrentContentMetadata()}
+    *  but hasn't been committed or rolled back.
+    *  
+    * @throws IllegalStateException if no "current" content metadata is available, either
+    *                               because {@link #getCurrentContentMetadata()} 
+    *                               hasn't been called, or because a cluster
+    *                               synchronization has been executed to completion
+    *                               since that call.
+    *
+    */
+   void installCurrentContentMetadata();
+
+   RepositoryItemMetadata getItemForAddition(String vfsPath) throws IOException;
+   /**
+    * Generate content metadata that would reflect what the metadata would 
+    * look like if an item with path vfsPath were added.
+    * 
+    * @param vfsPath
+    * @param contentIS
+    * @return
+    */
+   RepositoryContentMetadata getContentMetadataForAdd(RepositoryItemMetadata toAdd, InputStream contentIS) throws IOException;
+
+   VirtualFile getVirtualFileForItem(RepositoryItemMetadata item) throws IOException;
+
+   RepositoryContentMetadata getContentMetadataForRemove(VirtualFile vf) throws IOException;
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/LocalContentManager.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/LocalContentManagerFactory.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/LocalContentManagerFactory.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/LocalContentManagerFactory.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,54 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.local;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.Map;
+
+import org.jboss.profileservice.spi.ProfileKey;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationActionContext;
+
+/**
+ * Factory for a {@link LocalContentManager} that understands a
+ * particular type of URI.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public interface LocalContentManagerFactory<T extends SynchronizationActionContext>
+{
+   /**
+    * Indicates whether this factory can create a {@link LocalContentManager}
+    * that works with the given collection of URIs.
+    * 
+    * @param uris the URIs. Cannot be <code>null</code>
+    * @return <code>true</code> if a persister can be created, <code>false</code>
+    *         otherwise
+    */
+   boolean accepts(Collection<URI> uris);
+   
+   LocalContentManager<T> getRepositoryContentPersister(Map<String, URI> namedURIMap,
+         ProfileKey profileKey, String localNodeName);
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/LocalContentManagerFactory.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/AbstractLocalContentChangeAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/AbstractLocalContentChangeAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/AbstractLocalContentChangeAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,231 @@
+package org.jboss.system.server.profileservice.repository.clustered.local.file;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.jboss.logging.Logger;
+import org.jboss.system.server.profileservice.repository.clustered.sync.AbstractContentMetadataMutatorAction;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ContentModification;
+
+public abstract class AbstractLocalContentChangeAction
+   extends AbstractContentMetadataMutatorAction<FileBasedSynchronizationActionContext>
+{
+   private final File targetFile;
+   private final boolean targetWasDir;
+   private final boolean targetExists;
+   private final long targetTimestamp;
+   private File tempRollback;
+
+   /**
+    * Create a new AbstractLocalContentChangeAction.
+    * 
+    * @param targetFile the file whose content is to be changed
+    * @param context the overall context of the modification
+    * @param modification the modification
+    */
+   protected AbstractLocalContentChangeAction(File targetFile, FileBasedSynchronizationActionContext context, 
+         ContentModification modification)
+   {
+      super(context, modification);
+      
+      if (targetFile == null)
+      {
+         throw new IllegalArgumentException("Null targetFile");
+      }
+      
+      this.targetFile = targetFile;
+      this.targetWasDir = targetFile.isDirectory();
+      this.targetExists = targetFile.exists();
+      this.targetTimestamp = targetFile.lastModified();
+   }
+   
+   protected abstract Logger getLogger();
+
+   protected abstract boolean modifyTarget() throws IOException;
+
+   protected File getTargetFile()
+   {
+      return targetFile;
+   }
+   
+   @Override
+   protected void doCancel()
+   {
+      safeCleanup();
+   }
+   
+   @Override
+   protected void doComplete() throws Exception
+   {
+      // no-op
+   }
+
+   @Override
+   protected boolean doPrepare()
+   {
+      File backup = null;
+      try
+      {
+         if (targetExists)
+         {
+            if (!targetWasDir)
+            {
+               // Make a backup copy of target in case of rollback
+               backup = createTempFile();
+               FileUtil.localMove(targetFile, backup, targetTimestamp);
+               // assign after creation so the ref to the file
+               // indicates a successful write -- useful in rollback
+               this.tempRollback = backup;
+            }
+            else
+            {
+               // No backup copy needed; we can just recreate
+               targetFile.delete();
+            }
+         }
+         
+         return modifyTarget();
+      }
+      catch (Exception e)
+      {
+         getLogger().error("Caught exception in doPrepare() ", e);
+         if (backup != null && tempRollback == null)
+         {
+            // We failed during backup creation.
+            // Discard unneeded backup copy
+            backup.delete();
+         }
+      }
+      return false;
+   }
+
+   @Override
+   protected void doRollbackFromCancelled()
+   {
+      // no-op
+   }
+
+   @Override
+   protected void doRollbackFromComplete()
+   {
+      safeCleanup();
+   }
+
+   @Override
+   protected void doRollbackFromOpen()
+   {
+      safeCleanup();
+   }
+
+   @Override
+   protected void doRollbackFromPrepared()
+   {
+      boolean cleanRollback = true;
+      
+      rollbackContentMetadata();
+      
+      if (targetWasDir)
+      {
+         targetFile.delete();
+         targetFile.mkdirs();
+         targetFile.setLastModified(targetTimestamp);
+      }
+      else if (targetExists)
+      {
+         // Restore it
+         try
+         {
+            FileUtil.localMove(tempRollback, targetFile, this.targetTimestamp);
+         }
+         catch (IOException e)
+         {
+            getLogger().error("Failed restoring " + targetFile + " during rollback. " +
+                    "Backup copy is stored in " + tempRollback, e);
+            // Don't discard the rollback file
+            cleanRollback = false;
+         }
+      }
+      else
+      {
+         targetFile.delete();
+      }  
+      
+      safeCleanup(cleanRollback);
+   }
+
+   @Override
+   protected void doRollbackFromRollbackOnly()
+   {
+      boolean cleanRollback = true;
+      // We get here either from complete or from prepare. We
+      // can tell which by whether tempRollback exists
+      if (tempRollback != null && tempRollback.exists())
+      {
+         // Restore it
+         try
+         {
+            FileUtil.localMove(tempRollback, targetFile, targetTimestamp);
+         }
+         catch (IOException e)
+         {
+            getLogger().error("Failed restoring " + targetFile + " during rollback. " +
+            		"Backup copy is stored in " + tempRollback, e);
+            // Don't discard the rollback file
+            cleanRollback = false;
+         }
+      }
+      else if (targetWasDir)
+      {
+         if (targetFile.exists())
+         {
+            if (targetFile.isDirectory() == false)
+            {
+               targetFile.delete();
+               targetFile.mkdirs();
+               targetFile.setLastModified(targetTimestamp);
+            }
+         }
+         else
+         {
+            targetFile.mkdirs();
+            targetFile.setLastModified(targetTimestamp);
+         }
+      }
+      else if (targetExists == false)
+      {
+         // if we created one, get rid of it
+         targetFile.delete();
+      } 
+      
+      safeCleanup(cleanRollback);
+   }
+
+   @Override
+   protected void doCommit()
+   {
+      updateContentMetadata();
+      safeCleanup();
+   }
+
+   protected File createTempFile() throws IOException
+   {
+      FileBasedSynchronizationActionContext ctx = getContext();
+      File f = FileUtil.createTempFile(ctx.getTempDir(), ctx.getStoreName());
+      f.deleteOnExit();
+      return f;
+   }
+
+   protected synchronized void safeCleanup(boolean cleanRollback)
+   {
+      if (cleanRollback && tempRollback != null)
+      {
+         tempRollback.delete();
+      }      
+   }
+
+   protected void safeCleanup()
+   {
+      safeCleanup(true);
+   }
+
+}
\ No newline at end of file


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/AbstractLocalContentChangeAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/DirectoryTimestampUpdateAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/DirectoryTimestampUpdateAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/DirectoryTimestampUpdateAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,126 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.local.file;
+
+import java.io.File;
+
+import org.jboss.system.server.profileservice.repository.clustered.sync.AbstractContentMetadataMutatorAction;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ContentModification;
+
+/**
+ * RepositorySynchronizationAction that updates a directory lastModified time.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class DirectoryTimestampUpdateAction extends AbstractContentMetadataMutatorAction<FileBasedSynchronizationActionContext>
+{
+   private final File targetDir;
+   private final long rollbackTimestamp;
+   
+   /**
+    * Create a new DirectoryTimestampUpdateAction.
+    * 
+    * @param targetFile the directory whose timestamp is to be changed
+    * @param context the overall context of the modification
+    * @param modification the modification
+    */
+   public DirectoryTimestampUpdateAction(File targetDir,
+         FileBasedSynchronizationActionContext context,
+         ContentModification modification)
+   {
+      super(context, modification);
+      
+      if (targetDir == null)
+      {
+         throw new IllegalArgumentException("Null targetDir");
+      }
+      if (targetDir.exists() == false)
+      {
+         throw new IllegalArgumentException(targetDir + " does not exist");
+      }
+      if (targetDir.isDirectory() == false)
+      {
+         throw new IllegalArgumentException(targetDir + " is not a directory");
+      }
+      this.targetDir = targetDir;
+      this.rollbackTimestamp = targetDir.lastModified();
+   }
+
+   @Override
+   protected void doCancel()
+   {
+      // no-op
+   }
+
+   @Override
+   protected void doCommit()
+   {
+      updateContentMetadata();
+   }
+
+   @Override
+   protected void doComplete() throws Exception
+   {
+      // no-op
+   }
+
+   @Override
+   protected boolean doPrepare()
+   {
+      targetDir.setLastModified(getRepositoryContentModification().getItem().getTimestamp());
+      return true;
+   }
+
+   @Override
+   protected void doRollbackFromCancelled()
+   {
+      // no-op
+   }
+
+   @Override
+   protected void doRollbackFromComplete()
+   {
+      // no-op
+   }
+
+   @Override
+   protected void doRollbackFromOpen()
+   {
+      // no-op
+   }
+
+   @Override
+   protected void doRollbackFromPrepared()
+   {
+      targetDir.setLastModified(rollbackTimestamp);
+   }
+
+   @Override
+   protected void doRollbackFromRollbackOnly()
+   {
+      targetDir.setLastModified(rollbackTimestamp);
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/DirectoryTimestampUpdateAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileBasedSynchronizationActionContext.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileBasedSynchronizationActionContext.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileBasedSynchronizationActionContext.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,67 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.local.file;
+
+import java.io.File;
+
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationActionContext;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationId;
+
+/**
+ * {@link SynchronizationActionContext} subclass that provides additional
+ * contextual information useful to filesystem based actions.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class FileBasedSynchronizationActionContext extends SynchronizationActionContext
+{
+   private final File tempDir;
+   private final String storeName;
+   
+   public FileBasedSynchronizationActionContext(SynchronizationId<?> id, 
+         RepositoryContentMetadata inProgressMetadata, File tempDir, String storeName)
+   {
+      super(id, inProgressMetadata);
+      if (storeName == null)
+      {
+         throw new IllegalArgumentException("Null storeName");
+      }
+      this.storeName = storeName;
+      this.tempDir = tempDir;
+   }
+
+   public File getTempDir()
+   {
+      return tempDir;
+   }
+
+   public String getStoreName()
+   {
+      return storeName;
+   }
+   
+   
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileBasedSynchronizationActionContext.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileReadAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileReadAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileReadAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,189 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.local.file;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.jboss.logging.Logger;
+import org.jboss.system.server.profileservice.repository.clustered.sync.AbstractSynchronizationAction;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ByteChunk;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ContentModification;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationReadAction;
+
+/**
+ * {@link SynchronizationReadAction} that reads from a {@link File}.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class FileReadAction extends AbstractSynchronizationAction<FileBasedSynchronizationActionContext>
+      implements SynchronizationReadAction<FileBasedSynchronizationActionContext>
+{
+   private static final Logger log = Logger.getLogger(FileReadAction.class);
+   
+   /** 
+    * Max file transfer buffer size that we read at a time.
+    * This influences the number of times that we will invoke disk read/write file
+    * operations versus how much memory we will consume for a file transfer. 
+    */
+   public static final int MAX_CHUNK_BUFFER_SIZE = 512 * 1024;
+   
+   private final File file;
+   private InputStream stream;
+   
+   /**
+    * Create a new FileReadAction.
+    * 
+    * @param file the file to read
+    * @param context the overall context of the modification
+    * @param modification the modification
+    */
+   public FileReadAction(File file, FileBasedSynchronizationActionContext context, 
+                         ContentModification modification)
+   {
+      super(context, modification);
+      if (file == null)
+      {
+         throw new IllegalArgumentException("Null file");
+      }
+      this.file = file;
+   }
+   
+   // ------------------------------------  RepositorySynchronizationReadAction
+
+   public ByteChunk getNextBytes() throws IOException
+   {
+      InputStream is = getInputStream();
+      byte[] b = null;
+      int read = -1;
+      synchronized (is)
+      {
+         b = new byte[MAX_CHUNK_BUFFER_SIZE];
+         read = is.read(b);
+      }
+      return new ByteChunk(b, read);
+   }
+   
+   // --------------------------------------------------------------  Protected
+
+   @Override
+   protected void doCancel()
+   {
+      safeCloseStream();
+   }
+
+   @Override
+   protected void doCommit()
+   {
+      safeCloseStream();
+   }
+
+   @Override
+   protected void doComplete() throws Exception
+   {
+      safeCloseStream();
+   }
+
+   @Override
+   protected boolean doPrepare()
+   {
+      safeCloseStream();
+      return true;
+   }
+
+   @Override
+   protected void doRollbackFromCancelled()
+   {
+      safeCloseStream();
+   }
+
+   @Override
+   protected void doRollbackFromComplete()
+   {
+      safeCloseStream();
+   }
+
+   @Override
+   protected void doRollbackFromOpen()
+   {
+      safeCloseStream();
+   }
+
+   @Override
+   protected void doRollbackFromPrepared()
+   {
+      safeCloseStream();
+   }
+
+   @Override
+   protected void doRollbackFromRollbackOnly()
+   {
+      safeCloseStream();
+   }
+
+   private synchronized InputStream getInputStream() throws IOException
+   {
+      State s = getState();
+      if (s != State.OPEN && s != State.CANCELLED)
+      {
+         throw new IllegalStateException("Cannot read when state is " + s);
+      }
+      
+      if (stream == null)
+      {
+         FileInputStream fis = new FileInputStream(file);
+         stream = new BufferedInputStream(fis);
+      }
+      return stream;
+   }
+   
+   private synchronized void safeCloseStream()
+   {
+      if (stream != null)
+      {
+         synchronized (stream)
+         {
+            try
+            {
+               stream.close();
+            }
+            catch (IOException e)
+            {
+               ContentModification mod = getRepositoryContentModification();
+               log.debug("Caught exception closing stream for " + mod.getRootName() + 
+                     " " + mod.getItem().getRelativePath(), e);
+            }
+            finally
+            {
+               stream = null;
+            }
+         }
+      }
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileReadAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileUtil.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileUtil.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileUtil.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,136 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.local.file;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+
+import org.jboss.logging.Logger;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryItemMetadata;
+
+/**
+ * Utility methods related to filesystem operations.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class FileUtil
+{
+   private static final Logger log = Logger.getLogger(FileUtil.class);
+   
+   public static void localMove(File source, File destination, long modifiedTime) throws IOException 
+   {
+      // if we can simply rename the file, all the better
+      if(!source.renameTo(destination))
+      {
+         // otherwise, copy source to destination
+         OutputStream out = new FileOutputStream(destination);
+         InputStream in = new FileInputStream(source);
+         byte buffer[] = new byte[32*1024];
+         int bytesRead = 0;
+         while(bytesRead > -1) // until we hit end of source file
+         {  
+            bytesRead = in.read(buffer);
+            if(bytesRead > 0) 
+            {
+               out.write(buffer,0, bytesRead);
+            }
+         }
+         in.close();
+         out.close();
+      }
+      
+      destination.setLastModified(modifiedTime);
+   }
+   
+   public static File getFileForItem(URI rootURI, RepositoryItemMetadata item)
+   {
+      File f = new File(rootURI);
+      for (String element : item.getRelativePathElements())
+      {
+         f = new File(f, element);
+      }
+      return f;
+   }
+   
+   public static File createTempFile(String tmpDirName, String partitionName) throws IOException
+   {
+      if (tmpDirName == null)
+      {
+         return File.createTempFile(partitionName, "tmp");
+      }
+      else
+      {
+         return createTempFile(createTempDir(tmpDirName), partitionName);
+      }
+   }
+   
+   public static File createTempFile(File tmpDir, String partitionName) throws IOException
+   {
+      if (tmpDir.exists() == false)
+      {
+         tmpDir.mkdirs();
+      }
+      return File.createTempFile(partitionName, "tmp", tmpDir);      
+   }
+   
+   public static File createTempDir(String tmpDirName) throws IOException
+   {
+      File dir = new File(tmpDirName);
+      if (! dir.exists())
+      {
+         dir.mkdirs();
+      }
+      else if (! dir.isDirectory())
+      {
+         throw new IllegalStateException(dir + " already exists and is not a directory");
+      }
+      return dir;
+   }
+   
+   public static void safeCloseStream(OutputStream os, Object id)
+   {      
+      try
+      {
+         os.close();
+      }
+      catch (IOException e)
+      {
+         log.trace("Failed to close temporary output stream for " + id, e);
+      }
+   }
+
+   /**
+    * Prevent instantiation. 
+    */
+   private FileUtil()
+   {      
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileUtil.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileWriteAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileWriteAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileWriteAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,167 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.local.file;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.jboss.logging.Logger;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ByteChunk;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ContentModification;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationReadAction;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationWriteAction;
+
+/**
+ * {@link SynchronizationReadAction} that writes to a {@link File}.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class FileWriteAction extends AbstractLocalContentChangeAction
+      implements SynchronizationWriteAction<FileBasedSynchronizationActionContext>
+{
+   private static final Logger log = Logger.getLogger(FileWriteAction.class);
+   
+   private File tempFile;
+   private OutputStream stream;
+
+   /**
+    * Create a new FileWriteAction.
+    * 
+    * @param targetFile the file to write to
+    * @param context the overall context of the modification
+    * @param modification the modification
+    */
+   public FileWriteAction(File targetFile, FileBasedSynchronizationActionContext context, 
+         ContentModification modification)
+   {
+      super(targetFile, context, modification);
+   }
+
+   // -----------------------------------  RepositorySynchronizationWriteAction
+   
+   public void writeBytes(ByteChunk bytes) throws IOException
+   {
+      if (bytes == null)
+      {
+         throw new IllegalArgumentException("Null bytes");
+      }
+      if (bytes.getByteCount() < 0)
+      {
+         throw new IllegalArgumentException("Illegal byte count " + bytes.getByteCount());
+      }
+      OutputStream os = getOutputStream();
+      os.write(bytes.getBytes(), 0, bytes.getByteCount());
+   }
+
+   // --------------------------------------------------------------  Protected
+
+
+   @Override
+   protected void doComplete() throws Exception
+   {
+      // Done writing
+      safeCloseStream();
+      super.doComplete();
+   }
+   
+   @Override
+   protected boolean modifyTarget() throws IOException
+   {
+      // Our temp file replaces targetFile
+      FileUtil.localMove(tempFile, getTargetFile(), getRepositoryContentModification().getItem().getTimestamp());
+      return true;
+   }
+   
+   @Override
+   protected Logger getLogger()
+   {
+      return log;
+   }
+
+   protected synchronized void safeCleanup(boolean cleanRollback)
+   {
+      super.safeCleanup(cleanRollback);
+      safeCloseStream();
+      if (tempFile != null)
+      {
+         tempFile.delete();
+      }    
+   }
+   
+   // ----------------------------------------------------------------  Private
+   
+   private synchronized OutputStream getOutputStream() throws IOException
+   {
+      State s = getState();
+      if (s != State.OPEN && s != State.CANCELLED)
+      {
+         throw new IllegalStateException("Cannot write when state is " + s);
+      }
+      
+      if (stream == null)
+      {
+         FileOutputStream fos = new FileOutputStream(getTempFile());
+         stream = new BufferedOutputStream(fos);
+      }
+      return stream;
+   }
+   
+   private File getTempFile() throws IOException
+   {
+      if (tempFile == null)
+      {
+         tempFile = createTempFile();
+      }
+      return tempFile;
+   }
+   
+   private synchronized void safeCloseStream()
+   {
+      if (stream != null)
+      {
+         synchronized (stream)
+         {
+            try
+            {
+               stream.close();
+            }
+            catch (IOException e)
+            {
+               ContentModification mod = getRepositoryContentModification();
+               log.debug("Caught exception closing stream for " + mod.getRootName() + 
+                     " " + mod.getItem().getRelativePath(), e);
+            }
+            finally
+            {
+               stream = null;
+            }
+         }
+      }
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FileWriteAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FilesystemLocalContentManager.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FilesystemLocalContentManager.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FilesystemLocalContentManager.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,229 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.local.file;
+
+import java.io.File;
+import java.net.URI;
+import java.util.Map;
+
+import org.jboss.profileservice.spi.ProfileKey;
+import org.jboss.system.server.profileservice.repository.clustered.local.AbstractLocalContentManager;
+import org.jboss.system.server.profileservice.repository.clustered.local.ContentMetadataPersister;
+import org.jboss.system.server.profileservice.repository.clustered.local.LocalContentManager;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ContentModification;
+import org.jboss.system.server.profileservice.repository.clustered.sync.NoOpSynchronizationAction;
+import org.jboss.system.server.profileservice.repository.clustered.sync.RemoteRemovalAction;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SimpleSynchronizationRemoteAction;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationId;
+import org.jboss.system.server.profileservice.repository.clustered.sync.TwoPhaseCommitAction;
+
+/**
+ * {@link LocalContentManager} that persists to the local filesystem.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class FilesystemLocalContentManager extends AbstractLocalContentManager<FileBasedSynchronizationActionContext>
+{
+   private final File tmpDir;
+   
+   /**
+    * Create a new FilesystemRepositoryContentPersister.
+    * 
+    * @param namedURIMap
+    * @param storeName
+    * @param localNodeName
+    * @param contentMetadataPersister
+    */
+   protected FilesystemLocalContentManager(Map<String, URI> namedURIMap, ProfileKey profileKey, String localNodeName,
+         ContentMetadataPersister contentMetadataPersister, URI tempDirURI)
+   {
+      super(namedURIMap, profileKey, localNodeName, contentMetadataPersister);
+      
+      if (tempDirURI != null)
+      {
+         this.tmpDir = new File(tempDirURI);
+      }
+      else
+      {
+         this.tmpDir = null;
+      }
+   }
+   
+   // --------------------------------------------------------------  Protected
+
+   @Override
+   protected FileBasedSynchronizationActionContext createSynchronizationActionContext(
+         SynchronizationId<?> id, RepositoryContentMetadata toUpdate)
+   {
+      return new FileBasedSynchronizationActionContext(id, toUpdate, tmpDir, getStoreName());
+   }
+   
+   @Override
+   protected TwoPhaseCommitAction<FileBasedSynchronizationActionContext> createPullFromClusterAction(
+         ContentModification mod, boolean localLed)
+   {
+      File targetFile = FileUtil.getFileForItem(getRootURIForModification(mod), mod.getItem());
+      if (localLed)
+      {
+         return new FileWriteAction(targetFile, getSynchronizationActionContext(), mod);
+      }
+      else
+      {
+         return new FileReadAction(targetFile, getSynchronizationActionContext(), mod);    
+      }
+   }
+
+   @Override
+   protected TwoPhaseCommitAction<FileBasedSynchronizationActionContext> createPushToClusterAction(ContentModification mod,
+         boolean localLed)
+   {
+      File targetFile = FileUtil.getFileForItem(getRootURIForModification(mod), mod.getItem());
+      if (localLed)
+      {
+         return new FileReadAction(targetFile, getSynchronizationActionContext(), mod); 
+      }
+      else
+      {
+         return new FileWriteAction(targetFile, getSynchronizationActionContext(), 
+                                    mod);       
+      }
+   }
+
+   @Override
+   protected TwoPhaseCommitAction<FileBasedSynchronizationActionContext> createRemoveFromClusterAction(
+         ContentModification mod, boolean localLed)
+   {
+      if (localLed)
+      {
+         File targetFile = FileUtil.getFileForItem(getRootURIForModification(mod), mod.getItem());
+         return new RemoveFileAction(targetFile, getSynchronizationActionContext(), 
+                                     mod); 
+      }
+      else
+      {
+         // nothing to do on a remote node
+         return new NoOpSynchronizationAction<FileBasedSynchronizationActionContext>(getSynchronizationActionContext(), mod);         
+      }
+   }
+
+   @Override
+   protected TwoPhaseCommitAction<FileBasedSynchronizationActionContext> createRemoveToClusterAction(
+         ContentModification mod, boolean localLed)
+   {
+      if (localLed)
+      {
+         return new RemoteRemovalAction<FileBasedSynchronizationActionContext>(getSynchronizationActionContext(), mod);      
+      }
+      else
+      {
+         File targetFile = FileUtil.getFileForItem(getRootURIForModification(mod), mod.getItem());
+         return new RemoveFileAction(targetFile, getSynchronizationActionContext(), 
+                                     mod);       
+      }
+   }
+
+   @Override
+   protected TwoPhaseCommitAction<FileBasedSynchronizationActionContext> createDirectoryTimestampMismatchAction(ContentModification mod,
+         boolean localLed)
+   {
+      if (localLed)
+      {
+         File targetFile = FileUtil.getFileForItem(getRootURIForModification(mod), mod.getItem());
+         return new DirectoryTimestampUpdateAction(targetFile, getSynchronizationActionContext(), 
+                                                   mod);
+      }
+      else
+      {
+         // nothing to do on a remote node
+         return new NoOpSynchronizationAction<FileBasedSynchronizationActionContext>(getSynchronizationActionContext(), mod);            
+      }
+   }
+
+   @Override
+   protected TwoPhaseCommitAction<FileBasedSynchronizationActionContext> createMkdirFromClusterAction(ContentModification mod, boolean localLed)
+   {
+      if (localLed)
+      {
+         File targetFile = FileUtil.getFileForItem(getRootURIForModification(mod), mod.getItem());
+         return new MkDirAction(targetFile, getSynchronizationActionContext(), 
+                                mod);
+      }
+      else
+      {
+         // nothing to do on a remote node
+         return new NoOpSynchronizationAction<FileBasedSynchronizationActionContext>(getSynchronizationActionContext(), mod);          
+      }
+   }
+
+   @Override
+   protected TwoPhaseCommitAction<FileBasedSynchronizationActionContext> createMkdirToClusterAction(ContentModification mod, boolean localLed)
+   {
+      if (localLed)
+      {
+         return new SimpleSynchronizationRemoteAction<FileBasedSynchronizationActionContext>(getSynchronizationActionContext(), mod);
+      }
+      else
+      {
+         File targetFile = FileUtil.getFileForItem(getRootURIForModification(mod), mod.getItem());
+         return new MkDirAction(targetFile, getSynchronizationActionContext(), 
+                                mod);   
+      }
+   }
+
+   @Override
+   protected TwoPhaseCommitAction<FileBasedSynchronizationActionContext> createPrepareRmdirFromClusterAction(ContentModification mod, boolean localLed)
+   {
+      if (localLed)
+      {
+         File targetFile = FileUtil.getFileForItem(getRootURIForModification(mod), mod.getItem());
+         return new InitiateRmdirAction(targetFile, getSynchronizationActionContext(), 
+                                       mod);
+      }
+      else
+      {
+         // nothing to do on a remote node
+         return new NoOpSynchronizationAction<FileBasedSynchronizationActionContext>(getSynchronizationActionContext(), mod);
+      }
+   }
+
+   @Override
+   protected TwoPhaseCommitAction<FileBasedSynchronizationActionContext> createPrepareRmdirToClusterAction(ContentModification mod, boolean localLed)
+   {
+      if (localLed)
+      {
+         boolean initiation = true;
+         return new SimpleSynchronizationRemoteAction<FileBasedSynchronizationActionContext>(getSynchronizationActionContext(), mod, initiation);
+      }
+      else
+      {
+         File targetFile = FileUtil.getFileForItem(getRootURIForModification(mod), mod.getItem());
+         return new InitiateRmdirAction(targetFile, getSynchronizationActionContext(), 
+                                       mod);
+      }
+   }
+   
+   
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FilesystemLocalContentManager.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FilesystemLocalContentManagerFactory.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FilesystemLocalContentManagerFactory.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FilesystemLocalContentManagerFactory.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,124 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.local.file;
+
+import java.io.File;
+import java.net.URI;
+import java.util.Collection;
+import java.util.Map;
+
+import org.jboss.profileservice.spi.ProfileKey;
+import org.jboss.system.server.profileservice.repository.clustered.local.ContentMetadataPersister;
+import org.jboss.system.server.profileservice.repository.clustered.local.LocalContentManager;
+import org.jboss.system.server.profileservice.repository.clustered.local.LocalContentManagerFactory;
+
+/**
+ * {@link LocalContentManagerFactory} that creates a
+ * {@link FilesystemLocalContentManager}.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class FilesystemLocalContentManagerFactory implements LocalContentManagerFactory<FileBasedSynchronizationActionContext>
+{
+   private ContentMetadataPersister metadataPersister;
+   private URI tempDirURI;
+   
+   // -------------------------------------------------------------  Properties
+
+   public ContentMetadataPersister getMetadataPersister()
+   {
+      return metadataPersister;
+   }
+
+   public void setMetadataPersister(ContentMetadataPersister metadataPersister)
+   {
+      this.metadataPersister = metadataPersister;
+   }
+   
+   
+   
+   // --------------------------------------  RepositoryContentPersisterFactory
+   
+   public URI getTempDirURI()
+   {
+      return tempDirURI;
+   }
+
+   public void setTempDirURI(URI tempDirURI)
+   {
+      this.tempDirURI = tempDirURI;
+   }
+
+   public boolean accepts(Collection<URI> uris)
+   {
+      try
+      {
+         testURIs(uris);
+         return true;
+      }
+      catch (Exception e)
+      {
+         return false;
+      }
+   }
+
+   public LocalContentManager<FileBasedSynchronizationActionContext> getRepositoryContentPersister(Map<String, URI> namedURIMap, ProfileKey profileKey,
+         String localNodeName)
+   {
+      if (this.metadataPersister == null)
+      {
+         throw new IllegalStateException("Null metadataPersister; must configure a " + 
+               ContentMetadataPersister.class.getSimpleName());
+      }
+      // Don't trust they called accept()
+      testURIs(namedURIMap.values());
+      // OK, looks good
+      return new FilesystemLocalContentManager(namedURIMap, profileKey, localNodeName, metadataPersister, tempDirURI);
+   }   
+   
+   // ----------------------------------------------------------------  Private
+   
+   /**
+    * Confirms whether each element of <code>uris</code> can be passed to
+    * {@link File}'s constructor, resulting in a <code>File</code> that
+    * {@link File#exists() exists}.
+    * 
+    * @param uris the collection of uris
+    * 
+    * @throws IllegalArgumentException if any URI fails the above test.
+    */
+   private static void testURIs(Collection<URI> uris)
+   {
+      for (URI uri : uris)
+      {
+         File f = new File(uri);
+         if (!f.exists())
+         {
+            throw new IllegalArgumentException("No file found for URI " + uri);
+         }
+      }     
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/FilesystemLocalContentManagerFactory.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/InitiateRmdirAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/InitiateRmdirAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/InitiateRmdirAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,80 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.local.file;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.jboss.logging.Logger;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ContentModification;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationAction;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationInitiationAction;
+
+/**
+ * {@link SynchronizationAction} that does nothing normally, but restores
+ * a removed directory during the rollback phase.
+ * <p>
+ * The intent is this action would execute at the start of a processing of
+ * removing a directory tree, followed by other actions to remove the contents
+ * of the tree, followed by a {@link RemoveFileAction} to remove the directory.
+ * This action does nothing during that sequence. But, during a rollback
+ * of the overall synchronization, it restores the removed directory, ensuring
+ * the directory is in place when the child removals roll back.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class InitiateRmdirAction 
+      extends AbstractLocalContentChangeAction
+      implements SynchronizationInitiationAction<FileBasedSynchronizationActionContext>
+{
+   private static final Logger log = Logger.getLogger(InitiateRmdirAction.class);
+
+   /**
+    * Create a new PrepareRmdirAction.
+    */
+   public InitiateRmdirAction(File targetFile, FileBasedSynchronizationActionContext context, 
+         ContentModification modification)
+   {
+      super(targetFile, context, modification);
+   }
+
+
+   // --------------------------------------------------------------  Protected
+
+
+   @Override
+   protected boolean modifyTarget() throws IOException
+   {
+      return true;
+   }
+   
+   @Override
+   protected Logger getLogger()
+   {
+      return log;
+   }
+   
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/InitiateRmdirAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/MkDirAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/MkDirAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/MkDirAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,83 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.local.file;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.jboss.logging.Logger;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ContentModification;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationAction;
+
+/**
+ * {@link SynchronizationAction} that makes a directory.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class MkDirAction extends AbstractLocalContentChangeAction
+{
+   private static final Logger log = Logger.getLogger(MkDirAction.class);
+
+   /**
+    * Create a new MkDirAction.
+    * 
+    * @param targetFile the directory to create
+    * @param context the overall context of the modification
+    * @param modification the modification
+    */
+   public MkDirAction(File targetFile, FileBasedSynchronizationActionContext context, 
+         ContentModification modification)
+   {
+      super(targetFile, context, modification);
+   }
+
+
+   // --------------------------------------------------------------  Protected
+
+
+   @Override
+   protected boolean modifyTarget() throws IOException
+   {
+      File target = getTargetFile();
+      
+      boolean ok = (target.exists() == false || target.delete());
+      if (ok)
+      {
+         ok = target.mkdirs();
+         if (ok)
+         {
+            target.setLastModified(getRepositoryContentModification().getItem().getTimestamp());
+         }
+      }
+      return ok;
+   }
+   
+   @Override
+   protected Logger getLogger()
+   {
+      return log;
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/MkDirAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/RemoveFileAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/RemoveFileAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/RemoveFileAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,128 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.local.file;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.jboss.logging.Logger;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryItemMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryRootMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ContentModification;
+import org.jboss.system.server.profileservice.repository.clustered.sync.SynchronizationAction;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ContentModification.Type;
+
+/**
+ * {@link SynchronizationAction} that removes a {@link File}.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class RemoveFileAction extends AbstractLocalContentChangeAction
+{
+   private static final Logger log = Logger.getLogger(RemoveFileAction.class);
+
+   /**
+    * Create a new RemoveFileAction.
+    */
+   public RemoveFileAction(File targetFile, FileBasedSynchronizationActionContext context, 
+         ContentModification modification)
+   {
+      super(targetFile, context, modification);
+   }
+
+
+   // --------------------------------------------------------------  Protected
+
+
+   @Override
+   protected boolean modifyTarget() throws IOException
+   {
+      File target = getTargetFile();
+      return target.exists() == false || target.delete();
+   }
+   
+   @Override
+   protected Logger getLogger()
+   {
+      return log;
+   }
+
+
+   @Override
+   protected void doRollbackFromPrepared()
+   {
+      if (getRepositoryContentModification().getItem().isDirectory() == false)
+      {
+         super.doRollbackFromPrepared();
+      }
+      // else we assume there was a PrepareRmdir action that handled
+      // the rollback of the directory removal
+   }
+
+
+   @Override
+   protected void doRollbackFromRollbackOnly()
+   {
+      if (getRepositoryContentModification().getItem().isDirectory() == false)
+      {
+         super.doRollbackFromRollbackOnly();
+      }
+      // else we assume there was a PrepareRmdir action that handled
+      // the rollback of the directory removal
+   }
+
+
+   @Override
+   protected void updateContentMetadata()
+   {
+      ContentModification mod = getRepositoryContentModification();
+      RepositoryItemMetadata modItem = mod.getItem();
+      if (modItem.isRemoved())
+      {
+         // Just record it
+         super.updateContentMetadata();
+      }
+      else if (mod.getType() == Type.REMOVE_FROM_CLUSTER)
+      {
+         // An addition has been rejected. We don't record the item as
+         // removed in the metadata, we just remove it.
+         RepositoryContentMetadata contentMetadata = getContext().getInProgressMetadata();
+         RepositoryRootMetadata rmd = contentMetadata.getRepositoryRootMetadata(mod.getRootName());
+         rmd.removeItemMetadata(modItem.getRelativePathElements());         
+      }
+      else
+      {
+         // Add a record of the item, marked as removed
+         RepositoryItemMetadata markedRemoved = getMarkedRemovedItem(mod);
+         RepositoryContentMetadata contentMetadata = getContext().getInProgressMetadata();
+         RepositoryRootMetadata rmd = contentMetadata.getRepositoryRootMetadata(mod.getRootName());
+         rmd.addItemMetadata(markedRemoved);
+      }
+   }
+   
+   
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/local/file/RemoveFileAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/AbstractSortedMetadataContainer.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/AbstractSortedMetadataContainer.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/AbstractSortedMetadataContainer.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,224 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.metadata;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Base class for objects that maintain a sorted collection of child 
+ * {@link Identifiable} metadata and also provide efficient lookup capability
+ * based on a child item's {@link Identifiable#getId() id}.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public abstract class AbstractSortedMetadataContainer<K, T extends Identifiable<K>>
+   implements Serializable
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = 7130572488073615772L;
+   
+   protected SortedSet<T> sortedItems = new TreeSet<T>();
+   private transient Map<K, T> itemMap = new ConcurrentHashMap<K, T>();
+   private transient Collection<T> exposedCollection = new MetadataCollection();
+   
+   // --------------------------------------------------------------- Protected
+   
+   /**
+    * Gets a collection that can be exposed to external callers. Modifications
+    * to the collection affect the internal state of this object. The
+    * iterator exposed by this collection will provide items ordered by
+    * the natural ordering of <code>T</code>. The returned collection is not 
+    * thread safe.
+    */
+   protected Collection<T> getExposedCollection()
+   {
+      return exposedCollection;
+   }
+   
+   /**
+    * Gets the metadata object identified by <code>key</code>.
+    * 
+    * @param key the key
+    * @return the metadata, or <code>null</code> if <code>key</code> is unknown.
+    */
+   protected T getContainedMetadata(K key)
+   {
+      return itemMap.get(key);
+   }
+   
+   /**
+    * Gets an unmodifiable view of the {@link Identifiable#getId() ids} of the
+    * metadata stored in this container. 
+    * 
+    * @return the ids. Will not be <code>null</code>.
+    */
+   protected Set<K> getContainedMetadataIds()
+   {
+      return Collections.unmodifiableSet(itemMap.keySet());
+   }
+   
+   // ----------------------------------------------------------------- Private
+   
+   private void readObject(java.io.ObjectInputStream in)
+      throws IOException, ClassNotFoundException
+   {
+      in.defaultReadObject();
+      exposedCollection = new MetadataCollection();
+      itemMap = new ConcurrentHashMap<K, T>();
+      for (T item : sortedItems)
+      {
+         itemMap.put(item.getId(), item);
+      }
+   }
+   
+   private class MetadataCollection implements Collection<T>
+   {    
+      public boolean add(T toAdd)
+      {      
+         K id = toAdd.getId();
+         T existing = itemMap.put(id, toAdd);
+         boolean change = (toAdd.equals(existing) == false);
+         if (change)
+         {
+            if (existing != null)
+            {
+               sortedItems.remove(existing);
+            }
+            sortedItems.add(toAdd);
+         }
+         return change;
+      }
+
+      public boolean addAll(Collection<? extends T> c)
+      {
+         boolean mod = false;
+         for (T t : c)
+         {
+            if (add(t))
+            {
+               mod = true;
+            }
+         }
+         return mod;
+      }
+
+      public void clear()
+      {
+         itemMap.clear();
+         sortedItems.clear();
+      }
+
+      public boolean contains(Object o)
+      {
+         return sortedItems.contains(o);
+      }
+
+      public boolean containsAll(Collection<?> c)
+      {
+         return sortedItems.containsAll(c);
+      }
+
+      public boolean isEmpty()
+      {
+         return sortedItems.isEmpty();
+      }
+
+      public Iterator<T> iterator()
+      {
+         return sortedItems.iterator();
+      }
+
+      public boolean remove(Object toRemove)
+      {
+         boolean result = sortedItems.remove(toRemove);
+         if (result)
+         {
+            @SuppressWarnings("unchecked")
+            T item = (T) toRemove;
+            itemMap.remove(item.getId());
+         }
+         return result;
+      }
+
+      public boolean removeAll(Collection<?> c)
+      {
+         boolean mod = false;
+         for (Object o : c)
+         {
+            if (remove(o))
+            {
+               mod = true;
+            }
+         }
+         return mod;
+      }
+
+      public boolean retainAll(Collection<?> c)
+      {
+         throw new UnsupportedOperationException("retainAll is not supported");
+      }
+
+      public int size()
+      {
+         return sortedItems.size();
+      }
+
+      public Object[] toArray()
+      {
+         return sortedItems.toArray();
+      }
+
+      public <E> E[] toArray(E[] a)
+      {
+         return sortedItems.toArray(a);
+      }
+      
+      
+      public boolean equals(Object other)
+      {
+         if (other == this)
+         {
+            return true;
+         }
+         
+         return sortedItems.equals(other);
+      }
+      
+      public int hashCode()
+      {
+         return sortedItems.hashCode();
+      }
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/AbstractSortedMetadataContainer.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/ClusteredProfileSourceMetaData.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/ClusteredProfileSourceMetaData.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/ClusteredProfileSourceMetaData.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,48 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.metadata;
+
+import javax.xml.bind.annotation.XmlAttribute;
+
+import org.jboss.system.server.profile.repository.metadata.AbstractProfileSourceMetaData;
+
+/**
+ * @author Brian Stansberry
+ *
+ */
+public abstract class ClusteredProfileSourceMetaData extends AbstractProfileSourceMetaData
+{
+   private String partitionName;
+   
+   @XmlAttribute(name = "partition")
+   public String getPartitionName()
+   {
+      return partitionName;
+   }
+
+   public void setPartitionName(String partitionName)
+   {
+      this.partitionName = partitionName;
+   }  
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/ClusteredProfileSourceMetaData.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/HotDeploymentClusteredProfileSourceMetaData.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/HotDeploymentClusteredProfileSourceMetaData.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/HotDeploymentClusteredProfileSourceMetaData.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,35 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.metadata;
+
+
+/**
+ * @author Brian Stansberry
+ *
+ */
+public class HotDeploymentClusteredProfileSourceMetaData extends ClusteredProfileSourceMetaData
+{
+
+   
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/HotDeploymentClusteredProfileSourceMetaData.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/Identifiable.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/Identifiable.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/Identifiable.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,35 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.metadata;
+
+/**
+ *
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public interface Identifiable<T>
+{
+   T getId();
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/Identifiable.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/ImmutableClusteredProfileSourceMetaData.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/ImmutableClusteredProfileSourceMetaData.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/ImmutableClusteredProfileSourceMetaData.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,32 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.metadata;
+
+/**
+ * @author Brian Stansberry
+ *
+ */
+public class ImmutableClusteredProfileSourceMetaData extends ClusteredProfileSourceMetaData
+{
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/ImmutableClusteredProfileSourceMetaData.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/RepositoryContentMetadata.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/RepositoryContentMetadata.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/RepositoryContentMetadata.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,247 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.metadata;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Set;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlNsForm;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+import org.jboss.profileservice.spi.ProfileKey;
+import org.jboss.xb.annotations.JBossXmlSchema;
+
+/**
+ * Concise description of the contents of a ClusteredDeploymentRepository.
+ * 
+ * @author Brian Stansberry
+ */
+ at XmlRootElement(name="repository-content", namespace= "")
+ at JBossXmlSchema(ignoreUnresolvedFieldOrClass=false,
+      namespace="",
+      elementFormDefault=XmlNsForm.UNSET,
+      normalizeSpace=true)
+ at XmlType(name="repositoryContentType", propOrder={"repositories", "name", "server", "domain"})
+ at XmlAccessorType(XmlAccessType.NONE)
+public class RepositoryContentMetadata 
+   extends AbstractSortedMetadataContainer<String, RepositoryRootMetadata>
+   implements Serializable
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = -557008659849613674L;
+   
+   private String domain;
+   private String server;
+   private String name;
+   
+   /**
+    * Default constructor.
+    */
+   public RepositoryContentMetadata()
+   {
+      
+   }
+   
+   public RepositoryContentMetadata(ProfileKey key)
+   {
+      this();
+      
+      if (key == null)
+      {
+         throw new IllegalArgumentException("Null key");
+      }
+      setDomain(key.getDomain());
+      setServer(key.getServer());
+      setName(key.getName());
+   }
+   
+   /**
+    * Copy constructor.
+    * 
+    * @param toCopy the item to copy
+    * 
+    * @throws IllegalArgumentException if <code>toCopy</code> is <code>null</code>
+    */
+   public RepositoryContentMetadata(RepositoryContentMetadata toCopy)
+   {
+      this();
+      
+      if (toCopy == null)
+      {
+         throw new IllegalArgumentException("Null toCopy");
+      }
+      
+      setDomain(toCopy.getDomain());
+      setServer(toCopy.getServer());
+      setName(toCopy.getName());
+      
+      Collection<RepositoryRootMetadata> exposed = getExposedCollection();
+      for (RepositoryRootMetadata rmd : toCopy.getRepositories())
+      {
+         exposed.add(new RepositoryRootMetadata(rmd));
+      }
+   }
+   
+   @XmlElement(name = "repository-root", type = RepositoryRootMetadata.class, required=true)   
+   public Collection<RepositoryRootMetadata> getRepositories()
+   {
+      return getExposedCollection();
+   }
+
+   public void setRepositories(Collection<RepositoryRootMetadata> repositories)
+   {
+      Collection<RepositoryRootMetadata> internal = getExposedCollection();
+      internal.clear();
+      
+      if (repositories != null)
+      {
+         internal.addAll(repositories);
+      }
+   }
+
+   public RepositoryRootMetadata getRepositoryRootMetadata(String repositoryRoot)
+   {
+      return getContainedMetadata(repositoryRoot);
+   }
+   
+   public Set<String> getRootNames()
+   {
+      return getContainedMetadataIds();
+   }
+
+   @XmlAttribute(name = "name", required=true)
+   public String getName()
+   {
+      return name;
+   }
+
+   public void setName(String name)
+   {      
+      if (name == null)
+      {
+         throw new IllegalArgumentException("Null name");
+      }
+      this.name = name;
+   }
+   
+   @XmlAttribute(name = "server", required=true)
+   public String getServer()
+   {
+      return server;
+   }
+
+   public void setServer(String server)
+   {      
+      if (server == null)
+      {
+         throw new IllegalArgumentException("Null server");
+      }
+      this.server = server;
+   }
+
+   @XmlAttribute(name = "domain", required=true)
+   public String getDomain()
+   {
+      return domain;
+   }
+
+   public void setDomain(String domain)
+   {      
+      if (domain == null)
+      {
+         throw new IllegalArgumentException("Null domain");
+      }
+      this.domain = domain;
+   }
+
+   // --------------------------------------------------------------  Overrides
+
+   @Override
+   public boolean equals(Object obj)
+   {  
+      if (this == obj)
+      {
+         return true;
+      }
+      
+      if (obj instanceof RepositoryContentMetadata)
+      {
+         RepositoryContentMetadata other = (RepositoryContentMetadata) obj;
+         return (getExposedCollection().equals(other.getExposedCollection())
+                 && safeEquals(this.name, other.name)
+                 && safeEquals(this.server, other.server)
+                 && safeEquals(this.domain, other.domain));
+      }
+      
+      return false;
+   }
+
+   @Override
+   public int hashCode()
+   {
+
+      int result = 17;
+      result = 31 * result + (name == null ? 0 : name.hashCode());
+      result = 31 * result + (server == null ? 0 : server.hashCode());
+      result = 31 * result + (domain == null ? 0 : domain.hashCode());
+      result = 31 * result + getExposedCollection().hashCode();
+      return result;
+   }
+
+   @Override
+   public String toString()
+   {
+      StringBuilder sb = new StringBuilder(getClass().getName())
+            .append("[domain=").append(domain)
+            .append(",server=").append(server)
+            .append(",name=").append(name)
+            .append(",roots={");
+      boolean first = true;
+      for (String root : getRootNames())
+      {
+         if (!first)
+         {
+            sb.append(',');
+         }
+         else
+         {
+            first = false;
+         }
+         sb.append(root);
+      }
+      sb.append("}]");
+      return sb.toString();
+   }
+   
+   private static boolean safeEquals(Object a, Object b)
+   {
+      return (a == b || (a != null && a.equals(b)));
+   }
+   
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/RepositoryContentMetadata.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/RepositoryItemMetadata.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/RepositoryItemMetadata.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/RepositoryItemMetadata.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,378 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.metadata;
+
+import java.io.Serializable;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlTransient;
+import javax.xml.bind.annotation.XmlType;
+
+/**
+ * Description of an individual item (i.e. file) in a clustered repository.
+ * 
+ * @author Brian Stansberry
+ */
+ at XmlType(name="repositoryItemType", propOrder={"timestampAsString", "originatingNode", "removed", "directory", "relativePath"})
+public class RepositoryItemMetadata 
+   implements Identifiable<List<String>>, Serializable, Comparable<RepositoryItemMetadata>
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = 7712110893517082031L;
+   
+   private static final DateFormat dateFormat;
+   
+   static
+   {
+      dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");      
+   }
+   
+   /** 
+    * Marker value to pass to {@link #setTimestampAsString(String)} that will
+    * generate a timestamp equal to System.currentTimeMillis(). Useful for
+    * testing. 
+    */
+   public static String NOW = "NOW";
+   
+   private volatile String relativePath;
+   private volatile long timestamp;
+   private volatile boolean directory;
+   /** The parsed elements of relativePath. Lazy initialized */
+   private volatile transient List<String> pathElements;
+   
+   private volatile String originatingNode;
+   private volatile boolean removed;
+   private String rootName;
+   
+
+   public static List<String> getPathElements(String path)
+   {
+      String[] elements = path.split("/");
+      return Collections.unmodifiableList(Arrays.asList(elements));
+   }
+   
+   /**
+    * Constructor for XML parser.
+    */
+   public RepositoryItemMetadata()
+   {
+      
+   }
+   
+   public RepositoryItemMetadata(List<String> pathElements, long timestamp, String originatingNode, boolean directory, boolean removed)
+   {
+      setDirectory(directory);
+      setRelativePathElements(pathElements);
+      setTimestamp(timestamp);
+      setOriginatingNode(originatingNode);
+      setRemoved(removed);
+   }
+   
+   /**
+    * Copy constructor. Performs a deep copy of the path element list.
+    * 
+    * @param toCopy the item to copy
+    * 
+    * @throws NullPointerException if <code>toCopy</code> is <code>null</code>
+    */
+   public RepositoryItemMetadata(RepositoryItemMetadata toCopy)
+   {
+      this(toCopy.getRelativePathElements(), toCopy.getTimestamp(), 
+            toCopy.getOriginatingNode(), toCopy.isDirectory(), toCopy.isRemoved());
+   }
+   
+   public List<String> getId()
+   {
+      return getRelativePathElements();
+   }
+   
+   @XmlTransient
+   public String getRootName()
+   {
+      return rootName;
+   }
+
+   public void setRootName(String rootName)
+   {
+      this.rootName = rootName;
+   }
+   
+   @XmlAttribute(name = "relative-path", required=true)
+   public String getRelativePath()
+   {
+      return relativePath;
+   }
+   
+   public void setRelativePath(String path)
+   {
+      if (path != null && path.length() > 0 && '/' == path.charAt(0))
+      {
+         path = path.length() == 0 ? "" : path.substring(1);
+      }
+      this.relativePath = path;
+      this.pathElements = null;
+   }
+   
+   @XmlAttribute(name = "directory")
+   public boolean isDirectory()
+   {
+      return directory;
+   }
+
+   public void setDirectory(boolean directory)
+   {
+      this.directory = directory;
+      if (relativePath != null)
+      {
+         if (!directory && relativePath.endsWith("/"))
+         {
+            relativePath = relativePath.substring(0, relativePath.length() - 1);
+         }
+         else if (directory && relativePath.endsWith("/") == false)
+         {
+            relativePath += "/";
+         }
+      }
+   }
+
+   @XmlTransient
+   public long getTimestamp()
+   {
+      return timestamp;
+   }
+   
+   public void setTimestamp(long timestamp)
+   {
+      this.timestamp = timestamp;
+   }
+   
+   @XmlAttribute(name = "timestamp", required=true)
+   public String getTimestampAsString()
+   {
+      Date d = new Date(timestamp);
+      synchronized (dateFormat)
+      {
+         return dateFormat.format(d);
+      }
+   }
+   
+   public void setTimestampAsString(String timestamp)
+   {
+      if (NOW.equals(timestamp))
+      {
+         setTimestamp(System.currentTimeMillis());
+      }
+      else
+      {
+         try
+         {
+            synchronized (dateFormat)
+            {
+               Date d = dateFormat.parse(timestamp);
+               setTimestamp(d.getTime());
+            }
+         }
+         catch (ParseException e)
+         {
+            throw new RuntimeException("Failed to parse " + timestamp, e);
+         }
+      }
+   }
+   
+   @XmlTransient
+   public List<String> getRelativePathElements()
+   {
+      if (pathElements == null && relativePath != null)
+      {
+         String[] elements = relativePath.split("/");
+         setRelativePathElements(Arrays.asList(elements));
+      }
+      return pathElements;
+   }
+   
+   public void setRelativePathElements(List<String> pathElements)
+   {
+      if (pathElements == null)
+      {
+         this.pathElements = null;
+         this.relativePath = null;
+      }
+      else
+      {
+         this.pathElements = Collections.unmodifiableList(new ArrayList<String>(pathElements));
+         boolean first = true;
+         StringBuilder sb = new StringBuilder();
+         for (String element : pathElements)
+         {
+            if (!first)
+            {
+               sb.append('/');
+            }
+            else
+            {
+               first = false;
+            }
+            sb.append(element);
+         }
+         if (directory)
+         {
+            sb.append('/');
+         }
+         this.relativePath = sb.toString();
+      }
+   }  
+   
+   /**
+    * The name of the cluster node that propagated this version of
+    * the item to the cluster.
+    * 
+    * @return
+    */
+   @XmlAttribute(name = "originator", required=true)
+   public String getOriginatingNode()
+   {
+      return originatingNode;
+   }
+
+   public void setOriginatingNode(String originatingNode)
+   {
+      this.originatingNode = originatingNode;
+   }
+
+   @XmlAttribute(name = "removed")
+   public boolean isRemoved()
+   {
+      return removed;
+   }
+
+   public void setRemoved(boolean removed)
+   {
+      this.removed = removed;
+   }
+
+   /**
+    * Gets whether this item is a child of another item.
+    * 
+    * @param other the other item. Can be <code>null</code> in which case
+    *              this method will return <code>false</code>
+    *              
+    * @return <code>true</code> if other is not <code>null</code>, is a
+    *         {@link #isDirectory() directory} and this items path starts
+    *         with <code>other</code>'s path.
+    */
+   public boolean isChildOf(RepositoryItemMetadata other)
+   {
+      return other != null && other.isDirectory() && getRelativePath().startsWith(other.getRelativePath());
+   }
+   
+   // -------------------------------------------------------------- Comparable
+
+   public int compareTo(RepositoryItemMetadata o)
+   {
+      int result = 0;
+      if (this != o)
+      {
+         if (this.relativePath != o.relativePath)
+         {
+            if (this.relativePath != null)
+            {
+               result = o.relativePath == null ? 1 : this.relativePath.compareTo(o.relativePath);
+            }
+            else
+            {
+               result = -1;
+            }
+         }
+         
+         if (result == 0)
+         {
+            result = (int) (this.timestamp - o.timestamp);
+         }
+      }
+      return result;
+   }
+   
+   // -------------------------------------------------------------- Overrides 
+
+   @Override
+   public boolean equals(Object obj)
+   {
+      boolean result = (this == obj);
+      
+      if (!result && obj instanceof RepositoryItemMetadata)
+      {
+         RepositoryItemMetadata other = (RepositoryItemMetadata) obj;
+         result = (this.timestamp == other.timestamp) 
+                    && this.removed == other.removed 
+                    && this.directory == other.directory
+                    && safeEquals(this.getRelativePathElements(), other.getRelativePathElements()) 
+                    && safeEquals(this.originatingNode, other.originatingNode);
+      }
+      return result;
+   }
+
+   @Override
+   public int hashCode()
+   {
+      int result = 17;
+      result = 31 * result + ((int) (timestamp ^ (timestamp >>>32)));
+      result = 31 * result + (removed ? 0 : 1);
+      result = 31 * result + (directory ? 0 : 1);
+      List<String> elements = getRelativePathElements();
+      result = 31 * result + (elements == null ? 0 : elements.hashCode());
+      result = 31 * result + (originatingNode == null ? 0 : originatingNode.hashCode());
+      return result;
+   }
+
+   @Override
+   public String toString()
+   {
+      return new StringBuilder(getClass().getName())
+                    .append("[path='")
+                    .append(relativePath)
+                    .append(",timestamp=")
+                    .append(timestamp)
+                    .append(",originatingNode=")
+                    .append(originatingNode)
+                    .append(",removed=")
+                    .append(removed)
+                    .append(']').toString();
+   }
+   
+   // -------------------------------------------------------------- Private  
+
+   private static boolean safeEquals(Object a, Object b)
+   {
+      return (a == b || (a != null && a.equals(b)));
+   }
+   
+   
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/RepositoryItemMetadata.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/RepositoryRootMetadata.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/RepositoryRootMetadata.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/RepositoryRootMetadata.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,224 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.metadata;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+
+/**
+ * @author Brian Stansberry
+ *
+ */
+public class RepositoryRootMetadata  
+   extends AbstractSortedMetadataContainer<List<String>, RepositoryItemMetadata>
+   implements Identifiable<String>, Serializable, Comparable<RepositoryRootMetadata>
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = -4102001386900647551L;
+
+   private String name;
+   
+   /**
+    * Constructor for XML parser.
+    */
+   public RepositoryRootMetadata()
+   {      
+   }
+   
+   /**
+    * Create a new RepositoryRootMetadata with the given name.
+    * 
+    * @param name the name. Cannot be <code>null</code>
+    * 
+    * @throws IllegalArgumentException if name is <code>null</code>
+    */
+   public RepositoryRootMetadata(String name)
+   {
+      if (name == null)
+      {
+         throw new IllegalArgumentException("Null name");
+      }
+      setName(name);
+   }
+   
+   /**
+    * Copy constructor.
+    * 
+    * @param toCopy the item to copy
+    * 
+    * @throws NullPointerException if <code>toCopy</code> is <code>null</code>
+    */
+   public RepositoryRootMetadata(RepositoryRootMetadata toCopy)
+   {
+      this(toCopy.getName());
+      Collection<RepositoryItemMetadata> content = toCopy.getContent();
+      Collection<RepositoryItemMetadata> internal = getExposedCollection();
+      for(RepositoryItemMetadata item : content)
+      {
+         internal.add(new RepositoryItemMetadata(item));
+      }
+   }
+   
+   public String getId()
+   {
+      return name;
+   }
+   
+   @XmlAttribute(name = "name")
+   public String getName()
+   {
+      return name;
+   }
+
+   public void setName(String name)
+   {
+      this.name = name;
+   }
+   
+   @XmlElement(name = "content")
+   public Collection<RepositoryItemMetadata> getContent()
+   {
+      return getExposedCollection();
+   }
+
+   public void setContent(Collection<RepositoryItemMetadata> content)
+   {
+      Collection<RepositoryItemMetadata> internal = getExposedCollection();
+      internal.clear();
+      if (content != null)
+      {
+         for (RepositoryItemMetadata md : content)
+         {
+            internal.add(md);
+         }
+      }
+   }
+   
+   public RepositoryItemMetadata getItemMetadata(List<String> path)
+   {
+      return getContainedMetadata(path);
+   }
+   
+   public void addItemMetadata(RepositoryItemMetadata md)
+   {
+      getExposedCollection().add(md);
+   }
+
+   public boolean removeItemMetadata(List<String> path)
+   {   
+      RepositoryItemMetadata md = getItemMetadata(path);
+      if (md != null)
+      {
+         return getExposedCollection().remove(md);
+      }
+      return false;
+   }
+   
+   // -------------------------------------------------------------- Comparable
+   
+   public int compareTo(RepositoryRootMetadata other)
+   {
+      int result = 0;
+      // Null name comes later
+      if (this.name == null)
+      {
+         if (other.name != null)
+         {
+            result = 1;
+         }
+      }
+      else if (other.name == null)
+      {
+         result = -1;
+      }
+      else
+      {
+         result = this.name.compareTo(other.name);
+      }
+      
+      if (result == 0)
+      {
+         List<RepositoryItemMetadata> us = new ArrayList<RepositoryItemMetadata>(this.getExposedCollection());
+         List<RepositoryItemMetadata> them = new ArrayList<RepositoryItemMetadata>(other.getExposedCollection());
+         
+         result = them.size() - us.size();
+         if (result == 0)
+         {
+            for (int i = 0; i < us.size(); i++)
+            {
+               result = us.get(i).compareTo(them.get(i));
+               if (result != 0)
+               {
+                  break;
+               }
+            }
+         }
+      }
+      return result;
+      
+   }
+   
+   // -------------------------------------------------------------- Overrides 
+
+   @Override
+   public boolean equals(Object obj)
+   {
+      if (this == obj)
+      {
+         return true;
+      }
+      
+      if (obj instanceof RepositoryRootMetadata)
+      {
+         RepositoryRootMetadata other = (RepositoryRootMetadata) obj;
+         return (this.getExposedCollection().equals(other.getExposedCollection()) 
+                  && (this.name != null && this.name.equals(other.name)));
+      }
+      
+      return false;
+   }
+
+   @Override
+   public int hashCode()
+   {
+      int result = 17;
+      result = 31 * result + (name == null ? 0 : name.hashCode());
+      result = 31 * result + getExposedCollection().hashCode();
+      return result;
+   }
+
+   @Override
+   public String toString()
+   {
+      return new StringBuilder(getClass().getName())
+      .append("[name='")
+      .append(name)
+      .append(']').toString();
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/metadata/RepositoryRootMetadata.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractContentMetadataMutatorAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractContentMetadataMutatorAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractContentMetadataMutatorAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,86 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryItemMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryRootMetadata;
+
+/**
+ * Base class for {@link SynchronizationAction} implementations
+ * that mutate the {@link RepositoryContentMetadata} as part of their
+ * function.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public abstract class AbstractContentMetadataMutatorAction<T extends SynchronizationActionContext> 
+   extends AbstractSynchronizationAction<T>
+{
+   private final RepositoryItemMetadata rollbackMetadata;
+
+   /**
+    * Create a new AbstractContentMetadataMutatorAction.
+    *
+    * @param context the overall context of the modification
+    * @param modification the modification
+    */
+   protected AbstractContentMetadataMutatorAction(T context,
+         ContentModification modification)
+   {
+      super(context, modification);
+      
+      RepositoryContentMetadata contentMetadata = context.getInProgressMetadata();
+      RepositoryRootMetadata rmd = contentMetadata.getRepositoryRootMetadata(modification.getRootName());
+      if (rmd == null)
+      {
+         throw new IllegalStateException("Root " + modification.getRootName() + " unknown to " + contentMetadata);
+      }
+      this.rollbackMetadata = rmd.getItemMetadata(modification.getItem().getRelativePathElements());
+   }
+   
+   protected void updateContentMetadata()
+   {
+      ContentModification mod = getRepositoryContentModification();
+      RepositoryContentMetadata contentMetadata = getContext().getInProgressMetadata();
+      RepositoryRootMetadata rmd = contentMetadata.getRepositoryRootMetadata(mod.getRootName());
+      rmd.addItemMetadata(mod.getItem());
+   }
+   
+   protected void rollbackContentMetadata()
+   {
+      ContentModification mod = getRepositoryContentModification();
+      RepositoryContentMetadata contentMetadata = getContext().getInProgressMetadata();
+      RepositoryRootMetadata rmd = contentMetadata.getRepositoryRootMetadata(mod.getRootName());
+      if (rollbackMetadata == null)
+      {
+         rmd.removeItemMetadata(mod.getItem().getRelativePathElements());
+      }
+      else
+      {
+         rmd.addItemMetadata(rollbackMetadata);
+      }
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractContentMetadataMutatorAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractContentModificationGenerator.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractContentModificationGenerator.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractContentModificationGenerator.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,492 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Stack;
+import java.util.TreeSet;
+
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryItemMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryRootMetadata;
+
+
+/**
+ * Abstract superclass of classes that generate a list of 
+ * {@link ContentModification} from a pair
+ * of {@link RepositoryContentMetadata}.
+ * 
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public abstract class AbstractContentModificationGenerator
+{
+   // -----------------------------------------------------------------  Public
+   
+   public List<ContentModification> getModificationList(RepositoryContentMetadata base,
+                                                                  RepositoryContentMetadata modified)
+         throws InconsistentRepositoryStructureException
+   {
+      if (base == null)
+      {
+         throw new IllegalArgumentException("Null base");
+      }
+      if (modified == null)
+      {
+         throw new IllegalArgumentException("Null modified");
+      }
+      
+      Collection<RepositoryRootMetadata> baseRoots = base.getRepositories();
+      Collection<RepositoryRootMetadata> modifiedRoots = modified.getRepositories();
+      
+      // We validate consistent repository structure by 1) confirming
+      // same number of roots and 2) (below) confirming all base roots are in modified
+      if (baseRoots.size() != modifiedRoots.size())
+      {
+         throw new InconsistentRepositoryStructureException(base, modified);
+      }
+      
+      List<ContentModification> mods = new ArrayList<ContentModification>();
+      for (RepositoryRootMetadata root : base.getRepositories())
+      {
+         RepositoryRootMetadata newRoot = modified.getRepositoryRootMetadata(root.getName());
+         if (newRoot != null)
+         {
+            mods.addAll(getModificationList(root, newRoot));
+         }
+         else
+         {
+            throw new InconsistentRepositoryStructureException(base, modified);
+         }
+      }
+      
+      return mods;
+   }
+
+   // --------------------------------------------------------------  Protected
+
+   protected abstract void handleAddition(String rootName, 
+         RepositoryItemMetadata item, GeneratedModifications mods);
+
+   protected abstract void handleMissing(String rootName, 
+         RepositoryItemMetadata item, GeneratedModifications mods);
+
+
+
+   protected void handleMatch(String rootName, 
+         RepositoryItemMetadata base, 
+         RepositoryItemMetadata modified, 
+         GeneratedModifications mods)
+   {
+      // Most common case of all is no change
+      if (base.equals(modified) == false)
+      {         
+         // There was a change; how we handle depends on what changed
+         
+         if (base.isRemoved() && !modified.isRemoved())
+         {
+            // Reincarnation
+            handleAddition(rootName, modified, base, mods);
+         }
+         else if (modified.isRemoved() && !base.isRemoved())
+         {
+            // Item was removed on the remote node
+            handleRemoval(rootName, base, mods);
+         }
+         else if (base.isDirectory() && !modified.isDirectory())
+         {
+            // An exploded archive replaced by a zipped one
+            handleChangeFromDirectory(rootName, base, modified, mods);
+         }
+         else if (modified.isDirectory() && !base.isDirectory())
+         {
+            // A zipped archive replaced by an exploded one
+            handleChangeToDirectory(rootName, base, modified, mods);
+         }
+         else if (modified.isDirectory())
+         {
+            // Directory timestamp modification
+            handleDirectoryTimestampModification(rootName, base, modified, mods);
+         }
+         else
+         {
+            // Simple file change
+            handleSimpleModification(rootName, base, modified, mods);
+         }
+      }
+   }
+
+   protected abstract void handleSimpleModification(String rootName, RepositoryItemMetadata base, RepositoryItemMetadata modified,
+         GeneratedModifications mods);
+
+   protected abstract void handleDirectoryTimestampModification(String rootName, RepositoryItemMetadata base,
+         RepositoryItemMetadata modified, GeneratedModifications mods);
+
+   protected abstract void handleChangeToDirectory(String rootName, RepositoryItemMetadata base, RepositoryItemMetadata modified,
+         GeneratedModifications mods);
+
+   protected abstract void handleChangeFromDirectory(String rootName, RepositoryItemMetadata base,
+         RepositoryItemMetadata modified, GeneratedModifications mods);
+
+   protected abstract void handleRemoval(String rootName, RepositoryItemMetadata base, GeneratedModifications mods);
+
+   protected abstract void handleAddition(String rootName, RepositoryItemMetadata modified, RepositoryItemMetadata base,
+         GeneratedModifications mods);
+
+   protected void drainPreapprovedRemovals(GeneratedModifications mods)
+   {
+      ContentModification preapprovedRemoval;
+      while ((preapprovedRemoval = mods.popPreapprovedRemoveParent()) != null)
+      {
+         mods.addModification(preapprovedRemoval);
+      }
+   }
+   
+   protected static RepositoryItemMetadata getMarkedRemovedItem(RepositoryItemMetadata base)
+   {
+      RepositoryItemMetadata result = base;
+      if (result.isRemoved() == false)
+      {
+         result = new RepositoryItemMetadata(result);
+         result.setRemoved(true);
+      }
+      
+      return result;
+   }
+
+   // ----------------------------------------------------------------  Private
+   
+   private List<ContentModification> getModificationList(RepositoryRootMetadata base,
+                                                                   RepositoryRootMetadata modified)
+   {
+      List<ContentModification> mods = new ArrayList<ContentModification>();
+      
+      OwnedItem[] items = getOwnedItems(base, modified);
+
+      // Track directories that we have added or removed so we can flag
+      // their children for automatic modification approval/rejection. We 
+      // don't want to add/remove a directory and then later do something
+      // inconsistent with a child. For stuff involving a removal we use
+      // a Stack<RepositoryContentModification> so we can add the root removal
+      // to the overall list of mods *after* all the children. This is needed
+      // to allow the removal to be rolled back.
+      RepositoryItemMetadata preapprovedAddParent = null;
+      Stack<ContentModification> preapprovedRemoveParent = new Stack<ContentModification>();
+      Stack<ContentModification> prerejectedAddParent = new Stack<ContentModification>();
+      RepositoryItemMetadata prerejectedRemoveParent = null;
+      
+      for(int first = 0; first < items.length; first++)
+      {
+         GeneratedModifications pairmod = new GeneratedModifications(preapprovedAddParent, preapprovedRemoveParent, prerejectedAddParent, prerejectedRemoveParent);
+         
+         int next = first + 1;
+         if (next >= items.length)
+         {
+            // Last unmatched item
+            if (items[first].base)
+            {
+               // base w/o match == missing
+               handleMissing(base.getName(), items[first].item, pairmod);
+            }
+            else
+            {
+               // !base w/o match == addition
+               handleAddition(base.getName(), items[first].item, pairmod);
+            }
+         }
+         else if (items[first].itemPath.equals(items[next].itemPath))
+         {
+            handleMatch(base.getName(), items[first].item, items[next].item, pairmod);            
+            first++; // we just consumed "next"
+         }
+         else if (items[first].base)
+         {
+            // base w/o match == removal
+            handleMissing(base.getName(), items[first].item, pairmod);
+         }
+         else
+         {
+            // !base w/o match == addition
+            handleAddition(base.getName(), items[first].item, pairmod);
+         }
+         
+         mods.addAll(pairmod.getModifications());            
+         preapprovedAddParent = pairmod.getPreapprovedAddParent();
+         prerejectedRemoveParent = pairmod.getPrerejectedRemoveParent();
+      }
+      
+      // Any remaining mods on our stacks need to be flushed to the list
+      for (ContentModification mod : preapprovedRemoveParent)
+      {
+         mods.add(mod);
+      }
+      for (ContentModification mod : prerejectedAddParent)
+      {
+         mods.add(mod);
+      }
+      return mods;
+   }
+
+   private OwnedItem[] getOwnedItems(RepositoryRootMetadata base, RepositoryRootMetadata modified)
+   {
+      TreeSet<OwnedItem> ownedItems = new TreeSet<OwnedItem>();
+      for (RepositoryItemMetadata item : base.getContent())
+      {
+         ownedItems.add(new OwnedItem(item, true));
+      }
+      for (RepositoryItemMetadata item : modified.getContent())
+      {
+         ownedItems.add(new OwnedItem(item, false));
+      }
+      
+      OwnedItem[] ownedItemArray = ownedItems.toArray(new OwnedItem[ownedItems.size()]);
+      return ownedItemArray;
+   }
+   
+   protected static class GeneratedModifications
+   {
+      private RepositoryItemMetadata preapprovedAddParent;
+      private final Stack<ContentModification>  preapprovedRemoveParent;
+      private final Stack<ContentModification>  prerejectedAddParent;
+      private RepositoryItemMetadata prerejectedRemoveParent;
+      private final List<ContentModification> modifications = new ArrayList<ContentModification>();
+      
+      public GeneratedModifications(RepositoryItemMetadata preapprovedAddParent,
+            Stack<ContentModification> preapprovedRemoveParent, 
+            Stack<ContentModification> prerejectedAddParent,
+            RepositoryItemMetadata prerejectedRemoveParent)
+      {
+         if (preapprovedRemoveParent == null)
+         {
+            throw new IllegalArgumentException("Null preapprovedRemoveParent");
+         }
+         if (prerejectedAddParent == null)
+         {
+            throw new IllegalArgumentException("Null prerejectedAddParent");
+         }
+         this.preapprovedAddParent = preapprovedAddParent;
+         this.preapprovedRemoveParent = preapprovedRemoveParent;
+         this.prerejectedAddParent = prerejectedAddParent;
+         this.prerejectedRemoveParent = prerejectedRemoveParent;
+      }
+      
+      // ---------------------------------------------------------------  Public
+      
+      public void addModification(ContentModification mod)
+      {
+         modifications.add(mod);
+      }      
+
+      public List<ContentModification> getModifications()
+      {
+         return modifications;
+      }
+
+      public RepositoryItemMetadata getPreapprovedAddParent()
+      {
+         return preapprovedAddParent;
+      }
+
+      public void setPreapprovedAddParent(RepositoryItemMetadata preapprovedAddParent)
+      {
+         if (preapprovedAddParent != null)
+         {
+            validateCanCallSet();
+         }
+         this.preapprovedAddParent = preapprovedAddParent;
+      }
+
+      public ContentModification peekPreapprovedRemoveParent()
+      {
+         return preapprovedRemoveParent.size() == 0 ? null : preapprovedRemoveParent.peek();
+      }
+
+      public ContentModification popPreapprovedRemoveParent()
+      {
+         return preapprovedRemoveParent.size() == 0 ? null : preapprovedRemoveParent.pop();
+      }
+
+      public void pushPreapprovedRemoveParent(ContentModification toPush)
+      {
+         if (toPush == null)
+         {
+            throw new IllegalArgumentException("Null prerejectedAddParent");            
+         }
+         if (this.preapprovedAddParent != null)
+         {
+            throw new IllegalStateException("preapprovedAddParent already set");
+         }
+         else if (this.prerejectedAddParent.size() > 0)
+         {
+            throw new IllegalStateException("prerejectedAddParent already set");
+         }
+         else if (this.prerejectedRemoveParent != null)
+         {
+            throw new IllegalStateException("prerejectedRemoveParent already set");
+         }
+         
+         ContentModification peeked = peekPreapprovedRemoveParent();
+         if (peeked != null && toPush.getItem().isChildOf(peeked.getItem()) == false)
+         {
+            throw new IllegalArgumentException(toPush.getItem() + 
+                  " is not a child of existing item " + peeked.getItem());
+         }
+         
+         this.preapprovedRemoveParent.push(toPush);
+      }
+
+      public ContentModification peekPrerejectedAddParent()
+      {
+         return prerejectedAddParent.size() == 0 ? null : prerejectedAddParent.peek();
+      }
+
+      public ContentModification popPrerejectedAddParent()
+      {
+         return prerejectedAddParent.size() == 0 ? null : prerejectedAddParent.pop();
+      }
+
+      public void pushPrerejectedAddParent(ContentModification toPush)
+      {
+         if (toPush == null)
+         {
+            throw new IllegalArgumentException("Null prerejectedAddParent");            
+         }
+         if (this.preapprovedAddParent != null)
+         {
+            throw new IllegalStateException("preapprovedAddParent already set");
+         }
+         else if (this.preapprovedRemoveParent.size() > 0)
+         {
+            throw new IllegalStateException("preapprovedRemoveParent already set");
+         }
+         else if (this.prerejectedRemoveParent != null)
+         {
+            throw new IllegalStateException("prerejectedRemoveParent already set");
+         }
+         ContentModification peeked = peekPrerejectedAddParent();
+         if (peeked != null && toPush.getItem().isChildOf(peeked.getItem()) == false)
+         {
+            throw new IllegalArgumentException(toPush.getItem() + 
+                  " is not a child of existing item " + peeked.getItem());
+         }
+         
+         this.prerejectedAddParent.push(toPush);
+      }
+      
+      public RepositoryItemMetadata getPrerejectedRemoveParent()
+      {
+         return prerejectedRemoveParent;
+      }
+
+      public void setPrerejectedRemoveParent(RepositoryItemMetadata prerejectedRemoveParent)
+      {
+         if (prerejectedRemoveParent != null)
+         {
+            validateCanCallSet();
+         }
+         this.prerejectedRemoveParent = prerejectedRemoveParent;
+      }
+
+      private void validateCanCallSet()
+      {
+         if (this.preapprovedAddParent != null)
+         {
+            throw new IllegalStateException("preapprovedAddParent already set");
+         }
+         else if (this.preapprovedRemoveParent.size() > 0)
+         {
+            throw new IllegalStateException("preapprovedRemoveParent already set");
+         }
+         else if (this.prerejectedAddParent.size() > 0)
+         {
+            throw new IllegalStateException("prerejectedAddParent already set");
+         }
+         else if (this.prerejectedRemoveParent != null)
+         {
+            throw new IllegalStateException("prerejectedRemoveParent already set");
+         }
+      }
+      
+   }
+   
+   private static class OwnedItem implements Comparable<OwnedItem>
+   {
+      public final boolean base;
+      public final RepositoryItemMetadata item;
+      public final List<String> itemPath;
+      
+      private OwnedItem(RepositoryItemMetadata item, boolean base)
+      {
+         assert item != null : "item is null";
+         this.item = item;
+         this.itemPath = item.getRelativePathElements();
+         this.base = base;
+      }
+      
+      public int compareTo(OwnedItem other)
+      {
+         List<String> ourPath = itemPath;
+         List<String> otherPath = other.itemPath;
+         int result = 0;
+         for (int i = 0; i < ourPath.size(); i++)
+         {
+            if (i >= otherPath.size())
+            {
+               // We've got extra levels they don't
+               result = 1;
+               break;
+            }
+            
+            int comp = ourPath.get(i).compareTo(otherPath.get(i));
+            if (comp != 0)
+            {
+               result = (comp < 0) ? -1 : 1;
+               break;
+            }
+         }
+         
+         if (result == 0 && otherPath.size() != ourPath.size())
+         {
+            // They've got extra levels we don't
+            result = -1;
+         }
+         
+         if (result == 0)
+         {
+            // base comes before !base
+            if (base && !other.base)
+            {
+               result = -1;
+            }
+            else if (other.base && !base)
+            {
+               result = 1;
+            }
+         }
+         return result;
+      }
+   }
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractContentModificationGenerator.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractSynchronizationAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractSynchronizationAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractSynchronizationAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,235 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+import org.jboss.logging.Logger;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryItemMetadata;
+
+/**
+ * Abstract superclass of {@link SynchronizationAction} 
+ * implementations.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public abstract class AbstractSynchronizationAction<T extends SynchronizationActionContext>
+      implements TwoPhaseCommitAction<T>
+{
+   public enum State { OPEN, CANCELLED, CLOSED, PREPARED, COMMITTED, ROLLEDBACK, ROLLBACK_ONLY}
+   
+   private Logger log = Logger.getLogger(getClass());
+   
+   private final ContentModification modification;
+   private final T context;
+   private boolean cancelled = false;
+   private boolean complete = false;
+   private State state = State.OPEN;
+   
+   /**
+    * Create a new AbstractSynchronizationAction.
+    *
+    * @param context the overall context of the modification
+    * @param modification the modification
+    */
+   protected AbstractSynchronizationAction(T context, 
+         ContentModification modification)
+   {
+      if (context == null)
+      {
+         throw new IllegalArgumentException("Null context");
+      }
+      if (modification == null)
+      {
+         throw new IllegalArgumentException("Null modification");
+      }
+      this.context = context;
+      this.modification = modification;
+   }
+
+   public ContentModification getRepositoryContentModification()
+   {
+      return modification;
+   }
+
+   public T getContext()
+   {
+      return context;
+   }
+
+   public void cancel()
+   {
+      if (state == State.OPEN)
+      {
+         doCancel();
+         this.cancelled = true;
+         this.state = State.CANCELLED;
+      }
+   }
+
+   public void complete()
+   {
+      if (state == State.OPEN)
+      {
+         try
+         {
+            doComplete();
+            this.state = State.CLOSED;
+         }
+         catch (Exception e)
+         {
+            this.state = State.ROLLBACK_ONLY;
+         }
+         finally
+         {
+            this.complete = true;            
+         }
+      }
+   }
+
+   public boolean prepare()
+   {
+      boolean result = false;
+      switch (state)
+      {         
+         case OPEN:            
+            // Not all actions get executed; e.g. reads on nodes that don't
+            // get called. So we'll clean up.
+            complete();
+            if (state != State.CLOSED)
+            {
+               // break and return false
+               break;
+            }
+            // else fall through
+         case CLOSED:
+            result = doPrepare();
+            if (result)
+            {
+               state = State.PREPARED;
+               result = true;
+            }
+            else
+            {
+               state = State.ROLLBACK_ONLY;
+            }
+            break;
+         case PREPARED:
+         case COMMITTED:
+         case ROLLEDBACK:
+            log.warn("Should not call prepare on an item with state " + state);
+            // fall through
+         case CANCELLED:
+         case ROLLBACK_ONLY:
+            // fall out and return false
+            break;            
+      }
+      return result;
+   }
+   
+   
+   public void commit()
+   {
+      switch (state)
+      {   
+         case PREPARED:   
+            doCommit();
+            state = State.COMMITTED;
+            break;
+         case OPEN:
+         case CANCELLED:
+         case CLOSED:
+         case ROLLBACK_ONLY:
+         case COMMITTED:
+         case ROLLEDBACK:
+            log.warn("Should not call prepare on an item with state " + state);
+            break;            
+      }      
+   }
+
+   public void rollback()
+   {
+      switch (state)
+      {
+         case COMMITTED:
+         case ROLLEDBACK:
+            log.warn("Should not call prepare on an item with state " + state);
+            return; 
+         case OPEN:
+            doRollbackFromOpen();
+            break;
+         case CANCELLED:
+            doRollbackFromCancelled();
+            break;
+         case ROLLBACK_ONLY:
+            doRollbackFromRollbackOnly();
+            break;
+         case CLOSED:
+            doRollbackFromComplete();
+            break;
+         case PREPARED:   
+            doRollbackFromPrepared();            
+            break;           
+      }   
+      state = State.ROLLEDBACK;
+   }
+
+   public boolean isCancelled()
+   {
+      return this.cancelled;
+   }
+
+   public boolean isComplete()
+   {
+      return this.complete;
+   }
+   
+   public State getState()
+   {
+      return state;
+   }
+   
+   // --------------------------------------------------------------  Protected
+   
+   protected abstract void doCancel();
+   protected abstract void doComplete() throws Exception;
+   protected abstract boolean doPrepare();   
+   protected abstract void doCommit();   
+   protected abstract void doRollbackFromOpen();   
+   protected abstract void doRollbackFromCancelled();   
+   protected abstract void doRollbackFromRollbackOnly();   
+   protected abstract void doRollbackFromComplete();   
+   protected abstract void doRollbackFromPrepared();
+   
+   protected static RepositoryItemMetadata getMarkedRemovedItem(ContentModification base)
+   {
+      RepositoryItemMetadata result = base.getItem();
+      if (result.isRemoved() == false)
+      {
+         result = new RepositoryItemMetadata(result);
+         result.setRemoved(true);
+      }
+      
+      return result;
+   }
+
+}
\ No newline at end of file


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractSynchronizationAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractSynchronizationPolicy.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractSynchronizationPolicy.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractSynchronizationPolicy.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,507 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryItemMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryRootMetadata;
+
+/**
+ * Abstract base class to support implementations of {@link SynchronizationPolicy}.
+ * <p>
+ * Implements of the various RepositorySynchronizationPolicy 
+ * <i>acceptXXX</i> methods by checking if a Boolean property has been set
+ * dictating the response; if not delegates the call to one of the abstract
+ * protected methods that subclasses implement.
+ *  
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ *
+ */
+public abstract class AbstractSynchronizationPolicy implements SynchronizationPolicy
+{
+   /**
+    * Default value for {@link #getRemovalTrackingTime()}, equal to 30 days.
+    */
+   public static final long DEFAULT_REMOVAL_TRACKING_TIME = 30l * 24l * 60l * 60l * 1000l;
+   
+   private long removalTrackingTime = DEFAULT_REMOVAL_TRACKING_TIME;
+   private Boolean allowJoinAdditions;
+   private Boolean allowJoinReincarnations;
+   private Boolean allowJoinUpdates;
+   private Boolean allowJoinRemovals;
+   private Boolean allowMergeAdditions;
+   private Boolean allowMergeReincarnations;
+   private Boolean allowMergeUpdates;
+   private Boolean allowMergeRemovals;
+   private boolean developerMode = false;
+   
+   // -------------------------------------------------------------  Properties
+
+   /**
+    * Gets any fixed response to 
+    * {@link #acceptJoinAddition(RepositoryItemMetadata, RepositoryItemMetadata)}.
+    * 
+    * @return a fixed response, or <code>null</code> if there is no fixed
+    *         response and the call should be delegated to 
+    *         {@link #acceptAddition(RepositoryItemMetadata, RepositoryItemMetadata, boolean)}
+    */
+   public Boolean getAllowJoinAdditions()
+   {
+      return allowJoinAdditions;
+   }
+
+   /**
+    * Sets any fixed response to 
+    * {@link #acceptJoinAddition(RepositoryItemMetadata, RepositoryItemMetadata)}.
+    * 
+    * @param allow the fixed response, or <code>null</code> if there is no fixed
+    *         response and the call should be delegated to 
+    *         {@link #acceptAddition(RepositoryItemMetadata, RepositoryItemMetadata, boolean)}
+    */
+   public void setAllowJoinAdditions(Boolean allow)
+   {
+      this.allowJoinAdditions = allow;
+   }
+
+   /**
+    * Gets any fixed response to 
+    * {@link #acceptJoinReincarnation(RepositoryItemMetadata, RepositoryItemMetadata)}.
+    * 
+    * @return a fixed response, or <code>null</code> if there is no fixed
+    *         response and the call should be delegated to 
+    *         {@link #acceptReincarnation(RepositoryItemMetadata, RepositoryItemMetadata, boolean)}
+    */
+   public Boolean getAllowJoinReincarnations()
+   {
+      return allowJoinReincarnations;
+   }
+
+   /**
+    * Sets any fixed response to 
+    * {@link #acceptJoinReincarnation(RepositoryItemMetadata, RepositoryItemMetadata)}.
+    * 
+    * @param allow the fixed response, or <code>null</code> if there is no fixed
+    *         response and the call should be delegated to 
+    *         {@link #acceptReincarnation(RepositoryItemMetadata, RepositoryItemMetadata, boolean)}
+    */
+   public void setAllowJoinReincarnations(Boolean allow)
+   {
+      this.allowJoinReincarnations = allow;
+   }
+
+   /**
+    * Gets any fixed response to 
+    * {@link #acceptJoinUpdate(RepositoryItemMetadata, RepositoryItemMetadata)}.
+    * 
+    * @return a fixed response, or <code>null</code> if there is no fixed
+    *         response and the call should be delegated to 
+    *         {@link #acceptUpdate(RepositoryItemMetadata, RepositoryItemMetadata, boolean)}
+    */
+   public Boolean getAllowJoinUpdates()
+   {
+      return allowJoinUpdates;
+   }
+
+   /**
+    * Sets any fixed response to 
+    * {@link #acceptJoinUpdate(RepositoryItemMetadata, RepositoryItemMetadata)}.
+    * 
+    * @param allow the fixed response, or <code>null</code> if there is no fixed
+    *         response and the call should be delegated to 
+    *         {@link #acceptUpdate(RepositoryItemMetadata, RepositoryItemMetadata, boolean)}
+    */
+   public void setAllowJoinUpdates(Boolean allow)
+   {
+      this.allowJoinUpdates = allow;
+   }
+
+   /**
+    * Gets any fixed response to 
+    * {@link #acceptJoinRemoval(RepositoryItemMetadata, RepositoryItemMetadata)}.
+    * 
+    * @return a fixed response, or <code>null</code> if there is no fixed
+    *         response and the call should be delegated to 
+    *         {@link #acceptRemoval(RepositoryItemMetadata, RepositoryItemMetadata, boolean)}
+    */
+   public Boolean getAllowJoinRemovals()
+   {
+      return allowJoinRemovals;
+   }
+
+   /**
+    * Sets any fixed response to 
+    * {@link #acceptJoinRemoval(RepositoryItemMetadata, RepositoryItemMetadata)}.
+    * 
+    * @param allow the fixed response, or <code>null</code> if there is no fixed
+    *         response and the call should be delegated to 
+    *         {@link #acceptRemoval(RepositoryItemMetadata, RepositoryItemMetadata, boolean)}
+    */
+   public void setAllowJoinRemovals(Boolean allow)
+   {
+      this.allowJoinRemovals = allow;
+   }
+
+   /**
+    * Gets any fixed response to 
+    * {@link #acceptMergeAddition(RepositoryItemMetadata)}.
+    * 
+    * @return a fixed response, or <code>null</code> if there is no fixed
+    *         response and the call should be delegated to 
+    *         {@link #acceptAddition(RepositoryItemMetadata, RepositoryItemMetadata, boolean)}
+    */
+   public Boolean getAllowMergeAdditions()
+   {
+      return allowMergeAdditions;
+   }
+
+   /**
+    * Sets any fixed response to 
+    * {@link #acceptMergeAddition(RepositoryItemMetadata)}.
+    * 
+    * @param allow the fixed response, or <code>null</code> if there is no fixed
+    *         response and the call should be delegated to 
+    *         {@link #acceptAddition(RepositoryItemMetadata, RepositoryItemMetadata, boolean)}
+    */
+   public void setAllowMergeAdditions(Boolean allow)
+   {
+      this.allowMergeAdditions = allow;
+   }
+
+   /**
+    * Gets any fixed response to 
+    * {@link #acceptMergeReincarnation(RepositoryItemMetadata, RepositoryItemMetadata)}.
+    * 
+    * @return a fixed response, or <code>null</code> if there is no fixed
+    *         response and the call should be delegated to 
+    *         {@link #acceptReincarnation(RepositoryItemMetadata, RepositoryItemMetadata, boolean)}
+    */
+   public Boolean getAllowMergeReincarnations()
+   {
+      return allowMergeReincarnations;
+   }
+
+   /**
+    * Sets any fixed response to 
+    * {@link #acceptMergeReincarnation(RepositoryItemMetadata, RepositoryItemMetadata)}.
+    * 
+    * @param allow the fixed response, or <code>null</code> if there is no fixed
+    *         response and the call should be delegated to 
+    *         {@link #acceptReincarnation(RepositoryItemMetadata, RepositoryItemMetadata, boolean)}
+    */
+   public void setAllowMergeReincarnations(Boolean allow)
+   {
+      this.allowMergeReincarnations = allow;
+   }
+
+   /**
+    * Gets any fixed response to 
+    * {@link #acceptMergeUpdate(RepositoryItemMetadata, RepositoryItemMetadata)}.
+    * 
+    * @return a fixed response, or <code>null</code> if there is no fixed
+    *         response and the call should be delegated to 
+    *         {@link #acceptUpdate(RepositoryItemMetadata, RepositoryItemMetadata, boolean)}
+    */
+   public Boolean getAllowMergeUpdates()
+   {
+      return allowMergeUpdates;
+   }
+
+   /**
+    * Sets any fixed response to 
+    * {@link #acceptMergeUpdate(RepositoryItemMetadata, RepositoryItemMetadata)}.
+    * 
+    * @param allow the fixed response, or <code>null</code> if there is no fixed
+    *         response and the call should be delegated to 
+    *         {@link #acceptUpdate(RepositoryItemMetadata, RepositoryItemMetadata, boolean)}
+    */
+   public void setAllowMergeUpdates(Boolean allow)
+   {
+      this.allowMergeUpdates = allow;
+   }
+
+   /**
+    * Gets any fixed response to 
+    * {@link #acceptMergeRemoval(RepositoryItemMetadata, RepositoryItemMetadata)}.
+    * 
+    * @return a fixed response, or <code>null</code> if there is no fixed
+    *         response and the call should be delegated to 
+    *         {@link #acceptRemoval(RepositoryItemMetadata, RepositoryItemMetadata, boolean)}
+    */
+   public Boolean getAllowMergeRemovals()
+   {
+      return allowMergeRemovals;
+   }
+
+   /**
+    * Sets any fixed response to 
+    * {@link #acceptMergeRemoval(RepositoryItemMetadata, RepositoryItemMetadata)}.
+    * 
+    * @param allow the fixed response, or <code>null</code> if there is no fixed
+    *         response and the call should be delegated to 
+    *         {@link #acceptRemoval(RepositoryItemMetadata, RepositoryItemMetadata, boolean)}
+    */
+   public void setAllowMergeRemovals(Boolean allow)
+   {
+      this.allowMergeRemovals = allow;
+   }
+   
+   
+   /**
+    * Gets whether this policy is in a very lenient "developer mode" in which
+    * case it will return <code>true</code> to all <i>acceptXXX</i> calls.
+    * The purpose of this is to eliminate any need for development servers
+    * to coordinate system timestamps.
+    * 
+    * @return <code>true</code> if the policy is in developer mode.
+    */
+   public boolean isDeveloperMode()
+   {
+      return developerMode;
+   }
+
+   /**
+    * Sets whether this policy is in a very lenient "developer mode" in which
+    * case it will return <code>true</code> to all <i>acceptXXX</i> calls.
+    * The purpose of this is to eliminate any need for development servers
+    * to coordinate system timestamps.
+    * 
+    * @param developerMode <code>true</code> if the policy should be in developer mode.
+    */
+   public void setDeveloperMode(boolean developerMode)
+   {
+      this.developerMode = developerMode;
+   }
+
+   /**
+    * Gets how long in ms this policy should remembered removed items for
+    * use in detecting reincarnations. Default is {@link #DEFAULT_REMOVAL_TRACKING_TIME}.
+    * 
+    * @return the number of ms, or a number less than 1 to indicate removed
+    *         items should not be remembered.
+    */
+   public long getRemovalTrackingTime()
+   {
+      return removalTrackingTime;
+   }
+
+   /**
+    * Sets how long in ms this policy should remembered removed items for
+    * use in detecting reincarnations. Default is {@link #DEFAULT_REMOVAL_TRACKING_TIME}.
+    * 
+    * @param removalTrackingTime the number of ms, or a number less than 1 to 
+    *                            indicate removed items should not be remembered.
+    */
+   public void setRemovalTrackingTime(long removalTrackingTime)
+   {
+      this.removalTrackingTime = removalTrackingTime;
+   }
+   
+   // ----------------------------------------  RepositorySynchronizationPolicy  
+   
+   public boolean acceptJoinAddition(RepositoryItemMetadata toAdd, RepositoryItemMetadata joinersPrevious)
+   {
+      if (allowJoinAdditions != null)
+      {
+         return allowJoinAdditions.booleanValue();
+      }
+      
+      validateParams("toAdd", toAdd, true, null, false);
+      
+      return acceptAddition(toAdd, joinersPrevious, false);
+   }
+
+   public boolean acceptJoinReincarnation(RepositoryItemMetadata reincarnation, RepositoryItemMetadata current)
+   {
+      if (developerMode)
+      {
+         return true;
+      }
+      else if (allowJoinReincarnations != null)
+      {
+         return allowJoinReincarnations.booleanValue();
+      }
+      
+      validateParams("reincarnation", reincarnation, true, current, true);
+      
+      return acceptReincarnation(reincarnation, current, false);
+   }
+
+   public boolean acceptJoinRemoval(RepositoryItemMetadata current, RepositoryItemMetadata joinersItem)
+   {
+      if (developerMode)
+      {
+         return true;
+      }
+      else if (allowJoinRemovals != null)
+      {
+         return allowJoinRemovals.booleanValue();
+      }
+      
+      validateParams("toRemove", joinersItem, false, current, true);
+      
+      return acceptRemoval(current, joinersItem, false);
+   }
+
+   public boolean acceptJoinUpdate(RepositoryItemMetadata update, RepositoryItemMetadata current)
+   {
+      if (developerMode)
+      {
+         return true;
+      }
+      else if (allowJoinUpdates != null)
+      {
+         return allowJoinUpdates.booleanValue();
+      }
+      
+      validateParams("update", update, true, current, true);
+      
+      return acceptUpdate(update, current, true);
+   }
+
+   public boolean acceptMergeAddition(RepositoryItemMetadata toAdd)
+   {
+      if (developerMode)
+      {
+         return true;
+      }
+      else if (allowMergeAdditions != null)
+      {
+         return allowMergeAdditions.booleanValue();
+      }
+      
+      validateParams("toAdd", toAdd, true, null, false);
+      
+      return acceptAddition(toAdd, null, true);
+   }
+
+   public boolean acceptMergeReincarnation(RepositoryItemMetadata reincarnation, RepositoryItemMetadata current)
+   {
+      if (developerMode)
+      {
+         return true;
+      }
+      else if (allowMergeReincarnations != null)
+      {
+         return allowMergeReincarnations.booleanValue();
+      }
+      
+      validateParams("reincarnation", reincarnation, true, current, true);
+      
+      return acceptReincarnation(reincarnation, current, true);
+   }
+
+   public boolean acceptMergeRemoval(RepositoryItemMetadata current, RepositoryItemMetadata mergersView)
+   {
+      if (developerMode)
+      {
+         return true;
+      }
+      else if (allowMergeRemovals != null)
+      {
+         return allowMergeRemovals.booleanValue();
+      }
+      
+      validateParams("toRemove", mergersView, false, current, true);
+      
+      return acceptRemoval(current, mergersView, true);
+   }
+
+   public boolean acceptMergeUpdate(RepositoryItemMetadata update, RepositoryItemMetadata current)
+   {
+      if (developerMode)
+      {
+         return true;
+      }
+      else if (allowMergeUpdates != null)
+      {
+         return allowMergeUpdates.booleanValue();
+      }
+      
+      validateParams("update", update, true, current, true);
+      
+      return acceptUpdate(update, current, true);
+   }
+
+   public boolean purgeRemovedItems(RepositoryContentMetadata content)
+   {
+      if (content == null)
+      {
+         return false;
+      }
+      
+      boolean purged = false;
+      
+      long oldest = this.removalTrackingTime < 1 ? 0 : System.currentTimeMillis() - this.removalTrackingTime;
+      
+      for (RepositoryRootMetadata rrmd : content.getRepositories())
+      {
+         for (RepositoryItemMetadata rimd : rrmd.getContent())
+         {
+            if (rimd.isRemoved() && rimd.getTimestamp() < oldest)
+            {
+               rrmd.removeItemMetadata(rimd.getRelativePathElements());
+               purged = true;
+            }
+         }
+      }
+      
+      return purged;
+   }
+   
+   // -------------------------------------------------------------  Protected  
+   
+   protected abstract boolean acceptAddition(RepositoryItemMetadata toAdd, RepositoryItemMetadata joinersPrevious,
+         boolean merge);
+
+   protected abstract boolean acceptReincarnation(RepositoryItemMetadata reincarnation, RepositoryItemMetadata current,
+         boolean merge);
+
+   protected abstract boolean acceptRemoval(RepositoryItemMetadata current, RepositoryItemMetadata sendersView,
+         boolean merge);
+
+   protected abstract boolean acceptUpdate(RepositoryItemMetadata update, RepositoryItemMetadata current,
+         boolean merge);
+   
+   // ----------------------------------------------------------------  Private
+   
+   /** Utility to throw IAE if required params are null 
+    */
+   private static void validateParams(String changeName, 
+                                      RepositoryItemMetadata change, 
+                                      boolean requireChange, 
+                                      RepositoryItemMetadata current, 
+                                      boolean requireCurrent)
+   {      
+      if (change == null && requireChange)
+      {
+         throw new IllegalArgumentException("Null " + changeName);
+      }      
+      if (requireCurrent && current == null)
+      {
+         throw new IllegalArgumentException("Null current");
+      }      
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/AbstractSynchronizationPolicy.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/ByteChunk.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/ByteChunk.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/ByteChunk.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,106 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+import java.io.Serializable;
+
+/**
+ * Encapsulates the results of an IO read operation for transmission
+ * across the cluster. 
+ *
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class ByteChunk implements Serializable
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = -1278778998152090786L;
+   
+   private final int byteCount;
+   private final byte[] bytes;
+   
+   /**
+    * Create a new ByteChunk.
+    * 
+    * @param bytes source bytes. Note that this array will not necessarily
+    *              be defensively copied, so callers should not alter it
+    *              after passing it to this object.
+    * @param byteCount number of bytes in <code>bytes</code> that are valid;
+    *                  rest are filler. <code>-1</code> indicates end of stream.
+    */
+   public ByteChunk(byte[] bytes, int byteCount)
+   {
+      if (bytes == null)
+      {
+         throw new IllegalArgumentException("Null bytes");
+      }
+      if (byteCount < 0)
+      {
+         this.byteCount = -1;
+         this.bytes = null;
+      }
+      else
+      {
+         this.byteCount = byteCount;
+         int diff = bytes.length - byteCount;
+         // If we're wasting too much, discard unneeded bytes at the
+         // cost of a copy. TODO refine or discard this
+         if (diff > (10 * 1024) && diff > (bytes.length / 4))
+         {
+            this.bytes = new byte[byteCount];
+            System.arraycopy(bytes, 0, this.bytes, 0, byteCount);
+         }
+         else
+         {
+            this.bytes = bytes;
+         }
+      }         
+   }
+   
+   /**
+    * Gets the number of valid bytes.
+    * 
+    * @return the number of valid bytes, or <code>-1</code> to indicate end of stream
+    */
+   public int getByteCount()
+   {
+      return byteCount;
+   }
+   
+   /**
+    * The bytes contained by this chunk. The length of the array could be
+    * longer than {@link #getByteCount()} in which case the excess bytes
+    * are filler.
+    * 
+    * @return the bytes, or <code>null</code> if {@link #getByteCount()} would
+    *         return <code>-1</code>. Not that this may be a direct reference
+    *         to this object's internal byte buffer, so callers should not
+    *         alter the byte array.
+    */
+   public byte[] getBytes()
+   {      
+      return this.bytes;
+   }
+}
\ No newline at end of file


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/ByteChunk.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/ContentModification.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/ContentModification.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/ContentModification.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,133 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+import java.io.Serializable;
+
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryItemMetadata;
+
+/**
+ * Describes a modification that a node needs to make to synchronize
+ * its repository with the cluster.
+ * 
+ * @author Brian Stansberry
+ * 
+ * @version $Revision:$
+ */
+public class ContentModification implements Serializable
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = -9060367262266987206L;
+
+   public enum Type
+   {
+      /** 
+       * This node needs to pull the item from the cluster and store it locally.
+       */
+      PULL_FROM_CLUSTER,
+      /** This node it needs to remove this item locally. */
+      REMOVE_FROM_CLUSTER,
+      /** This node needs to push the item to the cluster. */
+      PUSH_TO_CLUSTER,
+      /** This node needs to push the item to the cluster. */
+      PUSH_INPUT_STREAM_TO_CLUSTER,
+      /** This node needs to tell the cluster to remove the item. */
+      REMOVE_TO_CLUSTER,
+      /** This node needs to make a new directory */
+      MKDIR_FROM_CLUSTER,
+      /** This node needs to tell the cluster to make a new directory */
+      MKDIR_TO_CLUSTER,
+      /** 
+       * This node needs to tell the cluster to prepare to remove a directory.
+       * The actual removal will come later, via a REMOVE_TO_CLUSTER, after
+       * all directory children are removed as well. The "prepare" modification
+       * allows the remote nodes to set up a {@link RepositorySynchronizationAction}
+       * to rollback the directory removal. 
+       */
+      PREPARE_RMDIR_TO_CLUSTER,
+      /** 
+       * This node needs to prepare to remove a directory.
+       * The actual removal will come later, via a REMOVE_FROM_CLUSTER, after
+       * all directory children are removed as well. The "prepare" modification
+       * allows the node to set up a {@link RepositorySynchronizationAction}
+       * to rollback the directory removal. 
+       */
+      PREPARE_RMDIR_FROM_CLUSTER,
+      /** 
+       * This node's directory with the same path as the related
+       * RepositoryItemMetadata has a different timestamp. What if anything
+       * the node should do about this is unspecified. The 
+       * RepositoryItemMetadata included in this RepositoryContentModification
+       * will be the cluster version, as this node should know its own version.
+       */
+      DIR_TIMESTAMP_MISMATCH,
+      /** 
+       * This node needs to add some metadata for an item that has been removed.
+       * Used when this node doesn't have the item either, but is lacking
+       * information on when it was removed.   The
+       * RepositoryItemMetadata included in this RepositoryContentModification
+       * will return <code>true</code> to {@link RepositoryItemMetadata#isRemoved()}.
+       */
+      REMOVAL_METADATA_FROM_CLUSTER
+   }
+   
+   private final Type type;
+   private final String rootName;
+   private final RepositoryItemMetadata item;
+   
+   public ContentModification(Type type, String rootName, RepositoryItemMetadata item)
+   {
+      if (type == null)
+      {
+         throw new IllegalArgumentException("Null type");
+      }
+      if (rootName == null)
+      {
+         throw new IllegalArgumentException("Null rootName");
+      }
+      if (item == null)
+      {
+         throw new IllegalArgumentException("Null item");
+      }
+      
+      this.type = type;
+      this.rootName = rootName;
+      this.item = item;
+   }
+
+   public Type getType()
+   {
+      return type;
+   }
+
+   public String getRootName()
+   {
+      return rootName;
+   }
+
+   public RepositoryItemMetadata getItem()
+   {
+      return item;
+   }
+   
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/ContentModification.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/ImmutableSynchronizationPolicy.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/ImmutableSynchronizationPolicy.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/ImmutableSynchronizationPolicy.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,145 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryItemMetadata;
+
+/**
+ * {@link SynchronizationPolicy} that does not accept any changes.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class ImmutableSynchronizationPolicy implements SynchronizationPolicy
+{
+   /** 
+    * Always returns <code>false</code>
+    * 
+    * {@inheritDoc}
+    * 
+    * @return <code>false</code>
+    * */
+   public boolean acceptJoinAddition(RepositoryItemMetadata toAdd, RepositoryItemMetadata joinersPrevious)
+   {
+      return false;
+   }
+
+   /** 
+    * Always returns <code>false</code>
+    * 
+    * {@inheritDoc}
+    * 
+    * @return <code>false</code>
+    * */
+   public boolean acceptJoinReincarnation(RepositoryItemMetadata reincarnation, RepositoryItemMetadata current)
+   {
+      return false;
+   }
+
+   /** 
+    * Always returns <code>false</code>
+    * 
+    * {@inheritDoc}
+    * 
+    * @return <code>false</code>
+    * */
+   public boolean acceptJoinRemoval(RepositoryItemMetadata current, RepositoryItemMetadata joinersItem)
+   {
+      return false;
+   }
+
+   /** 
+    * Always returns <code>false</code>
+    * 
+    * {@inheritDoc}
+    * 
+    * @return <code>false</code>
+    * */
+   public boolean acceptJoinUpdate(RepositoryItemMetadata update, RepositoryItemMetadata current)
+   {
+      return false;
+   }
+
+   /** 
+    * Always returns <code>false</code>
+    * 
+    * {@inheritDoc}
+    * 
+    * @return <code>false</code>
+    * */
+   public boolean acceptMergeAddition(RepositoryItemMetadata toAdd)
+   {
+      return false;
+   }
+
+   /** 
+    * Always returns <code>false</code>
+    * 
+    * {@inheritDoc}
+    * 
+    * @return <code>false</code>
+    * */
+   public boolean acceptMergeReincarnation(RepositoryItemMetadata reincarnation, RepositoryItemMetadata current)
+   {
+      return false;
+   }
+
+   /** 
+    * Always returns <code>false</code>
+    * 
+    * {@inheritDoc}
+    * 
+    * @return <code>false</code>
+    * */
+   public boolean acceptMergeRemoval(RepositoryItemMetadata current, RepositoryItemMetadata mergersView)
+   {
+      return false;
+   }
+
+   /** 
+    * Always returns <code>false</code>
+    * 
+    * {@inheritDoc}
+    * 
+    * @return <code>false</code>
+    * */
+   public boolean acceptMergeUpdate(RepositoryItemMetadata update, RepositoryItemMetadata current)
+   {
+      return false;
+   }
+
+   /** 
+    * Always returns <code>false</code>
+    * 
+    * {@inheritDoc}
+    * 
+    * @return <code>false</code>
+    * */
+   public boolean purgeRemovedItems(RepositoryContentMetadata content)
+   {
+      return false;
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/ImmutableSynchronizationPolicy.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/InconsistentRepositoryStructureException.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/InconsistentRepositoryStructureException.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/InconsistentRepositoryStructureException.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,114 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryRootMetadata;
+
+
+/**
+ * Exception indicating that 
+ * {@link RepositoryContentMetadata#getRepositories() the list of repository roots} 
+ * is inconsistent between two <code>RepositoryContentMetadata</code> objects. 
+ * The expected cause of this would be two repositories set up with incompatible
+ * sets of backing URIs.
+ * 
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class InconsistentRepositoryStructureException extends Exception
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = 8104994059142991240L;
+
+   public InconsistentRepositoryStructureException(RepositoryContentMetadata base, 
+                                                   RepositoryContentMetadata other)
+   {
+      super(buildExceptionMessage(base, other));
+   }
+
+   private static String buildExceptionMessage(RepositoryContentMetadata base, RepositoryContentMetadata other)
+   {
+      List<String> missing = new ArrayList<String>();
+      for (RepositoryRootMetadata root : base.getRepositories())
+      {
+         RepositoryRootMetadata otherRoot = other.getRepositoryRootMetadata(root.getName());
+         if (otherRoot == null)
+         {
+            missing.add(root.getName());
+         }
+      }
+      List<String> extra = new ArrayList<String>();
+      for (RepositoryRootMetadata root : other.getRepositories())
+      {
+         RepositoryRootMetadata baseRoot = base.getRepositoryRootMetadata(root.getName());
+         if (baseRoot == null)
+         {
+            extra.add(root.getName());
+         }
+      }
+      
+      StringBuilder sb = new StringBuilder("Inconsistent structure between repositories");
+      if (missing.size() > 0)
+      {
+         sb.append("; Other repository is missing roots ");
+         boolean first = true;
+         for (String name : missing)
+         {
+            if (!first)
+            {
+               sb.append(',');
+            }
+            else
+            {
+               first = false;
+            }
+            sb.append(name);
+         }
+      }
+      if (extra.size() > 0)
+      {
+         sb.append("; Other repository has extra roots ");
+         boolean first = true;
+         for (String name : extra)
+         {
+            if (!first)
+            {
+               sb.append(',');
+            }
+            else
+            {
+               first = false;
+            }
+            sb.append(name);
+         }
+         
+      }
+      
+      return sb.toString();
+   }
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/InconsistentRepositoryStructureException.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/LocalContentModificationGenerator.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/LocalContentModificationGenerator.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/LocalContentModificationGenerator.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,172 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryItemMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ContentModification.Type;
+
+
+/**
+ * Generates {@link ContentModification} object from a comparison
+ * of a current snapshot of local repository content to a official snapshot.
+ * This generator will only generate modifications that push content to the
+ * cluster or that tell the cluster to remove content; no modifications
+ * pulling content from the cluster or removing local content will be
+ * generated.
+ * <p>This generator should only be used when the node has been fully
+ * synchronized with the cluster; it assumes any changes are acceptable
+ * to the cluster.</p>
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class LocalContentModificationGenerator extends AbstractContentModificationGenerator
+{
+
+   @Override
+   protected void handleAddition(String rootName, 
+         RepositoryItemMetadata item, GeneratedModifications mods)
+   {      
+      // If this is an addition, it can't be the child of an earlier removal.
+      // So, drain any remaining preapproved removals
+      drainPreapprovedRemovals(mods);
+      Type type = item.isDirectory() ? Type.MKDIR_TO_CLUSTER : Type.PUSH_TO_CLUSTER;
+      mods.addModification(new ContentModification(type, rootName, item));
+   }
+
+//   @Override
+//   protected void handleMatch(String rootName, 
+//         RepositoryItemMetadata base, RepositoryItemMetadata modified, GeneratedModifications mods)
+//   {      
+//      drainPreapprovedRemovals(mods);
+//      if (base.equals(modified) == false)
+//      {
+//         if (base.isDirectory() != modified.isDirectory())
+//         {
+//            // Swapped exploded for zipped or vice versa
+//            if (!base.isRemoved())
+//            {
+//               mods.addModification(new ContentModification(Type.REMOVE_TO_CLUSTER, rootName, base));
+//            }
+//         }
+//         mods.addModification(new ContentModification(Type.PUSH_TO_CLUSTER, rootName, modified));
+//      }
+//   }
+
+   @Override
+   protected void handleMissing(String rootName, 
+         RepositoryItemMetadata item, GeneratedModifications mods)
+   {
+      if (!item.isRemoved())
+      {
+         handleRemoval(rootName, item, mods);
+      }
+      // else it's the brain-dead case where we have metadata recording the
+      // removal but no file -- 'cause it's removed! In which case there's no
+      // need to do anything.
+   }
+
+   @Override
+   protected void handleChangeToDirectory(String rootName, RepositoryItemMetadata base, RepositoryItemMetadata modified,
+         GeneratedModifications mods)
+   {
+      // This is a modification, so it can't be the child of an earlier removal.
+      // So, drain any remaining preapproved removals
+      drainPreapprovedRemovals(mods);
+      
+      mods.addModification(new ContentModification(Type.MKDIR_TO_CLUSTER, rootName, modified));      
+   }
+
+   @Override
+   protected void handleAddition(String rootName, RepositoryItemMetadata modified, RepositoryItemMetadata base,
+      GeneratedModifications mods)
+   {
+      handleAddition(rootName, modified, mods);      
+   }
+
+   @Override
+   protected void handleChangeFromDirectory(String rootName, RepositoryItemMetadata base,
+         RepositoryItemMetadata modified, GeneratedModifications mods)
+   {
+      // This is a modification, so it can't be the child of an earlier removal.
+      // So, drain any remaining preapproved removals
+      drainPreapprovedRemovals(mods);
+      
+      // We're going to need to remove all the content that was
+      // under "base" from all the nodes in the cluster before we
+      // can push the replacement file. So, we put the PUSH_TO_CLUSTER
+      // on the stack to be inserted once all the child removals
+      // are done.
+      ContentModification mod = new ContentModification(Type.PUSH_TO_CLUSTER, rootName, modified);
+      mods.pushPreapprovedRemoveParent(mod);
+      
+   }
+
+   @Override
+   protected void handleDirectoryTimestampModification(String rootName, RepositoryItemMetadata base,
+         RepositoryItemMetadata modified, GeneratedModifications mods)
+   {
+      // If this is a modification, it can't be the child of an earlier removal.
+      // So, drain any remaining preapproved removals
+      drainPreapprovedRemovals(mods);
+      
+      mods.addModification(new ContentModification(Type.DIR_TIMESTAMP_MISMATCH, rootName, base));      
+   }
+
+   @Override
+   protected void handleSimpleModification(String rootName, RepositoryItemMetadata base, RepositoryItemMetadata modified,
+         GeneratedModifications mods)
+   {
+      // If this is a modification, it can't be the child of an earlier removal.
+      // So, drain any remaining preapproved removals
+      drainPreapprovedRemovals(mods);
+      
+      mods.addModification(new ContentModification(Type.PUSH_TO_CLUSTER, rootName, modified));
+   }
+
+   @Override
+   protected void handleRemoval(String rootName, RepositoryItemMetadata item, GeneratedModifications mods)
+   {
+      ContentModification removedParent = mods.peekPreapprovedRemoveParent();
+      while (removedParent != null && item.isChildOf(removedParent.getItem()) == false)
+      {
+         mods.addModification(mods.popPreapprovedRemoveParent());
+         removedParent = mods.peekPreapprovedRemoveParent();
+      }
+      ContentModification removal = new ContentModification(Type.REMOVE_TO_CLUSTER, rootName, item);
+      if (item.isDirectory())
+      {
+         // Tell cluster to prepare for the removal
+         mods.addModification(new ContentModification(Type.PREPARE_RMDIR_TO_CLUSTER, rootName, item));
+         // Push the actual removal on the stack to execute when
+         // children are done
+         mods.pushPreapprovedRemoveParent(removal);
+      }
+      else
+      {
+         mods.addModification(removal);
+      }
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/LocalContentModificationGenerator.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/NoOpRepositorySynchronizationWriteAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/NoOpRepositorySynchronizationWriteAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/NoOpRepositorySynchronizationWriteAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,57 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+
+
+/**
+ * A {@link SynchronizationWriteAction} that throws the bytes on the
+ * floor.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class NoOpRepositorySynchronizationWriteAction<T extends SynchronizationActionContext> 
+      extends NoOpSynchronizationAction<T>
+      implements SynchronizationWriteAction<T>
+{
+   
+   /**
+    * Create a new NoOpRepositorySynchronizationWriteAction.
+    * 
+    * @param synchronizationId
+    * @param modification
+    */
+   public NoOpRepositorySynchronizationWriteAction(T context,
+         ContentModification modification)
+   {
+      super(context, modification);
+   }
+
+   public void writeBytes(ByteChunk bytes)
+   {
+      // no-op
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/NoOpRepositorySynchronizationWriteAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/NoOpSynchronizationAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/NoOpSynchronizationAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/NoOpSynchronizationAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,102 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+
+/**
+ * {@link SynchronizationAction} that does nothing. Intended
+ * for use in cases where a node is already in sync with the cluster with
+ * respect to a particular item.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class NoOpSynchronizationAction<T extends SynchronizationActionContext> 
+   extends AbstractSynchronizationAction<T>
+{
+   public NoOpSynchronizationAction(T context, 
+         ContentModification modification)
+   {
+      super(context, modification);
+   }
+   
+   // --------------------------------------------------------------  Protected
+
+   @Override
+   protected void doCancel()
+   {
+      // no-op      
+   }
+
+   @Override
+   protected void doComplete()
+   {
+      // no-op
+   }
+
+   @Override
+   protected boolean doPrepare()
+   {
+      return true;
+   }
+   
+   @Override 
+   protected void doCommit()
+   {
+      // no-op
+   }
+
+   @Override
+   protected void doRollbackFromCancelled()
+   {
+      // no-op
+   }
+
+   @Override
+   protected void doRollbackFromComplete()
+   {
+      // no-op
+   }
+
+   @Override
+   protected void doRollbackFromOpen()
+   {
+      // no-op
+   }
+
+   @Override
+   protected void doRollbackFromPrepared()
+   {
+      // no-op
+   }
+
+   @Override
+   protected void doRollbackFromRollbackOnly()
+   {
+      // no-op
+   }
+   
+   
+   
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/NoOpSynchronizationAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/RemoteContentModificationGenerator.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/RemoteContentModificationGenerator.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/RemoteContentModificationGenerator.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,526 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryItemMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryRootMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ContentModification.Type;
+
+/**
+ * Generates {@link ContentModification} objects from a comparison
+ * of a current snapshot of a remote node's repository content to the official 
+ * snapshot available on this node.  Intended for use when a new node joins the 
+ * cluster or a split of the cluster heals.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class RemoteContentModificationGenerator extends AbstractContentModificationGenerator
+{
+   private final SynchronizationPolicy policy;
+   private final RepositoryContentMetadata baseRemoteContent;
+   private final boolean merge;
+
+   /**
+    * Create a new RemoteRepositoryContentModificationGenerator to handle
+    * a cluster merge.
+    * 
+    * @param policy policy that decides whether to accept or reject changes
+    *               from the remote repository
+    */
+   public RemoteContentModificationGenerator(SynchronizationPolicy policy)
+   {
+      if (policy == null)
+      {
+         throw new IllegalArgumentException("Null policy");
+      }
+
+      this.policy = policy;
+      this.baseRemoteContent = null;
+      this.merge = true;
+   }
+
+   /**
+    * Create a new RemoteRepositoryContentModificationGenerator to handle a
+    * cluster node join.
+    * 
+    * @param policy policy that decides whether to accept or reject changes
+    *               from the remote repository
+    * @param baseRemoteContent remote node's view of its content as of the
+    *                          last time it was part of the cluster, or
+    *                          <code>null</code> if the node was never part
+    *                          of the cluster.
+    */
+   public RemoteContentModificationGenerator(SynchronizationPolicy policy,
+         RepositoryContentMetadata baseRemoteContent)
+   {
+      if (policy == null)
+      {
+         throw new IllegalArgumentException("Null policy");
+      }
+      if (baseRemoteContent == null)
+      {
+         throw new IllegalArgumentException("Null baseRemoteContent");
+      }
+
+      this.policy = policy;
+      this.baseRemoteContent = baseRemoteContent;
+      this.merge = false;
+   }
+   
+   // ----------------------------------------------------- Protected Overrides
+
+   @Override
+   protected void handleAddition(String rootName, 
+         RepositoryItemMetadata item, 
+         GeneratedModifications mods)
+   {
+      handleAddition(rootName, item, null, mods);
+   }
+   
+   @Override
+   protected void handleMissing(String rootName, RepositoryItemMetadata item, 
+         GeneratedModifications mods)
+   {
+      if (item.isRemoved())
+      {
+         // Remote node doesn't have file, just needs to add a missing RepositoryItemMetadata
+         handleRemovalMetadata(rootName, item, mods);
+      }
+      else 
+      {
+         handleRemoval(rootName, item, mods);
+      }
+   }
+   
+   @Override
+   protected void handleAddition(String rootName, 
+         RepositoryItemMetadata item, RepositoryItemMetadata removedVersion, 
+         GeneratedModifications mods)
+   {
+      // This is an add, so it can't be the child of an earlier removal.
+      // So, drain any remaining preapproved removals
+      drainPreapprovedRemovals(mods);
+      mods.setPrerejectedRemoveParent(null);
+      
+      Boolean allow = null;
+
+      // See if this addition is preapproved as part of adding a new tree
+      RepositoryItemMetadata  preapprovedAdd = mods.getPreapprovedAddParent();
+      if (preapprovedAdd != null)
+      {
+         if (item.isChildOf(preapprovedAdd))
+         {
+            // just keep the same one
+            allow = Boolean.TRUE;
+         }
+         else
+         {
+            // Clean up
+            mods.setPreapprovedAddParent(null);
+         }
+      }
+      else
+      {
+         // Addition wasn't preapproved. See if it was prerejected as part
+         // of rejecting adding a new tree
+         
+         ContentModification prerejectedParentMod = mods.peekPrerejectedAddParent();
+         while (prerejectedParentMod != null)
+         {
+            if (item.isChildOf(prerejectedParentMod.getItem()))
+            {
+               // rejected
+               allow = Boolean.FALSE;
+               break;
+            }
+            else
+            {
+               // We're done with children of prerejected parent so add the cached
+               // modification to the overall list. This will cause it to
+               // get executed *after* its children 
+               // (i.e. remove parent after removing children)
+               mods.addModification(mods.popPrerejectedAddParent());
+               // Start checking grandparent
+               prerejectedParentMod = mods.peekPrerejectedAddParent();
+            }
+         }
+      }
+      
+      if (allow == null)
+      {
+         // Check with our policy
+         allow = Boolean.valueOf(isAdditionApproved(rootName, item, removedVersion));
+      }
+      
+      if (allow.booleanValue())
+      {         
+         Type type = item.isDirectory() ? Type.MKDIR_TO_CLUSTER : Type.PUSH_TO_CLUSTER;
+         mods.addModification(new ContentModification(type, rootName, item));
+         
+         if (mods.getPreapprovedAddParent() == null && item.isDirectory())
+         {
+            mods.setPreapprovedAddParent(item);
+         }         
+      }
+      else
+      {         
+         // Addition not allowed; remote node must discard
+         
+         // If available use the removedVersion in ContentModifications to
+         // help keep it around in metadata
+         RepositoryItemMetadata modItem = removedVersion == null ? item : removedVersion;
+         ContentModification mod = new ContentModification(Type.REMOVE_FROM_CLUSTER, rootName, modItem);
+         if (item.isDirectory())
+         {
+            // Tell node to prepare for the removal
+            mods.addModification(new ContentModification(Type.PREPARE_RMDIR_FROM_CLUSTER, rootName, modItem));
+            // Push it on the stack so we execute it after dealing with
+            // all the children we will now reject as well
+            mods.pushPrerejectedAddParent(mod);
+         }
+         else
+         {
+            mods.addModification(mod);
+         }
+         // housekeeping
+         mods.setPreapprovedAddParent(null);
+      }
+   }
+
+   @Override
+   protected void handleChangeFromDirectory(String rootName, 
+         RepositoryItemMetadata base,
+         RepositoryItemMetadata modified,
+         GeneratedModifications mods)
+   {
+      // This is a modification, so it can't be the child of an earlier removal.
+      // So, drain any remaining preapproved removals
+      drainPreapprovedRemovals(mods);
+      mods.setPrerejectedRemoveParent(null);
+      // Also can't be child of an earlier add, so drain any remaining prerejected adds
+      drainPrerejectedAdds(mods);
+      mods.setPreapprovedAddParent(null);
+      
+      boolean allow = checkAllowUpdate(base, modified);
+      if (allow)
+      {
+         // We're going to need to remove all the content that was
+         // under "base" from all the nodes in the cluster before we
+         // can push the replacement file. So, we put the PUSH_TO_CLUSTER
+         // on the stack to be inserted once all the child removals
+         // are done.
+         ContentModification mod = new ContentModification(Type.PUSH_TO_CLUSTER, rootName, modified);
+         mods.pushPreapprovedRemoveParent(mod);
+      }
+      else
+      {
+         // We're going to need to also reject the removal of all the content 
+         // that is under "base".
+         mods.setPrerejectedRemoveParent(base);         
+         mods.addModification(new ContentModification(Type.MKDIR_FROM_CLUSTER, rootName, base));
+      }
+   }
+
+   @Override
+   protected void handleChangeToDirectory(String rootName, 
+         RepositoryItemMetadata base,
+         RepositoryItemMetadata modified,
+         GeneratedModifications mods)
+   {
+      // This is a modification, so it can't be the child of an earlier removal.
+      // So, drain any remaining preapproved removals
+      drainPreapprovedRemovals(mods);
+      mods.setPrerejectedRemoveParent(null);
+      // Also can't be child of an earlier add, so drain any remaining prerejected adds
+      drainPrerejectedAdds(mods);
+      mods.setPreapprovedAddParent(null);
+      
+      boolean allow = checkAllowUpdate(base, modified);
+      if (allow)
+      {
+         // We're going to need to add all the content that is
+         // under "modified".
+         mods.setPreapprovedAddParent(modified);         
+         mods.addModification(new ContentModification(Type.MKDIR_TO_CLUSTER, rootName, modified));       
+      }
+      else
+      {
+         // We're going to need to also reject the addition of all the content 
+         // that is under "modified".
+         ContentModification mod = new ContentModification(Type.PULL_FROM_CLUSTER, rootName, base);
+         mods.pushPrerejectedAddParent(mod);
+      }
+   }
+   
+   private void handleRemovalMetadata(String rootName, 
+         RepositoryItemMetadata item, GeneratedModifications mods)
+   {
+      // This is a removal, so it can't be a child of an earlier attempted add.
+      // So, drain any remaining prerejected adds
+      drainPrerejectedAdds(mods);
+      mods.setPreapprovedAddParent(null);
+      
+      // Don't lose track of pre-approval stack if there is one
+      ContentModification preapprovedRemove = mods.peekPreapprovedRemoveParent();
+      while (preapprovedRemove != null)
+      {
+         if (item.isChildOf(preapprovedRemove.getItem()))
+         {
+            // we're at the right level
+            break;
+         }
+         else
+         {
+            // We're done with children of preapproved parent so add the cached
+            // modification to the overall list. This will cause it to
+            // get executed *after* its children 
+            // (i.e. remove parent after removing children)
+            mods.addModification(mods.popPreapprovedRemoveParent());
+            // Start checking grandparent
+            preapprovedRemove = mods.peekPreapprovedRemoveParent();
+         }
+      }
+      
+      ContentModification mod = new ContentModification(Type.REMOVAL_METADATA_FROM_CLUSTER, rootName, item);
+      if (item.isDirectory())
+      {
+         mods.pushPreapprovedRemoveParent(mod);
+      }
+      else
+      {
+         mods.addModification(mod);
+      }
+   }
+   
+   @Override
+   protected void handleRemoval(String rootName, RepositoryItemMetadata item, GeneratedModifications mods)
+   {
+      // This is a removal, so it can't be a child of an earlier attempted add.
+      // So, drain any remaining prerejected adds
+      drainPrerejectedAdds(mods);
+      mods.setPreapprovedAddParent(null);
+      
+      Boolean allow = null;
+
+      // See if this removal is prerejected as part of removing a higher level tree
+      RepositoryItemMetadata prerejected = mods.getPrerejectedRemoveParent();
+      if (prerejected != null)
+      {
+         if (item.isChildOf(prerejected))
+         {
+            allow = Boolean.FALSE;
+         }
+         else
+         {
+            // Clean up
+            mods.setPrerejectedRemoveParent(null);
+         }
+      }
+      else
+      {
+         // Removal wasn't prerejected. See if it was preapproved as part
+         // of approving removing a higher level tree
+         ContentModification preapprovedRemove = mods.peekPreapprovedRemoveParent();
+         while (preapprovedRemove != null)
+         {
+            if (item.isChildOf(preapprovedRemove.getItem()))
+            {
+               // approved
+               allow = Boolean.TRUE;
+               break;
+            }
+            else
+            {
+               // We're done with children of preapproved parent so add the cached
+               // modification to the overall list. This will cause it to
+               // get executed *after* its children 
+               // (i.e. remove parent after removing children)
+               mods.addModification(mods.popPreapprovedRemoveParent());
+               // Start checking grandparent
+               preapprovedRemove = mods.peekPreapprovedRemoveParent();
+            }
+         }
+      }
+      
+      if (allow == null)
+      {
+         // Check with our policy
+         
+         if (merge)
+         {
+            allow = Boolean.valueOf(policy.acceptMergeRemoval(item, null));
+         }
+         else
+         {            
+            // See if the base version of the remote node was aware of the
+            // item being removed
+            RepositoryItemMetadata baseRemoteItem = getBaseRemoteItem(rootName, item);            
+            allow = Boolean.valueOf(policy.acceptJoinRemoval(item, baseRemoteItem));         
+         }
+      }
+      
+      if (allow)
+      {
+         ContentModification mod = new ContentModification(Type.REMOVE_TO_CLUSTER, rootName, item);
+         
+         if (item.isDirectory())
+         {
+            // Tell cluster to prepare for the removal
+            mods.addModification(new ContentModification(Type.PREPARE_RMDIR_TO_CLUSTER, rootName, item));
+            // Push the actual removal on the stack to execute when
+            // children are done
+            mods.pushPreapprovedRemoveParent(mod);
+         }
+         else
+         {
+            mods.addModification(mod);
+         }
+      }
+      else
+      {         
+         // Removal is rejected; tell remote node to pull ours
+         Type type = item.isDirectory() ? Type.MKDIR_FROM_CLUSTER : Type.PULL_FROM_CLUSTER;
+         mods.addModification(new ContentModification(type, rootName, item)); 
+         
+         if (mods.getPrerejectedRemoveParent() == null && item.isDirectory())
+         {
+            mods.setPrerejectedRemoveParent(item); 
+         }
+      }
+      
+   }
+
+   @Override
+   protected void handleSimpleModification(String rootName, RepositoryItemMetadata base,
+         RepositoryItemMetadata modified, GeneratedModifications mods)
+   {
+      // If this is a modification, it can't be the child of an earlier removal.
+      // So, drain any remaining preapproved removals
+      drainPreapprovedRemovals(mods);
+      mods.setPrerejectedRemoveParent(null);
+      // Also can't be child of an earlier add, so drain any remaining prerejected adds
+      drainPrerejectedAdds(mods);
+      mods.setPreapprovedAddParent(null);
+      
+      if (checkAllowUpdate(base, modified))
+      {
+         mods.addModification(new ContentModification(Type.PUSH_TO_CLUSTER, rootName, modified));
+      }
+      else
+      {
+         mods.addModification(new ContentModification(Type.PULL_FROM_CLUSTER, rootName, base));
+      }
+   }
+
+   @Override
+   protected void handleDirectoryTimestampModification(String rootName, RepositoryItemMetadata base,
+         RepositoryItemMetadata modified, GeneratedModifications mods)
+   {
+      // If this is a modification, it can't be the child of an earlier removal.
+      // So, drain any remaining preapproved removals
+      drainPreapprovedRemovals(mods);
+      mods.setPrerejectedRemoveParent(null);
+      // Also can't be child of an earlier add, so drain any remaining prerejected adds
+      drainPrerejectedAdds(mods);
+      mods.setPreapprovedAddParent(null);
+      
+      mods.addModification(new ContentModification(Type.DIR_TIMESTAMP_MISMATCH, rootName, base));
+   }
+   
+   // ----------------------------------------------------------------  Private
+
+   private boolean checkAllowUpdate(RepositoryItemMetadata current, RepositoryItemMetadata update)
+   {
+      boolean allow = false;
+      if (merge)
+      {
+         allow = policy.acceptMergeUpdate(update, current);
+      }
+      else
+      {
+         allow = policy.acceptJoinUpdate(update, current);
+      }
+      return allow;
+   }
+   
+   private RepositoryItemMetadata getBaseRemoteItem(String rootName, RepositoryItemMetadata item)
+   {
+      RepositoryItemMetadata existingItem = null;
+      if (baseRemoteContent != null)
+      {
+         RepositoryRootMetadata existingRoot = baseRemoteContent.getRepositoryRootMetadata(rootName);
+         if (existingRoot != null)
+         {
+            existingItem = existingRoot.getItemMetadata(item.getRelativePathElements());
+         }
+      }
+      return existingItem;
+   }
+
+   private boolean isAdditionApproved(String rootName, RepositoryItemMetadata item,
+         RepositoryItemMetadata removedVersion)
+   {
+      boolean allow;
+      
+      if (removedVersion == null)
+      {
+         if (merge)
+         {
+            allow = policy.acceptMergeAddition(item);
+         }
+         else
+         {
+            // See if the base version of the remote node was aware of the
+            // item being added
+            RepositoryItemMetadata baseRemoteItem = getBaseRemoteItem(rootName, item);
+            allow = Boolean.valueOf(policy.acceptJoinAddition(item, baseRemoteItem));
+         }         
+      }
+      else
+      {
+         if (merge)
+         {
+            allow = policy.acceptMergeReincarnation(item, removedVersion);
+         }
+         else
+         {
+            allow = policy.acceptJoinReincarnation(item, removedVersion);
+         }         
+      }
+
+      return allow;
+   }
+
+   private void drainPrerejectedAdds(GeneratedModifications mods)
+   {
+      ContentModification prerejectedAdd;
+      while ((prerejectedAdd = mods.popPrerejectedAddParent()) != null)
+      {
+         mods.addModification(prerejectedAdd);
+      }
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/RemoteContentModificationGenerator.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/RemoteRemovalAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/RemoteRemovalAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/RemoteRemovalAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,54 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryRootMetadata;
+
+/**
+ * {@link SynchronizationRemoteAction} that updates the local metadata
+ * recording the removal.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class RemoteRemovalAction<T extends SynchronizationActionContext> 
+   extends SimpleSynchronizationRemoteAction<T>
+{
+
+   public RemoteRemovalAction(T context, ContentModification modification)
+   {
+      super(context, modification, false);
+   }
+
+   @Override
+   protected void doCommit()
+   {
+      ContentModification mod = getRepositoryContentModification();
+      RepositoryContentMetadata contentMetadata = getContext().getInProgressMetadata();
+      RepositoryRootMetadata rmd = contentMetadata.getRepositoryRootMetadata(mod.getRootName());
+      rmd.addItemMetadata(getMarkedRemovedItem(mod));
+   }
+  
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/RemoteRemovalAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/RemovalMetadataInsertionAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/RemovalMetadataInsertionAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/RemovalMetadataInsertionAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,107 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryItemMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryRootMetadata;
+
+
+/**
+ * {@link SynchronizationAction} that modifies the node's metadata to insert
+ * a missing {@link RepositoryItemMetadata} that tracks a removed item.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class RemovalMetadataInsertionAction<T extends SynchronizationActionContext> 
+      extends NoOpSynchronizationAction<T>
+{
+   private RepositoryItemMetadata replaced;
+   
+   /**
+    * Create a new RemovalMetadataInsertionAction.
+    * 
+    * @param synchronizationId
+    * @param modification
+    */
+   public RemovalMetadataInsertionAction(T context,
+         ContentModification modification)
+   {
+      super(context, modification);
+      
+      if (modification.getItem().isRemoved() == false)
+      {
+         throw new IllegalArgumentException("Item " + modification.getItem() + 
+               " is not marked as removed");
+      }
+   }
+
+   @Override
+   protected boolean doPrepare()
+   {
+      boolean ok = false;
+      ContentModification mod = getRepositoryContentModification();
+      RepositoryContentMetadata toUpdate = getContext().getInProgressMetadata();
+      RepositoryRootMetadata rmd = toUpdate.getRepositoryRootMetadata(mod.getRootName());
+      if (rmd != null)
+      {
+         replaced = rmd.getItemMetadata(mod.getItem().getRelativePathElements());
+         if (replaced == null)
+         {
+            rmd.addItemMetadata(mod.getItem());
+            ok = true;
+         }
+      }
+      return ok;
+   }
+
+   @Override
+   protected void doRollbackFromComplete()
+   {
+      if (replaced != null)
+      {
+         ContentModification mod = getRepositoryContentModification();
+         RepositoryContentMetadata toUpdate = getContext().getInProgressMetadata();
+         RepositoryRootMetadata rmd = toUpdate.getRepositoryRootMetadata(mod.getRootName());
+         if (rmd != null)
+         {
+            rmd.addItemMetadata(replaced);
+         }         
+      }      
+   }
+
+   @Override
+   protected void doRollbackFromPrepared()
+   {
+      ContentModification mod = getRepositoryContentModification();
+      RepositoryContentMetadata toUpdate = getContext().getInProgressMetadata();
+      RepositoryRootMetadata rmd = toUpdate.getRepositoryRootMetadata(mod.getRootName());
+      if (rmd != null)
+      {
+         rmd.removeItemMetadata(mod.getItem().getRelativePathElements());
+      }   
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/RemovalMetadataInsertionAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SimpleSynchronizationRemoteAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SimpleSynchronizationRemoteAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SimpleSynchronizationRemoteAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,67 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+/**
+ * Simple implementation of marker interface {@link SynchronizationRemoteAction}.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class SimpleSynchronizationRemoteAction<T extends SynchronizationActionContext>  
+   extends NoOpSynchronizationAction<T>
+      implements SynchronizationRemoteAction<T>
+{
+   private final boolean initiation;
+   
+   /**
+    * Create a new NoOpRepositorySynchronizationRemoteAction.
+    * 
+    * @param synchronizationId
+    * @param modification
+    */
+   public SimpleSynchronizationRemoteAction(T context,
+         ContentModification modification)
+   {
+      this(context, modification, false);
+   }
+   
+   /**
+    * Create a new NoOpRepositorySynchronizationRemoteAction.
+    * 
+    * @param synchronizationId
+    * @param modification
+    */
+   public SimpleSynchronizationRemoteAction(T context,
+         ContentModification modification, boolean initiation)
+   {
+      super(context, modification);
+      this.initiation = initiation;
+   }
+   
+   public boolean isInitiation()
+   {
+      return initiation;
+   }
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SimpleSynchronizationRemoteAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/StreamReadAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/StreamReadAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/StreamReadAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,169 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.jboss.logging.Logger;
+
+/**
+ * {@link SynchronizationReadAction} that reads from a {@link File}.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class StreamReadAction<T extends SynchronizationActionContext> extends AbstractSynchronizationAction<T>
+      implements SynchronizationReadAction<T>
+{
+   private static final Logger log = Logger.getLogger(StreamReadAction.class);
+   
+   /** 
+    * Max file transfer buffer size that we read at a time.
+    * This influences the number of times that we will invoke disk read/write file
+    * operations versus how much memory we will consume for a file transfer. 
+    */
+   public static final int MAX_CHUNK_BUFFER_SIZE = 512 * 1024;
+   
+   private final InputStream stream;
+   
+   /**
+    * Create a new StreamReadAction.
+    * 
+    * @param stream the stream to read
+    * @param context the overall context of the modification
+    * @param modification the modification
+    */
+   public StreamReadAction(InputStream stream, T context, 
+                         ContentModification modification)
+   {
+      super(context, modification);
+      if (stream == null)
+      {
+         throw new IllegalArgumentException("Null stream");
+      }
+      this.stream = stream;
+   }
+   
+   // ------------------------------------  RepositorySynchronizationReadAction
+
+   public ByteChunk getNextBytes() throws IOException
+   {
+      InputStream is = getInputStream();
+      byte[] b = null;
+      int read = -1;
+      synchronized (is)
+      {
+         b = new byte[MAX_CHUNK_BUFFER_SIZE];
+         read = is.read(b);
+      }
+      return new ByteChunk(b, read);
+   }
+   
+   // --------------------------------------------------------------  Protected
+
+   @Override
+   protected void doCancel()
+   {
+      safeCloseStream();
+   }
+
+   @Override
+   protected void doCommit()
+   {
+      safeCloseStream();
+   }
+
+   @Override
+   protected void doComplete() throws Exception
+   {
+      safeCloseStream();
+   }
+
+   @Override
+   protected boolean doPrepare()
+   {
+      safeCloseStream();
+      return true;
+   }
+
+   @Override
+   protected void doRollbackFromCancelled()
+   {
+      safeCloseStream();
+   }
+
+   @Override
+   protected void doRollbackFromComplete()
+   {
+      safeCloseStream();
+   }
+
+   @Override
+   protected void doRollbackFromOpen()
+   {
+      safeCloseStream();
+   }
+
+   @Override
+   protected void doRollbackFromPrepared()
+   {
+      safeCloseStream();
+   }
+
+   @Override
+   protected void doRollbackFromRollbackOnly()
+   {
+      safeCloseStream();
+   }
+
+   private synchronized InputStream getInputStream() throws IOException
+   {
+      State s = getState();
+      if (s != State.OPEN && s != State.CANCELLED)
+      {
+         throw new IllegalStateException("Cannot read when state is " + s);
+      }
+      return stream;
+   }
+   
+   private synchronized void safeCloseStream()
+   {
+      synchronized (stream)
+      {
+         try
+         {
+            stream.close();
+         }
+         catch (IOException e)
+         {
+            ContentModification mod = getRepositoryContentModification();
+            log.debug("Caught exception closing stream for " + mod.getRootName() + 
+                  " " + mod.getItem().getRelativePath(), e);
+         }
+      }
+   }
+
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/StreamReadAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,74 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+
+/**
+ * Encapsulates a single action needed to help synchronize the contents
+ * of one node's repository with the rest of the cluster.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public interface SynchronizationAction<T extends SynchronizationActionContext>
+{
+   /**
+    * Gets the contextual information for the set of actions of which 
+    * this object is a member.
+    * 
+    * @return the context. Will not be <code>null</code>
+    */
+   T getContext();
+   
+   /**
+    * Gets the metadata describing this action.
+    * 
+    * @return the metadata. Will not be <code>null</code>
+    */
+   ContentModification getRepositoryContentModification();
+   
+   /**
+    * Cancel the action.
+    */
+   void cancel();
+   
+   /**
+    * Execute the action and if successful mark it as complete.
+    */
+   void complete();
+   
+   /**
+    * Gets whether {@link #complete()} has been invoked.
+    * 
+    * @return <code>true</code> if {@link #complete()} has been invoked
+    */
+   boolean isComplete();
+   
+   /**
+    * Gets whether {@link #complete()} has been invoked.
+    * 
+    * @return <code>true</code> if {@link #complete()} has been invoked
+    */
+   boolean isCancelled();
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationActionContext.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationActionContext.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationActionContext.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,66 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+
+
+/**
+ * Contextual information that a {@link SynchronizationAction} can use.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class SynchronizationActionContext
+{
+   private final SynchronizationId<?> id;
+   private final RepositoryContentMetadata inProgressMetadata;
+   
+   public SynchronizationActionContext(SynchronizationId<?> id, 
+         RepositoryContentMetadata inProgressMetadata)
+   {
+      if (id == null)
+      {
+         throw new IllegalArgumentException("Null id");
+      }
+      if (inProgressMetadata == null)
+      {
+         throw new IllegalArgumentException("Null inProgressMetadata");
+      }
+      this.id = id;
+      this.inProgressMetadata = inProgressMetadata;
+   }
+
+   public SynchronizationId<?> getId()
+   {
+      return id;
+   }
+
+   public RepositoryContentMetadata getInProgressMetadata()
+   {
+      return inProgressMetadata;
+   }
+   
+   
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationActionContext.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationId.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationId.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationId.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,104 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+import java.io.Serializable;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Unique id for a set of changes needed to synchronize a node's
+ * repository content with the cluster.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ * 
+ * @param T the type of the originator
+ */
+public class SynchronizationId<T extends Serializable> implements Serializable
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = -604832735573100571L;
+   
+   private static final long vm_base = System.currentTimeMillis();
+   private static final AtomicInteger count = new AtomicInteger();
+   
+   private final T originator;
+   private final long timestamp = vm_base;
+   private final int index = count.incrementAndGet();
+   
+   public SynchronizationId(T originator)
+   {
+      if (originator == null)
+      {
+         throw new IllegalArgumentException("Null originator");
+      }
+      this.originator = originator;
+   }
+
+   public T getOriginator()
+   {
+      return originator;
+   }
+
+   @Override
+   public boolean equals(Object obj)
+   {
+      if (this == obj)
+         return true;
+      
+      if (obj instanceof SynchronizationId)
+      {
+         SynchronizationId<?> other = (SynchronizationId<?>) obj;
+         return this.index == other.index 
+                  && this.timestamp == other.timestamp 
+                  && this.originator.equals(other.originator);
+      }
+      return false;
+   }
+
+   @Override
+   public int hashCode()
+   {
+      int result = 17;
+      result = 31 * result + index;
+      result = 31 * result + ((int) (timestamp ^ (timestamp >>>32)));
+      result = 31 * result + originator.hashCode();
+      return result;
+   }
+
+   @Override
+   public String toString()
+   {
+      return new StringBuilder(getClass().getName())
+      .append("[originator='")
+      .append(originator)
+      .append(",timestamp=")
+      .append(timestamp)
+      .append(",index=")
+      .append(index)
+      .append(']').toString();
+   }
+   
+   
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationId.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationInitiationAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationInitiationAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationInitiationAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,39 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+/**
+ * Marker interface for a {@link SynchronizationAction} that initiates a
+ * process that subsequent actions will drive to comletion. An example of this
+ * would be an action to initiate the removal of a directory, which would
+ * be driven to completion by subsequent actions to remove child content
+ * and finally an action to remove the directory itself..
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public interface SynchronizationInitiationAction<T extends SynchronizationActionContext>  
+   extends SynchronizationAction<T>
+{
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationInitiationAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationPolicy.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationPolicy.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationPolicy.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,186 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryItemMetadata;
+
+/**
+ * Policy to decide how to handle content updates from nodes attempting
+ * to join the cluster or from cluster merges. The policy is consulted on
+ * the "authoritative" node, i.e. the master node for the service on the
+ * cluster.
+ * 
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public interface SynchronizationPolicy
+{
+   
+   /**
+    * Should the item represented by <code>toAdd</code> that is available
+    * on a newly <i>joining</i> node be accepted for use around the cluster 
+    * when the cluster's records show no record of an item with the same path? 
+    * Such a case potentially could mean newly joining node was unaware of an 
+    * earlier removal that occurred while it was offline and that the cluster
+    * has also {@link #purgeRemovedItems(RepositoryContentMetadata) purged
+    * from its records}.
+    * 
+    * @param toAdd  the item to add
+    * @param joinersPrevious information, if available, on the timestamp of the
+    *                        item that was present on the joining node when
+    *                        it stopped. May be <code>null</code>, indicating
+    *                        the joining node was unaware of the item when stopped.
+    * @return <code>true</code> if the addition should be accepted
+    */
+   boolean acceptJoinAddition(RepositoryItemMetadata toAdd, 
+                              RepositoryItemMetadata joinersPrevious);
+   
+   /**
+    * Should the item represented by <code>reincarnation</code> that is available
+    * on a newly <i>joining</i> node be accepted for use around the cluster when 
+    * the cluster's records show an item with the same path was previously 
+    * removed? Such a case potentially could mean the newly joining node 
+    * was unaware of a removal that occurred while it was offline.
+    * 
+    * @param reincarnation  the new version of the item
+    * @param current the cluster's current version of the item, showing when
+    *        it was removed and by whom. The "when" should reflect this
+    *        node's time of removal, not the time on the node that originated
+    *        the removal.
+    * @return <code>true</code> if the reincarnation should be accepted
+    */
+   boolean acceptJoinReincarnation(RepositoryItemMetadata reincarnation, 
+                               RepositoryItemMetadata current);
+   
+   /**
+    * Should the item represented by <code>toRemove</code>that is unavailable
+    * on a merging set of nodes be removed from around the cluster when 
+    * the cluster's records show an item with the same path?  Such a case 
+    * potentially could mean the newly joining node was unaware of a new
+    * deployment of the item that occurred while it was offline.
+    * 
+    * @param current the cluster's current version of the item
+    * @param joinersItem the joining node's view of item to remove. May be null, 
+    *                 indicating the sender is unaware of the item. If not null,
+    *                 the timestamp of this item should reflect when the item 
+    *                 was removed, if known. If the time the item was removed is 
+    *                 not known, the timestamp should reflect the last known 
+    *                 timestamp of the item that was removed.
+    * @return <code>true</code> if the removal should be accepted
+    */
+   boolean acceptJoinRemoval(RepositoryItemMetadata current, 
+                             RepositoryItemMetadata joinersItem);
+   /**
+    * Should the item represented by <code>update</code> that is available
+    * on a newly <i>joining</i> node be accepted for use around the cluster when the 
+    * cluster's records show an item with the same path with a different version? 
+    * Such a case potentially could mean the newly joining node was unaware of 
+    * changes that occurred while it was offline. 
+    * 
+    * @param update  the new version of the item
+    * @param current the cluster's current version of the item
+    * @return <code>true</code> if the update should be accepted
+    */
+   boolean acceptJoinUpdate(RepositoryItemMetadata update, 
+                            RepositoryItemMetadata current);
+   
+   /**
+    * Should the item represented by <code>toAdd</code> that is available
+    * on a merging set of nodes be accepted for use around the cluster when the 
+    * cluster's records show no record of an item with the same path? Such a 
+    * case potentially could mean the merging nodes were unaware of an earlier 
+    * removal that occurred while the cluster was split and that the cluster
+    * has also {@link #purgeRemovedItems(RepositoryContentMetadata) purged
+    * from its records}.
+    * 
+    * @param toAdd  the item to add
+    * @return <code>true</code> if the addition should be accepted
+    */
+   boolean acceptMergeAddition(RepositoryItemMetadata toAdd);
+   
+   
+   /**
+    * Should the item represented by <code>reincarnation</code> that is available
+    * on a merging set of nodes be accepted for use around the cluster when the 
+    * cluster's records show an item with the same path was previously removed? 
+    * Such a case potentially could mean the merging nodes were unaware of a
+    * removal that occurred while the cluster was split.
+    * 
+    * @param reincarnation  the new version of the item
+    * @param current the cluster's current version of the item, showing when
+    *        it was removed and by whom
+    * @return <code>true</code> if the reincarnation should be accepted
+    */
+   boolean acceptMergeReincarnation(RepositoryItemMetadata reincarnation, 
+                               RepositoryItemMetadata current);
+   
+   /**
+    * Should the item represented by <code>toRemove</code> that is unavailable
+    * on a newly <i>joining</i> node be removed from around the cluster when 
+    * the cluster's records show an item with the same path?  Such a case 
+    * potentially could mean the newly joining node was unaware of a new
+    * deployment of the item that occurred  while the cluster was split.
+    * 
+    * @param current the cluster's current version of the item
+    * @param mergersView the merging node's view of item to remove. May be null, 
+    *                 indicating the sender is unaware of the item. If not null,
+    *                 the timestamp of this item should reflect when the item 
+    *                 was removed, if known. If the time the item was removed is 
+    *                 not known, the timestamp should reflect the last known 
+    *                 timestamp of the item that was removed.
+    * @return <code>true</code> if the removal should be accepted
+    */
+   boolean acceptMergeRemoval(RepositoryItemMetadata current, 
+                              RepositoryItemMetadata mergersView);
+   
+
+   /**
+    * Should the item represented by <code>update</code> that is available
+    * on a merging set of nodes be accepted for use around the cluster when the 
+    * cluster's records show an item with the same path with a different version? 
+    * Such a case potentially could mean the merging nodes were unaware of 
+    * changes that occurred while the cluster was split.
+    * 
+    * @param update  the new version of the item
+    * @param current the cluster's current version of the item
+    * @return <code>true</code> if the update should be accepted
+    */
+   boolean acceptMergeUpdate(RepositoryItemMetadata update, 
+                             RepositoryItemMetadata current);
+   
+   /**
+    * Request that the policy remove any {@link RepositoryItemMetadata} objects
+    * that are listed as {@link RepositoryItemMetadata#isRemoved() removed}
+    * if the policy no longer wishes to consider them in its decision making.
+    * Used to prevent perpetual growth in the size of the RepositoryContentMetadata
+    * by eventually purging records of removed items.
+    * 
+    * @param content the content. Cannot be <code>null</code>.
+    * 
+    * @return <code>true</code> if any items were purged, <code>false</code>
+    *         if not
+    */
+   boolean purgeRemovedItems(RepositoryContentMetadata content);
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationPolicy.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationReadAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationReadAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationReadAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,49 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+import java.io.IOException;
+
+
+
+/**
+ * A {@ link SynchronizationAction} that involves reading content from an item.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public interface SynchronizationReadAction<T extends SynchronizationActionContext> 
+   extends SynchronizationAction<T>
+{
+   /**
+    * Gets the next chunk of bytes from the item associated with this action.
+    * Each call to this method will retrieve more bytes
+    * 
+    * @return a ByteChunk.
+    * 
+    * @throws IOException if there is a problem reading the bytes.
+    * @throws IllegalStateException if {@link #isComplete()} would return <code>true</code>
+    */
+   ByteChunk getNextBytes() throws IOException;
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationReadAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationRemoteAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationRemoteAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationRemoteAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,43 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+/**
+ * Marker interface for a {@link SynchronizationAction} that a
+ * node should handle by telling the other nodes in the cluster to execute the
+ * {@link SynchronizationAction#getRepositoryContentModification() modification}.
+ * <p>
+ * An example of this would be handling of a 
+ * {@link ContentModification.Type#REMOVE_TO_CLUSTER} wherein the
+ * node driving the synchronization would tell the other nodes to
+ * execute the remove by sending an RPC with the modification.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public interface SynchronizationRemoteAction<T extends SynchronizationActionContext>  
+   extends SynchronizationAction<T>
+{
+   boolean isInitiation();
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationRemoteAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationWriteAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationWriteAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationWriteAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,49 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+import java.io.IOException;
+
+
+/**
+ * A SynchronizationAction that involves writing content to an item.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public interface SynchronizationWriteAction<T extends SynchronizationActionContext> 
+   extends SynchronizationAction<T>
+{
+   /**
+    * Write the given bytes to the item referenced by this action.
+    * 
+    * @param bytes the bytes. Cannot be <code>null</code>. The
+    *              {@link ByteChunk#getByteCount() byte count} must be greater
+    *              than -1.
+    * 
+    * @throws IOException
+    * @throws IllegalStateException if {@link #isComplete()} would return <code>true</code>
+    */
+   void writeBytes(ByteChunk bytes) throws IOException;
+}


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/SynchronizationWriteAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/TwoPhaseCommitAction.java
===================================================================
--- trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/TwoPhaseCommitAction.java	                        (rev 0)
+++ trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/TwoPhaseCommitAction.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,43 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.system.server.profileservice.repository.clustered.sync;
+
+import org.jboss.system.server.profileservice.repository.clustered.local.LocalContentManager;
+
+
+/**
+ * View of a {@link SynchronizationAction} used by an
+ * implementation of {@link LocalContentManager}, allowing the persister
+ * to take the action through a two phase commit process.
+ * 
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public interface TwoPhaseCommitAction<T extends SynchronizationActionContext>
+      extends SynchronizationAction<T>
+{    
+   boolean prepare();
+   void commit();
+   void rollback();
+}
\ No newline at end of file


Property changes on: trunk/system/src/main/org/jboss/system/server/profileservice/repository/clustered/sync/TwoPhaseCommitAction.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/tests/org/jboss/test/server/profileservice/clustered/test/JAXBRepositoryContentMetadataPersisterUnitTestCase.java
===================================================================
--- trunk/system/src/tests/org/jboss/test/server/profileservice/clustered/test/JAXBRepositoryContentMetadataPersisterUnitTestCase.java	                        (rev 0)
+++ trunk/system/src/tests/org/jboss/test/server/profileservice/clustered/test/JAXBRepositoryContentMetadataPersisterUnitTestCase.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,193 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.test.server.profileservice.clustered.test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.TimeZone;
+
+import junit.framework.TestCase;
+
+import org.jboss.profileservice.spi.ProfileKey;
+import org.jboss.system.server.profileservice.repository.clustered.local.JAXBRepositoryContentMetadataPersister;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryItemMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryRootMetadata;
+
+/**
+ * Tests of {@link JAXBRepositoryContentMetadataPersister}.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class JAXBRepositoryContentMetadataPersisterUnitTestCase extends TestCase
+{
+   private Set<File> toDelete = new HashSet<File>();
+   
+   /**
+    * Create a new JAXBRepositoryContentMetadataPersisterUnitTestCase.
+    * 
+    * @param name
+    */
+   public JAXBRepositoryContentMetadataPersisterUnitTestCase(String name)
+   {
+      super(name);
+   }
+   
+   protected void tearDown() throws Exception
+   {
+      for (File f : toDelete)
+      {
+         f.delete();         
+      }
+   }
+   
+   public void testSerializationDeserialization() throws Exception
+   {
+      ProfileKey key = new ProfileKey("domain", "server", "name");
+      RepositoryContentMetadata rcm = new RepositoryContentMetadata(key);
+      RepositoryRootMetadata rrm = new RepositoryRootMetadata("normal");
+      
+      RepositoryItemMetadata rim = new RepositoryItemMetadata();
+      rim.setRelativePath("/item");
+      rim.setOriginatingNode("192.168.100.1:1099");
+      rim.setTimestamp(1);
+      rrm.addItemMetadata(rim);
+      
+      rim = new RepositoryItemMetadata();
+      rim.setRelativePath("/removed_item");
+      rim.setRemoved(true);
+      rim.setOriginatingNode("192.168.100.1:1099");
+      rim.setTimestamp(2);
+      rrm.addItemMetadata(rim);
+      
+      rim = new RepositoryItemMetadata();
+      rim.setRelativePath("/dir.sar");
+      rim.setDirectory(true);
+      rim.setOriginatingNode("192.168.100.2:1099");
+      rim.setTimestamp(4);
+      rrm.addItemMetadata(rim);
+      rim = new RepositoryItemMetadata();
+      rim.setRelativePath("/dir.sar/item.jar");
+      rim.setOriginatingNode("192.168.100.2:1099");
+      rim.setTimestamp(4);
+      rrm.addItemMetadata(rim);
+      rim = new RepositoryItemMetadata();
+      rim.setRelativePath("/dir.sar/META-INF");
+      rim.setOriginatingNode("192.168.100.2:1099");
+      rim.setDirectory(true);
+      rim.setTimestamp(3);
+      rrm.addItemMetadata(rim);
+      rim = new RepositoryItemMetadata();
+      rim.setRelativePath("/dir.sar/META-INF/jboss-beans.xml");
+      rim.setDirectory(true);
+      rim.setOriginatingNode("192.168.100.2:1099");
+      rim.setTimestamp(3);
+      rrm.addItemMetadata(rim);
+      
+      rim = new RepositoryItemMetadata();
+      rim.setRelativePath("/removed_dir.ear");
+      rim.setDirectory(true);
+      rim.setOriginatingNode("192.168.100.2:1099");
+      rim.setTimestamp(7);
+      rim.setRemoved(true);
+      rrm.addItemMetadata(rim);
+      rim = new RepositoryItemMetadata();
+      rim.setRelativePath("/removed_dir.ear/ejb.jar");
+      rim.setOriginatingNode("192.168.100.2:1099");
+      rim.setTimestamp(5);
+      rim.setRemoved(true);
+      rrm.addItemMetadata(rim);
+      rim = new RepositoryItemMetadata();
+      rim.setRelativePath("/removed_dir.ear/META-INF");
+      rim.setDirectory(true);
+      rim.setOriginatingNode("192.168.100.3:1099");
+      rim.setTimestamp(7);
+      rim.setRemoved(true);
+      rrm.addItemMetadata(rim);
+      rim = new RepositoryItemMetadata();
+      rim.setRelativePath("/removed_dir.ear/META-INF/application.xml");
+      rim.setDirectory(true);
+      rim.setOriginatingNode("192.168.100.3:1099");
+      rim.setTimestamp(7);
+      rim.setRemoved(true);
+      rrm.addItemMetadata(rim);
+      rim = new RepositoryItemMetadata();
+      rim.setRelativePath("/removed_dir.ear/war.war");
+      rim.setOriginatingNode("192.168.100.3:1099");
+      rim.setTimestamp(6);
+      rim.setRemoved(true);
+      rrm.addItemMetadata(rim);
+      
+      RepositoryRootMetadata emptyRRM = new RepositoryRootMetadata("empty");
+      rim = new RepositoryItemMetadata();
+      rim.setRelativePath("/");
+      rim.setOriginatingNode("192.168.100.4:1099");
+      rim.setTimestamp(10);
+      rcm.setRepositories(Arrays.asList(new RepositoryRootMetadata[]{rrm, emptyRRM}));
+      
+      rim = new RepositoryItemMetadata();
+      rim.setRelativePath("/");
+      rim.setOriginatingNode("127.0.0.1:1099");
+      rim.setTimestamp(20);
+      emptyRRM.addItemMetadata(rim);
+      
+      File temp = new File(System.getProperty("java.io.tmpdir"));
+      
+      JAXBRepositoryContentMetadataPersister testee = new JAXBRepositoryContentMetadataPersister(temp.toURI());
+      testee.store("test", rcm);
+      
+      // Ensure we clean up
+      File stored = testee.getMetadataPath("test");
+      stored.deleteOnExit();
+      toDelete.add(stored);
+      
+      InputStreamReader isr = new InputStreamReader(new FileInputStream(stored));
+      StringWriter writer = new StringWriter();
+      int read;
+      while ((read = isr.read()) != -1)
+         writer.write(read);
+      writer.close();
+      System.out.println(writer.toString());
+      
+      RepositoryContentMetadata deserialized = testee.load("test");
+      
+      assertEquals(rcm, deserialized);
+   }
+   
+   public void testSplit()
+   {
+      String x = "/";
+      String[] split = x.split("/");
+      System.out.println(split.length);
+      for (String s : split)
+         System.out.println(s);
+   }
+
+}


Property changes on: trunk/system/src/tests/org/jboss/test/server/profileservice/clustered/test/JAXBRepositoryContentMetadataPersisterUnitTestCase.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/tests/org/jboss/test/server/profileservice/clustered/test/MockSynchronizationPolicy.java
===================================================================
--- trunk/system/src/tests/org/jboss/test/server/profileservice/clustered/test/MockSynchronizationPolicy.java	                        (rev 0)
+++ trunk/system/src/tests/org/jboss/test/server/profileservice/clustered/test/MockSynchronizationPolicy.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,63 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.test.server.profileservice.clustered.test;
+
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryItemMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.sync.AbstractSynchronizationPolicy;
+
+/**
+ *
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class MockSynchronizationPolicy extends AbstractSynchronizationPolicy
+{
+
+   @Override
+   protected boolean acceptAddition(RepositoryItemMetadata toAdd, RepositoryItemMetadata joinersPrevious, boolean merge)
+   {
+      return true;
+   }
+
+   @Override
+   protected boolean acceptReincarnation(RepositoryItemMetadata reincarnation, RepositoryItemMetadata current,
+         boolean merge)
+   {
+      return true;
+   }
+
+   @Override
+   protected boolean acceptRemoval(RepositoryItemMetadata current, RepositoryItemMetadata sendersView, boolean merge)
+   {
+      return true;
+   }
+
+   @Override
+   protected boolean acceptUpdate(RepositoryItemMetadata update, RepositoryItemMetadata current, boolean merge)
+   {
+      return true;
+   }
+
+}


Property changes on: trunk/system/src/tests/org/jboss/test/server/profileservice/clustered/test/MockSynchronizationPolicy.java
___________________________________________________________________
Name: svn:keywords
   + 

Added: trunk/system/src/tests/org/jboss/test/server/profileservice/clustered/test/RemoteContentModificationGeneratorUnitTestCase.java
===================================================================
--- trunk/system/src/tests/org/jboss/test/server/profileservice/clustered/test/RemoteContentModificationGeneratorUnitTestCase.java	                        (rev 0)
+++ trunk/system/src/tests/org/jboss/test/server/profileservice/clustered/test/RemoteContentModificationGeneratorUnitTestCase.java	2009-03-31 22:10:16 UTC (rev 86553)
@@ -0,0 +1,255 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * 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.jboss.test.server.profileservice.clustered.test;
+
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryContentMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryItemMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.metadata.RepositoryRootMetadata;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ContentModification;
+import org.jboss.system.server.profileservice.repository.clustered.sync.RemoteContentModificationGenerator;
+import org.jboss.system.server.profileservice.repository.clustered.sync.ContentModification.Type;
+
+/**
+ *
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class RemoteContentModificationGeneratorUnitTestCase extends TestCase
+{
+
+   /**
+    * Create a new RemoteContentModificationGeneratorUnitTestCase.
+    * 
+    * @param name
+    */
+   public RemoteContentModificationGeneratorUnitTestCase(String name)
+   {
+      super(name);
+   }
+   
+   public void testJoinAdditionToEmpty() throws Exception
+   {
+      RepositoryContentMetadata base = getNewRepositoryContentMetadata();
+
+      RepositoryContentMetadata modified = getNewRepositoryContentMetadata();
+      
+      RepositoryItemMetadata item = new RepositoryItemMetadata();
+      item.setRelativePath("item");
+      item.setOriginatingNode("localhost");
+      item.setTimestamp(1);
+      modified.getRepositories().iterator().next().getContent().add(item);
+      
+      RemoteContentModificationGenerator testee = 
+         new RemoteContentModificationGenerator(new MockSynchronizationPolicy(), getNewRepositoryContentMetadata());
+      
+      List<ContentModification> mods = testee.getModificationList(base, modified);
+      
+      assertEquals(1, mods.size());
+      assertEquals(Type.PUSH_TO_CLUSTER, mods.get(0).getType());
+      assertEquals(item, mods.get(0).getItem());
+   }
+   
+   public void testMergeAdditionToEmpty() throws Exception
+   {
+      RepositoryContentMetadata base = getNewRepositoryContentMetadata();
+
+      RepositoryContentMetadata modified = getNewRepositoryContentMetadata();
+      
+      RepositoryItemMetadata item = new RepositoryItemMetadata();
+      item.setRelativePath("item");
+      item.setOriginatingNode("localhost");
+      item.setTimestamp(1);
+      modified.getRepositories().iterator().next().getContent().add(item);
+      
+      RemoteContentModificationGenerator testee = 
+         new RemoteContentModificationGenerator(new MockSynchronizationPolicy());
+      
+      List<ContentModification> mods = testee.getModificationList(base, modified);
+      
+      assertEquals(1, mods.size());
+      assertEquals(Type.PUSH_TO_CLUSTER, mods.get(0).getType());
+      assertEquals(item, mods.get(0).getItem());
+   }
+   
+   public void testSimpleJoinAddition() throws Exception
+   {
+      RepositoryContentMetadata base = getNewRepositoryContentMetadata();
+      
+      RepositoryItemMetadata item1 = new RepositoryItemMetadata();
+      item1.setRelativePath("item1");
+      item1.setOriginatingNode("localhost");
+      item1.setTimestamp(1);
+      base.getRepositories().iterator().next().getContent().add(item1);
+
+      RepositoryContentMetadata modified = getNewRepositoryContentMetadata();
+      
+      item1 = new RepositoryItemMetadata();
+      item1.setRelativePath("item1");
+      item1.setOriginatingNode("localhost");
+      item1.setTimestamp(1);
+      modified.getRepositories().iterator().next().getContent().add(item1);
+      
+      RepositoryItemMetadata item2 = new RepositoryItemMetadata();
+      item2.setRelativePath("item2");
+      item2.setOriginatingNode("localhost");
+      item2.setTimestamp(1);
+      modified.getRepositories().iterator().next().getContent().add(item2);
+      
+      RemoteContentModificationGenerator testee = 
+         new RemoteContentModificationGenerator(new MockSynchronizationPolicy(), 
+                                                getNewRepositoryContentMetadata());
+      
+      List<ContentModification> mods = testee.getModificationList(base, modified);
+      
+      assertEquals(1, mods.size());
+      assertEquals(Type.PUSH_TO_CLUSTER, mods.get(0).getType());
+      assertEquals(item2, mods.get(0).getItem());
+   }
+   
+   public void testSimpleMergeAddition() throws Exception
+   {
+      RepositoryContentMetadata base = getNewRepositoryContentMetadata();
+      
+      RepositoryItemMetadata item1 = new RepositoryItemMetadata();
+      item1.setRelativePath("item1");
+      item1.setOriginatingNode("localhost");
+      item1.setTimestamp(1);
+      base.getRepositories().iterator().next().getContent().add(item1);
+
+      RepositoryContentMetadata modified = getNewRepositoryContentMetadata();
+      
+      item1 = new RepositoryItemMetadata();
+      item1.setRelativePath("item1");
+      item1.setOriginatingNode("localhost");
+      item1.setTimestamp(1);
+      modified.getRepositories().iterator().next().getContent().add(item1);
+      
+      RepositoryItemMetadata item2 = new RepositoryItemMetadata();
+      item2.setRelativePath("item2");
+      item2.setOriginatingNode("localhost");
+      item2.setTimestamp(1);
+      modified.getRepositories().iterator().next().getContent().add(item2);
+      
+      RemoteContentModificationGenerator testee = 
+         new RemoteContentModificationGenerator(new MockSynchronizationPolicy());
+      
+      List<ContentModification> mods = testee.getModificationList(base, modified);
+      
+      assertEquals(1, mods.size());
+      assertEquals(Type.PUSH_TO_CLUSTER, mods.get(0).getType());
+      assertEquals(item2, mods.get(0).getItem());
+   }
+   
+   public void testJoinSwap() throws Exception
+   {
+      RepositoryContentMetadata base = getNewRepositoryContentMetadata();
+      
+      RepositoryItemMetadata item1 = new RepositoryItemMetadata();
+      item1.setRelativePath("item1");
+      item1.setOriginatingNode("localhost");
+      item1.setTimestamp(1);
+      base.getRepositories().iterator().next().getContent().add(item1);
+
+      RepositoryContentMetadata modified = getNewRepositoryContentMetadata();
+      
+      RepositoryItemMetadata item2 = new RepositoryItemMetadata();
+      item2.setRelativePath("item2");
+      item2.setOriginatingNode("localhost");
+      item2.setTimestamp(1);
+      modified.getRepositories().iterator().next().getContent().add(item2);
+      
+      MockSynchronizationPolicy policy = new MockSynchronizationPolicy();
+      policy.setAllowJoinRemovals(Boolean.FALSE);
+      
+      RemoteContentModificationGenerator testee = 
+         new RemoteContentModificationGenerator(policy, getNewRepositoryContentMetadata());
+      
+      List<ContentModification> mods = testee.getModificationList(base, modified);
+      
+      assertEquals(2, mods.size());
+      assertEquals(Type.PULL_FROM_CLUSTER, mods.get(0).getType());
+      assertEquals(item1, mods.get(0).getItem());
+      assertEquals(Type.PUSH_TO_CLUSTER, mods.get(1).getType());
+      assertEquals(item2, mods.get(1).getItem());
+   }
+   
+   public void testMergeSwap() throws Exception
+   {
+      RepositoryContentMetadata base = getNewRepositoryContentMetadata();
+      
+      RepositoryItemMetadata item1 = new RepositoryItemMetadata();
+      item1.setRelativePath("item1");
+      item1.setOriginatingNode("localhost");
+      item1.setTimestamp(1);
+      base.getRepositories().iterator().next().getContent().add(item1);
+
+      RepositoryContentMetadata modified = getNewRepositoryContentMetadata();
+      
+      RepositoryItemMetadata item2 = new RepositoryItemMetadata();
+      item2.setRelativePath("item2");
+      item2.setOriginatingNode("localhost");
+      item2.setTimestamp(1);
+      modified.getRepositories().iterator().next().getContent().add(item2);
+      
+      MockSynchronizationPolicy policy = new MockSynchronizationPolicy();
+      policy.setAllowMergeRemovals(Boolean.FALSE);
+      
+      RemoteContentModificationGenerator testee = 
+         new RemoteContentModificationGenerator(policy);
+      
+      List<ContentModification> mods = testee.getModificationList(base, modified);
+      
+      assertEquals(2, mods.size());
+      assertEquals(Type.PULL_FROM_CLUSTER, mods.get(0).getType());
+      assertEquals(item1, mods.get(0).getItem());
+      assertEquals(Type.PUSH_TO_CLUSTER, mods.get(1).getType());
+      assertEquals(item2, mods.get(1).getItem());
+   }
+
+   private static RepositoryContentMetadata getNewRepositoryContentMetadata()
+   {
+     return getNewRepositoryContentMetadata("farm");
+   }
+   
+   private static RepositoryContentMetadata getNewRepositoryContentMetadata(String ... rootNames)
+   {
+      RepositoryContentMetadata base = new RepositoryContentMetadata();
+      base.setDomain("domain");
+      base.setServer("server");
+      base.setName("name");
+      for (String rootName : rootNames)
+      {
+         RepositoryRootMetadata root = new RepositoryRootMetadata();
+         root.setName(rootName);
+         base.getRepositories().add(root);
+      }
+      
+      return base;
+   }
+}


Property changes on: trunk/system/src/tests/org/jboss/test/server/profileservice/clustered/test/RemoteContentModificationGeneratorUnitTestCase.java
___________________________________________________________________
Name: svn:keywords
   + 




More information about the jboss-cvs-commits mailing list