svn commit: r1054691 - in /lenya/branches/BRANCH_2_1_X/src: impl/java/org/apache/lenya/cms/cluster/ impl/java/org/apache/lenya/cms/cluster/impl/ java/org/apache/lenya/cms/cluster/ java/org/apache/lenya/cms/repository/ modules/sitetree/java/src/org/apac...

[email protected] Mon, 03 Jan 2011 17:33:51 -0000
Newsgroups gmane.comp.cms.lenya.cvs
Message-ID <[email protected]>
Author: froethenbacher
Date: Mon Jan  3 17:33:50 2011
New Revision: 1054691

URL: http://svn.apache.org/viewvc?rev=1054691&view=rev
Log:
Added cluster manager.

Added:
    lenya/branches/BRANCH_2_1_X/src/impl/java/org/apache/lenya/cms/cluster/
    lenya/branches/BRANCH_2_1_X/src/impl/java/org/apache/lenya/cms/cluster/impl/
    lenya/branches/BRANCH_2_1_X/src/impl/java/org/apache/lenya/cms/cluster/impl/ClusterManagerImpl.java
    lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/cluster/
    lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/cluster/ClusterConfigurationException.java
    lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/cluster/ClusterManager.java
    lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/cluster/ClusterMode.java
    lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/config/cocoon-xconf/sourcenodercmlfactory.xconf
    lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/impl/
    lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/impl/SourceNodeRcmlFactoryImpl.java
    lenya/branches/BRANCH_2_1_X/src/webapp/lenya/config/cluster/
    lenya/branches/BRANCH_2_1_X/src/webapp/lenya/config/cluster/cluster.xconf
    lenya/branches/BRANCH_2_1_X/src/webapp/lenya/config/cocoon-xconf/cluster/
    lenya/branches/BRANCH_2_1_X/src/webapp/lenya/config/cocoon-xconf/cluster/cluster.xconf
Modified:
    lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/repository/RepositoryManagerImpl.java
    lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/repository/SessionImpl.java
    lenya/branches/BRANCH_2_1_X/src/modules/sitetree/java/src/org/apache/lenya/cms/site/tree2/SiteTreeImpl.java
    lenya/branches/BRANCH_2_1_X/src/modules/sitetree/java/src/org/apache/lenya/cms/site/tree2/SiteTreeMonitorImpl.java
    lenya/branches/BRANCH_2_1_X/src/modules/sitetree/java/src/org/apache/lenya/cms/site/tree2/TreeSiteManager.java
    lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/SourceNode.java
    lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/SourceNodeRCML.java
    lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/SourceNodeRcmlFactory.java

Added: lenya/branches/BRANCH_2_1_X/src/impl/java/org/apache/lenya/cms/cluster/impl/ClusterManagerImpl.java
URL: http://svn.apache.org/viewvc/lenya/branches/BRANCH_2_1_X/src/impl/java/org/apache/lenya/cms/cluster/impl/ClusterManagerImpl.java?rev=1054691&view=auto
==============================================================================
--- lenya/branches/BRANCH_2_1_X/src/impl/java/org/apache/lenya/cms/cluster/impl/ClusterManagerImpl.java (added)
+++ lenya/branches/BRANCH_2_1_X/src/impl/java/org/apache/lenya/cms/cluster/impl/ClusterManagerImpl.java Mon Jan  3 17:33:50 2011
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.cluster.impl;
+
+import org.apache.avalon.framework.activity.Initializable;
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
+import org.apache.avalon.framework.logger.AbstractLogEnabled;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceResolver;
+import org.apache.lenya.cms.cluster.ClusterManager;
+import org.apache.lenya.cms.cluster.ClusterConfigurationException;
+import org.apache.lenya.cms.cluster.ClusterMode;
+
+/**
+ * Cluster manager implementation.
+ *
+ * For configuration of the cluster see <tt>lenya/config/cluster.xconf</tt>
+ */
+public class ClusterManagerImpl extends AbstractLogEnabled
+implements ClusterManager, Initializable, Serviceable
+{
+
+    private final static String CONFIG_URI =
+        "context:/lenya/config/cluster/cluster.xconf";
+
+    private ServiceManager manager;
+
+    private boolean isClusterEnabled = false;
+    private ClusterMode clusterMode = ClusterMode.MASTER;
+
+    @Override
+    public boolean isClusterEnabled() {
+        return isClusterEnabled;
+    }
+
+    @Override
+    public boolean isMaster() {
+        return !isClusterEnabled() || ClusterMode.MASTER.equals(clusterMode);
+    }
+
+    @Override
+    public boolean isSlave() {
+        return isClusterEnabled() && ClusterMode.SLAVE.equals(clusterMode);
+    }
+
+    @Override
+    public void initialize() throws Exception {
+        SourceResolver resolver = null;
+        try {
+            resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
+            readConfiguration(resolver);
+        } catch (Exception e) {
+            if (getLogger().isErrorEnabled())
+                getLogger().error("Error reading cluster configuration", e);
+        } finally {
+            if (resolver != null)
+                manager.release(resolver);
+        }
+        if ( getLogger().isInfoEnabled()) {
+            if (isClusterEnabled()) {
+                getLogger().info("Running Lenya in cluster mode [" +
+                        clusterMode.getText() + "]");
+            } else {
+                getLogger().info("Lenya cluster mode disabled.");
+            }
+        }
+    }
+
+    /**
+     * Read cluster configuration.
+     * @param resolver Source resolver.
+     * @throws ClusterConfigurationException If reading cluster
+     *      configuration failed.
+     */
+    private void readConfiguration(SourceResolver resolver)
+    throws ClusterConfigurationException
+    {
+        try {
+            Source configSource = resolver.resolveURI(CONFIG_URI);
+            DefaultConfigurationBuilder builder =
+                new DefaultConfigurationBuilder();
+            Configuration config =
+                builder.build(configSource.getInputStream());
+            // Is cluster enabled.
+            Configuration enabledElem = config.getChild("enabled");
+            isClusterEnabled = enabledElem.getValueAsBoolean(false);
+            // Set cluster mode.
+            Configuration modeElem = config.getChild("mode");
+            String mode = modeElem.getValue("master");
+            if (ClusterMode.MASTER.getText().equals(mode)) {
+                clusterMode = ClusterMode.MASTER;
+            } else if (ClusterMode.SLAVE.getText().equals(mode)) {
+                clusterMode = ClusterMode.SLAVE;
+            } else {
+                if (getLogger().isWarnEnabled()) {
+                    getLogger().warn("Unknown cluster mode [" + mode + "]. " +
+                            "Setting cluster mode to master.");
+                }
+                clusterMode = ClusterMode.MASTER;
+            }
+        } catch (Exception e) {
+            throw new ClusterConfigurationException(
+                    "Error reading cluster configuration", e);
+        }
+    }
+
+    @Override
+    public void service(ServiceManager manager) throws ServiceException {
+        this.manager = manager;
+    }
+}

