pythonapi:一个用于 Python 的 CMIS API 库 第 1 部分: cmislib 介绍

  cmislib 介绍:个用于 Python 客户端 CMIS API

  对于 Content Management Interoperability Services (CMIS) 规范标准来说这是个忙碌春天OASIS 正在准备其 1.0 发布介绍说明存储库供应商正在努力完成服务器端实现内容管理社区开发人员正在发布客户端和 API以便使以标准方式探索和处理富内容存储库更容易

  常用缩写词

  ACL:访问控制列表

  API:应用接口

  HTTP:超文本传输协议

  OASIS:结构化信息标准促进组织

  REST:具象状态传输

  SDK:软件Software开发工具包

  SQL:结构化查询语言

  URL:统资源定位符

  WSDL:Web 服务描述语言

  XML:可扩展标记语言

  如果您曾经构建过以内容为中心应用就会知道首先遇到困难通常是解决如何和底层内容存储库通信问题团队(Team)首先在存储库 SDK 中获得初步方案然后您设计包含呈现层和内容服务层的间集成应用最后您按计划执行并买来点心庆祝成功但是遗憾除了点心会让您长胖的外整个过程要针对前端和后端每个新组合进行重复每个存储库都具有自己惟 API如果您应用和多个存储库通信(通常都是这样)那么您必须学习编码到多个接口

  幸运这个问题早已经解决了模式和 SQL 标准化的前存在模式相同IBM® 和其他公司创建关系数据库在 20 世纪 70 年代早期开始出现但是围绕 SQL 查询语言个正式标准直到 1986 年才问世旦标准问世的后尤其是 1992 年进行重大修订的后开发人员就可以创建前端应用并合理地保证它可以针对多个关系后端进行工作就像 SQL 为关系数据库应用所做CMIS 有潜力为以内容为中心应用做相同事情它规定种和后端交互标准方式不管底层存储库实现如何或者选用何种前端编程语言这次我们谈论不是行和列而是通常位于分层文件夹结构中非结构化和半结构化内容 — 般是某种文件

