diff --git a/libs/middletier/ArchiveRoute.hpp b/libs/middletier/ArchiveRoute.hpp
index 1c93c1e0ba22caf07901d9a216c8fd7c36ae6160..7cac25af331e8697e14a5fb49f2fb5eac139d6bc 100644
--- a/libs/middletier/ArchiveRoute.hpp
+++ b/libs/middletier/ArchiveRoute.hpp
@@ -24,7 +24,7 @@ public:
    *
    * @param storageClassName The name of the storage class that identifies the
    * source disk files.
-   * @param copyNb The tape copy number.
+   * @param copyNb The tape copy number.  Copy numbers start from 1.
    * @param tapePoolName The name of the destination tape pool.
    * @param creator The identity of the user that created the storage class.
    * @param comment Comment describing the storage class.
diff --git a/libs/middletier/FileSystemStorageClasses.cpp b/libs/middletier/FileSystemStorageClasses.cpp
index c303fb8579cc4654db5a01d8aeaac258e3964917..6b10a629bb2ea75914e13783343f96a20a64cdc4 100644
--- a/libs/middletier/FileSystemStorageClasses.cpp
+++ b/libs/middletier/FileSystemStorageClasses.cpp
@@ -97,6 +97,21 @@ std::list<cta::StorageClass> cta::FileSystemStorageClasses::getStorageClasses()
   return storageClasses;
 }
 
+//------------------------------------------------------------------------------
+// getStorageClass
+//------------------------------------------------------------------------------
+const cta::StorageClass &cta::FileSystemStorageClasses::getStorageClass(
+  const std::string &name) const {
+  std::map<std::string, FileSystemStorageClass>::const_iterator itor =
+    m_storageClasses.find(name);
+  if(itor == m_storageClasses.end()) {
+    std::ostringstream message;
+    message << "Storage class " << name << " does not exist";
+    throw Exception(message.str());
+  }
+  return itor->second.getStorageClass();
+}
+
 //------------------------------------------------------------------------------
 // incStorageClassUsageCount
 //------------------------------------------------------------------------------
diff --git a/libs/middletier/FileSystemStorageClasses.hpp b/libs/middletier/FileSystemStorageClasses.hpp
index 5a4fcff7a71d70e6e2f0fefa24897ab5b94b3d36..461dcd7e0ee83a31133091fb9c698f40f55a04f4 100644
--- a/libs/middletier/FileSystemStorageClasses.hpp
+++ b/libs/middletier/FileSystemStorageClasses.hpp
@@ -47,6 +47,14 @@ public:
    */
   std::list<StorageClass> getStorageClasses() const;
 
+  /**
+   * Returns the specified storage class.
+   *
+   * @param name The name of the storage class.
+   * @return The specified storage class.
+   */
+  const StorageClass &getStorageClass(const std::string &name) const;
+
   /**
    * Throws an exception if the specified storage class does not exist.
    *
diff --git a/libs/middletier/MockArchivalJobTable.cpp b/libs/middletier/MockArchivalJobTable.cpp
index 6f99653c4d6057231c359e3a16e18d14276029b1..32a1f9b5ebf645156992c95a68afe410bb12b97b 100644
--- a/libs/middletier/MockArchivalJobTable.cpp
+++ b/libs/middletier/MockArchivalJobTable.cpp
@@ -13,7 +13,8 @@ void cta::MockArchivalJobTable::createArchivalJob(
   const std::string &dstPath) {
   checkArchivalJobDoesNotAlreadyExist(dstPath);
 
-  ArchivalJob job(ArchivalJobState::NONE, srcUrl, dstPath, requester.user, time(NULL));
+  ArchivalJob job(ArchivalJobState::PENDING, srcUrl, dstPath, requester.user,
+    time(NULL));
 
   std::map<std::string, std::map<time_t, ArchivalJob> >::iterator poolItor =
     m_jobsTree.find(tapePoolName);
diff --git a/libs/middletier/MockArchiveRouteTable.cpp b/libs/middletier/MockArchiveRouteTable.cpp
index 06d5d7fade29daa03e6dbc4f656f536c3caca470..b0543efc63c1d7ca582fc7c4da11536ed4e38cf8 100644
--- a/libs/middletier/MockArchiveRouteTable.cpp
+++ b/libs/middletier/MockArchiveRouteTable.cpp
@@ -72,6 +72,24 @@ std::list<cta::ArchiveRoute> cta::MockArchiveRouteTable::
   return routes;
 }
 
+//------------------------------------------------------------------------------
+// getArchiveRoute
+//------------------------------------------------------------------------------
+const cta::ArchiveRoute &cta::MockArchiveRouteTable::getArchiveRoute(
+  const std::string &storageClassName,
+  const uint8_t copyNb) const {
+  const ArchiveRouteId routeId(storageClassName, copyNb);
+  std::map<ArchiveRouteId, ArchiveRoute>::const_iterator itor =
+    m_archiveRoutes.find(routeId);
+  if(itor == m_archiveRoutes.end()) {
+    std::ostringstream message;
+    message << "No archive route for storage class " << storageClassName <<
+      " copy number " << copyNb;
+    throw Exception(message.str());
+  }
+  return itor->second;
+}
+
 //------------------------------------------------------------------------------
 // checkArchiveRouteExists
 //------------------------------------------------------------------------------
diff --git a/libs/middletier/MockArchiveRouteTable.hpp b/libs/middletier/MockArchiveRouteTable.hpp
index 14577e792ebc99fb5ad69a94fb13e6de7a690ce1..f863a82087d95a191fd5d404fd5db94cef0c9bc2 100644
--- a/libs/middletier/MockArchiveRouteTable.hpp
+++ b/libs/middletier/MockArchiveRouteTable.hpp
@@ -51,6 +51,18 @@ public:
    */
   std::list<ArchiveRoute> getArchiveRoutes() const;
 