Added: lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/cluster/ClusterConfigurationException.java
URL: http://svn.apache.org/viewvc/lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/cluster/ClusterConfigurationException.java?rev=1054691&view=auto
==============================================================================
--- lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/cluster/ClusterConfigurationException.java (added)
+++ lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/cluster/ClusterConfigurationException.java Mon Jan  3 17:33:50 2011
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.cluster;
+
+/**
+ * Cluster configuration exception.
+ */
+public class ClusterConfigurationException extends Exception {
+
+    private static final long serialVersionUID = 197229761264754299L;
+
+    /**
+     * C'tor.
+     */
+    public ClusterConfigurationException() {
+    }
+
+    /**
+     * C'tor.
+     * @param message Message.
+     */
+    public ClusterConfigurationException(String message) {
+        super(message);
+    }
+
+    /**
+     * C'tor.
+     * @param cause Cause.
+     */
+    public ClusterConfigurationException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * C'tor.
+     * @param message Message.
+     * @param cause Cause.
+     */
+    public ClusterConfigurationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}

Added: lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/cluster/ClusterManager.java
URL: http://svn.apache.org/viewvc/lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/cluster/ClusterManager.java?rev=1054691&view=auto
==============================================================================
--- lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/cluster/ClusterManager.java (added)
+++ lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/cluster/ClusterManager.java Mon Jan  3 17:33:50 2011
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.cluster;
+
+import org.apache.avalon.framework.thread.ThreadSafe;
+
+/**
+ * Cluster manager interface.
+ * Classes implementing ClusterManager must be ThreadSafe i.e.
+ * are setup as singletons.
+ */
+public interface ClusterManager extends ThreadSafe
+{
+    /**
+     * Role org.apache.lenya.cms.cluster.ClusterManager
+     */
+    String ROLE = ClusterManager.class.getName();
+
+    /**
+     * Is clustering enabled.
+     * @return true if clustering is enabled, otherwise false.
+     */
+    public boolean isClusterEnabled();
+
+    /**
+     * Is Lenya instance in master mode.
+     * @return true if instance is master or clustering disabled, otherwise false.
+     */
+    public boolean isMaster();
+
+    /**
+     * Is Lenya instance in slave mode.
+     * @return true if clustering is enabled and instance is slave, otherwise false.
+     */
+    public boolean isSlave();
+
+}

Added: lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/cluster/ClusterMode.java
URL: http://svn.apache.org/viewvc/lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/cluster/ClusterMode.java?rev=1054691&view=auto
==============================================================================
--- lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/cluster/ClusterMode.java (added)
+++ lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/cluster/ClusterMode.java Mon Jan  3 17:33:50 2011
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.cluster;
+
+/**
+ * Cluster mode enumeration.
+ */
+public enum ClusterMode {
+
+    MASTER("master"),
+    SLAVE("slave");
+    
+    private String text;
+
+    private ClusterMode(String text) {
+        this.text = text;
+    }
+    
+    public String getText() {
+        return text;
+    }
+}

Modified: lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/repository/RepositoryManagerImpl.java
URL: http://svn.apache.org/viewvc/lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/repository/RepositoryManagerImpl.java?rev=1054691&r1=1054690&r2=1054691&view=diff
==============================================================================
--- lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/repository/RepositoryManagerImpl.java (original)
+++ lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/repository/RepositoryManagerImpl.java Mon Jan  3 17:33:50 2011
@@ -26,6 +26,7 @@ import org.apache.avalon.framework.servi
 import org.apache.avalon.framework.service.Serviceable;
 import org.apache.commons.lang.Validate;
 import org.apache.lenya.ac.Identity;
+import org.apache.lenya.cms.cluster.ClusterManager;
 
 import com.google.common.collect.MapMaker;
 