图 1. CMIS 提供个公共接口不管前端或后端是否包含图像


  本文介绍个用于从 Python 处理 CMIS 存储库客户端库叫做 cmislibcmislib 现在是 Apache Chemistry 项目部分目标是让 Python 开发人员编写可以处理任何兼容 CMIS 后端、以内容为中心应用更为轻松对于很多人来说API 是直接理解 CMIS 威力简单方式

  我们来看为何创建这个 API它为您做些什么它是如何开发(这里给出了提示以防您想要用自己喜欢语言编写个这样 API)cmislib 实际发挥作用些简单例子本系列篇文章将详细介绍该库个实际应用

  创建 API 动机

  出于很多原因用 Python 创建个用于 CMIS 客户端 API 似乎是个好主意些原因是战略上理想化;另外些原因更加偏向于战术上是从自身出发我们首先来看归类为 “Greater Good” 下原因

  遵从 CMIS 供应商必须提供个 Web 服务绑定和个 RESTful Atom Publishing Protocol (AtomPub) 绑定每种绑定相对于其他绑定来说都具有各自优点但是区别在于 CMIS 服务是如何跨区别服务器被发现和Web 服务绑定包含个可用于自动生成客户端代码 WSDL 文件如果愿意使用 Web 服务绑定您可以利用 CMIS 服务器 WSDL 生成自己客户端 API

  另方面RESTful AtomPub 绑定则缺少描述服务标准方式由于是 restful所以它所有服务都通过 URL 访问但是 CMIS 规范标准将特定 URL 留给每个供应商所以如果您想要使用 RESTful AtomPub 绑定编写将跨所有遵循 CMIS 提供商工作代码那么您在客户端还有点工作要做不是跨项目重复该工作而是让个开源项目为每个人做此工作

  下个原因跟应用开发人员和内容存储库供应商采纳有关开发人员可以观察 webinars 和博客帖子以及 Twitter 流量但是直到他们自己动手和亲眼看到流量值否则 CMIS 只不过是又个被大肆宣扬词汇如果软件Software仍旧置于包装盒中出售那么您可以想象包装盒上鲜艳 “爆炸式” 图像图像中广告词大声疾呼:“马上使用 CMIS 吧!”对于那些寻求拨开这些夸张宣传层层迷雾看看 CMIS 到底能够为他们及其应用做些什么人来说Python — 清洁、高产、便于安装 — 似乎是最佳选择cmislib API 向开发人员提供种直观、面向对象、互操作工作思路方法使他们免受实现细节困扰希望开发人员能够到 CMIS 中去 “观光”喜欢他们所见所闻然后将 CMIS 作为以内容为中心自定义应用和富内容存储库进行互操作种标准方式使用而不管客户端是使用 Python 还是其他语言构建

  如果仅有两个供应商采用 CMIS则上述全部目标将无法实现因此那些从某个标准获益人竭尽所能推动供应商采用该标准肯定是有意义cmislib 发行版包含了些单元测试这只是作为项最佳开发实战这个测试套件对于确保测试功能不会随着这个 API 发展而退化很有帮助同时也是以种重复方式来检验互操作性便捷思路方法但是真正 “炫酷” 地方在于这些单元测试作用是作为面向供应商个测试套件IBM、Alfresco™、OpenText 和 Nuxeo 都利用 cmislib 来发现了它们实现问题这并不局限于使用由社区构建各种 CMIS 工具和客户端来验证它们工作 cmislib 供应商这是非常好事!

  “所有对个和个对所有(all-for-one-and-one-for-all)” 是极好动机而且几乎不需要编写行代码每个开源项目都发端于某个开发人员想要挠处 “瘙痒”在这里这处 “瘙痒” 起源于 Optaros™ 开发个内联网项目其目是为客户提供 Django®(个基于 Python Web 应用开发框架)和 Alfresco(个开源内容管理平台)的间集成这个项目开发的时OASIS 还几乎没有 CMIS 这个概念这个集成在服务器端依赖 Alfresco Web Scripts(个用于将您自己 RESTful API 滚动到 Alfresco 框架)通过 HTTP 在基于 Django 呈现层和 Alfresco 存储库的间来回移动 XML集成效果很好但这是特定于 Alfresco 重构 Django 集成以利用 CMIS 似乎是个好主意我们选择首先使用 cmislib 作为个低级 Python API而不是使集成特定于 Django这样做好处是:通过利用 cmislib除 Django 外其他 Python 项目(比如 Zope® 和 Plone®)以及自定义 Python 应用都能够更轻松地和 CMIS 存储库集成

  最后个出于自身原因是有关开发人员生产力多数企业没有只需处理单个存储库这样好运通常解决方案不能预期只会使用某个特定存储库至少需要在开发过程中某个时点上有可以切换选项CMIS 标准显然有助于解决这些问题但实际上要高效地完成工作则还需些客户端库其他些项目正处于开发的中以便为 CMIS 提供基于 Java™ 和 PHP 客户端库但 Python 在呈现层上也非常流行因此个面向 CMIS 、基于 Python 客户端库很重要

  这个 API 功能

  cmislib 目标是抽象掉 CMIS 底层实现细节要在 CMIS 存储库的上构建解决方案开发人员不想、也无需了解 CMIS 工作方式相反cmislib 提供个容易理解对象模型任何使用个内容存储库或阅读 CMIS 介绍说明开发人员都能迅速熟悉这个模型开发人员使用 Repositories、Folders、Documents 和 ACLs 等自然内容管理概念而不是集合、条目和提要

  如前所述cmislib 使用 RESTful AtomPub 绑定和 CMIS 服务器通信个重要问题是:要确保 cmislib 不具有有关它正在处理后端存储库特定于供应商知识 —— 这个库将 CMIS 供应商当作个 “黑匣子”当您使用 cmislib 连接到个 CMIS 供应商时需要向该库提供那个 CMIS 供应商入口点(或者是服务 URL)和些凭证这个库通过询问服务器响应来确定如何进步和服务器交互

  例如假设您想获取列当前被签出文档CMIS 规范标准将告知您:

  存在个签出文档集合

   getCheckedOutDocs 服务时repositoryId 是个必要参数

  可以传入几个可选参数大部分用于处理结果集分页

  来自服务响应将是个 Atom 提要

  但是规范标准没有告诉您用于检索这个集合确切 URL这留给供应商处理cmislib 用途的就是确定那个 URL 以及如何将响应转换为可以以 Python 方式使用 Python 对象清单 1 展示了这个交互在 Python shell 中外观:

清单 1. 列示个存储库中签出文档

>>> rs = repo.getCheckedOutDocs 
 
>>> len(rs) 
2 
>>> for doc in rs: 
...   doc.title 
... 
u'Response XML (Working Copy)' 
u'd1 (Working Copy)' 


  类似地假设您想在个文档上执行个签出操作CMIS 规范标准将告知您:要执行个签出操作您需要发布个 Atom 条目到 checkedout 集合存储库将返回个 Atom 条目该条目表示刚刚签出对象 Private Working Copy (PWC)如果您使用 cmislib则无需担心如何确定那个集合如何构建和发布 Atom 条目 XML以及如何处理 XML 响应相反您只需对象上 checkout 思路方法cmislib 将返回个表示该 PWC Document 对象清单 2 展示了这个交互:

