+{
+ private static final String PLACEHOLDER_END = "}";
+ private static final String PLACEHOLDER_START = "${";
+ private Resource configFile;
+ private Config config;
+ private Properties properties;
+
+
+ /**
+ * Set the Hazelcast XML configuration file to use. This will be merged with the supplied
+ * Properties and parsed to produce a final {@link Config} object.
+ * @param configFile the configFile to set
+ */
+ public void setConfigFile(Resource configFile)
+ {
+ this.configFile = configFile;
+ }
+
+ /**
+ * Used to supply the set of Properties that the configuration file can reference.
+ *
+ * @param properties the properties to set
+ */
+ public void setProperties(Properties properties)
+ {
+ this.properties = properties;
+ }
+
+ /**
+ * Spring {@link InitializingBean} lifecycle method. Substitutes property placeholders for their
+ * corresponding values and creates a {@link Config Hazelcast configuration} with the post-processed
+ * XML file - ready for the {@link #getObject()} factory method to be used to retrieve it.
+ */
+ @Override
+ public void afterPropertiesSet() throws Exception
+ {
+ if (configFile == null)
+ {
+ throw new IllegalArgumentException("No configuration file specified.");
+ }
+ if (properties == null)
+ {
+ properties = new Properties();
+ }
+
+ // These configXML strings will be large and are therefore intended
+ // to be thrown away. We only want to keep the final Config object.
+ String rawConfigXML = getConfigFileContents();
+ String configXML = substituteProperties(rawConfigXML);
+ config = new InMemoryXmlConfig(configXML);
+ }
+
+ /**
+ * For the method parameter text
, replaces all occurrences of placeholders having
+ * the form ${property.name} with the value of the property having the key "property.name". The
+ * properties are supplied using {@link #setProperties(Properties)}.
+ *
+ * @param text The String to apply property substitutions to.
+ * @return String after substitutions have been applied.
+ */
+ private String substituteProperties(String text)
+ {
+ for (String propName : properties.stringPropertyNames())
+ {
+ String propValue = properties.getProperty(propName);
+ String quotedPropName = Pattern.quote(PLACEHOLDER_START + propName + PLACEHOLDER_END);
+ text = text.replaceAll(quotedPropName, propValue);
+ }
+
+ return text;
+ }
+
+ /**
+ * Opens the configFile {@link Resource} and reads the contents into a String.
+ *
+ * @return the contents of the configFile resource.
+ */
+ private String getConfigFileContents()
+ {
+ StringWriter writer = new StringWriter();
+ InputStream inputStream = null;
+ try
+ {
+ inputStream = configFile.getInputStream();
+ IOUtils.copy(inputStream, writer, "UTF-8");
+ return writer.toString();
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("Couldn't read configuration: " + configFile, e);
+ }
+ finally
+ {
+ try
+ {
+ if (inputStream != null)
+ {
+ inputStream.close();
+ }
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("Couldn't close stream", e);
+ }
+ }
+ }
+
+ /**
+ * FactoryBean's factory method. Returns the config with the property key/value
+ * substitutions in place.
+ */
+ @Override
+ public Config getObject() throws Exception
+ {
+ return config;
+ }
+
+ @Override
+ public Class> getObjectType()
+ {
+ return Config.class;
+ }
+
+ @Override
+ public boolean isSingleton()
+ {
+ return true;
+ }
+}
diff --git a/source/java/org/alfresco/repo/cluster/HazelcastConfigFactoryBeanTest.java b/source/java/org/alfresco/repo/cluster/HazelcastConfigFactoryBeanTest.java
new file mode 100644
index 0000000000..5a897852cf
--- /dev/null
+++ b/source/java/org/alfresco/repo/cluster/HazelcastConfigFactoryBeanTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2005-2012 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco 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 Alfresco. If not, see .
+ */
+package org.alfresco.repo.cluster;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Properties;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+
+import com.hazelcast.config.Config;
+
+/**
+ * Tests for the HazelcastConfigFactoryBean class.
+ *
+ * @author Matt Ward
+ */
+public class HazelcastConfigFactoryBeanTest
+{
+ private HazelcastConfigFactoryBean configFactory;
+ private Resource resource;
+ private Properties properties;
+
+ @Before
+ public void setUp() throws Exception
+ {
+ configFactory = new HazelcastConfigFactoryBean();
+ resource = new ClassPathResource("cluster-test/placeholder-test.xml");
+ configFactory.setConfigFile(resource);
+
+ properties = new Properties();
+ properties.setProperty("alfresco.hazelcast.password", "let-me-in");
+ properties.setProperty("alfresco.cluster.name", "cluster-name");
+ configFactory.setProperties(properties);
+
+ // Trigger the spring post-bean creation lifecycle method
+ configFactory.afterPropertiesSet();
+ }
+
+
+ @Test
+ public void testConfigHasNewPropertyValues() throws Exception
+ {
+ // Invoke the factory method.
+ Config config = configFactory.getObject();
+
+ assertEquals("let-me-in", config.getGroupConfig().getPassword());
+ assertEquals("cluster-name", config.getGroupConfig().getName());
+ }
+}
diff --git a/source/java/org/alfresco/repo/cluster/HazelcastInstanceFactory.java b/source/java/org/alfresco/repo/cluster/HazelcastInstanceFactory.java
new file mode 100644
index 0000000000..a55e6876cb
--- /dev/null
+++ b/source/java/org/alfresco/repo/cluster/HazelcastInstanceFactory.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2005-2012 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco 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 Alfresco. If not, see .
+ */
+package org.alfresco.repo.cluster;
+
+import com.hazelcast.config.Config;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
+
+/**
+ * Provides a way of lazily creating HazelcastInstances for a given configuration.
+ * The HazelcastInstance will not be created until {@link #newInstance()} is called.
+ *
+ * An intermediary class such as this is required in order to avoid starting
+ * Hazelcast instances when clustering is not configured/required. Otherwise
+ * simply by defining a HazelcastInstance bean clustering would spring into life.
+ *
+ * @author Matt Ward
+ */
+public class HazelcastInstanceFactory
+{
+ public Config config;
+
+ public HazelcastInstance newInstance()
+ {
+ return Hazelcast.newHazelcastInstance(config);
+ }
+
+ /**
+ * @param config the config to set
+ */
+ public void setConfig(Config config)
+ {
+ this.config = config;
+ }
+}
diff --git a/source/java/org/alfresco/repo/webdav/LockStore.java b/source/java/org/alfresco/repo/webdav/LockStore.java
index 2565c50ad1..22ef70ad89 100644
--- a/source/java/org/alfresco/repo/webdav/LockStore.java
+++ b/source/java/org/alfresco/repo/webdav/LockStore.java
@@ -28,7 +28,7 @@ import org.alfresco.service.cmr.repository.NodeRef;
* the actual values should be examined as necessary.
*
* Implementations of this interface should be fast, ideally an in-memory map. Implementations should also be thread-
- * and cluster-safe.
+ * and cluster-safe (if used in a cluster).
*
* @author Matt Ward
*/
diff --git a/source/java/org/alfresco/repo/webdav/LockStoreFactoryImpl.java b/source/java/org/alfresco/repo/webdav/LockStoreFactoryImpl.java
index 59521b07d1..bcb63393e6 100644
--- a/source/java/org/alfresco/repo/webdav/LockStoreFactoryImpl.java
+++ b/source/java/org/alfresco/repo/webdav/LockStoreFactoryImpl.java
@@ -20,14 +20,17 @@ package org.alfresco.repo.webdav;
import java.util.concurrent.ConcurrentMap;
+import org.alfresco.repo.cluster.HazelcastInstanceFactory;
import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.util.PropertyCheck;
import com.hazelcast.core.HazelcastInstance;
/**
* Default implementation of the {@link LockStoreFactory} interface. Creates {@link LockStore}s
- * backed by a Hazelcast distributed {@link java.util.Map Map}.
+ * backed by a Hazelcast distributed Map if the clusterName property is set,
+ * otherwise it creates a non-clustered {@link SimpleLockStore}.
*
* @see LockStoreFactory
* @see LockStoreImpl
@@ -35,20 +38,42 @@ import com.hazelcast.core.HazelcastInstance;
*/
public class LockStoreFactoryImpl implements LockStoreFactory
{
- private HazelcastInstance hazelcast;
+ private static final String HAZELCAST_MAP_NAME = "webdav-locks";
+ private HazelcastInstanceFactory hazelcastInstanceFactory;
+ private String clusterName;
+ /**
+ * This method should be used sparingly and the created {@link LockStore}s should be
+ * retained (this factory does not cache instances of them).
+ */
@Override
- public LockStore getLockStore()
+ public synchronized LockStore getLockStore()
{
- ConcurrentMap map = hazelcast.getMap("webdav-locks");
- return new LockStoreImpl(map);
+ if (!PropertyCheck.isValidPropertyString(clusterName))
+ {
+ return new SimpleLockStore();
+ }
+ else
+ {
+ HazelcastInstance instance = hazelcastInstanceFactory.newInstance();
+ ConcurrentMap map = instance.getMap(HAZELCAST_MAP_NAME);
+ return new LockStoreImpl(map);
+ }
}
/**
- * @param hazelcast the hazelcast to set
+ * @param hazelcastInstanceFactory the factory that will create a HazelcastInstance if required.
*/
- public void setHazelcast(HazelcastInstance hazelcast)
+ public synchronized void setHazelcastInstanceFactory(HazelcastInstanceFactory hazelcastInstanceFactory)
{
- this.hazelcast = hazelcast;
+ this.hazelcastInstanceFactory = hazelcastInstanceFactory;
+ }
+
+ /**
+ * @param clusterName the clusterName to set
+ */
+ public synchronized void setClusterName(String clusterName)
+ {
+ this.clusterName = clusterName;
}
}
diff --git a/source/java/org/alfresco/repo/webdav/SimpleLockStore.java b/source/java/org/alfresco/repo/webdav/SimpleLockStore.java
new file mode 100644
index 0000000000..03976f8094
--- /dev/null
+++ b/source/java/org/alfresco/repo/webdav/SimpleLockStore.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2005-2012 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco 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 Alfresco. If not, see .
+ */
+package org.alfresco.repo.webdav;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+
+/**
+ * {@link LockStore} implementation for use in a non-clustered environment.
+ *
+ * @author Matt Ward
+ */
+public class SimpleLockStore extends LockStoreImpl
+{
+ public SimpleLockStore()
+ {
+ super(new ConcurrentHashMap());
+ }
+}
diff --git a/source/test-resources/cluster-test/placeholder-test.xml b/source/test-resources/cluster-test/placeholder-test.xml
new file mode 100644
index 0000000000..9bbcaab1a7
--- /dev/null
+++ b/source/test-resources/cluster-test/placeholder-test.xml
@@ -0,0 +1,166 @@
+
+
+
+
+ ${alfresco.cluster.name}
+ ${alfresco.hazelcast.password}
+
+
+ 5701
+
+
+ 224.2.2.3
+ 54327
+
+
+ 127.0.0.1
+
+
+ my-access-key
+ my-secret-key
+
+ us-west-1
+
+ hazelcast-sg
+ type
+ hz-nodes
+
+
+
+ 10.10.1.*
+
+
+
+ PBEWithMD5AndDES
+
+ thesalt
+
+ thepass
+
+ 19
+
+
+
+ RSA/NONE/PKCS1PADDING
+
+ thekeypass
+
+ local
+
+ JKS
+
+ thestorepass
+
+ keystore
+
+
+
+ 16
+ 64
+ 60
+
+
+
+ 0
+
+ default
+
+
+
+
+
+
+
\ No newline at end of file