@@ -37,6 +38,7 @@ public class RepositoryManagerImpl exten
         Serviceable {
 
     protected ServiceManager manager;
+    private ClusterManager cluster;
     // Cache unmodifiable sessions per identity.
     protected ConcurrentMap<Identity, Session> sharedSessions =
         new MapMaker().softKeys().softValues().expiration(30, TimeUnit.MINUTES).makeMap();
@@ -46,11 +48,18 @@ public class RepositoryManagerImpl exten
      */
     public void service(ServiceManager manager) throws ServiceException {
         this.manager = manager;
+        cluster = (ClusterManager) manager.lookup(ClusterManager.ROLE);
     }
 
     @Override
     public Session createSession(Identity identity, boolean modifiable) throws RepositoryException {
         Validate.notNull(identity, "identity must not be null");
+        // Check that instance is not running in cluster slave mode
+        // if session is modifiable.
+        if (modifiable == true && cluster.isSlave()) {
+            throw new RepositoryException("Can't create a modifiable session. " +
+            		"Instance is running in clustered mode as slave.");
+        }
         if (modifiable) {
             if (getLogger().isDebugEnabled())
                 getLogger().debug("Created modifiable session.");

Modified: lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/repository/SessionImpl.java
URL: http://svn.apache.org/viewvc/lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/repository/SessionImpl.java?rev=1054691&r1=1054690&r2=1054691&view=diff
==============================================================================
--- lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/repository/SessionImpl.java (original)
+++ lenya/branches/BRANCH_2_1_X/src/java/org/apache/lenya/cms/repository/SessionImpl.java Mon Jan  3 17:33:50 2011
@@ -19,7 +19,6 @@ package org.apache.lenya.cms.repository;
 
 import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ConcurrentMap;
@@ -98,7 +97,6 @@ public class SessionImpl extends Abstrac
         String id;
         UUIDGenerator generator = null;
         try {
-
             generator = (UUIDGenerator) this.manager.lookup(UUIDGenerator.ROLE);
             id = generator.nextUUID();
         } catch (Exception e) {
@@ -152,10 +150,8 @@ public class SessionImpl extends Abstrac
             throw new RepositoryException(e);
         }
 
-        for (Iterator i = this.events.iterator(); i.hasNext();) {
-            RepositoryEvent event = (RepositoryEvent) i.next();
-            for (Iterator l = this.listeners.iterator(); l.hasNext();) {
-                RepositoryListener listener = (RepositoryListener) l.next();
+        for (RepositoryEvent event : events) {
+            for (RepositoryListener listener : listeners) {
                 listener.eventFired(event);
             }
         }
@@ -259,7 +255,7 @@ public class SessionImpl extends Abstrac
         getUnitOfWork().removeLock(lockable);
     }
 
-    private Set listeners = new HashSet();
+    private Set<RepositoryListener> listeners = new HashSet<RepositoryListener>();
 
     public void addListener(RepositoryListener listener) throws RepositoryException {
         if (this.listeners.contains(listener)) {
@@ -273,7 +269,7 @@ public class SessionImpl extends Abstrac
         return this.listeners.contains(listener);
     }
 
-    private List events = new ArrayList();
+    private List<RepositoryEvent> events = new ArrayList<RepositoryEvent>();
 
     public synchronized void enqueueEvent(RepositoryEvent event) {
         if (!isModifiable()) {

Modified: lenya/branches/BRANCH_2_1_X/src/modules/sitetree/java/src/org/apache/lenya/cms/site/tree2/SiteTreeImpl.java
URL: http://svn.apache.org/viewvc/lenya/branches/BRANCH_2_1_X/src/modules/sitetree/java/src/org/apache/lenya/cms/site/tree2/SiteTreeImpl.java?rev=1054691&r1=1054690&r2=1054691&view=diff
==============================================================================
--- lenya/branches/BRANCH_2_1_X/src/modules/sitetree/java/src/org/apache/lenya/cms/site/tree2/SiteTreeImpl.java (original)
+++ lenya/branches/BRANCH_2_1_X/src/modules/sitetree/java/src/org/apache/lenya/cms/site/tree2/SiteTreeImpl.java Mon Jan  3 17:33:50 2011
@@ -17,7 +17,6 @@
  */
 package org.apache.lenya.cms.site.tree2;
 
-import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
@@ -52,15 +51,6 @@ public class SiteTreeImpl extends Abstra
     protected ServiceManager manager;
     private RootNode root;
     private int revision;
-    /**
-     * Last modification date of site tree file.
-     */
-    private Date lastModified;
-    /**
-     * Site tree file has been modified on file system and site
-     * tree should be reloaded.
-     */
-    private boolean reload; 
 
     /**
      * @param manager The service manager.
@@ -152,7 +142,7 @@ public class SiteTreeImpl extends Abstra
         TreeWriter writer = null;
         try {
             writer = (TreeWriter) this.manager.lookup(TreeWriter.ROLE);
-            int revision = getRevision(getRepositoryNode()) + 1;
+            revision = getRevision(getRepositoryNode()) + 1;
             writer.writeTree(this);
         } catch (RuntimeException e) {
             throw e;
@@ -230,8 +220,8 @@ public class SiteTreeImpl extends Abstra
         return parentPath;
     }
 
-    private Map path2node = new HashMap();
-    private Map uuidLanguage2link = new HashMap();
+    private Map<String, SiteNode> path2node = new HashMap<String, SiteNode>();
+    private Map<String, Link> uuidLanguage2link = new HashMap<String, Link>();
 
     protected void nodeAdded(SiteNode node) {
         String path = node.getPath();
@@ -268,12 +258,12 @@ public class SiteTreeImpl extends Abstra
         this.path2node.remove(path);
     }
 
-    protected Map getUuidLanguage2Link() {
+    protected Map<String, Link> getUuidLanguage2Link() {
         load();
         return this.uuidLanguage2link;
     }
 
-    protected Map getPath2Node() {
+    protected Map<String, SiteNode> getPath2Node() {
         load();
         return this.path2node;
     }
@@ -292,8 +282,8 @@ public class SiteTreeImpl extends Abstra
 
     public boolean containsInAnyLanguage(String uuid) {
         Assert.notNull("uuid", uuid);
-        Set set = getUuidLanguage2Link().keySet();
-        String[] keys = (String[]) set.toArray(new String[set.size()]);
+        Set<String> set = getUuidLanguage2Link().keySet();
+        String[] keys = set.toArray(new String[set.size()]);
         for (int i = 0; i < keys.length; i++) {
             if (keys[i].startsWith(uuid + ":")) {
                 return true;

Modified: lenya/branches/BRANCH_2_1_X/src/modules/sitetree/java/src/org/apache/lenya/cms/site/tree2/SiteTreeMonitorImpl.java
URL: http://svn.apache.org/viewvc/lenya/branches/BRANCH_2_1_X/src/modules/sitetree/java/src/org/apache/lenya/cms/site/tree2/SiteTreeMonitorImpl.java?rev=1054691&r1=1054690&r2=1054691&view=diff
==============================================================================
--- lenya/branches/BRANCH_2_1_X/src/modules/sitetree/java/src/org/apache/lenya/cms/site/tree2/SiteTreeMonitorImpl.java (original)
+++ lenya/branches/BRANCH_2_1_X/src/modules/sitetree/java/src/org/apache/lenya/cms/site/tree2/SiteTreeMonitorImpl.java Mon Jan  3 17:33:50 2011
@@ -31,6 +31,7 @@ import org.apache.commons.io.monitor.Fil
 import org.apache.commons.io.monitor.FileAlterationMonitor;
 import org.apache.commons.io.monitor.FileAlterationObserver;
 import org.apache.commons.lang.Validate;
+import org.apache.lenya.cms.cluster.ClusterManager;
 import org.apache.lenya.cms.site.tree.SiteTree;
 
 /**
@@ -41,45 +42,61 @@ import org.apache.lenya.cms.site.tree.Si
 public class SiteTreeMonitorImpl extends AbstractLogEnabled
 implements SiteTreeMonitor, Serviceable, Startable, Initializable, ThreadSafe
 {
-    private ServiceManager serviceManager;
-    private FileAlterationMonitor fileAlterationMonitor;
+    private FileAlterationMonitor fileAlterationMonitor =
+        new FileAlterationMonitor();
+    private ClusterManager cluster;
+    private boolean monitorEnabled = false;
     private HashMap<String, SiteTree> siteTreeMap =
         new HashMap<String, SiteTree>();
     private HashMap<String, SiteTreeMonitorListener> listenerMap =
         new HashMap<String, SiteTreeMonitorListener>();
 
     @Override
-    public void service(ServiceManager serviceManager) throws ServiceException {
-        this.serviceManager = serviceManager;
+    public void service(ServiceManager manager) throws ServiceException {
+        cluster = (ClusterManager) manager.lookup(ClusterManager.ROLE);
     }
 
     @Override
     public void start() throws Exception {
-        Validate.notNull(fileAlterationMonitor, "Not initialized.");
-        if (getLogger().isDebugEnabled())
-            getLogger().debug("Site tree monitor started.");
-        fileAlterationMonitor.start();
+        // Only start site tree monitor if running as slave in clustered mode.
+        if (monitorEnabled) {
+            if (getLogger().isDebugEnabled())
+                getLogger().debug("Site tree monitor started.");
+            fileAlterationMonitor.start();
+        }
     }
 
     @Override
     public void stop() throws Exception {
-        Validate.notNull(fileAlterationMonitor, "Not initialized.");
-        if (getLogger().isDebugEnabled())
-            getLogger().debug("Site tree monitor stopped.");
-        fileAlterationMonitor.stop();
+        if (monitorEnabled) {
+            if (getLogger().isDebugEnabled())
+                getLogger().debug("Site tree monitor stopped.");
+            fileAlterationMonitor.stop();
+        }
     }
 
     @Override
     public void initialize() throws Exception {
-        if (getLogger().isDebugEnabled())
-            getLogger().debug("Site tree monitor initialized.");
-        fileAlterationMonitor = new FileAlterationMonitor();
+        if (cluster.isClusterEnabled() && cluster.isSlave()) {
+            if (getLogger().isDebugEnabled())
+                getLogger().debug("Site tree monitor initialized.");
+            monitorEnabled = true;
+        } else {
+            if (getLogger().isDebugEnabled())
+                getLogger().debug("Site tree monitor not enabled as not" +
+                        "running as slave in cluster mode.");
+        }
     }
 
     @Override
     public void addListener(SiteTree siteTree,
             SiteTreeMonitorListener listener)
     {
+        Validate.notNull(siteTree, "siteTree must not be null");
+        Validate.notNull(listener, "listener must not be null");
+        if (!monitorEnabled) {
+            return;
+        }
         // Publication/area content directory.
         File contentDir = siteTree.getPublication().getContentDirectory(
                 siteTree.getArea());

Modified: lenya/branches/BRANCH_2_1_X/src/modules/sitetree/java/src/org/apache/lenya/cms/site/tree2/TreeSiteManager.java
URL: http://svn.apache.org/viewvc/lenya/branches/BRANCH_2_1_X/src/modules/sitetree/java/src/org/apache/lenya/cms/site/tree2/TreeSiteManager.java?rev=1054691&r1=1054690&r2=1054691&view=diff
==============================================================================
--- lenya/branches/BRANCH_2_1_X/src/modules/sitetree/java/src/org/apache/lenya/cms/site/tree2/TreeSiteManager.java (original)
+++ lenya/branches/BRANCH_2_1_X/src/modules/sitetree/java/src/org/apache/lenya/cms/site/tree2/TreeSiteManager.java Mon Jan  3 17:33:50 2011
@@ -23,6 +23,9 @@ import java.util.List;
 
 import org.apache.avalon.framework.service.ServiceException;
 import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.thread.ThreadSafe;
+import org.apache.commons.lang.Validate;
+import org.apache.lenya.cms.cluster.ClusterManager;
 import org.apache.lenya.cms.publication.Area;
 import org.apache.lenya.cms.publication.Document;
 import org.apache.lenya.cms.publication.DocumentException;
@@ -44,10 +47,12 @@ import org.apache.lenya.cms.site.tree.Si
  * Tree-based site manager.
  */
 public class TreeSiteManager extends AbstractSiteManager
-implements SiteTreeMonitorListener
+implements SiteTreeMonitorListener, ThreadSafe
 {
     
     private SiteTreeMonitor siteTreeMonitor;
+    private ClusterManager cluster;
+
     private static HashMap<String, SiteTree> siteTreeMap =
         new HashMap<String, SiteTree>();
 
@@ -60,6 +65,7 @@ implements SiteTreeMonitorListener
      * @throws SiteException if an error occurs.
      */
     protected SiteTree getTree(Area area) throws SiteException {
+        Validate.notNull(area, "area must not be null");
         String key = getKey(area);
         SiteTree sitetree;
         RepositoryItemFactory factory = new SiteTreeFactory(this.manager, getLogger());
@@ -69,8 +75,9 @@ implements SiteTreeMonitorListener
         } catch (Exception e) {
             throw new SiteException(e);
         }
-        // Only support site tree reloading for live area.
-        if (area.getName().equals(Publication.LIVE_AREA)) {
+        // Only support site tree reloading for live area
+        // and in clustered mode as slave.
+        if (cluster.isSlave() && area.getName().equals(Publication.LIVE_AREA)) {
             if (!siteTreeMap.containsKey(key)) {
                 siteTreeMonitor.addListener(sitetree, this);
                 siteTreeMap.put(key, sitetree);
@@ -220,7 +227,7 @@ implements SiteTreeMonitorListener
         }
         SiteTree tree = getTree(areaObj);
         SiteNode[] preOrder = tree.preOrder();
-        List docs = new ArrayList();
+        List<Document> docs = new ArrayList<Document>();
         for (int i = 0; i < preOrder.length; i++) {
             String[] langs = preOrder[i].getLanguages();
             for (int l = 0; l < langs.length; l++) {
@@ -232,7 +239,7 @@ implements SiteTreeMonitorListener
 
     public DocumentLocator[] getRequiredResources(DocumentFactory map, DocumentLocator loc)
             throws SiteException {
-        List ancestors = new ArrayList();
+        List<DocumentLocator> ancestors = new ArrayList<DocumentLocator>();
         DocumentLocator locator = loc;
         while (locator.getParent() != null) {
             DocumentLocator parent = locator.getParent();
@@ -305,6 +312,7 @@ implements SiteTreeMonitorListener
     public void service(ServiceManager manager) throws ServiceException {
         super.service(manager);
         siteTreeMonitor = (SiteTreeMonitor) manager.lookup(SiteTreeMonitor.ROLE);
+        cluster = (ClusterManager) manager.lookup(ClusterManager.ROLE);
     }
 
     @Override
@@ -328,5 +336,5 @@ implements SiteTreeMonitorListener
                         "Reloading skipped.");
         }
     }
-
+    
 }

Added: lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/config/cocoon-xconf/sourcenodercmlfactory.xconf
URL: http://svn.apache.org/viewvc/lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/config/cocoon-xconf/sourcenodercmlfactory.xconf?rev=1054691&view=auto
==============================================================================
--- lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/config/cocoon-xconf/sourcenodercmlfactory.xconf (added)
+++ lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/config/cocoon-xconf/sourcenodercmlfactory.xconf Mon Jan  3 17:33:50 2011
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xconf xpath="/cocoon" unless="/cocoon/component[@class = 'org.apache.lenya.cms.repository.SourceNodeRcmlFactory']">
+  <component
+    logger="lenya.nodefactory.source"
+    role="org.apache.lenya.cms.repository.SourceNodeRcmlFactory"
+    class="org.apache.lenya.cms.repository.impl.SourceNodeRcmlFactoryImpl"/>
+</xconf>
\ No newline at end of file

Modified: lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/SourceNode.java
URL: http://svn.apache.org/viewvc/lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/SourceNode.java?rev=1054691&r1=1054690&r2=1054691&view=diff
==============================================================================
--- lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/SourceNode.java (original)
+++ lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/SourceNode.java Mon Jan  3 17:33:50 2011
@@ -19,6 +19,7 @@ package org.apache.lenya.cms.repository;
 
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayList;
 import java.util.Collection;
 
 import org.apache.avalon.framework.logger.AbstractLogEnabled;
@@ -297,19 +298,19 @@ public class SourceNode extends Abstract
     /**
      * 
      */
-    public Collection getChildren() throws RepositoryException {
+    public Collection<SourceNode> getChildren() throws RepositoryException {
         SourceResolver resolver = null;
         TraversableSource source = null;
         try {
             resolver = (SourceResolver) this.manager.lookup(SourceResolver.ROLE);
             source = (TraversableSource) resolver.resolveURI(this.contentSource.getRealSourceUri());
-            Collection children = source.getChildren();
-            java.util.Iterator iterator = children.iterator();
-            java.util.Vector newChildren = new java.util.Vector();
-            while (iterator.hasNext()) {
-                TraversableSource child = (TraversableSource) iterator.next();
+            @SuppressWarnings("unchecked")
+            Collection<TraversableSource> children = source.getChildren();
+            ArrayList<SourceNode> newChildren = new ArrayList<SourceNode>();
+            for (TraversableSource child : children) {
                 newChildren.add(new SourceNode(getSession(),
-                        getSourceURI() + "/" + child.getName(), this.manager, getLogger()));
+                        getSourceURI() + "/" + child.getName(),
+                        this.manager, getLogger()));
             }
             return newChildren;
         } catch (Exception e) {
@@ -377,9 +378,13 @@ public class SourceNode extends Abstract
     private Persistable persistable;
 
     protected synchronized RCML getRcml() {
-        // RCML is cached by factory. So don't cache it here.
-        SourceNodeRcmlFactory factory = SourceNodeRcmlFactory.getInstance();
-        return factory.getRcml(this, this.manager);
+        try {
+            SourceNodeRcmlFactory factory = (SourceNodeRcmlFactory)
+                    manager.lookup(SourceNodeRcmlFactory.ROLE);
+            return factory.getRcml(this);
+        } catch (ServiceException e) {
+            throw new RuntimeException("Error getting RCML", e);
+        }
     }
 
     public History getHistory() {

Modified: lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/SourceNodeRCML.java
URL: http://svn.apache.org/viewvc/lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/SourceNodeRCML.java?rev=1054691&r1=1054690&r2=1054691&view=diff
==============================================================================
--- lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/SourceNodeRCML.java (original)
+++ lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/SourceNodeRCML.java Mon Jan  3 17:33:50 2011
@@ -24,13 +24,12 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Vector;
 
-import org.apache.avalon.framework.service.ServiceException;
 import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.commons.lang.Validate;
 import org.apache.excalibur.source.SourceResolver;
 import org.apache.lenya.cms.cocoon.source.SourceUtil;
 import org.apache.lenya.cms.rc.CheckInEntry;
@@ -53,7 +52,7 @@ public class SourceNodeRCML implements R
 
     private boolean dirty = false;
     private int maximalNumberOfEntries = 5;
-    private Vector entries;
+    private Vector<RCMLEntry> entries;
 
     private ServiceManager manager;
 
@@ -61,7 +60,7 @@ public class SourceNodeRCML implements R
     private String metaSourceUri;
     private long lastModified;
 
-    private static Map ELEMENTS = new HashMap();
+    private static Map<Short, String> ELEMENTS = new HashMap<Short, String>();
     protected static final String ELEMENT_CHECKIN = "CheckIn";
     protected static final String ELEMENT_CHECKOUT = "CheckOut";
     protected static final String ELEMENT_BACKUP = "Backup";
@@ -87,12 +86,13 @@ public class SourceNodeRCML implements R
      * @param metaSourceUri The meta source URI.
      * @param manager The service manager.
      */
-    public SourceNodeRCML(String contentSourceUri, String metaSourceUri, ServiceManager manager) {
+    public SourceNodeRCML(SourceNode sourceNode, ServiceManager manager) {
+        Validate.notNull(sourceNode, "sourceNode must not be null");
         this.maximalNumberOfEntries = 200;
         this.maximalNumberOfEntries = (2 * this.maximalNumberOfEntries) + 1;
         this.manager = manager;
-        this.contentSourceUri = contentSourceUri;
-        this.metaSourceUri = metaSourceUri;
+        this.contentSourceUri = sourceNode.getContentSource().getRealSourceUri();
+        this.metaSourceUri = sourceNode.getMetaSource().getRealSourceUri();
     }
 
     protected static final String RCML_EXTENSION = ".rcml";
@@ -149,15 +149,14 @@ public class SourceNodeRCML implements R
 
         String identity = node.getSession().getIdentity().getUser().getId();
 
-        Vector entries = getEntries();
-        if (entries.size() == 0) {
+        if (getEntries().isEmpty()) {
             if (type == ci) {
                 throw new IllegalStateException("Can't check in - not checked out.");
             }
         } else {
             RCMLEntry latestEntry = getLatestEntry();
             if (type == latestEntry.getType()) {
-                String elementName = (String) ELEMENTS.get(Short.valueOf(type));
+                String elementName = ELEMENTS.get(Short.valueOf(type));
                 throw new IllegalStateException("RCML entry type <" + elementName
                         + "> not allowed twice in a row. Before: [" + latestEntry.getIdentity()
                         + "], now: [" + identity + "], node: [" + this.contentSourceUri + "]");
@@ -220,9 +219,7 @@ public class SourceNodeRCML implements R
         try {
             NamespaceHelper helper = new NamespaceHelper(NAMESPACE, "", ELEMENT_XPSREVISIONCONTROL);
             Element root = helper.getDocument().getDocumentElement();
-            Vector entries = getEntries();
-            for (Iterator i = entries.iterator(); i.hasNext();) {
-                RCMLEntry entry = (RCMLEntry) i.next();
+            for (RCMLEntry entry : getEntries()) {
                 Element element = saveToXml(helper, entry);
                 root.appendChild(element);
             }
@@ -264,9 +261,7 @@ public class SourceNodeRCML implements R
      * @throws RevisionControlException if an error occurs
      */
     public RCMLEntry getLatestEntry(short type) throws RevisionControlException {
-        Vector entries = getEntries();
-        for (Iterator i = entries.iterator(); i.hasNext();) {
-            RCMLEntry entry = (RCMLEntry) i.next();
+        for (RCMLEntry entry : getEntries()) {
             if (entry.getType() == type) {
                 return entry;
             }
@@ -275,11 +270,10 @@ public class SourceNodeRCML implements R
     }
 
     public RCMLEntry getLatestEntry() throws RevisionControlException {
-        Vector entries = getEntries();
-        if (entries.isEmpty()) {
+        if (getEntries().isEmpty()) {
             return null;
         } else {
-            return (RCMLEntry) entries.firstElement();
+            return getEntries().firstElement();
         }
     }
 
@@ -348,9 +342,9 @@ public class SourceNodeRCML implements R
      * @return Vector of all check out and check in entries in this RCML-file
      * @throws RevisionControlException if an error occurs
      */
-    public synchronized Vector getEntries() throws RevisionControlException {
+    public synchronized Vector<RCMLEntry> getEntries() throws RevisionControlException {
         if (this.entries == null) {
-            this.entries = new Vector();
+            this.entries = new Vector<RCMLEntry>();
             String uri = getRcmlSourceUri();
             try {
                 if (SourceUtil.exists(uri, this.manager)) {
@@ -375,11 +369,9 @@ public class SourceNodeRCML implements R
      * @return Vector of all entries in this RCML-file with a backup
      * @throws Exception if an error occurs
      */
-    public synchronized Vector getBackupEntries() throws Exception {
-        Vector entries = getEntries();
-        Vector backupEntries = new Vector();
-        for (Iterator i = entries.iterator(); i.hasNext();) {
-            RCMLEntry entry = (RCMLEntry) i.next();
+    public synchronized Vector<RCMLEntry> getBackupEntries() throws Exception {
+        Vector<RCMLEntry> backupEntries = new Vector<RCMLEntry>();
+        for (RCMLEntry entry : getEntries()) {
             if (entry.getType() == RCML.ci && ((CheckInEntry) entry).hasBackup()) {
                 backupEntries.add(entry);
             }
@@ -441,10 +433,9 @@ public class SourceNodeRCML implements R
      * @throws RevisionControlException if an error occurs
      */
     public synchronized void pruneEntries() throws RevisionControlException {
-        Vector entries = getEntries();
-        RCMLEntry[] array = (RCMLEntry[]) entries.toArray(new RCMLEntry[entries.size()]);
+        RCMLEntry[] array = getEntries().toArray(new RCMLEntry[entries.size()]);
 
-        for (int i = this.maximalNumberOfEntries; i < entries.size(); i++) {
+        for (int i = this.maximalNumberOfEntries; i < array.length; i++) {
             // remove the backup file associated with this entry
             RCMLEntry entry = array[i];
             if (entry.getType() == ci && ((CheckInEntry) entry).hasBackup()) {
@@ -497,15 +488,13 @@ public class SourceNodeRCML implements R
      */
     public String[] getBackupsTime() throws Exception {
 
-        Vector entries = getEntries();
-        List times = new ArrayList();
-        for (Iterator i = entries.iterator(); i.hasNext();) {
-            RCMLEntry entry = (RCMLEntry) i.next();
+        List<String> times = new ArrayList<String>();
+        for (RCMLEntry entry : getEntries()) {
             if (entry.getType() == ci && ((CheckInEntry) entry).hasBackup()) {
                 times.add(Long.toString(entry.getTime()));
             }
         }
-        return (String[]) times.toArray(new String[times.size()]);
+        return times.toArray(new String[times.size()]);
 
     }
 
@@ -549,10 +538,7 @@ public class SourceNodeRCML implements R
         SourceNodeRCML otherRcml = (SourceNodeRCML) ((SourceNode) otherNode).getRcml();
 
         try {
-
-            Vector backupEntries = otherRcml.getBackupEntries();
-            for (Iterator i = backupEntries.iterator(); i.hasNext();) {
-                RCMLEntry entry = (RCMLEntry) i.next();
+            for (RCMLEntry entry : otherRcml.getBackupEntries()) {
                 long time = entry.getTime();
                 String otherContentUri = otherRcml.getBackupSourceUri(otherSourceNode
                         .getContentSource(), time);
@@ -566,10 +552,8 @@ public class SourceNodeRCML implements R
                 SourceUtil.copy(this.manager, otherMetaUri, thisMetaUri);
             }
 
-            this.entries = new Vector();
-            Vector otherEntries = otherRcml.getEntries();
-            for (Iterator i = otherEntries.iterator(); i.hasNext();) {
-                RCMLEntry entry = (RCMLEntry) i.next();
+            this.entries = new Vector<RCMLEntry>();
+            for (RCMLEntry entry : otherRcml.getEntries()) {
                 RCMLEntry newEntry = null;
                 switch (entry.getType()) {
                 case co:
@@ -620,9 +604,8 @@ public class SourceNodeRCML implements R
     }
 
     public boolean isCheckedOutBySession(Session session) throws RevisionControlException {
-        Vector entries = getEntries();
-        if (entries.size() > 0) {
-            RCMLEntry entry = (RCMLEntry) entries.get(0);
+        if (!getEntries().isEmpty()) {
+            RCMLEntry entry = entries.firstElement();
             String otherSessionId = entry.getSessionId();
             if (entry.getType() == co) {
                 // not restricted to session

Modified: lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/SourceNodeRcmlFactory.java
URL: http://svn.apache.org/viewvc/lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/SourceNodeRcmlFactory.java?rev=1054691&r1=1054690&r2=1054691&view=diff
==============================================================================
--- lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/SourceNodeRcmlFactory.java (original)
+++ lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/SourceNodeRcmlFactory.java Mon Jan  3 17:33:50 2011
@@ -17,44 +17,24 @@
  */
 package org.apache.lenya.cms.repository;
 
-import java.util.HashMap;
-
-import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.thread.ThreadSafe;
 import org.apache.lenya.cms.rc.RCML;
 
 /**
- * Factory for source node RCML objects.
+ * Source node RCML factory interface.
+ * Implementations must be thread safe.
  */
-public class SourceNodeRcmlFactory {
-
-    private static SourceNodeRcmlFactory instance = new SourceNodeRcmlFactory();
+public interface SourceNodeRcmlFactory extends ThreadSafe {
 
     /**
-     * @return The singleton instance.
+     * Role org.apache.lenya.cms.repository.SourceNodeRcmlFactory
      */
-    public static SourceNodeRcmlFactory getInstance() {
-        return instance;
-    }
-
-    private HashMap<String, SourceNodeRCML> uri2rcml =
-        new HashMap<String, SourceNodeRCML>();
-
-    private SourceNodeRcmlFactory() {
-    }
+    String ROLE = SourceNodeRcmlFactory.class.getName();
 
     /**
-     * @param node The node.
-     * @param manager The service manager.
+     * Get RCML object.
+     * @param node Source node.
      * @return An RCML object.
      */
-    public synchronized RCML getRcml(SourceNode node, ServiceManager manager) {
-        String uri = node.getSourceURI();
-        SourceNodeRCML rcml = uri2rcml.get(uri);
-        if (rcml == null || (rcml != null && rcml.isModifiedExternally())) {
-            rcml = new SourceNodeRCML(node.getContentSource().getRealSourceUri(), node
-                    .getMetaSource().getRealSourceUri(), manager);
-            this.uri2rcml.put(uri, rcml);
-        }
-        return rcml;
-    }
+    RCML getRcml(SourceNode node);
 }

Added: lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/impl/SourceNodeRcmlFactoryImpl.java
URL: http://svn.apache.org/viewvc/lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/impl/SourceNodeRcmlFactoryImpl.java?rev=1054691&view=auto
==============================================================================
--- lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/impl/SourceNodeRcmlFactoryImpl.java (added)
+++ lenya/branches/BRANCH_2_1_X/src/modules/sourcerepository/java/src/org/apache/lenya/cms/repository/impl/SourceNodeRcmlFactoryImpl.java Mon Jan  3 17:33:50 2011
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.repository.impl;
+
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
+import org.apache.commons.lang.Validate;
+import org.apache.lenya.cms.cluster.ClusterManager;
+import org.apache.lenya.cms.rc.RCML;
+import org.apache.lenya.cms.repository.SourceNode;
+import org.apache.lenya.cms.repository.SourceNodeRCML;
+import org.apache.lenya.cms.repository.SourceNodeRcmlFactory;
+
+import com.google.common.collect.MapMaker;
+
+/**
+ * Source node RCML factory implementation.
+ */
+public class SourceNodeRcmlFactoryImpl
+implements SourceNodeRcmlFactory, Serviceable
+{
+    private ServiceManager manager;
+    private ClusterManager cluster;
+
+    private ConcurrentMap<String, SourceNodeRCML> uri2rcml;
+
+    /**
+     * C'tor.
+     */
+    public SourceNodeRcmlFactoryImpl() {
+        // Create cache map for RCML objects.
+        uri2rcml = new MapMaker().softValues().makeMap();
+    }
+
+    @Override
+    public void service(ServiceManager manager) throws ServiceException {
+        this.manager = manager;
+        cluster = (ClusterManager) manager.lookup(ClusterManager.ROLE);
+    }
+
+    /**
+     * @param node The node.
+     * @param manager The service manager.
+     * @return An RCML object.
+     */
+    public synchronized RCML getRcml(SourceNode node) {
+        Validate.notNull(node, "node must not be null");
+        String uri = node.getSourceURI();
+        SourceNodeRCML rcml = uri2rcml.get(uri);
+        // Reload RCML if modified externally and running in clustered
+        // mode as slave.
+        if (rcml == null || isReloadRCML(rcml)) {
+            rcml = new SourceNodeRCML(node, manager);
+            this.uri2rcml.put(uri, rcml);
+        }
+        return rcml;
+    }
+
+    /**
+     * Check if RCML needs to be reloaded.
+     * Only check for external modifications if running in clustered
+     * mode as slave.
+     * @param rcml Source node RCML.
+     * @return true if RCML needs to be reloaded, otherwise false.
+     */
+    private boolean isReloadRCML(SourceNodeRCML rcml) {
+        Validate.notNull(rcml, "rcml must not be null");
+        return cluster.isClusterEnabled() && cluster.isSlave() &&
+                rcml.isModifiedExternally();
+    }
+}

Added: lenya/branches/BRANCH_2_1_X/src/webapp/lenya/config/cluster/cluster.xconf
URL: http://svn.apache.org/viewvc/lenya/branches/BRANCH_2_1_X/src/webapp/lenya/config/cluster/cluster.xconf?rev=1054691&view=auto
==============================================================================
--- lenya/branches/BRANCH_2_1_X/src/webapp/lenya/config/cluster/cluster.xconf (added)
+++ lenya/branches/BRANCH_2_1_X/src/webapp/lenya/config/cluster/cluster.xconf Mon Jan  3 17:33:50 2011
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<cluster>
+
+  <!-- Cluster enabled [true|false] -->
+  <enabled>false</enabled>
+
+  <!-- Cluster mode [master|slave] -->
+  <mode>master</mode>
+
+</cluster>

Added: lenya/branches/BRANCH_2_1_X/src/webapp/lenya/config/cocoon-xconf/cluster/cluster.xconf
URL: http://svn.apache.org/viewvc/lenya/branches/BRANCH_2_1_X/src/webapp/lenya/config/cocoon-xconf/cluster/cluster.xconf?rev=1054691&view=auto
==============================================================================
--- lenya/branches/BRANCH_2_1_X/src/webapp/lenya/config/cocoon-xconf/cluster/cluster.xconf (added)
+++ lenya/branches/BRANCH_2_1_X/src/webapp/lenya/config/cocoon-xconf/cluster/cluster.xconf Mon Jan  3 17:33:50 2011
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<!-- $Id: usecases-workflow-deactivate.xconf 348547 2005-11-23 20:13:01Z chestnut $ -->
+
+  <xconf xpath="/cocoon" unless="/cocoon/component[@role = 'org.apache.lenya.cms.cluster.ClusterManager']">
+    <component class="org.apache.lenya.cms.cluster.impl.ClusterManagerImpl" logger="lenya.cocoon.components"
+        role="org.apache.lenya.cms.cluster.ClusterManager"/>
+  </xconf>