清单 2. 签出个文档

>>> doc.title 
u'testdoc' 
>>> pwc = doc.checkout 
>>> pwc.title 
u'testdoc (Working Copy)' 
>>> doc.checkedOut 
True 


  开发和测试思路方法

  有点很重要:cmislib 编写应该尽可能遵循 CMIS 规范标准因此步是并排打开 Eclipse 和 CMIS 规范标准标出(stub out)类和思路方法我将对规范标准交叉引用添加到行内(in-line)注释中以便稍后我返回来实现该思路方法时能够迅速发现规范标准内适用点

  我从开始就建立起构建流程、文档和源代码控制尽早建立这些内容很重要这样其他开发人员才能快速加入项目并进入角色

  代码以迭代方式演变每次迭代都首先为新功能编写单元测试然后是实际编码最后是通过单元测试我从些基础功能开始比如查询存储库以了解它功能检索对象和对象属性来验证常规思路方法然后我以此为基础继续进行开始全面编写操作、签出/签入、关系和 ACLs

  开始测试很困难不存在参考实现(那时 Apache Chemistry 参考实现中 AtomPub 绑定是只读)且供应商还在开发他们实现Alfresco 是 CMIS 个早期采用者当时拥有最成熟实现因此我从那里开始当大多数单元测试都针对 Alfresco 顺利完成后我就开始针对已经公开发布 CMIS 实现其他供应商进行测试IBM 慷慨地主动公开他们实现添加这个实现使我大开眼界但这对于 cmislib 和所有相关供应商都是个极好练习我们在客户端和服务器端都发现了些问题如果没有这种互操作性测试我们不可能那么快地发现问题

  如果您正在开发 cmislib 这样 CMIS 工具或 APIs重要是要针对尽可能多区别服务器进行测试CMIS 规范标准是全新且供应商实现还不够成熟即使那些宣称完全遵守规范标准草案供应商实现也是如此已发现常见问题分为以下 3类:

  不完整实现CMIS 还非常新发现以下情况很正常:缺失服务(ACL、关系、策略以及当时似乎最不支持更改日志)还没有收到支持强制特性(例如没有提供强制集合和链接)以及硬编码

  对规范标准区别解释CMIS 规范标准是个编写良好、容易阅读文档但有些内容仍然需要解释例如在草案 6 的前checkedout 集合内容比较模糊该集合包含是签出对象 Private Working Copies (PWCs) 还是签出对象本身?区别供应商对这解释都区别并根据它们解释来实现那个特定问题已经解决(PWCs 是正确答案如果您感到好奇话)但您可以从中看出那可能会使编写个互操作客户端出现困难

  不良假设某个供应商对 CMIS 规范标准扩展有时明显有时不明显如果您针对个服务器编码您 API 并将其视为参考实现那么您就等于做出了这样个假设:其他实现将以相同方式运行现在问题是:还没有个带有 AtomPub 绑定 CMIS 参考实现是功能齐备且 100% 遵守 CMIS 规范标准

  几个举例

  本系列下篇文章将通过详细介绍个 Python 脚本来展示 cmislib 库实际应用该脚本用于批量加载个包含数字资产和元数据 CMIS 存储库下面基本举例来自 cmislib 文档用于展示如何使用这个 API 从 Python 交互式 shell 执行些常见操作其中包括:获取存储库相关信息使用 Folders 和 Documents以及通过 CMIS 查询、路径、对象 ID 或关系查找对象

  获取个存储库对象

  对于使用 CMIS 存储库能够完成任务来说CmisClient 和 Repository 对象是共同出发点获取个例子很简单 —— 只需知道存储库服务 URL 和凭证操作步骤如下:

  在命令行中输入 python 并按下 Enter 键启动 Python shell

  导入 CmisClient:

>>> from cmislib.model import CmisClient 

  将 CmisClient 指向存储库服务 URL:

>>> client = CmisClient('http://cmis.alfresco.com/s/cmis', 'admin', 'admin') 

  存储库都拥有个 ID — 如果您知道这个 ID也可以通过 ID 获取存储库在本例中我们将询问客户端以获取默认存储库 ID

>>> repo = client.defaultRepository 
>>> repo.id u'83beb297-a6fa-4ac5-844b-98c871c0eea9' 


  现在可以获取存储库属性了下面 for 循环将列示 cmislib 知道有关存储库所有信息(为了更加简洁我截短了输出清单)

