一个用于 Python 的 CMIS API 库,第 2 部分: 使用 Python 和 cmislib 构建真正的 ECM 工具

创建一个样例应用程序

在这个关于 CMIS 和 Python 的系列的第 2 部分中,使用 Python cmislib 库构建一个类似于 xcopy 的数据填充和迁移工具。这个工具不仅能够将本地文件系统 xcopy 到任意 CMIS 存储库,还具有 JPG Exif 数据意识,能够在复制过程中保留这种数据(如果可能的话)。详细了解这个工具的源代码,以及如何在命令行上使用该工具。另外,您还可以下载功能齐备的源代码。

Jay A. Brown, 高级工程师, WSO2 Inc

Jay Brown 的照片Jay Brown 是 IBM 的 Software Group 的高级工程师。他在 Shearson Lehman、General Electric 和 FileNet 从事了 21 年软件开发,最近 11 年主要为 FileNet 和 IBM 构建和设计 ECM 系统。在 P8 Architecture 团队工作的两年时间内,Jay 为 IBM 的 P8 4.x Content Engine (C.E.) APIs(Java 和 .Net)设计并构建了代码生成器。此后,他参与设计和构建了 CMIS 服务器。Brown 先生现在是 CMIS 服务器的开发主管。



2010 年 5 月 04 日

结合 Python 和 CMIS

本系列其他文章

Python 和 CMIS 联合工作时的表现更好。要简要了解 Oasis Content Management Interoperability Services (CMIS) 规范和 cmislib,请参阅本系列第 1 部分(参见 参考资料 中的链接)。

背景知识

这是本系列的第二篇文章,包含 3 个主要部分:

常用缩写词

  • API:应用程序编程接口
  • ECM:企业内容管理
  • IDE:集成开发环境
  • OASIS:结构化信息标准促进组织
  • PDF:可移植文档格式
  • REST:具象状态传输
  • URL:统一资源定位符
  • XML:可扩展标记语言
  1. Python 和 CMIS — 本部分简要介绍并讨论 Python 为何是编写 CMIS 相关工具的理想语言。
  2. 代码详解 — 本部分详细解释源代码各部分如何相互配合,以便您可以针对其他类型的元数据和源轻松扩展它。
  3. 运行工具 — 本部分探索这个工具的运行时特征,以及如何设置依赖项。如果您对该工具如何形成以及怎样工作的解释不感兴趣,只想下载并使用它,那么可以直接跳到 运行工具 部分。

通过工具构建学习一门新语言

在我开始撰写本文的一个月之前,我正在寻找一个能够用于自学 Python 的小项目。对我而言,在需要学习一门新语言时,我可以从头到尾阅读一本教科书,但一周之后我就会完全忘记书里讲了些什么。我永远也不会真正了解那种语言及其相关工具将如何为我服务。如果不使用那种语言做一些有用的事,该语言的相关概念就不会在我的记忆中得到巩固。碰巧最近我和 Jeff Potts 一起合作,以便解决他的新的 CMIS Python 库(cmislib)和 IBM CMIS 技术预览服务器之间的一些互操作性问题。这使我有机会思考如何为长期存在的系统构建工具的问题。

长期存在的系统的工具

首先,我将通过多年来一直近距离接触的一个例子来解释什么是 “长期存在的系统”。一旦部署,ECM 系统就有可能在一个公司或部门运行很长一段时间。替换它们可能会是一项艰巨、复杂且代价高昂的任务,这就是如果它们表现良好就不要改动它们的原因所在。因此,这些系统往往能够在多次合并和并购中幸免,长期生存下来。因此,当我谈论 CMIS 存储库时,从定义上来说,我谈论的往往也是这些系统。长期存在的系统积累了一些非常成熟的由用户和管理员创建的工具。我认为结合了 cmislib 库的 Python 脚本有潜力这样存在下去。但愿您在看到这两种技术联合使用的一个简单却强大的示例之后能够同意我的观点。

为何使用标准解释语言?