+  /**
+   * Returns the specified archive route.
+   *
+   * @param storageClassName The name of the storage class that identifies the
+   * source disk files.
+   * @param copyNb The tape copy number.
+   * @return The specified archive route.
+   */
+  const ArchiveRoute &getArchiveRoute(
+    const std::string &storageClassName,
+    const uint8_t copyNb) const;
+
   /**
    * Throws an exception if the specified archive route does not exist.
    *
diff --git a/libs/middletier/MockMiddleTierUser.cpp b/libs/middletier/MockMiddleTierUser.cpp
index 67f8856b31ce222f3620c0607c2a8ec1b14d1c69..ec8f4669327d69255189389f3a11ed85f11b6268 100644
--- a/libs/middletier/MockMiddleTierUser.cpp
+++ b/libs/middletier/MockMiddleTierUser.cpp
@@ -259,12 +259,41 @@ void cta::MockMiddleTierUser::archiveToDirectory(
 
   const std::string inheritedStorageClassName = dstDirNode.getFileSystemEntry().
     getEntry().getStorageClassName();
+  if(inheritedStorageClassName.empty()) {
+    std::ostringstream message;
+    message << "Directory " << inheritedStorageClassName << " does not have a "
+      "storage class";
+    throw Exception(message.str());
+  }
+
+  const StorageClass storageClass = m_db.storageClasses.getStorageClass(
+    inheritedStorageClassName);
+  if(0 == storageClass.getNbCopies()) {
+    std::ostringstream message;
+    message << "Storage class " << inheritedStorageClassName << " of directory "
+      << dstDir << " has zero tape copies";
+    throw Exception(message.str());
+  }
+
+  checkStorageClassIsFullyRouted(storageClass);
+
   const std::list<std::string> dstFileNames = Utils::getEnclosedNames(srcUrls);
   checkDirNodeDoesNotContainFiles(dstDir, dstDirNode, dstFileNames);
 
-  for(std::list<std::string>::const_iterator itor = dstFileNames.begin();
-    itor != dstFileNames.end(); itor++) {
-    const std::string &dstFileName = *itor;
+  std::list<std::string>::const_iterator srcItor = srcUrls.begin();
+  for(std::list<std::string>::const_iterator dstItor = dstFileNames.begin();
+    dstItor != dstFileNames.end(); srcItor++, dstItor++) {
+    const std::string &srcUrl = *srcItor;
+    const std::string &dstFileName = *dstItor;
+    const std::string dstPath = dstDir + dstFileName;
+
+    for(uint8_t copyNb = 1; copyNb <= storageClass.getNbCopies(); copyNb++) {
+      const ArchiveRoute &route = m_db.archiveRoutes.getArchiveRoute(
+        inheritedStorageClassName, copyNb);
+      m_db.archivalJobs.createArchivalJob(requester, route.getTapePoolName(),
+        srcUrl, dstPath);
+    }
+
     DirectoryEntry dirEntry(DirectoryEntry::ENTRYTYPE_FILE, dstFileName,
       inheritedStorageClassName);
     dstDirNode.addChild(new FileSystemNode(m_db.storageClasses, dirEntry));
@@ -289,6 +318,17 @@ void cta::MockMiddleTierUser::checkDirNodeDoesNotContainFiles(
   }
 }
 
+//------------------------------------------------------------------------------
+// checkStorageClassIsFullyRouted
+//------------------------------------------------------------------------------
+void cta::MockMiddleTierUser::checkStorageClassIsFullyRouted(
+  const StorageClass &storageClass) const {
+  for(uint8_t copyNb = 1; copyNb <= storageClass.getNbCopies(); copyNb++) {
+    m_db.archiveRoutes.checkArchiveRouteExists(storageClass.getName(),
+      copyNb);
+  }
+}
+
 //------------------------------------------------------------------------------
 // archiveToFile
 //------------------------------------------------------------------------------
@@ -314,6 +354,33 @@ void cta::MockMiddleTierUser::archiveToFile(
 
   const std::string inheritedStorageClassName =
     enclosingNode.getFileSystemEntry().getEntry().getStorageClassName();
+
+  if(inheritedStorageClassName.empty()) {
+    std::ostringstream message;
+    message << "Directory " << inheritedStorageClassName << " does not have a "
+      "storage class";
+    throw Exception(message.str());
+  }
+
+  const StorageClass &storageClass = m_db.storageClasses.getStorageClass(
+    inheritedStorageClassName);
+  if(0 == storageClass.getNbCopies()) {
+    std::ostringstream message;
+    message << "Storage class " << inheritedStorageClassName << " of directory "
+      << enclosingDir << " has zero tape copies";
+    throw Exception(message.str());
+  }
+
+  checkStorageClassIsFullyRouted(storageClass);
+
+  const std::string &srcUrl = srcUrls.front();
+  for(uint8_t copyNb = 1; copyNb <= storageClass.getNbCopies(); copyNb++) {
+    const ArchiveRoute &route = m_db.archiveRoutes.getArchiveRoute(
+      inheritedStorageClassName, copyNb);
+    m_db.archivalJobs.createArchivalJob(requester, route.getTapePoolName(),
+      srcUrl, dstFile);
+  }
+
   DirectoryEntry dirEntry(DirectoryEntry::ENTRYTYPE_FILE, enclosedName,
     inheritedStorageClassName);
   enclosingNode.addChild(new FileSystemNode(m_db.storageClasses, dirEntry));
@@ -334,18 +401,29 @@ void cta::MockMiddleTierUser::checkUserIsAuthorisedToArchive(
 std::map<cta::TapePool, std::list<cta::ArchivalJob> >
   cta::MockMiddleTierUser::getArchivalJobs(
   const SecurityIdentity &requester) const {
-  const std::map<std::string, std::map<time_t, cta::ArchivalJob> >
+  const std::map<std::string, std::map<time_t, ArchivalJob> >
    jobsByPoolName = m_db.archivalJobs.getArchivalJobs(requester);
 
-  std::map<cta::TapePool, std::list<cta::ArchivalJob> > jobs;
-
-  for(std::map<std::string, std::map<time_t, cta::ArchivalJob> >::const_iterator
-    byPoolNameItor = jobsByPoolName.begin();
-    byPoolNameItor != jobsByPoolName.end();
-    byPoolNameItor++) {
+  std::map<TapePool, std::list<ArchivalJob> > allJobs;
+
+  for(std::map<std::string, std::map<time_t, ArchivalJob> >::const_iterator
+    poolItor = jobsByPoolName.begin();
+    poolItor != jobsByPoolName.end();
+    poolItor++) {
+    const std::string &tapePoolName = poolItor->first;
+    const std::map<time_t, ArchivalJob> &timeToJobMap = poolItor->second;
+    if(!timeToJobMap.empty()) {
+      const TapePool tapePool = m_db.tapePools.getTapePool(tapePoolName);
+      std::list<cta::ArchivalJob> poolJobs;
+      for(std::map<time_t, ArchivalJob>::const_iterator jobItor =
+        timeToJobMap.begin(); jobItor != timeToJobMap.end(); jobItor++) {
+        poolJobs.push_back(jobItor->second);
+      }
+      allJobs[tapePool] = poolJobs;
+    }
   }
 
-  return jobs;
+  return allJobs;
 }
 
 //------------------------------------------------------------------------------
diff --git a/libs/middletier/MockMiddleTierUser.hpp b/libs/middletier/MockMiddleTierUser.hpp
index 12ce3a08ec6ae5c75cc321627df74319db2913c5..6a1dc907c9ae608aaab49daae496b267daed6270 100644
--- a/libs/middletier/MockMiddleTierUser.hpp
+++ b/libs/middletier/MockMiddleTierUser.hpp
@@ -276,6 +276,13 @@ private:
     const std::list<std::string> &srcUrls,
     const std::string &dstDir);
 
+  /**
+   * Throws an exception if the specified storage class is not fully routed.
+   *
+   * @param storageClass The storage class;
+   */
+  void checkStorageClassIsFullyRouted(const StorageClass &storageClass) const;
+
   /**
    * Archives the specified list of source files to the specified destination
    * file within the archive namespace.
@@ -313,10 +320,10 @@ private:
    * @param dirNode The file-system node representing the directory.
    * @param fileNames The file names to be searched for.
    */
