diff --git a/config/alfresco/alfresco-shared.properties b/config/alfresco/alfresco-shared.properties
index 142fce6173..42502b77a7 100644
--- a/config/alfresco/alfresco-shared.properties
+++ b/config/alfresco/alfresco-shared.properties
@@ -14,3 +14,7 @@ avm.remote.host=localhost
# AVMRemote API
avm.remote.port=50500
+# Remote RMI services
+rmi.services.remote.port=${avm.remote.port}
+rmi.services.remote.host=${avm.remote.host}
+
diff --git a/config/alfresco/extension/remote-loader-context.xml b/config/alfresco/extension/remote-loader-context.xml
new file mode 100644
index 0000000000..eb2a78757b
--- /dev/null
+++ b/config/alfresco/extension/remote-loader-context.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.remote.FileFolderRemote
+
+
+ org.alfresco.FileFolderRemote
+
+
+ ${rmi.services.remote.port}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.remote.LoaderRemote
+
+
+ org.alfresco.LoaderRemote
+
+
+ ${rmi.services.remote.port}
+
+
+
+
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml
index 25983f85ce..df78c655c1 100644
--- a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml
+++ b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml
@@ -492,4 +492,21 @@
store.key.identifier = :storeIdentifier)
+
+ select
+ count(node.id)
+ from
+ org.alfresco.repo.domain.hibernate.NodeImpl as node
+
+
+
+ select
+ count(node.id)
+ from
+ org.alfresco.repo.domain.hibernate.NodeImpl as node
+ where
+ node.store.key.protocol = :protocol and
+ node.store.key.identifier = :identifier
+
+
diff --git a/source/java/org/alfresco/repo/model/filefolder/loader/AbstractLoaderThread.java b/source/java/org/alfresco/repo/model/filefolder/loader/AbstractLoaderThread.java
new file mode 100644
index 0000000000..11cff034a5
--- /dev/null
+++ b/source/java/org/alfresco/repo/model/filefolder/loader/AbstractLoaderThread.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.model.filefolder.loader;
+
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+
+/**
+ * A description of what the remote loader should do.
+ *
+ * @author Derek Hulley
+ */
+public abstract class AbstractLoaderThread extends Thread
+{
+ protected final LoaderSession session;
+ protected final String loaderName;
+ protected final int testPeriod;
+ protected final int testTotal;
+ protected final int testLoadDepth;
+
+ private AtomicBoolean mustStop;
+
+ public AbstractLoaderThread(
+ LoaderSession session,
+ String loaderName,
+ int testPeriod,
+ int testTotal,
+ int testLoadDepth)
+ {
+ super(LoaderSession.THREAD_GROUP, "LoaderThread-" + loaderName);
+
+ this.session = session;
+ this.loaderName = loaderName;
+ this.testPeriod = testPeriod;
+ this.testTotal = testTotal;
+ this.testLoadDepth = testLoadDepth;
+
+ this.mustStop = new AtomicBoolean(false);
+ }
+
+ /**
+ * Notify the running thread to exit at the first available opportunity.
+ */
+ public void setStop()
+ {
+ mustStop.set(true);
+ }
+
+ public abstract String getSummary();
+
+ @Override
+ public void run()
+ {
+ Random random = new Random();
+
+ int testCount = 0;
+ while (!mustStop.get())
+ {
+ try
+ {
+ // Choose a server
+ int serverCount = session.getRemoteServers().size();
+ int serverIndex = random.nextInt(serverCount);
+ LoaderServerProxy serverProxy = session.getRemoteServers().get(serverIndex);
+
+ // Choose a working root node
+ int nodeCount = session.getWorkingRootNodeRefs().size();
+ int nodeIndex = random.nextInt(nodeCount);
+ NodeRef workingRootNodeRef = session.getWorkingRootNodeRefs().get(nodeIndex);
+
+ long startTime = System.currentTimeMillis();
+ doLoading(serverProxy, workingRootNodeRef);
+ long endTime = System.currentTimeMillis();
+
+ // Have we done this enough?
+ testCount++;
+ if (testCount > testTotal)
+ {
+ break;
+ }
+
+ // Do we wait or continue immediately
+ long duration = endTime - startTime;
+ long mustWait = (testPeriod * 1000L) - duration;
+ if (mustWait >= 5)
+ {
+ synchronized(this)
+ {
+ this.wait(mustWait);
+ }
+ }
+ }
+ catch (Throwable e)
+ {
+ session.logError(e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * @param serverProxy the server to load
+ * @param workingRootNodeRef the root of the hierarchy to use
+ * @throws Exception any exception will be handled
+ */
+ protected abstract void doLoading(
+ LoaderServerProxy serverProxy,
+ NodeRef workingRootNodeRef) throws Exception;
+}
diff --git a/source/java/org/alfresco/repo/model/filefolder/loader/FileFolderRemoteLoader.java b/source/java/org/alfresco/repo/model/filefolder/loader/FileFolderRemoteLoader.java
new file mode 100644
index 0000000000..fd6a3e0b8e
--- /dev/null
+++ b/source/java/org/alfresco/repo/model/filefolder/loader/FileFolderRemoteLoader.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.model.filefolder.loader;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.service.cmr.repository.StoreRef;
+
+/**
+ * A class that communicates with the remote FileFolder interface to perform
+ * loading of the system using the standard models.
+ *
+ * TODO: The transport interface must be hidden behind and implementation
+ * of the standard FileFolderService interface.
+ *
+ * @author Derek Hulley
+ * @since 2.1
+ */
+public class FileFolderRemoteLoader
+{
+ private static final String STORE_PROTOCOL = "FileFolderRemoteLoader";
+
+ private Properties properties;
+ private String username;
+ private String password;
+
+ private LoaderSession session;
+ private AbstractLoaderThread[] threads;
+
+ public FileFolderRemoteLoader(Properties properties, String username, String password)
+ {
+ this.properties = properties;
+ this.username = username;
+ this.password = password;
+ }
+
+ public synchronized void initialize() throws Exception
+ {
+ if (session != null || threads != null)
+ {
+ throw new AlfrescoRuntimeException("Application already initialized");
+ }
+ session = FileFolderRemoteLoader.makeSession(username, password, properties);
+ threads = FileFolderRemoteLoader.makeThreads(session, properties);
+ }
+
+ public synchronized void start()
+ {
+ if (session == null || threads == null)
+ {
+ throw new AlfrescoRuntimeException("Application not initialized");
+ }
+ // Fire up the threads
+ for (Thread thread : threads)
+ {
+ thread.start();
+ }
+ }
+
+ public synchronized void stop()
+ {
+ // Stop the threads
+ for (AbstractLoaderThread thread : threads)
+ {
+ thread.setStop();
+ }
+ // Now join each thread to make sure they all finish their current operation
+ for (AbstractLoaderThread thread : threads)
+ {
+ try
+ {
+ thread.join();
+ }
+ catch (InterruptedException e) {}
+ }
+ // Get each one's summary
+ for (AbstractLoaderThread thread : threads)
+ {
+ String summary = thread.getSummary();
+ session.logNormal(summary);
+ }
+ }
+
+ public static final String PROP_SESSION_NAME = "session.name";
+ public static final String PROP_SESSION_VERBOSE = "session.verbose";
+ public static final String PROP_SESSION_SOURCE_DIR = "session.sourceDir";
+ public static final String PROP_SESSION_STORE_IDENTIFIERS = "session.storeIdentifiers";
+ public static final String PROP_SESSION_RMI_URLS = "session.rmiUrls";
+ public static final String PROP_SESSION_FOLDER_PROFILE = "session.folderProfile";
+
+ /**
+ * Factory method to construct a session using the given properties.
+ */
+ private static LoaderSession makeSession(String username, String password, Properties properties) throws Exception
+ {
+ // Name
+ String name = properties.getProperty(PROP_SESSION_NAME);
+ FileFolderRemoteLoader.checkProperty(PROP_SESSION_STORE_IDENTIFIERS, name);
+
+ // Verbose
+ String verboseStr = properties.getProperty(PROP_SESSION_VERBOSE);
+ boolean verbose = verboseStr == null ? false : Boolean.parseBoolean(verboseStr);
+
+ // Source files
+ String sourceDirStr = properties.getProperty(PROP_SESSION_SOURCE_DIR);
+ File sourceDir = new File(sourceDirStr);
+
+ // Stores
+ String storeIdentifiersStr = properties.getProperty(PROP_SESSION_STORE_IDENTIFIERS);
+ FileFolderRemoteLoader.checkProperty(PROP_SESSION_STORE_IDENTIFIERS, storeIdentifiersStr);
+ StringTokenizer tokenizer = new StringTokenizer(storeIdentifiersStr, ",");
+ Set storeRefs = new HashSet();
+ while (tokenizer.hasMoreTokens())
+ {
+ String storeIdentifier = tokenizer.nextToken().trim();
+ storeRefs.add(new StoreRef(STORE_PROTOCOL, storeIdentifier));
+ }
+ FileFolderRemoteLoader.checkProperty(PROP_SESSION_STORE_IDENTIFIERS, storeRefs);
+
+ // RMI URLs
+ String rmiUrlsStr = properties.getProperty(PROP_SESSION_RMI_URLS);
+ FileFolderRemoteLoader.checkProperty(PROP_SESSION_RMI_URLS, rmiUrlsStr);
+ tokenizer = new StringTokenizer(rmiUrlsStr, ",");
+ Set rmiUrls = new HashSet();
+ while (tokenizer.hasMoreTokens())
+ {
+ String rmiUrl = tokenizer.nextToken().trim();
+ rmiUrls.add(rmiUrl);
+ }
+ FileFolderRemoteLoader.checkProperty(PROP_SESSION_STORE_IDENTIFIERS, rmiUrls);
+
+ // RMI URLs
+ String folderProfilesStr = properties.getProperty(PROP_SESSION_FOLDER_PROFILE);
+ FileFolderRemoteLoader.checkProperty(PROP_SESSION_FOLDER_PROFILE, folderProfilesStr);
+ tokenizer = new StringTokenizer(folderProfilesStr, ",");
+ ArrayList folderProfilesList = new ArrayList(5);
+ while (tokenizer.hasMoreTokens())
+ {
+ String folderProfileStr = tokenizer.nextToken().trim();
+ Integer folderProfile = Integer.valueOf(folderProfileStr);
+ folderProfilesList.add(folderProfile);
+ }
+ FileFolderRemoteLoader.checkProperty(PROP_SESSION_FOLDER_PROFILE, folderProfilesList);
+ int[] folderProfiles = new int[folderProfilesList.size()];
+ for (int i = 0; i < folderProfiles.length; i++)
+ {
+ folderProfiles[i] = folderProfilesList.get(i);
+ }
+
+ // Construct
+ LoaderSession session = new LoaderSession(
+ username,
+ password,
+ name,
+ rmiUrls,
+ storeRefs,
+ verbose,
+ sourceDir,
+ folderProfiles);
+
+ // Initialize the session
+ session.initialize();
+
+ // Done
+ return session;
+ }
+
+ /**
+ * Factory method to construct the worker threads.
+ */
+ private static AbstractLoaderThread[] makeThreads(LoaderSession session, Properties properties) throws Exception
+ {
+ ArrayList threads = new ArrayList(3);
+ // Iterate over the properties and pick out the thread descriptors
+ for (String propertyName : properties.stringPropertyNames())
+ {
+ if (!propertyName.startsWith("test.load."))
+ {
+ continue;
+ }
+ String name = propertyName;
+
+ // Get the type of the test
+ int lastIndex = propertyName.indexOf(".", 10);
+ if (lastIndex < 0)
+ {
+ throw new LoaderClientException(
+ "Invalid test loader property. " +
+ "It should be of the form 'test.load.upload.xyz=...': " + propertyName);
+ }
+ String type = propertyName.substring(10, lastIndex);
+
+ // Get the values
+ String valuesStr = properties.getProperty(propertyName);
+ FileFolderRemoteLoader.checkProperty(propertyName, valuesStr);
+ // Parse it into the well-known values
+ int[] values = new int[] {1, 0, -1, 1};
+ int index = 0;
+ StringTokenizer tokenizer = new StringTokenizer(valuesStr, ",");
+ while (tokenizer.hasMoreTokens())
+ {
+ String value = tokenizer.nextToken().trim();
+ values[index] = Integer.parseInt(value);
+ index++;
+ if (index >= values.length)
+ {
+ break;
+ }
+ }
+ int testCount = values[0];
+ int testPeriod = values[1];
+ int testTotal = values[2];
+ int testDepth = values[3];
+
+ // Construct
+ for (int i = 0; i < testCount; i++)
+ {
+ AbstractLoaderThread thread = null;
+ if (type.equals("upload"))
+ {
+ thread = new LoaderUploadThread(session, name, testPeriod, testTotal, testDepth);
+ }
+ else
+ {
+ throw new LoaderClientException("Unknown test type: " + name);
+ }
+ threads.add(thread);
+ }
+ }
+ // Done
+ AbstractLoaderThread[] ret = new AbstractLoaderThread[threads.size()];
+ return threads.toArray(ret);
+ }
+
+ /**
+ * Checks for null values and empty collections.
+ */
+ private static void checkProperty(String propertyName, Object propertyValue) throws LoaderClientException
+ {
+ if (propertyValue == null)
+ {
+ throw new LoaderClientException("'" + propertyName + "' must be provided.");
+ }
+ else if (propertyValue instanceof Collection)
+ {
+ Collection propertyCollection = (Collection) propertyValue;
+ int size = propertyCollection.size();
+ if (size == 0)
+ {
+ throw new LoaderClientException("'" + propertyName + "' does not have any values.");
+ }
+ }
+ }
+
+ public static void main(String ... args)
+ {
+ Map argMap = ripArgs(args);
+ String username = argMap.get("username");
+ String password = argMap.get("password");
+ String config = argMap.get("config");
+ if (username == null || password == null || config == null)
+ {
+ printUsage();
+ System.exit(1);
+ }
+ try
+ {
+ File propertiesFile = new File(config);
+ if (!propertiesFile.exists())
+ {
+ System.err.println("Unable to find config file: " + config);
+ }
+ Properties properties = new Properties();
+ properties.load(new FileInputStream(propertiesFile));
+
+ FileFolderRemoteLoader app = new FileFolderRemoteLoader(properties, username, password);
+
+ // Initialize
+ app.initialize();
+
+ // Run
+ app.start();
+
+ synchronized(app)
+ {
+ app.wait(1000L);
+ }
+// System.out.println(""
+
+ // Finish off
+ app.stop();
+ }
+ catch (LoaderClientException e)
+ {
+ System.err.println(e.getMessage());
+ System.exit(1);
+ }
+ catch (Throwable e)
+ {
+ System.err.println("A failure prevented proper execution.");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ System.exit(0);
+ }
+
+ private static Map ripArgs(String ... args)
+ {
+ Map argsMap = new HashMap(5);
+ for (String arg : args)
+ {
+ int index = arg.indexOf('=');
+ if (!arg.startsWith("--") || index < 0 || index == arg.length() - 1)
+ {
+ // Ignore it
+ continue;
+ }
+ String name = arg.substring(2, index);
+ String value = arg.substring(index + 1, arg.length());
+ argsMap.put(name, value);
+ }
+ return argsMap;
+ }
+
+ private static void printUsage()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("\n")
+ .append("Usage\n")
+ .append(" java -jar ... --username= --password= --config= \n");
+ System.out.println(sb.toString());
+ }
+}
diff --git a/source/java/org/alfresco/repo/model/filefolder/loader/LoaderClientException.java b/source/java/org/alfresco/repo/model/filefolder/loader/LoaderClientException.java
new file mode 100644
index 0000000000..932be5ab28
--- /dev/null
+++ b/source/java/org/alfresco/repo/model/filefolder/loader/LoaderClientException.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.model.filefolder.loader;
+
+/**
+ * Helper class to catch non-critical issues.
+ *
+ * @author Derek Hulley
+ */
+public class LoaderClientException extends Exception
+{
+ private static final long serialVersionUID = -3306363763048881956L;
+
+ public LoaderClientException(String msg)
+ {
+ super(msg);
+ }
+}
diff --git a/source/java/org/alfresco/repo/model/filefolder/loader/LoaderServerProxy.java b/source/java/org/alfresco/repo/model/filefolder/loader/LoaderServerProxy.java
new file mode 100644
index 0000000000..98fadb8a19
--- /dev/null
+++ b/source/java/org/alfresco/repo/model/filefolder/loader/LoaderServerProxy.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.model.filefolder.loader;
+
+import org.alfresco.service.cmr.remote.FileFolderRemote;
+import org.alfresco.service.cmr.remote.LoaderRemote;
+
+/**
+ * Helper class to store remote service connections.
+ *
+ * @author Derek Hulley
+ */
+public class LoaderServerProxy
+{
+ public final String rmiUrl;
+ public final String ticket;
+ public final FileFolderRemote fileFolderRemote;
+ public final LoaderRemote loaderRemote;
+
+ public LoaderServerProxy(
+ String rmiUrl,
+ String ticket,
+ FileFolderRemote fileFolderRemote,
+ LoaderRemote loaderRemote)
+ {
+ this.rmiUrl = rmiUrl;
+ this.ticket = ticket;
+ this.fileFolderRemote = fileFolderRemote;
+ this.loaderRemote = loaderRemote;
+ }
+}
+
diff --git a/source/java/org/alfresco/repo/model/filefolder/loader/LoaderSession.java b/source/java/org/alfresco/repo/model/filefolder/loader/LoaderSession.java
new file mode 100644
index 0000000000..7fd95c83dd
--- /dev/null
+++ b/source/java/org/alfresco/repo/model/filefolder/loader/LoaderSession.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.model.filefolder.loader;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.remote.FileFolderRemoteClient;
+import org.alfresco.service.cmr.model.FileInfo;
+import org.alfresco.service.cmr.remote.LoaderRemote;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.springframework.remoting.rmi.RmiProxyFactoryBean;
+
+/**
+ * A description of session details.
+ *
+ * @author Derek Hulley
+ * @since 2.2
+ */
+public class LoaderSession
+{
+ public static final ThreadGroup THREAD_GROUP = new ThreadGroup("FileFolderRemoteLoader");
+
+ private String username;
+ private String password;
+ private String name;
+ private Set rmiUrls;
+ private Set storeRefs;
+ private boolean verbose;
+ private File outputFile;
+ private File sourceDir;
+ private int[] folderProfiles;
+
+ private List remoteServers;
+ private List workingRootNodeRefs;
+ private File[] sourceFiles;
+ private OutputStream outStream;
+ private OutputStream errStream;
+
+ /**
+ *
+ */
+ public LoaderSession(
+ String username,
+ String password,
+ String name,
+ Set rmiUrls,
+ Set storeRefs,
+ boolean verbose,
+ File sourceDir,
+ int[] folderProfiles)
+ {
+ this.username = username;
+ this.password = password;
+ this.name = name;
+ this.rmiUrls = rmiUrls;
+ this.storeRefs = storeRefs;
+ this.verbose = verbose;
+ this.sourceDir = sourceDir;
+ this.folderProfiles = folderProfiles;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public List getRemoteServers()
+ {
+ return remoteServers;
+ }
+
+ public List getWorkingRootNodeRefs()
+ {
+ return workingRootNodeRefs;
+ }
+
+ public File[] getSourceFiles()
+ {
+ return sourceFiles;
+ }
+
+ public int[] getFolderProfiles()
+ {
+ return folderProfiles;
+ }
+
+ /**
+ * Initialize the object before first use.
+ */
+ public synchronized void initialize() throws Exception
+ {
+ if (remoteServers != null)
+ {
+ throw new AlfrescoRuntimeException("The client has already been initialized");
+ }
+ remoteServers = LoaderSession.connect(rmiUrls, username, password);
+ workingRootNodeRefs = LoaderSession.makeStores(remoteServers, storeRefs);
+ LoaderSession.checkClustering(remoteServers, workingRootNodeRefs);
+
+ // Create the output file, if necessary
+ if (outputFile != null)
+ {
+ File outputDir = outputFile.getParentFile();
+ if (!outputDir.exists())
+ {
+ outputDir.mkdirs();
+ }
+ if (outputFile.exists())
+ {
+ System.out.println("The output file " + outputFile + " already exists.");
+ System.out.println("Are you sure you want to overwrite the file?");
+ int in = System.in.read();
+ if (in != 'Y' && in != 'y')
+ {
+ throw new LoaderClientException("The output file " + outputFile + " already exists");
+ }
+ }
+ }
+
+ // Get the source files
+ sourceFiles = LoaderSession.getSourceFiles(sourceDir);
+
+ // Construct output and error files
+ long time = System.currentTimeMillis();
+ File outFile = new File("./LoaderSession-" + name + "-"+ time + "-out.txt");
+ File errFile = new File("./LoaderSession-" + name + "-"+ time + "-err.txt");
+ outStream = new BufferedOutputStream(new FileOutputStream(outFile));
+ errStream = new BufferedOutputStream(new FileOutputStream(errFile));
+ }
+
+ public synchronized void close()
+ {
+ try { outStream.close(); } catch (Throwable e) {}
+ try { errStream.close(); } catch (Throwable e) {}
+
+ outStream = null;
+ errStream = null;
+ }
+
+ /**
+ * Connect to the remote servers.
+ */
+ private static List connect(Set rmiUrls, String username, String password) throws Exception
+ {
+ List remoteServers = new ArrayList(rmiUrls.size());
+ for (String rmiUrl : rmiUrls)
+ {
+ try
+ {
+ // Ensure the RMI URL is consistent
+ if (!rmiUrl.endsWith("/"))
+ {
+ rmiUrl += "/";
+ }
+ // Get the FileFolderServiceTransport
+ FileFolderRemoteClient fileFolderRemote = new FileFolderRemoteClient(rmiUrl);
+ // Get the LoaderServiceTransport
+ RmiProxyFactoryBean loaderFactory = new RmiProxyFactoryBean();
+ loaderFactory.setRefreshStubOnConnectFailure(true);
+ loaderFactory.setServiceInterface(LoaderRemote.class);
+ loaderFactory.setServiceUrl(rmiUrl + LoaderRemote.SERVICE_NAME);
+ loaderFactory.afterPropertiesSet();
+ LoaderRemote loaderRemote = (LoaderRemote) loaderFactory.getObject();
+
+ // Authenticate
+ String ticket = loaderRemote.authenticate(username, password);
+
+ // Store the service references
+ LoaderServerProxy remoteServer = new LoaderServerProxy(
+ rmiUrl,
+ ticket,
+ fileFolderRemote,
+ loaderRemote);
+ remoteServers.add(remoteServer);
+ }
+ catch (Throwable e)
+ {
+ // Failed to connect.
+ System.err.println("\n" +
+ "ERROR: Failed to establish connection to server: \n" +
+ " Server: " + rmiUrl + "\n" +
+ " Error: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+ // Check that there is at least one server
+ if (remoteServers.size() == 0)
+ {
+ throw new LoaderClientException("No remote servers are available");
+ }
+ return remoteServers;
+ }
+
+ private static List makeStores(List remoteServers, Set storeRefs) throws Exception
+ {
+ // Take the first server and ensure that all the stores are available
+ LoaderServerProxy remoteServer = remoteServers.get(0);
+
+ List workingRootNodeRefs = new ArrayList(10);
+ for (StoreRef storeRef : storeRefs)
+ {
+ NodeRef workingRootNodeRef = remoteServer.loaderRemote.getOrCreateWorkingRoot(remoteServer.ticket, storeRef);
+ workingRootNodeRefs.add(workingRootNodeRef);
+ }
+ // Done
+ return workingRootNodeRefs;
+ }
+
+ private static void checkClustering(
+ List remoteServers,
+ List workingRootNodeRefs) throws Exception
+ {
+ List problems = new ArrayList(10);
+ for (LoaderServerProxy remoteServer : remoteServers)
+ {
+ String ticket = remoteServer.ticket;
+ for (NodeRef workingRootNodeRef : workingRootNodeRefs)
+ {
+ try
+ {
+ // Get the working root node. If the cluster is correctly configured,
+ // it will be available on all the servers.
+ FileInfo fileInfo = remoteServer.fileFolderRemote.getFileInfo(ticket, workingRootNodeRef);
+ if (fileInfo == null)
+ {
+ problems.add("Cannot find the working root node on server: " + remoteServer.rmiUrl);
+ continue;
+ }
+ // Now look for and create a file against which to store some content
+ String sampleFilename = "InitialSample.txt";
+ String sampleContent = "Sample content";
+ NodeRef sampleNodeRef = remoteServer.fileFolderRemote.searchSimple(ticket, workingRootNodeRef, sampleFilename);
+ if (sampleNodeRef == null)
+ {
+ FileInfo sampleFileInfo = remoteServer.fileFolderRemote.create(
+ ticket, workingRootNodeRef, sampleFilename, ContentModel.TYPE_CONTENT);
+ sampleNodeRef = sampleFileInfo.getNodeRef();
+ // Write some content
+ byte[] bytes = sampleContent.getBytes("UTF-8");
+ remoteServer.fileFolderRemote.putContent(ticket, sampleNodeRef, bytes, sampleFilename);
+ }
+ // Get the content and ensure that it is the same
+ byte[] bytes = remoteServer.fileFolderRemote.getContent(ticket, sampleNodeRef);
+ if (bytes == null)
+ {
+ problems.add("Sample content was not found on server: " + remoteServer.rmiUrl);
+ continue;
+ }
+ String checkContent = new String(bytes, "UTF-8");
+ if (!checkContent.equals(sampleContent))
+ {
+ problems.add("The sample content differed from expected: " + remoteServer.rmiUrl);
+ continue;
+ }
+ }
+ catch (Throwable e)
+ {
+ System.err.println("ERROR: Failure whilst checking server: " + remoteServer.rmiUrl);
+ e.printStackTrace();
+ problems.add(e.getMessage());
+ }
+ }
+ }
+ // Check for issues
+ if (problems.size() > 0)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("\n")
+ .append("The working root node references could not be found on all the remote servers.\n")
+ .append("Please ensure that all the remote servers listed are active in a single cluster.");
+ for (String problem : problems)
+ {
+ sb.append("\n")
+ .append(" ").append(problem);
+ }
+ throw new LoaderClientException(sb.toString());
+ }
+ }
+
+ private static File[] getSourceFiles(File sourceDir) throws Exception
+ {
+ // Ensure that the source directory is present, if specified
+ if (sourceDir != null)
+ {
+ if (!sourceDir.exists())
+ {
+ throw new LoaderClientException("The source directory to contain upload files is missing: " + sourceDir);
+ }
+ // Check that there is something in it
+ File[] allFiles = sourceDir.listFiles();
+ ArrayList sourceFiles = new ArrayList(allFiles.length);
+ for (File file : allFiles)
+ {
+ if (file.isDirectory())
+ {
+ continue;
+ }
+ sourceFiles.add(file);
+ }
+ File[] ret = new File[sourceFiles.size()];
+ return sourceFiles.toArray(ret);
+ }
+ else
+ {
+ return new File[] {};
+ }
+ }
+
+ public void logVerbose(String msg)
+ {
+ if (!verbose)
+ {
+ // Just ignore it
+ }
+ logNormal(msg);
+ }
+
+ public synchronized void logNormal(String msg)
+ {
+ if (outStream == null)
+ {
+ return;
+ }
+ try
+ {
+ byte[] bytes = msg.getBytes("UTF-8");
+ outStream.write(bytes);
+ outStream.write('\n');
+ outStream.flush();
+ }
+ catch (Throwable e)
+ {
+ System.err.println("Failed to write message to output file: " + e.getMessage());
+ }
+ }
+
+ public synchronized void logError(String msg)
+ {
+ if (errStream == null)
+ {
+ return;
+ }
+ try
+ {
+ byte[] bytes = msg.getBytes("UTF-8");
+ errStream.write(bytes);
+ outStream.write('\n');
+ errStream.flush();
+ }
+ catch (Throwable e)
+ {
+ System.err.println("Failed to write message to output file: " + e.getMessage());
+ }
+ }
+}
diff --git a/source/java/org/alfresco/repo/model/filefolder/loader/LoaderUploadThread.java b/source/java/org/alfresco/repo/model/filefolder/loader/LoaderUploadThread.java
new file mode 100644
index 0000000000..0cd6afc41a
--- /dev/null
+++ b/source/java/org/alfresco/repo/model/filefolder/loader/LoaderUploadThread.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.model.filefolder.loader;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+
+/**
+ * A description of what the remote loader should do.
+ *
+ * @author Derek Hulley
+ */
+public class LoaderUploadThread extends AbstractLoaderThread
+{
+ public LoaderUploadThread(
+ LoaderSession session,
+ String loaderName,
+ int testPeriod,
+ int testTotal,
+ int testLoadDepth)
+ {
+ super(session, loaderName, testPeriod, testTotal, testLoadDepth);
+ }
+
+ @Override
+ protected void doLoading(LoaderServerProxy serverProxy, NodeRef workingRootNodeRef) throws Exception
+ {
+ int totalNodeCount = serverProxy.loaderRemote.getNodeCount(
+ serverProxy.ticket);
+ int storeNodeCount = serverProxy.loaderRemote.getNodeCount(
+ serverProxy.ticket,
+ workingRootNodeRef.getStoreRef());
+ session.logVerbose("Nodes: " + totalNodeCount + ". Store Nodes: " + storeNodeCount);
+ }
+
+ @Override
+ public String getSummary()
+ {
+ return "Tanker";
+ }
+}
diff --git a/source/java/org/alfresco/repo/node/db/NodeDaoService.java b/source/java/org/alfresco/repo/node/db/NodeDaoService.java
index ba9a7aa214..2523ebadbd 100644
--- a/source/java/org/alfresco/repo/node/db/NodeDaoService.java
+++ b/source/java/org/alfresco/repo/node/db/NodeDaoService.java
@@ -284,6 +284,15 @@ public interface NodeDaoService
*/
public List getPropertyValuesByActualType(DataTypeDefinition actualDataTypeDefinition);
+ /**
+ * @return Returns the total number of nodes in the ADM repository
+ */
+ public int getNodeCount();
+ /**
+ * @return Returns the total number of nodes in the ADM store
+ */
+ public int getNodeCount(final StoreRef storeRef);
+
public Transaction getTxnById(long txnId);
public Transaction getLastTxn();
public Transaction getLastRemoteTxn();
diff --git a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java
index 70a79a47e0..31c943a4b6 100644
--- a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java
+++ b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java
@@ -105,6 +105,9 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
private static final String QUERY_GET_SOURCE_ASSOCS = "node.GetSourceAssocs";
private static final String QUERY_GET_NODES_WITH_PROPERTY_VALUES_BY_ACTUAL_TYPE = "node.GetNodesWithPropertyValuesByActualType";
private static final String QUERY_GET_SERVER_BY_IPADDRESS = "server.getServerByIpAddress";
+
+ private static final String QUERY_GET_NODE_COUNT = "node.GetNodeCount";
+ private static final String QUERY_GET_NODE_COUNT_FOR_STORE = "node.GetNodeCountForStore";
private static final String QUERY_GET_NODE_STATUSES_FOR_STORE = "node.GetNodeStatusesForStore";
private static final String QUERY_GET_CHILD_ASSOCS_FOR_STORE = "node.GetChildAssocsForStore";
@@ -367,6 +370,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
* @param protocol the store protocol
* @param identifier the store identifier
*/
+ @SuppressWarnings("unchecked")
public void deleteStore(final String protocol, final String identifier)
{
// ensure that the store exists
@@ -1368,6 +1372,48 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
return convertedValues;
}
+ /**
+ * {@inheritDoc}
+ */
+ public int getNodeCount()
+ {
+ HibernateCallback callback = new HibernateCallback()
+ {
+ public Object doInHibernate(Session session)
+ {
+ Query query = session.getNamedQuery(QUERY_GET_NODE_COUNT);
+ query.setMaxResults(1)
+ .setReadOnly(true);
+ return query.uniqueResult();
+ }
+ };
+ Long count = (Long) getHibernateTemplate().execute(callback);
+ // done
+ return count.intValue();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getNodeCount(final StoreRef storeRef)
+ {
+ HibernateCallback callback = new HibernateCallback()
+ {
+ public Object doInHibernate(Session session)
+ {
+ Query query = session.getNamedQuery(QUERY_GET_NODE_COUNT_FOR_STORE);
+ query.setString("protocol", storeRef.getProtocol())
+ .setString("identifier", storeRef.getIdentifier())
+ .setMaxResults(1)
+ .setReadOnly(true);
+ return query.uniqueResult();
+ }
+ };
+ Long count = (Long) getHibernateTemplate().execute(callback);
+ // done
+ return count.intValue();
+ }
+
/*
* Queries for transactions
*/
diff --git a/source/java/org/alfresco/repo/remote/FileFolderRemoteClient.java b/source/java/org/alfresco/repo/remote/FileFolderRemoteClient.java
new file mode 100644
index 0000000000..a1de8781a0
--- /dev/null
+++ b/source/java/org/alfresco/repo/remote/FileFolderRemoteClient.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.remote;
+
+import java.util.List;
+
+import org.alfresco.service.cmr.model.FileExistsException;
+import org.alfresco.service.cmr.model.FileInfo;
+import org.alfresco.service.cmr.model.FileNotFoundException;
+import org.alfresco.service.cmr.remote.FileFolderRemote;
+import org.alfresco.service.cmr.repository.ContentData;
+import org.alfresco.service.cmr.repository.ContentReader;
+import org.alfresco.service.cmr.repository.ContentWriter;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+import org.springframework.remoting.rmi.RmiProxyFactoryBean;
+
+/**
+ * Client side implementation of the remotable FileFolder interface.
+ *
+ * @author Derek Hulley
+ * @since 2.2
+ */
+public class FileFolderRemoteClient implements FileFolderRemote
+{
+ private String rmiUrl;
+ private FileFolderRemote remotePeer;
+
+ public FileFolderRemoteClient(String rmiUrl)
+ {
+ // Ensure the RMI URL is consistent
+ if (!rmiUrl.endsWith("/"))
+ {
+ rmiUrl += "/";
+ }
+
+ this.rmiUrl = rmiUrl;
+ // Connect
+ connect();
+ }
+
+ private void connect()
+ {
+ // Get the FileFolderServiceTransport
+ RmiProxyFactoryBean fileFolderFactory = new RmiProxyFactoryBean();
+ fileFolderFactory.setRefreshStubOnConnectFailure(true);
+ fileFolderFactory.setServiceInterface(FileFolderRemote.class);
+ fileFolderFactory.setServiceUrl(rmiUrl + FileFolderRemote.SERVICE_NAME);
+ fileFolderFactory.afterPropertiesSet();
+ FileFolderRemote fileFolderRemote = (FileFolderRemote) fileFolderFactory.getObject();
+ this.remotePeer = fileFolderRemote;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List list(String ticket, final NodeRef contextNodeRef)
+ {
+ return remotePeer.list(ticket, contextNodeRef);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List listFiles(String ticket, final NodeRef folderNodeRef)
+ {
+ return remotePeer.listFiles(ticket, folderNodeRef);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List listFolders(String ticket, final NodeRef contextNodeRef)
+ {
+ return remotePeer.listFolders(ticket, contextNodeRef);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public NodeRef searchSimple(String ticket, final NodeRef contextNodeRef, final String name)
+ {
+ return remotePeer.searchSimple(ticket, contextNodeRef, name);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List search(
+ String ticket,
+ final NodeRef contextNodeRef,
+ final String namePattern,
+ final boolean includeSubFolders)
+ {
+ return remotePeer.search(ticket, contextNodeRef, namePattern, includeSubFolders);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List search(
+ String ticket,
+ final NodeRef contextNodeRef,
+ final String namePattern,
+ final boolean fileSearch,
+ final boolean folderSearch,
+ final boolean includeSubFolders)
+ {
+ return remotePeer.search(ticket, contextNodeRef, namePattern, fileSearch, folderSearch, includeSubFolders);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public FileInfo rename(String ticket, final NodeRef fileFolderRef, final String newName) throws FileExistsException, FileNotFoundException
+ {
+ return remotePeer.rename(ticket, fileFolderRef, newName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public FileInfo move(String ticket, final NodeRef sourceNodeRef, final NodeRef targetParentRef, final String newName)
+ throws FileExistsException, FileNotFoundException
+ {
+ return remotePeer.move(ticket, sourceNodeRef, targetParentRef, newName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public FileInfo copy(String ticket, final NodeRef sourceNodeRef, final NodeRef targetParentRef, final String newName)
+ throws FileExistsException, FileNotFoundException
+ {
+ return remotePeer.copy(ticket, sourceNodeRef, targetParentRef, newName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public FileInfo create(String ticket, final NodeRef parentNodeRef, final String name, final QName typeQName) throws FileExistsException
+ {
+ return remotePeer.create(ticket, parentNodeRef, name, typeQName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void delete(String ticket, final NodeRef nodeRef)
+ {
+ remotePeer.delete(ticket, nodeRef);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public FileInfo makeFolders(String ticket, final NodeRef parentNodeRef, final List pathElements, final QName folderTypeQName)
+ {
+ return remotePeer.makeFolders(ticket, parentNodeRef, pathElements, folderTypeQName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List getNamePath(String ticket, final NodeRef rootNodeRef, final NodeRef nodeRef) throws FileNotFoundException
+ {
+ return remotePeer.getNamePath(ticket, rootNodeRef, nodeRef);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public FileInfo resolveNamePath(String ticket, final NodeRef rootNodeRef, final List pathElements) throws FileNotFoundException
+ {
+ return remotePeer.resolveNamePath(ticket, rootNodeRef, pathElements);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public FileInfo getFileInfo(String ticket, final NodeRef nodeRef)
+ {
+ return remotePeer.getFileInfo(ticket, nodeRef);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ContentData putContent(String ticket, NodeRef nodeRef, byte[] bytes, String filename)
+ {
+ return remotePeer.putContent(ticket, nodeRef, bytes, filename);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public byte[] getContent(String ticket, NodeRef nodeRef)
+ {
+ return remotePeer.getContent(ticket, nodeRef);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ContentReader getReader(String ticket, NodeRef nodeRef)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ContentWriter getWriter(String ticket, NodeRef nodeRef)
+ {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/source/java/org/alfresco/repo/remote/FileFolderRemoteServer.java b/source/java/org/alfresco/repo/remote/FileFolderRemoteServer.java
new file mode 100644
index 0000000000..c4db4dcc2e
--- /dev/null
+++ b/source/java/org/alfresco/repo/remote/FileFolderRemoteServer.java
@@ -0,0 +1,591 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.remote;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.Charset;
+import java.util.List;
+
+import net.sf.acegisecurity.Authentication;
+
+import org.alfresco.repo.content.encoding.ContentCharsetFinder;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.transaction.RetryingTransactionHelper;
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+import org.alfresco.service.cmr.model.FileExistsException;
+import org.alfresco.service.cmr.model.FileFolderService;
+import org.alfresco.service.cmr.model.FileInfo;
+import org.alfresco.service.cmr.model.FileNotFoundException;
+import org.alfresco.service.cmr.remote.FileFolderRemote;
+import org.alfresco.service.cmr.repository.ContentData;
+import org.alfresco.service.cmr.repository.ContentReader;
+import org.alfresco.service.cmr.repository.ContentWriter;
+import org.alfresco.service.cmr.repository.MimetypeService;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.security.AuthenticationService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.service.transaction.TransactionService;
+
+/**
+ * Server side implementation of the FileFolderService transport
+ * layer. This is the class that gets exported remotely as it contains the
+ * explicit ticket arguments.
+ *
+ * @author Derek Hulley
+ * @since 2.2
+ */
+public class FileFolderRemoteServer implements FileFolderRemote
+{
+ private RetryingTransactionHelper retryingTransactionHelper;
+ private AuthenticationService authenticationService;
+ private FileFolderService fileFolderService;
+ private MimetypeService mimetypeService;
+
+ /**
+ * @param transactionService provides transactional support and retrying
+ */
+ public void setTransactionService(TransactionService transactionService)
+ {
+ this.retryingTransactionHelper = transactionService.getRetryingTransactionHelper();
+ }
+
+ /**
+ * @param authenticationService the service that will validate the tickets
+ */
+ public void setAuthenticationService(AuthenticationService authenticationService)
+ {
+ this.authenticationService = authenticationService;
+ }
+
+ /**
+ * @param filefolderService the service that will do the work
+ */
+ public void setFileFolderService(FileFolderService filefolderService)
+ {
+ this.fileFolderService = filefolderService;
+ }
+
+ /**
+ * @param mimetypeService used to determine the character encoding
+ */
+ public void setMimetypeService(MimetypeService mimetypeService)
+ {
+ this.mimetypeService = mimetypeService;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List list(String ticket, final NodeRef contextNodeRef)
+ {
+ Authentication authentication = AuthenticationUtil.getCurrentAuthentication();
+ try
+ {
+ authenticationService.validate(ticket);
+ // Make the call
+ RetryingTransactionCallback> callback = new RetryingTransactionCallback>()
+ {
+ public List execute() throws Throwable
+ {
+ return fileFolderService.list(contextNodeRef);
+ }
+ };
+ return retryingTransactionHelper.doInTransaction(callback, true, true);
+ }
+ finally
+ {
+ AuthenticationUtil.setCurrentAuthentication(authentication);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List listFiles(String ticket, final NodeRef folderNodeRef)
+ {
+ Authentication authentication = AuthenticationUtil.getCurrentAuthentication();
+ try
+ {
+ authenticationService.validate(ticket);
+ // Make the call
+ RetryingTransactionCallback> callback = new RetryingTransactionCallback>()
+ {
+ public List execute() throws Throwable
+ {
+ return fileFolderService.listFiles(folderNodeRef);
+ }
+ };
+ return retryingTransactionHelper.doInTransaction(callback, true, true);
+ }
+ finally
+ {
+ AuthenticationUtil.setCurrentAuthentication(authentication);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List listFolders(String ticket, final NodeRef contextNodeRef)
+ {
+ Authentication authentication = AuthenticationUtil.getCurrentAuthentication();
+ try
+ {
+ authenticationService.validate(ticket);
+ // Make the call
+ RetryingTransactionCallback> callback = new RetryingTransactionCallback>()
+ {
+ public List execute() throws Throwable
+ {
+ return fileFolderService.listFolders(contextNodeRef);
+ }
+ };
+ return retryingTransactionHelper.doInTransaction(callback, true, true);
+ }
+ finally
+ {
+ AuthenticationUtil.setCurrentAuthentication(authentication);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public NodeRef searchSimple(String ticket, final NodeRef contextNodeRef, final String name)
+ {
+ Authentication authentication = AuthenticationUtil.getCurrentAuthentication();
+ try
+ {
+ authenticationService.validate(ticket);
+ // Make the call
+ RetryingTransactionCallback callback = new RetryingTransactionCallback()
+ {
+ public NodeRef execute() throws Throwable
+ {
+ return fileFolderService.searchSimple(contextNodeRef, name);
+ }
+ };
+ return retryingTransactionHelper.doInTransaction(callback, true, true);
+ }
+ finally
+ {
+ AuthenticationUtil.setCurrentAuthentication(authentication);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List search(
+ String ticket,
+ final NodeRef contextNodeRef,
+ final String namePattern,
+ final boolean includeSubFolders)
+ {
+ Authentication authentication = AuthenticationUtil.getCurrentAuthentication();
+ try
+ {
+ authenticationService.validate(ticket);
+ // Make the call
+ RetryingTransactionCallback> callback = new RetryingTransactionCallback>()
+ {
+ public List execute() throws Throwable
+ {
+ return fileFolderService.search(contextNodeRef, namePattern, includeSubFolders);
+ }
+ };
+ return retryingTransactionHelper.doInTransaction(callback, true, true);
+ }
+ finally
+ {
+ AuthenticationUtil.setCurrentAuthentication(authentication);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List search(
+ String ticket,
+ final NodeRef contextNodeRef,
+ final String namePattern,
+ final boolean fileSearch,
+ final boolean folderSearch,
+ final boolean includeSubFolders)
+ {
+ Authentication authentication = AuthenticationUtil.getCurrentAuthentication();
+ try
+ {
+ authenticationService.validate(ticket);
+ // Make the call
+ RetryingTransactionCallback> callback = new RetryingTransactionCallback>()
+ {
+ public List execute() throws Throwable
+ {
+ return fileFolderService.search(contextNodeRef, namePattern, fileSearch, folderSearch, includeSubFolders);
+ }
+ };
+ return retryingTransactionHelper.doInTransaction(callback, true, true);
+ }
+ finally
+ {
+ AuthenticationUtil.setCurrentAuthentication(authentication);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public FileInfo rename(String ticket, final NodeRef fileFolderRef, final String newName) throws FileExistsException, FileNotFoundException
+ {
+ Authentication authentication = AuthenticationUtil.getCurrentAuthentication();
+ try
+ {
+ authenticationService.validate(ticket);
+ // Make the call
+ RetryingTransactionCallback callback = new RetryingTransactionCallback()
+ {
+ public FileInfo execute() throws Throwable
+ {
+ return fileFolderService.rename(fileFolderRef, newName);
+ }
+ };
+ return retryingTransactionHelper.doInTransaction(callback, false, true);
+ }
+ finally
+ {
+ AuthenticationUtil.setCurrentAuthentication(authentication);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public FileInfo move(String ticket, final NodeRef sourceNodeRef, final NodeRef targetParentRef, final String newName)
+ throws FileExistsException, FileNotFoundException
+ {
+ Authentication authentication = AuthenticationUtil.getCurrentAuthentication();
+ try
+ {
+ authenticationService.validate(ticket);
+ // Make the call
+ RetryingTransactionCallback callback = new RetryingTransactionCallback()
+ {
+ public FileInfo execute() throws Throwable
+ {
+ return fileFolderService.move(sourceNodeRef, targetParentRef, newName);
+ }
+ };
+ return retryingTransactionHelper.doInTransaction(callback, false, true);
+ }
+ finally
+ {
+ AuthenticationUtil.setCurrentAuthentication(authentication);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public FileInfo copy(String ticket, final NodeRef sourceNodeRef, final NodeRef targetParentRef, final String newName)
+ throws FileExistsException, FileNotFoundException
+ {
+ Authentication authentication = AuthenticationUtil.getCurrentAuthentication();
+ try
+ {
+ authenticationService.validate(ticket);
+ // Make the call
+ RetryingTransactionCallback callback = new RetryingTransactionCallback()
+ {
+ public FileInfo execute() throws Throwable
+ {
+ return fileFolderService.copy(sourceNodeRef, targetParentRef, newName);
+ }
+ };
+ return retryingTransactionHelper.doInTransaction(callback, false, true);
+ }
+ finally
+ {
+ AuthenticationUtil.setCurrentAuthentication(authentication);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public FileInfo create(String ticket, final NodeRef parentNodeRef, final String name, final QName typeQName) throws FileExistsException
+ {
+ Authentication authentication = AuthenticationUtil.getCurrentAuthentication();
+ try
+ {
+ authenticationService.validate(ticket);
+ // Make the call
+ RetryingTransactionCallback callback = new RetryingTransactionCallback()
+ {
+ public FileInfo execute() throws Throwable
+ {
+ return fileFolderService.create(parentNodeRef, name, typeQName);
+ }
+ };
+ return retryingTransactionHelper.doInTransaction(callback, false, true);
+ }
+ finally
+ {
+ AuthenticationUtil.setCurrentAuthentication(authentication);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void delete(String ticket, final NodeRef nodeRef)
+ {
+ Authentication authentication = AuthenticationUtil.getCurrentAuthentication();
+ try
+ {
+ authenticationService.validate(ticket);
+ // Make the call
+ RetryingTransactionCallback