需要为这些系统开发工具时,比较让人放心的做法是运行经过解释的脚本。我猜想,这对我而言是控制的感觉。假设您拥有一个用 Java™ 或 C++ 语言编写的工具且需要修复和更改一些地方,这并不总是一项轻松的工作,即使您拥有源代码。您是否多次尝试过重新编译多年以前编写的代码,最后却发现需要某个特殊的构建环境、某些库,以及一些没有记录、早就忘记了的设置?不错,大多数开发人员肯定会备份他们的源代码,但是备份构建环境要困难得多,通常只能在更严格维护的环境中才能完成。这个问题通常不属于工具领域,尽管它很重要。但如果使用脚本,构建环境可能只包含一个常用的文本编辑器和现成的运行时。(注意:我并非建议在进行重要的 Python 编程时不使用一个良好的 Python IDE,比如带有 Pydev 的 Eclipse。)

当我得知以后可以将我的脚本迁移到另一种类型的操作系统并且行为不会改变时,我此前提到过的控制感甚至更加强烈了。如果我确信自己能够这样做,那么我更有可能花费时间来编写一个更好的工具,因为我知道今后不必针对另一个平台重新编写它。只要能够得到某个指定平台的解释程序,我就不必担心。而且,只要这种语言像 Python 一样拥有大量流行的支持,可用性就不成问题。这些天,我进行开发时在 Microsoft® Windows® 和 Linux® 之间频繁切换,我甚至不知道两年以后我将使用哪个系统。这就是我们需要思考的问题,以及最近我为何非常喜欢 Python 的原因。

选择要解决的问题

我在 IBM 强化 CMIS 服务器时需要用到的一个工具是一个不错的存储库填充工具。当然,CMIS 存储库中的数据并不仅仅是文档负载,还可能包括很多与该文档相关的元数据。开发人员都经常使用的一种带有元数据的常见文档类型就是 JPG 图像。我之所以选择 JPG 文件用于测试的原因是,它们的头部通常拥有一组丰富有趣的元数据,这意味着我不必额外编写代码来表达虚构的值。这种 EXchangeable Image File Format (Exif) 数据对那些涉足数码摄影的人来说并不陌生。如果您还不熟悉这种格式,建议您先参阅关于这个主题的 Wikipedia 文章(参见 参考资料 中的链接)。

工具要求

您即将创建的工具需要完成以下任务:

  1. 将本地文件系统中的文件的层次结构复制到任何指定的 CMIS 兼容存储库,并将文件的文件名保留为新的 CMIS 文档的 cmis:name。
  2. 如果这个工具在 xcopy 期间遇到 JPG 类型文件,还要尝试将与图像关联的所有 Exif 数据复制到存储库中,前提是假设存储库包含兼容的属性定义。这是这个工具真正有趣的地方。尽管 xcopy 功能本身非常有用,但是用一篇文章专门介绍它可能有些单调乏味(虽然您可以将这个工具只用作一个简单的旧文件系统到 CMIS 的 xcopy,如果这是您需要这个工具的惟一原因的话)。

代码详解

现在,您可以查看这个工具的参数和代码了。

定义输入