-void checkDirNodeDoesNotContainFiles(
-  const std::string &dirPath,
-  const FileSystemNode &dirNode, 
-  const std::list<std::string> &fileNames);
+  void checkDirNodeDoesNotContainFiles(
+    const std::string &dirPath,
+    const FileSystemNode &dirNode, 
+    const std::list<std::string> &fileNames);
 
 }; // class MockMiddleTierUser
 
diff --git a/libs/middletier/MockTapePoolTable.cpp b/libs/middletier/MockTapePoolTable.cpp
index d38bb9cc8bfbe2283af2d1403cb7a921af66ba79..3a0e17bf48050b59cc5eb48c6e237d09ea18ff06 100644
--- a/libs/middletier/MockTapePoolTable.cpp
+++ b/libs/middletier/MockTapePoolTable.cpp
@@ -72,3 +72,17 @@ std::list<cta::TapePool> cta::MockTapePoolTable::getTapePools(
   }
   return tapePools;
 }
+
+//------------------------------------------------------------------------------
+// getTapePool
+//------------------------------------------------------------------------------
+const cta::TapePool &cta::MockTapePoolTable::getTapePool(
+  const std::string &name) const {
+  std::map<std::string, TapePool>::const_iterator itor = m_tapePools.find(name);
+  if(itor == m_tapePools.end()) {
+    std::ostringstream message;
+    message << "Tape pool " << name << " does not exist";
+    throw(Exception(message.str()));
+  }
+  return itor->second;
+}
diff --git a/libs/middletier/MockTapePoolTable.hpp b/libs/middletier/MockTapePoolTable.hpp
index e15e51ca33b4ed30f8d4de0d93edc47e292e715f..e280299348f5c86442e71922dce930df5f7b55ef 100644
--- a/libs/middletier/MockTapePoolTable.hpp
+++ b/libs/middletier/MockTapePoolTable.hpp
@@ -69,6 +69,14 @@ public:
   std::list<TapePool> getTapePools(
     const SecurityIdentity &requester) const;
 
