diff --git a/config/vufind/MultiBackend.ini b/config/vufind/MultiBackend.ini
new file mode 100644
index 0000000000000000000000000000000000000000..28356f953ba37105bc7200e2b4cbe505f2d45bc4
--- /dev/null
+++ b/config/vufind/MultiBackend.ini
@@ -0,0 +1,42 @@
+[General]
+; This setting controls the MultiBackend driver's behavior when it can't
+; determine which specific driver should be used to service a particular
+; request. Select "use_first" if you would like MultiBackend to return
+; the value generated by the first driver capable of responding to the
+; specified method; select "merge" if you would like all potentially
+; relevant responses to be combined into a single response (only works
+; for array-oriented data).
+default_fallback_driver_selection = use_first
+
+; (Optional) The name of a driver instance to use by default if no specific
+; instance can be determined as the best option (must correspond with a key
+; from the [Drivers] section below if set -- omit to have no default driver)
+;default_driver = "instance1"
+
+; This section is for declaring which driver to use for each institution.
+; The key should be the Source ID of a specific institution, and the value
+; should be the name of an ILS driver.
+;      Example: instance1 = Voyager
+; In this case, the Voyager driver would be loaded and configured using an
+; instance1.ini file (which you should create as a copy of Voyager.ini).
+[Drivers]
+;instance1 = Voyager
+;instance2 = Voyager
+;instance3 = Unicorn
+;instance4 = Voyager
+
+; This section allows for the use of custom delimeters between user data
+; and the identifying information that allows the MultiBackend driver to
+; determine which internal driver to use for a given request.
+;
+; Currently only custom login delimiters are supported.
+;
+; You should not set the delimiters to be common characters.
+[Delimiters]
+login = "	"
+
+; This section allows you to override the default functionality set by
+; default_fallback_driver_selection on a method-by-method basis. See above
+; for explanations of legal values (merge/use_first)
+[FallbackDriverSelectionOverride]
+;getMyFines = merge
diff --git a/module/VuFind/src/VuFind/ILS/Driver/MultiBackend.php b/module/VuFind/src/VuFind/ILS/Driver/MultiBackend.php
new file mode 100644
index 0000000000000000000000000000000000000000..615e6ba3b674e121975a978c2daaff1bedf99d52
--- /dev/null
+++ b/module/VuFind/src/VuFind/ILS/Driver/MultiBackend.php
@@ -0,0 +1,708 @@
+<?php
+/**
+ * Multiple Backend Driver.
+ *
+ * PHP version 5
+ *
+ * Copyright (C) The National Library of Finland 2012.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind
+ * @package  ILSdrivers
+ * @author   Ere Maijala <ere.maijala@helsinki.fi>
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/building_an_ils_driver Wiki
+ */
+namespace VuFind\ILS\Driver;
+
+use VuFind\Config\Reader as ConfigReader,
+    VuFind\Exception\ILS as ILSException,
+    Zend\ServiceManager\ServiceLocatorAwareInterface,
+    Zend\ServiceManager\ServiceLocatorInterface;
+
+/**
+ * Multiple Backend Driver.
+ *
+ * This driver allows to use multiple backends determined by a record id or
+ * user id prefix (e.g. source.12345).
+ *
+ * @category VuFind
+ * @package  ILSdrivers
+ * @author   Ere Maijala <ere.maijala@helsinki.fi>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/building_an_ils_driver Wiki
+ */
+class MultiBackend extends AbstractBase implements ServiceLocatorAwareInterface
+{
+    /**
+     * The serviceLocator instance (implementing ServiceLocatorAwareInterface).
+     *
+     * @var object
+     */
+    protected $serviceLocator;
+
+    /**
+     * The array of configured driver names.
+     *
+     * @var string[]
+     */
+    protected $drivers = array();
+
+    /**
+     * The default driver to use
+     *
+     * @var string
+     */
+    protected $defaultDriver;
+
+    /**
+     * The array of cached pre-instantiated drivers
+     *
+     * @var object[]
+     */
+     protected $cache = array();
+
+    /**
+     * The array of booleans letting us know if a
+     * driver in the cache has been initialized.
+     *
+     * @var boolean[]
+     */
+     protected $isInitialized = array();
+
+    /**
+     * The array of driver configuration options.
+     *
+     * @var string[]
+     */
+    protected $config = array();
+
+
+    /**
+     * The seperating values to be used for each ILS.
+     * Not yet implemented
+     * @var object
+     */
+    protected $delimiters = array();
+
+
+    /**
+     * Set the driver configuration.
+     *
+     * @param Config $config The configuration to be set
+     *
+     * @return void
+     */
+    public function setConfig($config)
+    {
+        $this->config = $config;
+    }
+
+    /**
+     * Initialize the driver.
+     *
+     * Validate configuration and perform all resource-intensive tasks needed to
+     * make the driver active.
+     *
+     * @throws ILSException
+     * @return void
+     */
+    public function init()
+    {
+        if (empty($this->config)) {
+            throw new ILSException('Configuration needs to be set.');
+        }
+        $this->drivers = $this->config['Drivers'];
+        $this->defaultDriver = isset($this->config['General']['default_driver'])?
+            $this->config['General']['default_driver']:
+            null;
+        $this->delimiters['login']
+            = (isset($this->config['Delimiters']['login']) ?
+                   $this->config['Delimiters']['login'] :
+                   '   ');
+        $this->getDriverConfig($this->defaultDriver);
+    }
+
+
+    /**
+     * Set the service locator.
+     *
+     * @param ServiceLocatorInterface $serviceLocator Locator to register
+     *
+     * @return Manager
+     */
+    public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
+    {
+        $this->serviceLocator = $serviceLocator;
+        return $this;
+    }
+
+    /**
+     * Get the service locator.
+     *
+     * @return \Zend\ServiceManager\ServiceLocatorInterface
+     */
+    public function getServiceLocator()
+    {
+        return $this->serviceLocator;
+    }
+
+    /**
+     * Extract local ID from the given prefixed ID
+     *
+     * @param string $id        The id to be split
+     * @param string $delimiter The delimiter to be used
+     *
+     * @return string  Local ID
+     */
+    protected function getLocalId($id, $delimiter = '.')
+    {
+        $pos = strrpos($id, $delimiter);
+        if ($pos > 0) {
+            return substr($id, $pos + 1);
+        }
+        //error_log("MultiBackend: Can't find local id in '$id'");
+        return $id;
+    }
+
+    /**
+     * Extract source from the given ID
+     *
+     * @param string $id        The id to be split
+     * @param string $delimiter The delimiter to be used
+     *
+     * @return string  Source
+     */
+    protected function getSource($id, $delimiter = '.')
+    {
+        $pos = strrpos($id, $delimiter);
+        if ($pos > 0) {
+            return substr($id, 0, $pos);
+        }
+        //error_log("MultiBackend: Can't find source id in '$id' using '$delimiter'");
+        return $id;
+    }
+
+    /**
+     * Find the correct driver for the correct configuration file for the
+     * given source and cache an initialized copy of it.
+     *
+     * @param string $source The source name of the driver to get.
+     *
+     * @return mixed  On success a driver object, otherwise null.
+     */
+    protected function getDriver($source)
+    {
+        if (!isset($this->isInitialized[$source])
+            || !$this->isInitialized[$source]
+        ) {
+            $driverInst = null;
+
+            // And we don't have a copy in our cache...
+            if (!isset($this->cache[$source])) {
+                // Get an uninitialized copy
+                $driverInst = $this->getUninitializedDriver($source);
+            } else {
+                // Otherwise, use the uninitialized cached copy
+                $driverInst = $this->cache[$source];
+            }
+
+            // If we have a driver, initialize it.  That version has already
+            // been cached.
+            if ($driverInst) {
+                $this->initializeDriver($driverInst, $source);
+            } else {
+                return null;
+            }
+        }
+        return $this->cache[$source];
+    }
+
+    /**
+     * Find the correct driver for the correct configuration file
+     * for the given source.  For performance reasons, we do not
+     * want to initialize the driver yet if it hasn't been already.
+     *
+     * @param string $source the source title for the driver.
+     *
+     * @return mixed On success an unintiialized driver object, otherwise null.
+     */
+    protected function getUninitializedDriver($source)
+    {
+        $source = strtolower($source);
+
+        // We don't really care if it's initialized here.  If it is, then there's
+        // still no added overhead of returning an initialized driver.
+        if (isset($this->cache[$source])) {
+            return $this->cache[$source];
+        }
+
+        if (isset($this->drivers[$source])) {
+            $driver = $this->drivers[$source];
+            $config = $this->getDriverConfig($source);
+            try
+            {
+                $driverInst = $this->getServiceLocator()->get($driver);
+                $driverInst->setConfig($config);
+                $this->cache[$source] = $driverInst;
+                $this->isInitialized[$source] = false;
+                return $driverInst;
+            } catch (Exception $e) {
+                $msg = "MultiBackend: error initializing driver '$driver': ";
+                $msg = $msg . $e->__toString();
+                //error_log($msg);
+                return null;
+            }
+        } else {
+            //error_log("$source is not in drivers[]");
+        }
+        return null;
+    }
+
+    /**
+     * Initialize an uninitialized driver.
+     *
+     * @param object $driver The driver object to be initialized
+     * @param string $source The source related to the driver for caching purposes.
+     *
+     * @return no returns, getting an error without this comment though.
+     */
+    protected function initializeDriver($driver, $source)
+    {
+        if (!isset($this->isInitialized[$source])
+            || !$this->isInitialized[$source]
+        ) {
+            try
+            {
+                $driver->init();
+                $this->isInitialized[$source] = true;
+                $this->cache[$source] = $driver;
+            } catch (Exception $e) {
+                $msg = "MultiBackend: error initializing driver '$driver': ";
+                $msg = $msg . $e->__toString();
+                //error_log($msg);
+            }
+        }
+    }
+
+    /**
+     * Get configuration for the ILS driver.  We will load an .ini file named
+     * after the driver class and number if it exists;
+     * otherwise we will return an empty array.
+     *
+     * @param string $source The source id to use for determining the
+     * configuration file
+     *
+     * @return array   The configuration of the driver
+     */
+    protected function getDriverConfig($source)
+    {
+        // Determine config file name based on class name:
+        try {
+            $config = ConfigReader::getConfig($source);
+        } catch (\Zend\Config\Exception\RuntimeException $e) {
+            // Configuration loading failed; probably means file does not
+            // exist -- just return an empty array in that case:
+            return array();
+        }
+        return $config->toArray();
+    }
+
+    /**
+     * Change local ID's to global ID's in the given array
+     *
+     * @param mixed  $data         The data to be modified, normally
+     * array or array of arrays
+     * @param string $source       Source code
+     * @param array  $modifyFields Fields to be modified in the array
+     *
+     * @return mixed     Modified array or empty/null if that input was
+     *                   empty/null
+     */
+    protected function addIdPrefixes($data, $source,
+        $modifyFields = array('id', 'cat_username')
+    ) {
+
+        if (!isset($data) || empty($data) ) {
+            return $data;
+        }
+        $array = is_array($data) ? $data : array($data);
+
+        foreach ($array as $key => $value) {
+            if (is_array($value)) {
+                $array[$key] = $this->addIdPrefixes(
+                    $value, $source, $modifyFields
+                );
+            } else {
+                if (in_array($key, $modifyFields)) {
+                    $array[$key] = $source . '.' . $value;
+                }
+            }
+        }
+        return is_array($data) ? $array : $array[0];
+    }
+
+    /**
+    * Change global ID's to local ID's in the given array
+    *
+    * @param mixed  $data         The data to be modified, normally
+    * array or array of arrays
+    * @param string $source       Source code
+    * @param array  $modifyFields Fields to be modified in the array
+    *
+    * @return mixed     Modified array or empty/null if that input was
+    *                   empty/null
+    */
+    protected function stripIdPrefixes($data, $source,
+        $modifyFields = array('id', 'cat_username')
+    ) {
+
+        if (!isset($data) || empty($data)) {
+            return $data;
+        }
+        $array = is_array($data) ? $data : array($data);
+
+        foreach ($array as $key => $value) {
+            if (is_array($value)) {
+                $array[$key] = $this->stripIdPrefixes(
+                    $value, $source, $modifyFields
+                );
+            } else {
+                if (in_array($key, $modifyFields)
+                    && strncmp($source . '.', $value, strlen($source) + 1) == 0
+                ) {
+                        $array[$key] = substr($value, strlen($source) + 1);
+                }
+            }
+        }
+        return is_array($data) ? $array : $array[0];
+    }
+
+    /**
+     * Get Status
+     *
+     * This is responsible for retrieving the status information of a certain
+     * record.
+     *
+     * @param string $id The record id to retrieve the holdings for
+     *
+     * @return mixed     On success, an associative array with the following keys:
+     * id, availability (boolean), status, location, reserve, callnumber; on
+     * failure, a PEAR_Error.
+     * @access public
+     */
+    public function getStatus($id)
+    {
+        return $this->getHolding($id);
+    }
+
+    /**
+     * Get Statuses
+     *
+     * This is responsible for retrieving the status information for a
+     * collection of records.
+     *
+     * @param array $ids The array of record ids to retrieve the status for
+     *
+     * @return mixed     An array of getStatus() return values on success,
+     * a PEAR_Error object otherwise.
+     * @access public
+     */
+    public function getStatuses($ids)
+    {
+        $items = array();
+        foreach ($ids as $id) {
+            $items[] = $this->getHolding($id);
+        }
+        return $items;
+    }
+
+    /**
+     * Get Holding
+     *
+     * This is responsible for retrieving the holding information of a certain
+     * record.
+     *
+     * @param string $id     The record id to retrieve the holdings for
+     * @param array  $patron Patron data
+     *
+     * @return mixed     On success, an associative array with the following keys:
+     * id, availability (boolean), status, location, reserve, callnumber, duedate,
+     * number, barcode; on failure, a PEAR_Error.
+     * @access public
+     */
+    public function getHolding($id, $patron = false)
+    {
+        $source = $this->getSource($id);
+        $driver = $this->getDriver($source);
+        if ($driver) {
+            $holdings = $driver->getHolding($this->getLocalId($id), $patron);
+            if ($holdings) {
+                return $this->addIdPrefixes($holdings, $source);
+            }
+        } else {
+            //error_log("No driver for '$id' found-");
+        }
+        return Array();
+    }
+
+    /**
+     * Get Purchase History
+     *
+     * This is responsible for retrieving the acquisitions history data for the
+     * specific record (usually recently received issues of a serial).
+     *
+     * @param string $id The record id to retrieve the info for
+     *
+     * @return mixed     An array with the acquisitions data on success, PEAR_Error
+     * on failure
+     * @access public
+     */
+    public function getPurchaseHistory($id)
+    {
+        $source = $this->getSource($id);
+        $driver = $this->getDriver($source);
+        if ($driver) {
+            return $driver->getPurchaseHistory($this->getLocalId($id));
+        }
+        return null;
+    }
+
+    /**
+     * patronLogin function.  This function handles patron logins in a multiple
+     * backend environment by looping through all available driver configurations
+     * until it finds one that allows the login, then returns the login information
+     * supplied by that ILS.
+     *
+     * @param string $username The username to log in with
+     * @param string $password The password to log in with
+     *
+     * @return mixed   Associative array of patron info on successful login,
+     *                 null on unsuccessful login.
+     */
+    public function patronLogin($username, $password)
+    {
+
+        $pos = strrpos($username, $this->delimiters['login']);
+        $login = null;
+        if ($pos > 0) {
+            $key = $this->getSource($username, $this->delimiters['login']);
+            $user = $this->getLocalID($username, $this->delimiters['login']);
+            $login = $this->getDriver($key)->patronLogin($user, $password);
+            return $login;
+        }
+        foreach ($this->drivers as $key => $driver) {
+            $login =  $this->getDriver($key)->patronLogin($username, $password);
+            if ($login) {
+                $login['cat_username']
+                    = $key.$this->delimiters['login'].$login['cat_username'];
+                return $login;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Function developed to reduce code duplication in supportsMethod() and __call()
+     *
+     * @param array $params Array of passed parameters
+     *
+     * @return mixed Finds the driver instance associated with a pre-indexed catalog.
+     *               Null if cat_username is not pre-indexed with the catalog name.
+     */
+    protected function getInstanceFromParams($params)
+    {
+        if (isset($params[0]["cat_username"])) {
+            $instName = $this->getSource(
+                $params[0]["cat_username"],
+                $this->delimiters['login']
+            );
+            if (strlen($params[0]["cat_username"])>strlen($instName)) {
+                return $instName;
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * Helper method to determine whether or not a certain method can be
+     * called on this driver.  Required method for any smart drivers.
+     *
+     * @param string $method The name of the called method.
+     * @param array  $params Array of passed parameters
+     *
+     * @return boolean  True if the method can be called with the given
+     *                  parameters, false otherwise.
+     */
+    public function supportsMethod($method, $params)
+    {
+        //First we see if we can determine what instance the user is connected with
+        $instance = $this->getInstanceFromParams($params);
+        if ($instance) {
+            $driverInst = $this->getUninitializedDriver($instance);
+            return  is_callable(array($driverInst, $method));
+        }
+
+        //Falling back, we try to use a default driver if it's set
+        $instance = $this->defaultDriver;
+        if ($instance) {
+            $driverInst = $this->getUninitializedDriver($instance);
+            return is_callable(array($driver, $method));
+        }
+
+        //Lastly, we see if any of the drivers we have support the function
+        foreach ($this->drivers as $key => $driver) {
+            $driverInst = $this->getUninitializedDriver($key);
+            if (is_callable(array($driverInst, $method))) {
+                  return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * This method runs a given method on a given driver instance with the given
+     * params.
+     *
+     * @param string    $instName   The name of the driver instance to use.
+     * @param string    $methodName The name of the method to be called
+     * @param array     $params     Array of passed parameters
+     * @param reference &$called    A reference to a passed in boolean to determine
+     *                              if the function was actually called and returned
+     *                              false, or if it just didn't run.
+     *
+     * @return boolean  False if the method could not be run, and the return
+     *                  of the method if it could be.
+     */
+    protected function runIfPossible($instName, $methodName, $params, &$called)
+    {
+        if ($instName) {
+            $driverInst = $this->getUninitializedDriver($instName);
+            if (is_callable(array($driverInst, $methodName))) {
+                $this->initializeDriver($driverInst, $instName);
+                $funcReturn =  call_user_func_array(
+                    array($driverInst, $methodName), $params
+                );
+
+                // Because things like getMyFines return false if you have no fines,
+                // we need a different way of knowing if the function was called.
+                $called = true;
+                return $funcReturn;
+            }
+        }
+        $called = false;
+        return $called;
+    }
+
+    /**
+     * Determine what behavior a method should have if we are unable to determine
+     * a specific ILS to associate with it.
+     *
+     * @param string $methodName The name of the method to be called
+     *
+     * @return string  The behavior to be used.
+     */
+    protected function getMethodBehavior($methodName)
+    {
+        $var = 'default_fallback_driver_selection';
+        $default = isset($this->config['General'][$var])?
+                        $this->config['General'][$var] :
+                        "use_first";
+        $section = 'FallbackDriverSelectionOverride';
+        return isset($this->config[$section][$methodName])?
+                        $this->config[$section][$methodName] :
+                        $default;
+    }
+
+
+    /**
+     * A method to run a given method if we are unable to determine an ILS driver to
+     * associate with the method call.
+     *
+     * @param string    $methodName The name of the method to be called
+     * @param array     $params     Array of passed parameters
+     * @param reference &$called    A reference to a passed in boolean to determine
+     *                              if the function was actually called and returned
+     *                              false, or if it just didn't run.
+     *
+     * @return boolean  False if the method could not be run, and the return
+     *                  of the method if it could be.
+     */
+    protected function runMethodNoILS($methodName, $params, &$called)
+    {
+        $behavior = $this->getMethodBehavior($methodName);
+        $funcWasCalled = false;
+        $returnArray = array();
+        // Here we loop through evry instance we have access to and change what
+        // we do based off of the configuration behavior.
+        foreach ($this->drivers as $key => $driver) {
+            $funcReturn = $this->runIfPossible($key, $methodName, $params, $called);
+            if ($called) {
+                if ($behavior == "use_first" || !is_array($funcReturn)) {
+                    return $funcReturn;
+                } else if ($behavior == "merge") {
+                    $funcWasCalled = true;
+                    $returnArray = array_merge($returnArray, $funcReturn);
+                }
+            }
+            $called = false;
+        }
+
+        //We only return something if we were able to call this method on an ILS,
+        //Otherwise it should be handled like an uncallable function below.
+        if ($funcWasCalled) {
+            $called = true;
+            return $returnArray;
+        }
+        return false;
+    }
+    /**
+     * Default method -- pass along calls to the driver if available; return
+     * false otherwise.  This allows custom functions to be implemented in
+     * the driver without constant modification to the MultiBackend class.
+     *
+     * @param string $methodName The name of the called method.
+     * @param array  $params     Array of passed parameters.
+     *
+     * @return mixed             Varies by method (false if undefined method)
+     */
+    public function __call($methodName, $params)
+    {
+        $called = false;
+        //Try for the driver associated with the user
+        $instName = $this->getInstanceFromParams($params);
+        $funcReturn = $this->runIfPossible($instName, $methodName, $params, $called);
+        if ($called) {
+            return $funcReturn;
+        }
+
+        //Get the driver associated with the current instance
+        $instName = $this->defaultDriver;
+        $funcReturn = $this->runIfPossible($instName, $methodName, $params, $called);
+        if ($called) {
+            return $funcReturn;
+        }
+
+        $funcReturn = $this->runMethodNoILS($methodName, $params, $called);
+        if ($called) {
+            return $funcReturn;
+        }
+        throw new ILSException('Cannot call method: ' . $methodName);
+    }
+}
+