>>> repo.name u'Main Repository' 
>>> info = repo.info 
>>> for k,v in info.items: 
  ...   pr "%s:%s" % (k,v) 
  ...   
  cmisSpecicationTitle:Version 1.0 Committee Draft 04 
  cmisVersionSupported:1.0 
  repositoryDescription:None 
  productVersion:3.2.0 (r2 2440) 
  rootFolderId:workspace://SpacesStore/aa1ecedf-9551-49c5-831a-0502bb43f348 
  repositoryId:83beb297-a6fa-4ac5-844b-98c871c0eea9 
  repositoryName:Main Repository 
  vendorName:Alfresco 
  productName:Alfresco Repository (Community) 


  使用 Folders 和 Documents

  获取 Repository 对象的后现在可以开始使用存储库中对象比如 Folders 和 Documents

  在 root 中创建个新文件夹如果您直在跟随我们操作那么为您文件夹提供个独特名称如果要针对个公共存储库进行测试

>>> root = repo.rootFolder 
>>> someFolder = root.createFolder('someFolder') 
>>> someFolder.id 
u'workspace://SpacesStore/91f344ef-84e7-43d8-b379-959c0be7e8fc' 


  然后您可以创建些内容:

>>> someFile = open('test.txt', 'r') 
>>> someDoc = someFolder.createDocument('Test Document', contentFile=someFile) 


  如果愿意可以转储新创建文档属性(以下是部分清单):

>>> props = someDoc.properties 
>>> for k,v in props.items: 
...   pr '%s:%s' % (k,v) 
... 
cmis:contentStreamMimeType:text/plain 
cmis:creationDate:2009-12-18T10:59:26.667-06:00 
cmis:baseTypeId:cmis:document 
cmis:isLatestMajorVersion:false 
cmis:isImmutable:false 
cmis:isMajorVersion:false 
cmis:objectId:workspace://SpacesStore/2cf36ad5-92b0-4731-94a4-9f3fef25b479 


  获取对象

  可以通过以下几种方式来获取个对象:

  运行个 CMIS 查询

  请求存储库提供个对应特定路径或对象 ID 对象

  使用个文件夹 “子” 文件夹和/或 “后代” 文件夹遍历存储库

  获取通过个关系绑定在源对象和目标对象

  下面些举例展示了如何使用这些选项:

  通过全文本搜索查找上小节中创建文档

>>> results = repo.query("select * from cmis:document where contains('test')") 
>>> for result in results: 
...   pr result.name 
... 
Test Document2 
Example test script.js 


  或者也可以通过对象路径获取对象例如:

>>> someDoc = repo.getObjectByPath('/someFolder/Test Document') 
>>> someDoc.id 
u'workspace://SpacesStore/2cf36ad5-92b0-4731-94a4-9f3fef25b479' 


  或者使用对象 ID例如:

>>> someDoc = repo.getObject('workspace://SpacesStore/2cf36ad5-94a4-9f3fef25b479') 
>>> someDoc.name 
u'Test Document' 


  Folder 对象拥有 getChildren 和 getDescendants 思路方法用于返回个可分页结果集:

>>> children= someFolder.getChildren 
>>> for child in children: 
...   pr child.name 
... 
Test Document 
Test Document2 


  Folders 和 Documents 都有个 getRelationships思路方法用于返回组 Relationship 对象Relationship 对象能够在关系端提供源对象和目标对象

>>> rel = testDoc.getRelationships(SubRelationshipTypes='true')[0] 
>>> rel.source.name 
'testDoc1' 
>>> rel.target.name 
'testDoc2' 
>>> rel.properties['cmis:objectTypeId'] 
'R:sc:relatedDocuments' 


  在本系列篇文章中您将看到如何使用这个 API 其他部分包括检索对象类型定义能力

  结束语

  本文简要介绍了 cmislib它从何而来它能做什么以及些基本例子希望本文激发了您对 CMIS 兴趣并打算深入了解它如果您对 Python 感兴趣不妨好好了解 cmislib如果没兴趣可探究这里列出其他工具和客户端库最后CMIS 社区需要您您可以从很多方面帮助该社区:

  如果没有用您喜欢语言编写客户端库您就编写个作为开源项目

  帮助您存储库供应商测试他们 CMIS 实现



  为您喜欢门户或呈现框架编写集成以使的容易处理 CMIS 库

  努力钻研现有开源 CMIS 项目(比如 cmislib)和 Apache Chemistry

  通过加入 OASIS Technical Committee为 CMIS 规范标准贡献力量



Tags:  pythonapi中文 pythonapichm python简介 pythonapi

延伸阅读

最新评论

发表评论