+  /**
+   * Returns the specified tape pool.
+   *
+   * @param name The name of the tape pool.
+   * @return The specified tape pool.
+   */
+  const TapePool &getTapePool(const std::string &name) const;
+
 private:
 
   /**
diff --git a/libs/middletier/TapePool.cpp b/libs/middletier/TapePool.cpp
index 307672fc75a8e99888ab2c5797ed9543ca9b9154..244d8b0fdac65c2cea970cebf2c581693d3e98c9 100644
--- a/libs/middletier/TapePool.cpp
+++ b/libs/middletier/TapePool.cpp
@@ -27,6 +27,13 @@ cta::TapePool::TapePool(
   m_comment(comment) {
 }
 
+//------------------------------------------------------------------------------
+// operator<
+//------------------------------------------------------------------------------
+bool cta::TapePool::operator<(const TapePool &rhs) const throw() {
+  return m_name < rhs.m_name;
+}
+
 //------------------------------------------------------------------------------
 // getName
 //------------------------------------------------------------------------------
diff --git a/libs/middletier/TapePool.hpp b/libs/middletier/TapePool.hpp
index 8c23c0487e12b36e21fa1cb03f89747984c8e10a..4bc122b19c8eb9e5012a3dd3737b4d25fe54f782 100644
--- a/libs/middletier/TapePool.hpp
+++ b/libs/middletier/TapePool.hpp
@@ -37,6 +37,13 @@ public:
     const time_t creationTime,
     const std::string &comment);
 
+  /**
+   * Less than operator.
+   *
+   * @param rhs The right-hand side of the operator.
+   */
+  bool operator<(const TapePool &rhs) const throw();
+
   /**
    * Returns the name of the tape pool.
    *