diff --git a/src/python/dm/cat_web_service/__init__.py b/src/python/dm/cat_web_service/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..bdbb0843b9f02b7cb5042f7899110655e2a03b60
--- /dev/null
+++ b/src/python/dm/cat_web_service/__init__.py
@@ -0,0 +1 @@
+__version__ = "Development Snapshot"
diff --git a/src/python/dm/cat_web_service/api/__init__.py b/src/python/dm/cat_web_service/api/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..bdbb0843b9f02b7cb5042f7899110655e2a03b60
--- /dev/null
+++ b/src/python/dm/cat_web_service/api/__init__.py
@@ -0,0 +1 @@
+__version__ = "Development Snapshot"
diff --git a/src/python/dm/cat_web_service/api/catRestApi.py b/src/python/dm/cat_web_service/api/catRestApi.py
new file mode 100755
index 0000000000000000000000000000000000000000..ee18415afa04dd26541f549e59e59f82427ec7dc
--- /dev/null
+++ b/src/python/dm/cat_web_service/api/catRestApi.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+from dm.common.api.dmRestApi import DmRestApi
+from dm.common.utility.configurationManager import ConfigurationManager
+
+class CatRestApi(DmRestApi):
+    """ Base CAT DM REST api class. """
+
+    def __init__(self, username=None, password=None, host=None, port=None, protocol=None):
+        if host == None:
+            host = ConfigurationManager.getInstance().getCatWebServiceHost()
+        if port == None:
+            port = ConfigurationManager.getInstance().getCatWebServicePort()
+        DmRestApi.__init__(self, username, password, host, port, protocol)
+
+#######################################################################
+# Testing.
+
+if __name__ == '__main__':
+    pass
+
+
diff --git a/src/python/dm/cat_web_service/api/catRestApiFactory.py b/src/python/dm/cat_web_service/api/catRestApiFactory.py
new file mode 100755
index 0000000000000000000000000000000000000000..b578e11ef57a3b6b9e9e7423c2beea78ad3b8245
--- /dev/null
+++ b/src/python/dm/cat_web_service/api/catRestApiFactory.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+
+from dm.common.utility.loggingManager import LoggingManager
+from dm.common.utility.configurationManager import ConfigurationManager
+
+class CatRestApiFactory:
+
+    CONFIG_SECTION_NAME = 'CatRestApiFactory'
+    USERNAME_KEY = 'username'
+    PASSWORD_FILE_KEY = 'passwordfile'
+    HOST_KEY = 'host'
+    PORT_KEY = 'port'
+    PROTOCOL_KEY = 'protocol'
+
+    __logger = None
+    __username = None
+    __password = None
+    __host = None
+    __port = None
+    __protocol = None
+
+    @classmethod
+    def getLogger(cls):
+        if cls.__logger is None:
+            cls.__logger = LoggingManager.getInstance().getLogger(cls.__name__)
+        return cls.__logger
+
+    @classmethod
+    def __getConfiguration(cls):
+        if cls.__username is None:
+            cls.__username = ConfigurationManager.getInstance().getConfigOption(cls.CONFIG_SECTION_NAME, cls.USERNAME_KEY)
+            cls.__password = open(ConfigurationManager.getInstance().getConfigOption(cls.CONFIG_SECTION_NAME, cls.PASSWORD_FILE_KEY)).read().strip()
+            cls.__host = ConfigurationManager.getInstance().getConfigOption(cls.CONFIG_SECTION_NAME, cls.HOST_KEY)
+            cls.__port = ConfigurationManager.getInstance().getConfigOption(cls.CONFIG_SECTION_NAME, cls.PORT_KEY)
+            cls.__protocol = ConfigurationManager.getInstance().getConfigOption(cls.CONFIG_SECTION_NAME, cls.PROTOCOL_KEY)
+        return (cls.__username, cls.__password, cls.__host, cls.__port, cls.__protocol)
+
+    @classmethod
+    def getFileRestApi(cls):
+        from userRestApi import FileRestApi
+        (username, password, host, port, protocol) = cls.__getConfiguration()
+        api = FileRestApi(username, password, host, port, protocol)
+        return api
+
+####################################################################
+# Testing
+
+if __name__ == '__main__':
+    pass
+
diff --git a/src/python/dm/cat_web_service/api/fileRestApi.py b/src/python/dm/cat_web_service/api/fileRestApi.py
new file mode 100755
index 0000000000000000000000000000000000000000..6ba48a61fa348ec4343208edd725f75da319a047
--- /dev/null
+++ b/src/python/dm/cat_web_service/api/fileRestApi.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python
+
+import os
+import urllib
+import json
+
+from dm.common.utility.encoder import Encoder
+from dm.common.exceptions.dmException import DmException
+from dm.common.exceptions.invalidRequest import InvalidRequest
+from dm.common.objects.fileMetadata import FileMetadata
+from catRestApi import CatRestApi
+
+class FileRestApi(CatRestApi):
+    
+    def __init__(self, username=None, password=None, host=None, port=None, protocol=None):
+        CatRestApi.__init__(self, username, password, host, port, protocol)
+
+    @CatRestApi.execute
+    def addExperimentFile(self, fileInfo):
+        experimentName = fileInfo.get('experimentName')
+        if not experimentName:
+            raise InvalidRequest('File metadata must contain experimentName key.')
+        fileName = fileInfo.get('name')
+        if not fileName:
+            raise InvalidRequest('File metadata must contain name key.')
+        url = '%s/filesByExperiment/%s/%s' % (self.getContextRoot(), experimentName, fileName)
+        url += '?fileInfo=%s' % (Encoder.encode(json.dumps(fileInfo)))
+        responseData = self.sendRequest(url=url, method='POST')
+        return FileMetadata(responseData)
+
+    @CatRestApi.execute
+    def updateExperimentFile(self, fileInfo):
+        experimentName = fileInfo.get('experimentName')
+        if not experimentName:
+            raise InvalidRequest('File metadata must contain experimentName key.')
+        fileName = fileInfo.get('name')
+        if not fileName:
+            raise InvalidRequest('File metadata must contain name key.')
+        url = '%s/filesByExperiment/%s/%s' % (self.getContextRoot(), experimentName, fileName)
+        url += '?fileInfo=%s' % (Encoder.encode(json.dumps(fileInfo)))
+        responseData = self.sendRequest(url=url, method='PUT')
+        return FileMetadata(responseData)
+
+    @CatRestApi.execute
+    def updateFileById(self, fileInfo):
+        id = fileInfo.get('id')
+        if not id:
+            raise InvalidRequest('File metadata must contain id key.')
+        url = '%s/files/%s' % (self.getContextRoot(), id)
+        url += '?fileInfo=%s' % (Encoder.encode(json.dumps(fileInfo)))
+        responseData = self.sendRequest(url=url, method='PUT')
+        return FileMetadata(responseData)
+
+    @CatRestApi.execute
+    def getFiles(self, queryDict={}):
+        url = '%s/files' % (self.getContextRoot())
+        url += '?queryDict=%s' % (Encoder.encode(json.dumps(queryDict)))
+        responseData = self.sendRequest(url=url, method='GET')
+        return self.toDmObjectList(responseData, FileMetadata)
+
+    @CatRestApi.execute
+    def getExperimentFiles(self, experimentName, queryDict={}):
+        if not experimentName:
+            raise InvalidRequest('Invalid experiment name provided.')
+        url = '%s/filesByExperiment/%s' % (self.getContextRoot(), experimentName)
+        url += '?queryDict=%s' % (Encoder.encode(json.dumps(queryDict)))
+        responseData = self.sendRequest(url=url, method='GET')
+        return self.toDmObjectList(responseData, FileMetadata)
+
+    @CatRestApi.execute
+    def getFileById(self, id):
+        if not id:
+            raise InvalidRequest('Invalid file id provided.')
+        url = '%s/files/%s' % (self.getContextRoot(), id)
+        responseData = self.sendRequest(url=url, method='GET')
+        return FileMetadata(responseData)
+
+    @CatRestApi.execute
+    def getExperimentFile(self, experimentName, fileName):
+        if not experimentName:
+            raise InvalidRequest('Invalid experiment name provided.')
+        if not fileName:
+            raise InvalidRequest('Invalid file name provided.')
+        url = '%s/filesByExperiment/%s/%s' % (self.getContextRoot(), experimentName, fileName)
+        responseData = self.sendRequest(url=url, method='GET')
+        return FileMetadata(responseData)
+
+#######################################################################
+# Testing.
+
+if __name__ == '__main__':
+    api = FileRestApi()
+    print api.getFiles()
+    print api.getFileById('556de0059e058b0ef4c4413b')
+    print api.getFileByName('xyz-001')
+
+    import time
+    t = long(time.time())
+    fileInfo = {
+        'name' : 'sv-%s' % t,
+        'experimentName' : 'exp1',
+        'power' : 12,
+        'powerUnits' : 'kW',
+        'force' : 15,
+        'forceUnits' : 'N',
+        'cKey' : {'a' : 1, 'b' : 'B', 'c' : 2.2},
+    }
+    fileMetadata = api.addFileByName(fileInfo)
+
+    print '\nADDED FILE:\n', fileMetadata
+ 
+    fileInfo['updateKey'] = 'here is desc'
+    fileMetadata = api.updateFileByName(fileInfo)
+    print '\nUPDATED FILE:\n', fileMetadata
+
+    fileInfo['updateKey2'] = 'new desc'
+    fileInfo['id'] = fileMetadata.get('id')
+    fileMetadata = api.updateFileById(fileInfo)
+    print '\nUPDATED FILE:\n', fileMetadata
+
+    queryDict = { 'experimentName' : 'exp2' }
+    print '\nQUERY FILES:\n', api.getFiles(queryDict=queryDict)
+
diff --git a/src/python/dm/cat_web_service/cli/__init__.py b/src/python/dm/cat_web_service/cli/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..bdbb0843b9f02b7cb5042f7899110655e2a03b60
--- /dev/null
+++ b/src/python/dm/cat_web_service/cli/__init__.py
@@ -0,0 +1 @@
+__version__ = "Development Snapshot"
diff --git a/src/python/dm/cat_web_service/cli/addExperimentFileCli.py b/src/python/dm/cat_web_service/cli/addExperimentFileCli.py
new file mode 100755
index 0000000000000000000000000000000000000000..4e7c1f86646f8b03ce213e76d35e43e3bf528746
--- /dev/null
+++ b/src/python/dm/cat_web_service/cli/addExperimentFileCli.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+
+from dm.cat_web_service.api.fileRestApi import FileRestApi
+from dm.common.exceptions.invalidRequest import InvalidRequest
+from catWebServiceSessionCli import CatWebServiceSessionCli
+
+class AddExperimentFileCli(CatWebServiceSessionCli):
+    def __init__(self):
+        CatWebServiceSessionCli.__init__(self, validArgCount=self.ANY_NUMBER_OF_POSITIONAL_ARGS)
+        self.addOption('', '--name', dest='fileName', help='File name.')
+        self.addOption('', '--experiment', dest='experimentName', help='Experiment name.')
+
+    def checkArgs(self):
+        if self.options.experimentName is None:
+            raise InvalidRequest('Experiment name must be provided.')
+        if self.options.fileName is None:
+            raise InvalidRequest('File name must be provided.')
+
+    def getExperimentName(self):
+        return self.options.experimentName
+
+    def getFileName(self):
+        return self.options.fileName
+
+    def runCommand(self):
+        self.parseArgs(usage="""
+    dm-add-experiment-file --name=FILENAME --experiment=EXPERIMENTNAME 
+        [key1:value1, key2:value2, ...]
+
+Description:
+    Adds experiment file to the metadata catalog. All provided key/value pairs 
+    are interpreted as file metadata.
+        """)
+        self.checkArgs()
+        api = FileRestApi(self.getLoginUsername(), self.getLoginPassword(), self.getServiceHost(), self.getServicePort(), self.getServiceProtocol())
+        fileInfo = self.splitArgsIntoDict()
+        fileInfo['experimentName'] = self.getExperimentName()
+        fileInfo['name'] = self.getFileName()
+        fileMetadata = api.addExperimentFile(fileInfo)
+        print fileMetadata.getDisplayString(self.getDisplayKeys(), self.getDisplayFormat())
+
+#######################################################################
+# Run command.
+if __name__ == '__main__':
+    cli = AddExperimentFileCli()
+    cli.run()
+
diff --git a/src/python/dm/cat_web_service/cli/catWebServiceCli.py b/src/python/dm/cat_web_service/cli/catWebServiceCli.py
new file mode 100755
index 0000000000000000000000000000000000000000..d93e93e0ba7165dd806abb9ac8bd339b394333f4
--- /dev/null
+++ b/src/python/dm/cat_web_service/cli/catWebServiceCli.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+
+from dm.common.cli.dmRestCli import DmRestCli
+from dm.common.utility.configurationManager import ConfigurationManager
+
+class CatWebServiceCli(DmRestCli):
+    """ DM CAT web service cli class. """
+
+    def __init__(self, validArgCount=0):
+        DmRestCli.__init__(self, validArgCount)
+
+    def getDefaultServiceHost(self):
+        return ConfigurationManager.getInstance().getCatWebServiceHost()
+
+    def getDefaultServicePort(self):
+        return ConfigurationManager.getInstance().getCatWebServicePort()
+                        
diff --git a/src/python/dm/cat_web_service/cli/catWebServiceSessionCli.py b/src/python/dm/cat_web_service/cli/catWebServiceSessionCli.py
new file mode 100755
index 0000000000000000000000000000000000000000..61386f99bb29165ca0aa696b5504cba2f5973e4d
--- /dev/null
+++ b/src/python/dm/cat_web_service/cli/catWebServiceSessionCli.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+from dm.common.cli.dmRestSessionCli import DmRestSessionCli
+from dm.common.utility.osUtility import OsUtility
+from dm.common.utility.configurationManager import ConfigurationManager
+
+class CatWebServiceSessionCli(DmRestSessionCli):
+    """ DM CAT web service session cli class. """
+
+    DEFAULT_SESSION_CACHE_FILE = OsUtility.getUserHomeDir() + '/.dm/.cat.session.cache'
+
+    def __init__(self, validArgCount=0):
+        DmRestSessionCli.__init__(self, validArgCount)
+        ConfigurationManager.getInstance().setSessionCacheFile(CatWebServiceSessionCli.DEFAULT_SESSION_CACHE_FILE)
+
+    def getDefaultServiceHost(self):
+        return ConfigurationManager.getInstance().getCatWebServiceHost()
+
+    def getDefaultServicePort(self):
+        return ConfigurationManager.getInstance().getCatWebServicePort()
+
diff --git a/src/python/dm/cat_web_service/cli/getExperimentFileCli.py b/src/python/dm/cat_web_service/cli/getExperimentFileCli.py
new file mode 100755
index 0000000000000000000000000000000000000000..a397feb5d3970c9a2a4fd06a0dbe21507a6710d7
--- /dev/null
+++ b/src/python/dm/cat_web_service/cli/getExperimentFileCli.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+from dm.cat_web_service.api.fileRestApi import FileRestApi
+from dm.common.exceptions.invalidRequest import InvalidRequest
+from catWebServiceSessionCli import CatWebServiceSessionCli
+
+class GetExperimentFileCli(CatWebServiceSessionCli):
+    def __init__(self):
+        CatWebServiceSessionCli.__init__(self, validArgCount=self.ANY_NUMBER_OF_POSITIONAL_ARGS)
+        self.addOption('', '--name', dest='fileName', help='File name.')
+        self.addOption('', '--experiment', dest='experimentName', help='Experiment name.')
+
+    def checkArgs(self):
+        if self.options.experimentName is None:
+            raise InvalidRequest('Experiment name must be provided.')
+        if self.options.fileName is None:
+            raise InvalidRequest('File name must be provided.')
+
+    def getExperimentName(self):
+        return self.options.experimentName
+
+    def getFileName(self):
+        return self.options.fileName
+
+    def runCommand(self):
+        self.parseArgs(usage="""
+    dm-get-experiment-file --name=FILENAME --experiment=EXPERIMENTNAME 
+
+Description:
+    Retrieve experiment file metadata from the catalog. 
+        """)
+        self.checkArgs()
+        api = FileRestApi(self.getLoginUsername(), self.getLoginPassword(), self.getServiceHost(), self.getServicePort(), self.getServiceProtocol())
+        fileMetadata = api.getExperimentFile(self.getExperimentName(), self.getFileName())
+        print fileMetadata.getDisplayString(self.getDisplayKeys(), self.getDisplayFormat())
+
+#######################################################################
+# Run command.
+if __name__ == '__main__':
+    cli = GetExperimentFileCli()
+    cli.run()
+
diff --git a/src/python/dm/cat_web_service/cli/getExperimentFilesCli.py b/src/python/dm/cat_web_service/cli/getExperimentFilesCli.py
new file mode 100755
index 0000000000000000000000000000000000000000..54b0d3fb51506a8d6de96f55614602df70739dec
--- /dev/null
+++ b/src/python/dm/cat_web_service/cli/getExperimentFilesCli.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+from dm.cat_web_service.api.fileRestApi import FileRestApi
+from dm.common.exceptions.invalidRequest import InvalidRequest
+from catWebServiceSessionCli import CatWebServiceSessionCli
+
+class GetExperimentFilesCli(CatWebServiceSessionCli):
+    def __init__(self):
+        CatWebServiceSessionCli.__init__(self, validArgCount=self.ANY_NUMBER_OF_POSITIONAL_ARGS)
+        self.addOption('', '--experiment', dest='experimentName', help='Experiment name.')
+
+    def checkArgs(self):
+        if self.options.experimentName is None:
+            raise InvalidRequest('Experiment name must be provided.')
+
+    def getExperimentName(self):
+        return self.options.experimentName
+
+    def runCommand(self):
+        self.parseArgs(usage="""
+    dm-get-experiment-files --experiment=EXPERIMENTNAME 
+        [key1:value1, key2:value2, ...]
+
+Description:
+    Retrieve experiment files from the metadata catalog. Only those files that
+    match provided key/value metadata pairs will be returned. If no specific
+    metadata key/values are requested, all experiment files will be returned. 
+        """)
+        self.checkArgs()
+        api = FileRestApi(self.getLoginUsername(), self.getLoginPassword(), self.getServiceHost(), self.getServicePort(), self.getServiceProtocol())
+        queryDict = self.splitArgsIntoDict()
+        fileMetadataList = api.getExperimentFiles(self.getExperimentName(), queryDict)
+        for fileMetadata in fileMetadataList:
+            print fileMetadata.getDisplayString(self.getDisplayKeys(), self.getDisplayFormat())
+
+#######################################################################
+# Run command.
+if __name__ == '__main__':
+    cli = GetExperimentFilesCli()
+    cli.run()
+
diff --git a/src/python/dm/cat_web_service/cli/getFilesCli.py b/src/python/dm/cat_web_service/cli/getFilesCli.py
new file mode 100755
index 0000000000000000000000000000000000000000..e5e5a027bc201cfa6bb446b068da25fec21cb2b0
--- /dev/null
+++ b/src/python/dm/cat_web_service/cli/getFilesCli.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+
+from dm.cat_web_service.api.fileRestApi import FileRestApi
+from dm.common.exceptions.invalidRequest import InvalidRequest
+from catWebServiceSessionCli import CatWebServiceSessionCli
+
+class GetFilesCli(CatWebServiceSessionCli):
+    def __init__(self):
+        CatWebServiceSessionCli.__init__(self, validArgCount=self.ANY_NUMBER_OF_POSITIONAL_ARGS)
+
+    def checkArgs(self):
+        pass
+
+    def runCommand(self):
+        self.parseArgs(usage="""
+    dm-get-files 
+        [key1:value1, key2:value2, ...]
+
+Description:
+    Retrieve files from the metadata catalog. Only those files that
+    match provided key/value metadata pairs will be returned. If no specific
+    metadata key/values are requested, all experiment files will be returned. 
+        """)
+        self.checkArgs()
+        api = FileRestApi(self.getLoginUsername(), self.getLoginPassword(), self.getServiceHost(), self.getServicePort(), self.getServiceProtocol())
+        queryDict = self.splitArgsIntoDict()
+        fileMetadataList = api.getFiles(queryDict)
+        for fileMetadata in fileMetadataList:
+            print fileMetadata.getDisplayString(self.getDisplayKeys(), self.getDisplayFormat())
+
+#######################################################################
+# Run command.
+if __name__ == '__main__':
+    cli = GetFilesCli()
+    cli.run()
+
diff --git a/src/python/dm/cat_web_service/cli/updateExperimentFileCli.py b/src/python/dm/cat_web_service/cli/updateExperimentFileCli.py
new file mode 100755
index 0000000000000000000000000000000000000000..d5bfa2f3ae099862283015b3861aa89780e88913
--- /dev/null
+++ b/src/python/dm/cat_web_service/cli/updateExperimentFileCli.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+from dm.cat_web_service.api.fileRestApi import FileRestApi
+from dm.common.exceptions.invalidRequest import InvalidRequest
+from catWebServiceSessionCli import CatWebServiceSessionCli
+
+class UpdateExperimentFileCli(CatWebServiceSessionCli):
+    def __init__(self):
+        CatWebServiceSessionCli.__init__(self, validArgCount=self.ANY_NUMBER_OF_POSITIONAL_ARGS)
+        self.addOption('', '--name', dest='fileName', help='File name.')
+        self.addOption('', '--experiment', dest='experimentName', help='Experiment name.')
+
+    def checkArgs(self):
+        if self.options.experimentName is None:
+            raise InvalidRequest('Experiment name must be provided.')
+        if self.options.fileName is None:
+            raise InvalidRequest('File name must be provided.')
+
+    def getExperimentName(self):
+        return self.options.experimentName
+
+    def getFileName(self):
+        return self.options.fileName
+
+    def runCommand(self):
+        self.parseArgs(usage="""
+    dm-update-experiment-file --name=FILENAME --experiment=EXPERIMENTNAME 
+        [key1:value1, key2:value2, ...]
+
+Description:
+    Updates experiment file in the metadata catalog. All provided key/value 
+    pairs are interpreted as file metadata, and will be merged with existing
+    metadata, overwriting values for existing keys, and adding values for
+    new keys.
+        """)
+        self.checkArgs()
+        api = FileRestApi(self.getLoginUsername(), self.getLoginPassword(), self.getServiceHost(), self.getServicePort(), self.getServiceProtocol())
+        fileInfo = self.splitArgsIntoDict()
+        fileInfo['experimentName'] = self.getExperimentName()
+        fileInfo['name'] = self.getFileName()
+        fileMetadata = api.updateExperimentFile(fileInfo)
+        print fileMetadata.getDisplayString(self.getDisplayKeys(), self.getDisplayFormat())
+
+#######################################################################
+# Run command.
+if __name__ == '__main__':
+    cli = UpdateExperimentFileCli()
+    cli.run()
+
diff --git a/src/python/dm/cat_web_service/service/__init__.py b/src/python/dm/cat_web_service/service/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..bdbb0843b9f02b7cb5042f7899110655e2a03b60
--- /dev/null
+++ b/src/python/dm/cat_web_service/service/__init__.py
@@ -0,0 +1 @@
+__version__ = "Development Snapshot"
diff --git a/src/python/dm/cat_web_service/service/catWebService.py b/src/python/dm/cat_web_service/service/catWebService.py
new file mode 100755
index 0000000000000000000000000000000000000000..6d51ad5ef78a31e8711898dae6631ec303e8a94f
--- /dev/null
+++ b/src/python/dm/cat_web_service/service/catWebService.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+
+#
+# DM CAT Web Service
+#
+
+from dm.common.service.dmRestWebServiceBase import DmRestWebServiceBase
+from dm.common.utility.dmModuleManager import DmModuleManager
+from dm.common.utility.configurationManager import ConfigurationManager
+from catWebServiceRouteMapper import CatWebServiceRouteMapper
+
+class CatWebService(DmRestWebServiceBase):
+ 
+    def __init__(self):
+        DmRestWebServiceBase.__init__(self, CatWebServiceRouteMapper)
+
+    def initDmModules(self):
+        self.logger.debug('Initializing dm modules')
+
+        # Add modules that will be started.
+        moduleManager = DmModuleManager.getInstance()
+        self.logger.debug('Initialized dm modules')
+
+    def getDefaultServerHost(self):
+        return ConfigurationManager.getInstance().getServiceHost()
+
+    def getDefaultServerPort(self):
+        return ConfigurationManager.getInstance().getServicePort()
+
+####################################################################
+# Run service
+
+if __name__ == '__main__':
+    ConfigurationManager.getInstance().setServiceName('cat-web-service')
+    service = CatWebService();
+    service.run()
diff --git a/src/python/dm/cat_web_service/service/catWebServiceRouteMapper.py b/src/python/dm/cat_web_service/service/catWebServiceRouteMapper.py
new file mode 100755
index 0000000000000000000000000000000000000000..f84f2e4972a87dbadf50c3ede29fa53448857607
--- /dev/null
+++ b/src/python/dm/cat_web_service/service/catWebServiceRouteMapper.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+
+#
+# Route mapper for DM CAT web service.
+#
+
+import sys
+import os
+
+import cherrypy
+from dm.common.utility.loggingManager import LoggingManager
+from dm.common.utility.configurationManager import ConfigurationManager
+from dm.common.service.loginRouteDescriptor import LoginRouteDescriptor
+from fileRouteDescriptor import FileRouteDescriptor
+
+class CatWebServiceRouteMapper:
+
+    @classmethod
+    def setupRoutes(cls):
+        """ Setup RESTFul routes. """
+        logger = LoggingManager.getInstance().getLogger(cls.__name__)
+        contextRoot = ConfigurationManager.getInstance().getContextRoot()
+        logger.debug('Using context root: %s' % contextRoot)
+
+        # Get routes.
+        routes = LoginRouteDescriptor.getRoutes() 
+        routes += FileRouteDescriptor.getRoutes()
+
+        # Add routes to dispatcher. 
+        d = cherrypy.dispatch.RoutesDispatcher()
+        for route in routes:
+            logger.debug('Connecting route: %s' % route)
+            d.connect(route['name'], route['path'], action=route['action'], controller=route['controller'], conditions=dict(method=route['method']))
+        return d
+
+
diff --git a/src/python/dm/cat_web_service/service/fileController.py b/src/python/dm/cat_web_service/service/fileController.py
new file mode 100755
index 0000000000000000000000000000000000000000..f1a6f43b73c51d1e02bf9e17e714a5ac0062b015
--- /dev/null
+++ b/src/python/dm/cat_web_service/service/fileController.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+
+import cherrypy
+import json
+from dm.common.utility.encoder import Encoder
+from dm.common.service.dmController import DmController
+from dm.cat_web_service.service.impl.fileControllerImpl import FileControllerImpl
+
+class FileController(DmController):
+
+    def __init__(self):
+        DmController.__init__(self)
+        self.fileControllerImpl = FileControllerImpl()
+
+    @cherrypy.expose
+    @DmController.execute
+    def addExperimentFile(self, experimentName, fileName, **kwargs):
+        if not experimentName:
+            raise InvalidRequest('Invalid experiment name provided.')
+        if not fileName:
+            raise InvalidRequest('Invalid file name provided.')
+        encodedFileInfo = kwargs.get('fileInfo')
+        if not encodedFileInfo:
+            raise InvalidRequest('Invalid file info provided.')
+        fileInfo = json.loads(Encoder.decode(encodedFileInfo))
+        fileInfo['name'] = fileName
+        fileInfo['experimentName'] = experimentName
+        response = self.fileControllerImpl.addExperimentFile(fileInfo).getFullJsonRep()
+        self.logger.debug('Added file %s: %s' % (fileName,response))
+        return response
+
+    @cherrypy.expose
+    @DmController.execute
+    def updateExperimentFile(self, experimentName, fileName, **kwargs):
+        if not experimentName:
+            raise InvalidRequest('Invalid experiment name provided.')
+        if not fileName:
+            raise InvalidRequest('Invalid file name provided.')
+        encodedFileInfo = kwargs.get('fileInfo')
+        if not encodedFileInfo:
+            raise InvalidRequest('Invalid file info provided.')
+        fileInfo = json.loads(Encoder.decode(encodedFileInfo))
+        fileInfo['name'] = fileName
+        fileInfo['experimentName'] = experimentName
+        response = self.fileControllerImpl.updateExperimentFile(fileInfo).getFullJsonRep()
+        self.logger.debug('Updated file %s: %s' % (fileName,response))
+        return response
+
+    @cherrypy.expose
+    @DmController.execute
+    def updateFileById(self, id, **kwargs):
+        if not id:
+            raise InvalidRequest('Invalid file id provided.')
+        encodedFileInfo = kwargs.get('fileInfo')
+        if not encodedFileInfo:
+            raise InvalidRequest('Invalid file info provided.')
+        fileInfo = json.loads(Encoder.decode(encodedFileInfo))
+        response = self.fileControllerImpl.updateFileById(fileInfo).getFullJsonRep()
+        self.logger.debug('Updated file id %s: %s' % (id,response))
+        return response
+
+    @cherrypy.expose
+    @DmController.execute
+    def getFiles(self, **kwargs):
+        encodedQueryDict = kwargs.get('queryDict')
+        queryDict = {}
+        if encodedQueryDict:
+            queryDict = json.loads(Encoder.decode(encodedQueryDict))
+        return self.listToJson(self.fileControllerImpl.getFiles(queryDict=queryDict))
+
+    @cherrypy.expose
+    @DmController.execute
+    def getExperimentFiles(self, experimentName, **kwargs):
+        if not experimentName:
+            raise InvalidRequest('Invalid experiment name provided.')
+        encodedQueryDict = kwargs.get('queryDict')
+        queryDict = {}
+        if encodedQueryDict:
+            queryDict = json.loads(Encoder.decode(encodedQueryDict))
+        return self.listToJson(self.fileControllerImpl.getExperimentFiles(experimentName, queryDict=queryDict))
+
+    @cherrypy.expose
+    @DmController.execute
+    def getFileById(self, id, **kwargs):
+        if not id:
+            raise InvalidRequest('Invalid id provided.')
+        response = self.fileControllerImpl.getFileById(id).getFullJsonRep()
+        self.logger.debug('Returning file id %s: %s' % (id,response))
+        return response
+
+    @cherrypy.expose
+    @DmController.execute
+    def getExperimentFile(self, experimentName, fileName, **kwargs):
+        if not experimentName:
+            raise InvalidRequest('Invalid experiment name provided.')
+        if not fileName:
+            raise InvalidRequest('Invalid file name provided.')
+        response = self.fileControllerImpl.getExperimentFile(experimentName, fileName).getFullJsonRep()
+        self.logger.debug('Returning file %s: %s' % (fileName,response))
+        return response
+
diff --git a/src/python/dm/cat_web_service/service/fileRouteDescriptor.py b/src/python/dm/cat_web_service/service/fileRouteDescriptor.py
new file mode 100755
index 0000000000000000000000000000000000000000..5f5f4bd9d5d65d942aca08ef7a02a708dd18d87b
--- /dev/null
+++ b/src/python/dm/cat_web_service/service/fileRouteDescriptor.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+
+#
+# User route descriptor.
+#
+
+from dm.common.utility.configurationManager import ConfigurationManager
+from fileController import FileController
+
+class FileRouteDescriptor:
+
+    @classmethod
+    def getRoutes(cls):
+        contextRoot = ConfigurationManager.getInstance().getContextRoot()
+
+        # Static instances shared between different routes
+        fileController = FileController()
+
+        # Define routes.
+        routes = [
+
+            # Add experiment file
+            {
+                'name' : 'addExperimentFile',
+                'path' : '%s/filesByExperiment/:(experimentName)/:(fileName)' % contextRoot,
+                'controller' : fileController,
+                'action' : 'addExperimentFile',
+                'method' : ['POST']
+            },
+
+            # Update file by id
+            {
+                'name' : 'updateFileById',
+                'path' : '%s/files/:(id)' % contextRoot,
+                'controller' : fileController,
+                'action' : 'updateFileById',
+                'method' : ['PUT']
+            },
+
+            # Update experiment file by name
+            {
+                'name' : 'updateExperimentFile',
+                'path' : '%s/filesByExperiment/:(experimentName)/:(fileName)' % contextRoot,
+                'controller' : fileController,
+                'action' : 'updateExperimentFile',
+                'method' : ['PUT']
+            },
+
+            # Get file info list
+            {
+                'name' : 'getFiles',
+                'path' : '%s/files' % contextRoot,
+                'controller' : fileController,
+                'action' : 'getFiles',
+                'method' : ['GET']
+            },
+
+            # Get experiment file info list 
+            {
+                'name' : 'getExperimentFiles',
+                'path' : '%s/filesByExperiment/:(experimentName)' % contextRoot,
+                'controller' : fileController,
+                'action' : 'getExperimentFiles',
+                'method' : ['GET']
+            },
+
+            # Get file by id
+            {
+                'name' : 'getFileById',
+                'path' : '%s/files/:(id)' % contextRoot,
+                'controller' : fileController,
+                'action' : 'getFileById',
+                'method' : ['GET']
+            },
+
+            # Get experiment file by name
+            {
+                'name' : 'getExperimentFile',
+                'path' : '%s/filesByExperiment/:(experimentName)/:(fileName)' % contextRoot,
+                'controller' : fileController,
+                'action' : 'getExperimentFile',
+                'method' : ['GET']
+            },
+
+        ]
+       
+        return routes
+
+
diff --git a/src/python/dm/cat_web_service/service/impl/__init__.py b/src/python/dm/cat_web_service/service/impl/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..bdbb0843b9f02b7cb5042f7899110655e2a03b60
--- /dev/null
+++ b/src/python/dm/cat_web_service/service/impl/__init__.py
@@ -0,0 +1 @@
+__version__ = "Development Snapshot"
diff --git a/src/python/dm/cat_web_service/service/impl/fileControllerImpl.py b/src/python/dm/cat_web_service/service/impl/fileControllerImpl.py
new file mode 100755
index 0000000000000000000000000000000000000000..3ce0413206c82b8f3a06c0955a5bc4dbaca94085
--- /dev/null
+++ b/src/python/dm/cat_web_service/service/impl/fileControllerImpl.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+#
+# Implementation for file controller.
+#
+
+from dm.common.objects.dmObject import DmObject
+from dm.common.objects.dmObjectManager import DmObjectManager
+from dm.common.mongodb.api.fileMongoDbApi import FileMongoDbApi
+
+class FileControllerImpl(DmObjectManager):
+    """ File controller implementation class. """
+
+    def __init__(self):
+        DmObjectManager.__init__(self)
+        self.fileMongoDbApi = FileMongoDbApi()
+
+    def addExperimentFile(self, fileInfo):
+        return self.fileMongoDbApi.addExperimentFile(fileInfo)
+
+    def updateExperimentFile(self, fileInfo):
+        return self.fileMongoDbApi.updateExperimentFile(fileInfo)
+
+    def updateFileById(self, fileInfo):
+        return self.fileMongoDbApi.updateFileById(fileInfo)
+
+    def getFiles(self, queryDict):
+        return self.fileMongoDbApi.getFiles(queryDict=queryDict)
+
+    def getExperimentFiles(self, experimentName, queryDict):
+        return self.fileMongoDbApi.getExperimentFiles(experimentName, queryDict=queryDict)
+
+    def getFileById(self, id):
+        return self.fileMongoDbApi.getFileById(id)
+
+    def getExperimentFile(self, experimentName, fileName):
+        return self.fileMongoDbApi.getExperimentFile(experimentName, fileName)
+