现在我们来定义这个工具将被如何使用。首先,我根据原来的 xcopy 建模该工具,以便它将是一个命令行工具。参数是:

  • -s 复制的源目录。
  • -f 文件过滤器(例如,*.doc、*.jpg、*.* 等)。
  • -t target path 复制操作的目标目录的完整路径(例如:/pictures/Fiji_August_2010/)。这个工具将假定目标路径存在并自动创建所有子文件夹。
  • serviceURL 目标 CMIS 存储库的 XML 服务文档的完整 URL(例如:http://localhost:9080/cmis/service)。
  • targetClassName 用于指定将为新文档创建的 cmis:document 的子类的可选类类型。

    例如,一位摄影师的内容管理系统可能拥有一个名为 CmisJpg 的类。这个 CmisJpg 类包含某些常用 Exif 值的属性定义,这位摄影师可能会在搜索她的图像目录时查询这些值。如果这个参数未指定,这个工具将把所有文档创建为 cmis:document 类型。

  • debug 调试模式(可选)。

    参数值为 true 时,调试模式只会尝试重新创建目标目录结构,但不会复制任何文档。

    如果省略,默认值为 false(复制所有数据)。

代码

现在我将逐步介绍这个工具代码的一些重要部分,描述它们是如何工作的。注意,为简便起见,我不会详细介绍所有代码,但是我跳过的部分都比较简单,是不言而喻的。要获取整个文件(和注释),请参见 下载 部分。下面是我将讨论的项目的高级列表:

  • 将 6 个运行时参数读入工具。
  • 初始化 cmislib 库并获取一个存储库对象,该对象将用作与 CMIS 存储库的所有通信的根。
  • 验证目标文件夹和目标类定义有效并存在于目标库中。
  • 审查基本 xcopy 逻辑。
  • 从 JPG 文件读入 Exif 头部数据。
  • 使用 cmislib 创建一个带有元数据的文档。
  • 基于类型将 Exif 数据动态填充到目标类的属性中。

步骤 1:解析参数

首先需要在运行时将这 6 个参数(参见 定义输入)传入工具。这里,我决定将它们分割为两个类别。第一类是我希望在命令行上传入的值(我不想在命令行上指定所有 6 个参数,因为它们中的一半对于一个给定的存储库不会有太多改变)。由于我是根据 xcopy 建模的,所以我将只接受前三个参数(源、目标和过滤器)。其他三个参数怎么办?对于它们,我将使用一个 .cfg 文本文件,因为它们对于一个给定存储而言是静态的。将这个配置文件放置到脚本所在的目录中,并将其命名为 cmisxcopy.cfg。

清单 1. 样例 cmisxcopy.cfg 文件
[cmis_repository]
# service url for the repository that you will be copying to
serviceURL=http://localhost:8080/p8cmis/resources/DaphneA/Service

# TARGET CLASS
# the cmis:objectTypeId of the class that you wish to create
targetClassName=cmis:document
# DEBUG MODE
debug=false

#USER CREDENTIALS
user_id=admin
password=password

标准 Python 库 ConfigParser 将有效地读取这个配置文件数据,如 清单 2 所示:

清单 2. 使用 ConfigParser 读取配置文件值
import ConfigParser

# config file related constants
configFileName = 'cmisxcopy.cfg'
cmisConfigSectionName = 'cmis_repository'

# read in the config values
config = ConfigParser.RawConfigParser()
config.read(configFileName)
try:
    UrlCmisService = config.get(cmisConfigSectionName, "serviceURL")
    targetClassName = config.get(cmisConfigSectionName, "targetClassName")
    user_id = config.get(cmisConfigSectionName, "user_id")
    password = config.get(cmisConfigSectionName, "password")
    debugMode = config.get(cmisConfigSectionName, "debug")
except:
    print "There was a problem finding the config file:" + configFileName + \

    " or one of the settings in the [" + cmisConfigSectionName + "] section ."
    sys.exit()

要实现这个目的,一种更简单的方法是额外使用一个 config.py 文件,其中只包含这三个常量。然后,主脚本只需导入这个 config.py 并直接使用那些变量即可。下面我将解释命令行解析。

清单 3 展示了如何使用这个 Python 库中的 optparse 来进行其他三个参数的命令行解析。设置 usage 字符串来显示一个提示,以免提交无效的参数。使用 add_option() 方法分别为源、目标和过滤器添加 -s、-t 和 -f 参数。最后,执行一个 parse_args() 将这些值序列化到您的 options 对象中。

清单 3. 使用 optparse 收集命令行参数
from optparse import OptionParser

usage = "usage: %prog -s sourcePathToCopy -t targetPathOnRepository 
    -f fileFilter(default=*.*)"
parser = OptionParser(usage=usage)

## get the values for source and target from the command line
parser.add_option("-s", "--source", action="store", type="string", dest="source", 
    help="Top level of local source directory tree to copy")
parser.add_option("-t", "--target", action="store", type="string", dest="target", 
    help="path to (existing) target CMIS folder. All children will be created 
        during copy.")
parser.add_option("-f", "--filter", action="store", type="string", dest="filter", 
    default="*.*", help="File filter. e.g. *.jpg or *.* ")

(options, args) = parser.parse_args()
startingSourceFolderForCopy = options.source
targetCmisFolderStartingPath = options.target

步骤 2:使用 cmislib 初始化您的 CMIS 连接

清单 4 最终开始执行一些真正的 CMIS 工作。首先必须导入 cmislib 模块。要下载这个库的最新版本,请参见 参考资料 中的 cmislib 链接。首先,使用此前(在清单 23 中)获取的值初始化客户端对象;然后,获取 defaultRepository 对象 repo。接下来,使用 getObjectByPath(path) 调用尝试获取目标文件夹。(注意,cmislib 能够帮助您轻松获取这个对象,就像从这个标准 Python 库获取一个本地文件夹对象一样。)如果这个操作由于某种原因失败(比如指定的文件夹不存在),则这个任务将失败并显示一条恰当的消息。那么,您需要使用 getTypeDefinition() 调用对目标类型定义执行一个类似的健全性检查。

拥有这两个有效的 cmislib 对象后,您知道您拥有的与目标系统相关的所有信息都是正确的,因此可以继续进行处理。注意用于初始化 folderCacheDict dictionary 对象的那一行。如果稍后再次需要这个文件夹对象,可以从这个缓存获取它,而不是再往返一次去获取它。注意,这个缓存对于您使用的特定遍历算法并不真正需要,我在这里使用它只是为了展示当您将来需要扩展这个工具时应该怎样做。

清单 4. 初始化 CmisClient 并获取目标文件夹和目标类对象
from cmislib.model import CmisClient

# initialize the client object based on the passed in values
client = CmisClient(UrlCmisService, user_id, password)
repo = client.defaultRepository

# test to see if the target folder is valid
targetCmisLibFolder = None
try:
    targetCmisLibFolder = repo.getObjectByPath(targetCmisFolderStartingPath)
except: 
    # terminate if we can't get a folder object
    print "The target folder specified can not be found:" + targetCmisFolderStartingPath
    sys.exit()

# initialize the folder cache with  the starting folder
folderCacheDict = {targetCmisFolderStartingPath : targetCmisLibFolder} 

# test to see if the target class type is valid
targetTypeDef = None
try:
    targetTypeDef = repo.getTypeDefinition(targetClassName)
except: 
    # terminate if we can't get the target class type definition object
    print "The target class type specified can not be found:" + targetClassName
    sys.exit()

步骤 3:实现基本的 xcopy 逻辑

在这个步骤中,您将遍历源文件系统树。您将寻找需要复制的文件,在目标文件系统中创建任何必要的子目录,以便复制后的层次结构能够匹配。要遍历源目录结构,需要使用 Python 模块 os 中的 walk() 方法。这将为源文件系统树中的每个目录返回一个 “三元组”(dirname、dirs 和 files),您将使用这个三元组来供给您的 processDirectory 方法(参见 下载 中的完整清单)。然后,processDirectory() 函数继续创建目标目录(如果还不存在)并传递到 copyFilesToCmis() 方法,以将这些文件实际复制到新创建的目标文件夹中。这个方法将迭代接收到的每个文件,过滤出没有请求的文件,并获取 .jpg 文件的 Exif 数据。我们还将在稍后讨论元数据时深入介绍 copyFilesToCmis() 方法。

步骤 4:读取 Exif 数据

对于遇到的每个类型为 JPG 的文件,您需要提取所有的 Exif 值,以便它们可以保留在目标对象中。因此,当您需要读取 JPG 头部(Exif)数据时,有很多方法可以解决这个问题。这里我不想自己编写一个方法,因为已经有几个现成的库。我选择使用 exif-py,因为它以一种非常常见的方式返回标记:一个 “键/值” 对字典,其中所有的值都是字符串。(参见 参考资料 中的链接下载 exif-py,以便运行您的脚本。)我认为,如果您想在这里使用一些更 “洋气”(或更自定义)的库来替换 exif-py,您可以轻易做到这一点,因为我料想大多数库都使用一个字典来表示属性集合,即便不是,您也可以轻松地将它们调整为这样做。完成这个任务的实际代码非常简单,如 清单 5 所示。

清单 5. 从一个 JPG 文件读取 Exif 数据
import EXIF
def getExifTagsForFile(filename):
    f = open(filename, 'rb')
    tags = EXIF.process_file(f)
    return tags

步骤 5:带有元数据的文档创建

在这个步骤中,您需要使用一列属性在目标存储库中创建一个文档,这些属性必须设置,以便 CMIS 存储库确切知道要创建什么。例如,在 CMIS 中,当您将一个新文档 POST(在逻辑上表示创建)到一个文件夹时,正是对象上的属性列表告知 CMIS 要实例化什么。您的意思是要创建 cmis:document 的一个实例呢还是想要名为 CmisJpg 的文档的一个子类呢?这个信息正是通过属性列表来进行通信的。

我原以为在这样一个工具中,元数据将是使事情变得复杂(需要更多代码)的地方。但是我惊喜地发现,只需很少的代码就可以实现这种类型的映射。我要向 Jeff Potts(cmislib 的作者)脱帽致敬,是他使这一切如此轻松!

对于将在目标文件夹中创建的每个文档,将调用 createCMISDoc(),即 清单 6 中的外层方法。作为最后一个参数传入的 propBag 是从 getExifTagsForFile 方法获取的 Exif 标记列表。您对 createPropertyBag() 执行一个调用,以便设置 cmis:objectTypeId 属性(这指定要创建的对象的类型)并将所有标记处理到类型适当的对象中,这些对象将匹配那个特定属性的目标存储库的定义。最后,目标文件夹对象中的实际文档创建只需一行代码:newDoc = folder.createDocument(…)

清单 6. createCMISDoc 方法
def createCMISDoc(folder, targetClass, docLocalPath, docName, propBag):
    """
    Create document in CMIS repository in the folder specified.  
    Create the document of type targetClass
    Take stream for this document from docLocalPath
    Set the name of the document to be docName 
    Set the properties on the object using the propBag

    """
    
    def createPropertyBag(sourceProps, targetClassObj):
        """
        Take the exif tags and return a props collection to submit 
          on the doc create method
        """
        
        # set the class object id first. 
        propsForCreate =  {'cmis:objectTypeId':targetClassObj.id}
        for sourceProp in sourceProps:
            # First see if there is a matching property by display name
            if (sourceProp in targetClassObj.propsKeyedByDisplayName):
                # there’s a matching property in the repo's class type !
                print "Found matching metadata: " + sourceProp
                # now make the data fit
                addPropertyOfTheCorrectTypeToPropbag(propsForCreate, 
                       targetClassObj.propsKeyedByDisplayName[sourceProp],
                         sourceProps[sourceProp] )

        return propsForCreate

    props = createPropertyBag(propBag, targetClass)
    f = open(docLocalPath, 'rb')
    newDoc = folder.createDocument(docName, props, contentFile=f)
    print "Cmislib create returned id=" + newDoc.id
    f.close()

步骤 6. 到目标文档的动态元数据映射

清单 7 所示,本文的最后一个方法用于处理动态元数据映射:从 Exif 数据中的属性映射到为目标文档类定义的属性。这个方法是 addPropetyOfThecorrectTypeToPropbag();我想说的是,我喜欢这种具有描述性的函数名称。这个方法将完成整个脚本中最复杂的工作,但是如您所见,它非常简单,这要归功于 cmislib 的作用。例如,如果 valueToAdd(在来自 exif-py 时总是一个字符串)包含值 56 且 typeObj 属性类型是 int,那么您将它转换为一个适当的 int 对象并设置这个值。如果目标存储库认为它应该是一个字符串,那么就不用转换它。如果转换没有效果(比如这个值包含 f2.0),那么转换将失败并跳过这个属性,但文档仍将创建。因此,不管您在目标 CMIS 存储库中如何设置属性定义,这个代码将尝试使其有效。

清单 7. addPropertyOfTheCorrectTypeToPropbag 方法
def addPropertyOfTheCorrectTypeToPropbag(targetProps, typeObj, valueToAdd):
    """
    Determine what type 'typeObj' is, then convert 'valueToAdd' 
    to that type and set it in targetProps if the property is updateable. 
    Currently only supports 3 types:  string, integer and datetime
    """
    cmisUpdateability = typeObj.getUpdatability()
    cmisPropType = typeObj.getPropertyType()
    cmisId = typeObj.id
    if (cmisUpdateability == "readwrite"):   
        # first lets handle string types
        if (cmisPropType == 'string'):
            # this will be easy
            targetProps[cmisId] = valueToAdd
        if (cmisPropType == 'integer'):
            try:
                intValue = int(valueToAdd.values[0])
                targetProps[cmisId] = intValue
            except: print "error converting int property id:" + cmisId 
        if (cmisPropType == 'datetime'):
            try:
                dateValue = valueToAdd.values
                dtVal = datetime.datetime.strptime(dateValue ,
                         "%Y:%m:%d %H:%M:%S")

                targetProps[cmisId] = dtVal
            except: print "error converting datetime property id:" \
                 + cmisId

图 1 展示了 FileNet® Enterprise Manager 工具,该工具显示了新创建的 CmisJpg 类,以及几个我为测试设置的以 Exif 命名的样例属性。还记得这个代码的工作方式吗,属性的显示名就是键值。这个代码需要添加的就是在目标类中存在一个属性,它的显示名将准确匹配 Exif 标记中的属性的名称。

图 1. 显示 CmisJpg 属性的 FileNet P8 Content Engine 管理工具的屏幕截图
显示 CmisJpg 属性的 FileNet P8 Content Engine 管理工具的屏幕截图

一个关于类型定义和 ID 的注意事项:CMIS 中的对象类型定义包含一个惟一的 ID,用于标识该类型以及一个更用户友好的显示名。这个 cmisxcopy 脚本试图将 Exif 属性名称映射到 CMIS 端上的相同名称。之所以选择映射到 CMIS 对象类型的显示名而不是 ID,其原因是 ID 不必与显示名完全相同。在某些存储库中 ID 和显示名也许相同,而在其他存储库中可能完全不同,比如 GUID。这是使用多个存储库进行测试发现的结果。这个 ID 的任何值都是合法的 CMIS;这里的关键是:如果客户想安全地遵守 CMIS 规范,就不要对它们进行任何假设。由于我需要确保能够获取正确的属性,因此我选择匹配显示名,因为显示名最可能匹配。


运行工具

现在,是时候安装这个工具并试用它了。

先决条件

首先必须下载一些文件(参见 参考资料)。

使用 easy_install 安装 cmislib

  1. 参考资料 中的 Python 安装工具下载所需的安装工具,根据针对您的平台的安装说明安装它们。这些工具对于安装其他的库也是很方便的。
  2. 转到您的 Scripts 目录并运行以下命令:easy_install cmislib
  3. 这就行了!easy_install 工具将启动,找到 cmislib,下载它,然后将它安装到您的 Python 环境中。
  • 安装 Python 2.6.4(最新的 2.x 版本)。
  • 下载针对本文的完整源代码,将其放置到一个目录中,您将从该目录执行它(我将这个目录称为 src)。
  • 下载 cmislib tar.gz 版本,将这个 cmislib 目录放置到您的 source 目录中的 src 目录下。因此,在您的 src 目录中,您现在应该能够看到一个 cmislib 子目录,它包含 4 个 .py 文件(init、exceptions、model 和 net)。之所以在这里使用手动安装(而不是 setup_tools),其原因是您将拥有一个可移植的脚本,无需修改本地 Python 环境就可以运行这个脚本。这又涉及到我在前面讨论过的长期存在的问题。
  • 下载 exif-py 并将 exif.py 文件放置到您的 src 目录中。

要准备运行这个工具,首先需要编辑 cmisxcopy.cfg(参见 参考资料 中的下载链接),这个文件位于 src 目录中,cmisxcopy.py 脚本也在其中。第一次运行时,您也许希望复制一些不带任何自定义元数据的文件,因此,您将把 targetClassName 参数设置为(总是存在的)cmis:document。然后,将 serviceURL 设置为一个有效值,其后跟上 user_idpassword。这样,您就准备好运行这个工具了。

清单 8. cmisxcopy.cfg 文件
          serviceURL=<your service url here>
          targetClassName=cmis:document
          # DEBUG MODE
          debug=false
          #USER CREDENTIALS
          user_id=<user name to use for authentication>
          password=<password here>

接下来,确保 exif-py 和 cmislib 位于您的路径上,或者位于 上一小节 中描述的相同目录中。特别是,Exif.py 文件应该与您的其他源文件(.py)位于相同的目录中。对于 cmislib 而言,包含其 5 个文件的 cmislib 目录应该与您的源文件位于相同的目录中,或者使用 easy_install 方法将 cmislib 安装到您的 Python 环境中。要获取 easy_install 说明,请参阅 使用 easy_install 安装 cmislib.

最后,通过输入以下命令确保 Python 位于您的路径上:

python -V

这个命令应该返回如下结果:

Python 2.6.4

注意:我使用 Python 2.6.4 版测试这个代码。

用户故事

假设您想将您的夏威夷度假照片复制到您的存储库以便发表。您的文件位于本地驱动器上的 C:\photos\hawaiiVacationTree 目录中, 您在 CMIS 存储库上创建了一个目标目录 /photos/Hawaii。另外,那个目录树中还有一些您不想包括的视频,因此您包含了一个过滤器 *.jpg,这样您就只会得到那些照片。您输入的命令如下:

python cmisxcopy.py -s C:\photos\hawaiiVacationTree -t /photos/hawaii -f *.jpg

稍后,当您在 CMIS 存储库中建立了一个名为 JPGs 的类,且它带有与您需要的 Exif 值对应的属性定义之后,您就可以编辑您的 cmisxcopy.cfg 并将 targetClassName 更改为:

targetClassName=Jpgs

然后重新运行相同的命令 — 或者更改目标目录,要是您不想在相同的文件夹中拥有副本的话。您将那些照片复制到 CMIS 存储库时,原始 Exif 标记中的所有元数据将被保留。

图 2. cmisxcopy 之后指向 Alfresco 的公共服务器上的目标目录的 FireFox 连接器
cmisxcopy 之后指向 Alfresco 的公共服务器上的目标目录的 FireFox 连接器

关于 CMIS 测试的一个注意事项

构建 CMIS 客户端或工具时要记住一点:如果只使用一个存储库进行测试,要构建一个与 CMIS 真正兼容的客户端几乎是不可能的。如果只使用一个存储库进行测试,那么您构建的客户端只是针对那个存储库,而不是一个真正的 CMIS 客户端。请总是使用至少两个兼容存储库进行测试,以确保遵循 Oasis CMIS 规范。因此,我使用了 IBM 服务器和 Alfresco™ 公共服务器来测试了这个代码。使我的工作变得轻松的是,Jeff Potts 已经针对一个存储库完成了确保 cmislib 与 CMIS 规范兼容的工作,因此,获取这个原型来使用 IBM CMIS 服务器之后,我第一次使用 Alfresco 服务器就取得了成功。

针对未来开发的建议

我给这个工具留下了两个非常明显的扩展,作为读者的练习。第一个、也是最明显的一个是,允许源文件结构也充当一个 CMIS 系统。这个小小的改动将使这个工具成为一个通用的跨存储库迁移工具。第二个也许不那么明显,即支持来自其他文件类型(比如 PDF、MP3 或 Microsoft® Office 文档)的元数据。例如,如果我的本地文件系统上有一个 Word 文档,该文档包含一个名为 invoiceNumber 的属性名,值为 US900201292339,并且 CMIS 目标类拥有一个名为 invoiceNumber 的字符串属性,那么当我将这个文档 CmisXcopy 到 CMIS 时,它将正确复制,并带有那个额外的数据。

祝您编码愉快!


结束语

本系列其他文章

现在您已经看到,以一种与所有 CMIS 兼容库兼容的方法来编写针对 ECM 存储库的复杂操作脚本有多么简单!如果您还没有深入了解 CMIS,或者下次当您需要在您的 CMIS ECM 系统上完成一些可编写脚本的工作时如果必须考虑使用 cmislib 和 Python,那么希望本文能够激发您深入研究 CMIS 的兴趣。


下载

描述名字大小
样例配置和 Python 脚本sourceForArticle.zip5KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Open source, SOA and web services
ArticleID=487223
ArticleTitle=一个用于 Python 的 CMIS API 库,第 2 部分: 使用 Python 和 cmislib 构建真正的 ECM 工具
publish-date=05042010