Apache Lenya 2.0 介绍与核心概念

系统介绍 Apache Lenya 2.0 的项目历史、Cocoon 2.1 管线架构、Publication、内容模型、访问控制与发布工作流等核心概念,并给出实测运行证据与退役状态说明。

Apache LenyaLenyaApacheCMS2026/10/15

Apache Lenya 2.0 介绍与核心概念

重要状态说明:Apache Lenya 已于 2015 年 4 月经董事会决议退役,2015 年 7 月正式迁入 Apache Attic(退役项目档案馆)。终版为 2.0.4,基于 Cocoon 2.1 体系构建。本项目当前适合学习研究与传统系统(遗产系统)维护,不建议任何新项目采用。

一、项目背景与历史沿革

1.1 从 Lenya 到 Apache 顶级项目

Apache Lenya 是一个基于 Java 与 XML 的开源内容管理系统(Content Management System,CMS),最初由瑞士的 Wyona 公司于 2001 年前后启动开发,早期名称为 "Wyona CMS"。2003 年它进入 Apache 软件基金会孵化器,2004 年 9 月正式成为 Apache 顶级项目(Top Level Project)。Lenya 的设计目标是让非技术编辑人员也能安全地维护结构化的 XML 网站内容,同时为开发人员保留高度可定制的 XML 处理管线。

2010 年前后发布的 2.0.4 是 Lenya 的最后一个官方发行版本,构建于 Apache Cocoon 2.1 体系之上。此后项目活跃度持续下降,最终在 2015 年 4 月 22 日的 Apache 董事会会议上决议退役,同年 7 月完成向 Apache Attic 的迁移。Attic 中的项目官网、源码仓库与邮件列表均转为只读归档。

1.2 终版与获取途径

由于项目退役,官方下载入口已迁移到归档服务器。在实验服务器(Ubuntu 22.04)上通过归档地址可以取到两个有代表性的发行物:

  • 源码版:apache-lenya-2.0.4-src.zip(约 111 MB,MD5 校验值 0e125533edaf35187c025bc01849f285);
  • 历史二进制版:apache-lenya-1.2.5-bin.tar.gz(约 31 MB,面向更早一代的 Tomcat 环境)。

本文的所有结论均在 Ubuntu 22.04 服务器上实测验证:使用 JDK 8(openjdk 8u504)通过 Ant 构建源码 2.0.4 成功,随后以内置 Jetty 5.1.4 与外置 Tomcat 9 两种方式分别启动,页面均正常访问。响应头中可以清晰看到版本信息,例如内置 Jetty 方式下的响应头:

HTTP/1.1 200 OK
Server: Jetty/5.1.4 (Linux/6.8.0-138-generic amd64 java/1.8.0_504
X-Cocoon-Version: 2.1.12-dev
Content-Type: text/html; charset=utf-8

页面标题为 "Apache Lenya - Content Management System"。这说明即使跨越十余年,Lenya 2.0.4 仍能在现代 Linux 发行版上完成构建并运行,只是需要处理若干兼容性问题(详见本专栏安装部署篇的排障实录)。

二、整体架构:站在 Cocoon 2.1 的肩膀上

2.1 XML 发布框架底座

Lenya 2.0 并没有自己发明 Web 框架,而是完整构建在 Apache Cocoon 2.1 之上。Cocoon 是 Apache 老一代 XML 发布框架,其核心思想是"管线(Pipeline)":一个请求依次经过生成器(Generator,把来源转成 SAX 事件流)、若干转换器(Transformer,如 XSLT)、再到序列化器(Serializer,输出 HTML/XML/PDF 等)。Lenya 的页面渲染、编辑表单、导航菜单全部由这样的管线组合完成。

在实测中,构建产物 build/lenya/webapp/WEB-INF/lib 目录下共有 174 个 jar 包,几乎全部来自 Cocoon 体系,例如 avalon-framework、cocoon-core 相关模块、xalan-2.7.1、xercesImpl-2.9.1 等。同时 webapp 内部还保留着完整的 Cocoon 2.1.12-dev 源码与构建脚本(externals/cocoon_2_1_x 目录),构建 Lenya 时会先组装 Cocoon,再把 Lenya 的模块补丁叠加进去。这也解释了为什么 Lenya 2.0.4 的源码包体积超过 100 MB——它实际捆绑了一个完整的 Cocoon 源码树。

"模块补丁叠加"并非一句空话,构建日志里能看到具体的叠加动作。实测构建日志的 prepare-cocoon 阶段依次执行三项配置补丁并写回最终 web.xml:

PATCH web.xml
Processing: src/confpatch/form-encoding.xweb
Processing: src/confpatch/overwrite-uploads.xweb
Processing: src/confpatch/upload-max-size.xweb
Writing: build/lenya/webapp/WEB-INF/web.xml

三个补丁文件分别负责表单编码、上传覆盖策略与上传大小上限,也就是说 Lenya 对 Cocoon 的 web.xml 做了体系化的再加工,而不是简单复用。随后的 webapp 阶段复制默认出版站点(日志中可见 Copy publication 指向 src/pubs/default),compile-src 阶段把 Lenya 本体源码编译为 WEB-INF/lib/lenya-2.0.4-impl.jar,prepare-modules 阶段则把全部模块的 sitemap 片段(generators、transformers、comments 等 xmap)经 XSLT 处理后合并进根 sitemap.xmap。理解这条装配线,就能明白为什么 Lenya 的 sitemap.xmap 体积巨大:它是三十余个模块的组件声明与管线片段动态拼装的结果。

2.2 Avalon 与组件模型

Cocoon 2.1 建立在 Apache Avalon 组件框架之上:组件通过角色(Role)与提示(Hint)查找,例如实测日志中反复出现的

DEBUG core.manager - IdentityMapImpl::get() looked up type [publication], key [default] in map, is it there ? true

就是 Avalon 风格的组件池在按 "publication/default" 这样的键取组件。理解这一点对阅读 Lenya 源码非常重要:大多数功能类都实现了 Serviceable 接口,通过 this.manager.lookup(Xxx.ROLE) 获取协作对象。

2.3 Servlet 运行环境层

Lenya 2.0.4 既是独立的 Web 应用(webapp 目录即一个可直接部署的 Servlet 应用),也自带一个 Jetty 5.1.4 以便"开箱即跑"。官方启动脚本 lenya.sh 拉起 Java 进程的方式颇有年代特色:它不直接调用 java 主类,而是借助发行包 tools/loader 下的自定义 Loader 类,把主类名与 jar 仓库位置以系统属性传入。实测重启后 ps -ef | grep [j]ava 得到的完整命令行如下(换行仅为排版):

/usr/lib/jvm/java-8-openjdk-amd64/bin/java -Xms256M -Xmx512M -Djava.awt.headless=true
-Djava.version=1.6 -cp ./tools/loader -Djava.endorsed.dirs=./build/lenya/webapp/WEB-INF/lib/endorsed
-Dorg.xml.sax.parser=org.apache.xerces.parsers.SAXParser -Djetty.port=8888
-Dloader.jar.repositories=./tools/jetty/lib:./tools/jetty/extra/lib:./tools/jetty/extra/ext:./tools/jetty/ext
-Dwebapp=./build/lenya/webapp -Dhome=. -Dorg.mortbay.util.URI.charset=ISO-8859-1
-Dloader.main.class=org.mortbay.jetty.plus.Server Loader ./tools/jetty/conf/main.xml

从这条命令行可以读出大量运行机制:堆参数 -Xms256M -Xmx512M 来自脚本内 JAVA_OPTIONS 变量;endorsed 目录指向 webapp 内置的 WEB-INF/lib/endorsed,用于让 Xalan/Xerces 等老版本 XML 组件优先于 JDK 自带版本加载;jetty.port=8888 以系统属性注入 Jetty 配置;真正的主类 org.mortbay.jetty.plus.Server 通过 loader.main.class 交给 Loader 反射调用,配置文件是 tools/jetty/conf/main.xml。脚本中还定义了 CLI 变量指向 org.apache.lenya.Main,说明同一脚本也能以命令行模式运行管理任务,只是默认动作是 servlet。

生产部署时也可以把它作为 ROOT 应用放进 Tomcat(实测 Tomcat 9 可用,需要把 WEB-INF/lib/endorsed 子目录下的 jar 上移到 WEB-INF/lib,并在 JRE 的 lib/endorsed 目录放置 Xalan/Xerces)。这种"一个 Web 应用、两种 Servlet 运行环境"的形态在 2010 年代的 Java CMS 中非常典型。

2.4 模块化体系

Lenya 的功能不是一个大单体,而是由 src/modules 下的功能模块拼装而成,构建时统一注册进 sitemap 与组件配置。实测源码树中的模块清单如下:

bxe cforms collection contactform development editors export fckeditor kupu
languageselector lenyadoc linkcheck links lucene menubar navigation neutron
news notification opendocument prettyprinting profiling resource sitetree
sourcerepository svg tinymce usecasedocument webdav xhtml

这份清单本身就是 Lenya 能力版图的速览:编辑器就有 bxe、kupu、fckeditor、tinymce、editors 五个条目,说明项目在所见即所得编辑上反复尝试过多代方案;lucene 是基于 Apache Lucene 的全文检索集成,linkcheck 是发布前链接有效性检查,news 与 notification 分别对应新闻列表与通知,webdav 与 neutron 提供远程内容读写通道,opendocument 与 svg 是富文档格式支持,cforms 承载表单,sitetree 与 navigation 支撑导航树,sourcerepository 则是仓库访问的底层抽象。模块数量多而边界清晰,这是它作为"框架型 CMS"区别于"应用型 CMS"的直接证据——部署者按需裁剪模块,而不是接受整套固定功能。

三、核心概念一:Publication(出版站点)

3.1 什么是 Publication

Publication 是 Lenya 最顶层的组织单元,可以理解为"一个完整的站点/频道"。一个 Lenya 实例可以承载多个 Publication,每个 Publication 拥有独立的内容仓库、导航树、用户与策略、工作流定义。发行版自带一个名为 default 的示例出版站点,位于 webapp 之下的 lenya/pubs/default 目录:

build/lenya/webapp/lenya/pubs/default/
├── config/
│   ├── access-control/     # 用户、组、策略
│   │   ├── passwd/
│   │   └── policies/
│   └── workflow/workflow.xml
├── content/
│   ├── authoring/          # 编辑区内容
│   └── live/               # 已发布内容
├── sitemap.xmap
└── lenya.xconf

实测中,创建新页面后 sitetree.xml(编辑区导航树)会立即出现新节点:

<node id="myfirst" uuid="12a85db0-c80d-11f1-b628-e0673eff98e1">
  <label xml:lang="en">My First Lenya Page</label>
</node>

3.2 双区模型:authoring 与 live

每个 Publication 划分为若干"区(Area)",最核心的是两个:

  • authoring(编辑区):编辑人员日常工作的场所,页面可以处于草稿、待审等工作流状态,内容随时可改;
  • live(发布区):面向公众的正式站点,只有经过工作流发布的内容才会出现在这里。

实测演示了完整链路:新页面在发布前访问 /default/live/index.html 返回 404;执行发布工作流之后同一 URL 返回 200,标题正常显示。authoring 与 live 在文件系统上对应 content/authoring 与 content/live 两个目录,内容文件按 UUID 组织(每个文档一个 UUID 目录,其中 en 子目录是英文内容,en.meta 保存元数据,en.rcml 保存修订控制信息)。

此外还有 trash(回收站)、archive(归档)两个辅助区,供删除与归档操作使用,构成完整的文档生命周期。

四、核心概念二:内容模型

4.1 资源类型(Resource Type)

Lenya 中的每一页都有一个"资源类型",决定其编辑表单、XML Schema 与渲染方式。默认出版站点内置 xhtml(普通页面)、links、cforms、opendocument 等类型。实测创建页面时,请求必须显式携带 doctype=xhtml 参数,否则服务端会抛出

UsecaseException: ResourceTypeSelector/null
Attempted to retrieve component with null hint

这正是 Avalon 组件按提示查找的报错形态:资源类型选择器没有拿到类型名。

4.2 XML 内容与 Dublin Core 元数据

页面正文是 XHTML 片段,元数据采用都柏林核心(Dublin Core)词汇:标题、创建者、主题、出版者、权利等。实测创建页面的表单字段即包括 dublincore.title、dublincore.creator、dublincore.subject、dublincore.publisher、dublincore.rights。落盘后的内容文件头部带有这些元数据,正文则是干净的 XHTML。这种"元数据与正文分离但同文件存储"的做法,让 Lenya 可以直接用 XSLT 做各种聚合(列表页、RSS、导航等)。

en.meta 文件内部并非只有都柏林核心一组。实测读取新建页面的 en.meta,其结构是三个 element-set 的组合:第一组挂在 http://purl.org/dc/elements/1.1/ 命名空间下,存放 date、creator、title 等都柏林核心字段;第二组挂在 workflow 命名空间下,存放工作流历史,每执行一次事件就追加一条形如 "1 event:submit state:review user:lenya machine:127.0.0.1 var:is_live=false" 的记录,序号即工作流版本号,事件、目标状态、操作者、来源地址与变量取值全部入账;第三组挂在 document 命名空间下,登记 extension、mimeType、contentType 与 resourceType。这份文件因此同时是元数据中心、工作流台账与资源类型登记表,排查"页面处于什么状态、谁改的"时它是最直接的证据源。

4.3 多语言

Lenya 原生支持多语言:同一 UUID 文档目录下按语言代码存放内容(en、de 等),导航树 label 带 xml:lang 属性,编辑界面可切换语言。站点管理用例中还提供 deleteLanguage(删除某语言版本)等操作。

4.4 sitetree 与 UUID

站点结构保存在 content/authoring/sitetree.xml 与 content/live/sitetree.xml。节点用 id 与 uuid 标识,uuid 保证文档在重命名、移动后引用不断链。内部渲染管线会把 URL(如 /default/authoring/myfirst.html)映射到 uuid,再把 uuid 解析回 URL,实测日志中可以看到 UuidToUrlTransformer 与 uuid2url 的调用痕迹。

实测导入示例内容后的 sitetree.xml 还暴露了三个结构细节。其一,根元素 site 带 revision="2" 属性,说明站点树自身也有版本号,每次结构变更递增;其二,每个节点可携带多个 xml:lang 不同的 label(示例内容同时给出 de 与 en 两组标签),节点还可带 visibleinnav="false" 属性把"存在但不进导航"的页面(如搜索页)与普通页面区分开;其三,节点支持任意层级嵌套,示例站点在 doctypes 之下又挂了 xhtml-document、links、opendocument、cforms 四个子节点,导航菜单的多级结构完全由这份文件的嵌套关系表达。新建页面时若勾选了 visibleInNav,节点会被追加到树尾并携带标题作为标签,实测新节点形如 id="fresh2026" 且只带 en 一种语言的标签,与导入内容的双语标签形成直观对照。

五、核心概念三:访问控制

5.1 用户、组、角色、策略四层模型

Lenya 的权限体系由四个概念组成:

  • 用户(User):如默认账号 lenya、alice,口令以 MD5 摘要存储于 config/access-control/passwd 下的 .iml 文件;
  • 组(Group):默认有 editor、reviewer、sitemanager、admin;
  • 角色(Role):visit、edit、review、publish、sitemanager、admin、session 等;
  • 策略(Policy):按 URL 子树把组映射为角色,如 authoring/subtree-policy.acml 中规定 editor 组获得 edit 角色、reviewer 组获得 review 角色。

实测证明这套模型是强约束:默认的 lenya 账号虽属 admin 组,但该组在 authoring 子树只被授予 admin 角色;工作流的 publish 事件要求文档 URL 上同时具备 edit 与 review 角色,因此发布请求被拒,页面提示

The event publish is not executable on document "My First Lenya Page"

直到把 lenya 用户加入 reviewer 组(.iml 中补充 <group>reviewer</group>)并重启后,发布按钮才出现,发布才成功。这是理解 Lenya 权限模型最有说服力的实证。

5.2 用例级策略

除 URL 级策略外,还有 usecase-policies.xml 对每个"用例"(登录、登出、编辑、发布等)声明所需角色,例如 tab.revisions 用例要求 sitemanager 或 edit 角色。策略解析发生在 UsecaseServlet 与菜单变换器中,实测日志里 UsecaseMenuTransformer 会逐一检查菜单中各用例的前置条件。

身份体系的落盘文件值得单独展开,实测 passwd 与上层 access-control 目录中的文件按后缀分为五类:.iml 是用户身份文件(lenya.iml、alice.iml),.gml 是组定义文件(admin.gml、editor.gml、reviewer.gml、sitemanager.gml),.rml 是角色登记文件(admin、edit、review、session、sitemanager、visit 各一个),.ipml 是机器身份文件(localhost.ipml,把来源地址当作访问主体),access-control.xml 则是全局装配入口。目录里还保留着 cifs.properties.sample 与 ldap.properties.sample 两个样例配置,说明 Lenya 设计上支持把 CIFS 域与 LDAP 目录接入为身份源,账号体系并非只能本地文件维护——对遗产系统而言,这意味着接手时先要弄清线上实际启用了哪种身份源,不能只盯着 passwd 目录。

六、核心概念四:版本控制与工作流

6.1 校验出/校验入(Checkout / CheckIn)

Lenya 对每个文档维护修订控制信息(RCML 文件)。编辑前必须 checkout,保存后 checkin,形成带时间戳与操作者身份的版本链。实测文档的 en.rcml 内容形如:

<XPSRevisionControl xmlns="">
  <CheckIn backup="true" identity="lenya" session="12a7c170-c80d-11f1-b628-e0673eff98e1" time="1792009389851" version="1"/>
  <CheckOut identity="lenya" session="213900f0-c80d-11f1-b628-e0673eff98e1" time="1792009414272"/>
</XPSRevisionControl>

若文档处于被某会话 checkout 的状态,其他会话的保存会被拒绝,日志出现 "lockInvolvedObjects() can not execute, object [node lenya://lenya/pubs/default/content/authoring/12a85db0-c80d-11f1-b628-e0673eff98e1/en] is already checked out"。编辑后的保存通过 WebDAV 风格的 PUT 完成,实测以 PUT 提交 XHTML 到 /default/authoring/myfirst.xml?lenya.module=webdav&lenya.event=edit 得到 204 No Content,正文立即更新。

6.2 工作流状态机

默认工作流定义了五个状态:authoring(初始)、review、live、trash、archive,以及连接它们的转换(transition),例如 authoring→review(事件 submit)、review→live(事件 publish)、live→authoring(事件 deactivate)。每个转换可带角色条件,publish 转换要求 review 与 edit 角色。实测中提交工作流后页面状态栏显示

Workflow State: review
not live

发布成功后同一位置显示 live,且 live 区页面可访问。整个流程把"编辑、审核、发布"三种职责分离开,是 Lenya 区别于简单静态生成器的核心能力。

完整的工作流定义文件(config/workflow/workflow.xml)中声明的转换远不止三条,实测逐条提取如下:authoring→review 由 submit 事件触发、要求 edit 角色;authoring→live 由 publish 事件触发、同时要求 review 与 edit 两个角色;review→live 同样由 publish 触发、只要求 review 角色;review→authoring 由 reject 事件触发、要求 review 角色,这就是审核驳回通道;live→authoring 与 authoring→authoring 均由 edit 事件触发、要求 edit 角色,前者对应"下线回编辑区",后者对应"已上线内容继续修改"。三条路径到 live 的差异设计很能说明问题:从 authoring 直接发布的文档必须同时具备编辑与审核两种角色授权,相当于强制一人分饰两角时系统仍要求双重授权;而从 review 区发布的文档只需审核角色,职责分离就此落地。trash 与 archive 两个状态由删除、归档用例驱动,不在主工作流文件中展开。

七、源码与构建形态

7.1 源码包结构

2.0.4 源码包解压后包含:src/(Lenya 本体 Java 源码与模块)、externals/cocoon_2_1_x/(捆绑的 Cocoon 源码与预构建 webapp)、tools/(内置 Ant 1.7.1)、build.properties(构建属性模板)、build.sh 与 lenya.sh(构建与启动脚本)。构建属性中可见 src.java.version=1.4 等历史烙印。

7.2 构建与启动实测

实测构建主线为:复制 build.properties 为 local.build.properties,同步 src/cocoon 下的 local.build.properties 与 local.blocks.properties 到 externals/cocoon_2_1_x,安装 patch 工具,然后执行 ./build.sh,日志显示

check-build-properties:
 BUILD PROPERTIES OK
cocoon:
Copying 35 files to /opt/lenya-src/apache-lenya-2.0.4-src/build/lenya/webapp/WEB-INF/lib
BUILD SUCCESSFUL
Total time: 3 seconds

随后 ./lenya.sh 以 Jetty 启动,8888 端口即可访问。构建脚本自带 -Xmx512M 的内存配置,与本文实测堆参数一致。完整的依赖、校验、排障与卸载过程见本专栏安装部署篇。

7.3 启动脚本的端口管理与管理端口疑点

lenya.sh 对端口的管理完全由构建属性文件驱动,实测脚本中的取值逻辑是:先读 local.build.properties 中的 web.app.server.jetty.port 与 web.app.server.jetty.admin.port,缺省时回退读 build.properties,随后以系统属性 -Djetty.port 与 -Djetty.admin.port 注入 Jetty 配置。实测属性文件第 76、77 行的值分别为 8888 与 8889,启动日志同步打印:

INFO: Jetty Port is 8888
INFO: Jetty Admin Port is 8889

构建成功的横幅里也会提示 "(Jetty Admin Port: 8889)"。但需要澄清一个容易误判的现象:8889 是 Jetty 管理监听器的预留端口,其配置位于 tools/jetty/conf/admin.xml,而默认启动只加载 main.xml,admin.xml 并未被装配,实测对 8889 端口发起请求得到连接拒绝。因此把 8889 当作现成的管理接口去探测会扑空;若确需该管理监听器,要自行把 admin.xml 加入启动配置。这个细节对遗产系统巡检很有价值——端口表里出现的 8889 不等于真实暴露的服务面。

另一个值得记录的兼容性处理是 -Djava.version=1.6。老版 ICU4J 在初始化时会解析系统属性 java.version,遇到 "1.8.0_504" 这类更新号超过 255 的字符串会抛出版本号越界异常;实测环境在 JAVA_OPTIONS 中追加该参数,把属性伪装成 1.6,使 ICU4J 的解析路径恢复正常。它与"注释掉 DELI 组件"是同一问题的两条解决路线,实践中二选一即可,实测环境两者叠加使用,运行稳定。

八、适用场景与局限

Lenya 的价值定位在今天已经非常明确:其一,作为学习材料,它是研究 XML 发布管线、Avalon 组件模型、双区内容仓库与角色化工作流的完整标本;其二,作为维护参考,极少数仍在运行的老站点需要依据本文这类实测资料排障升级。其局限同样明显:技术栈停留在 Servlet 2.4/XSLT 1.0 时代,现代前端、REST 化程度低,社区已停止维护,安全补丁不再发布。因此,除学习与遗产系统维护外,不建议在新建项目中采用。

与同年代的 Java 内容管理系统横向比较,Lenya 的选型特征更加清晰。同属 Apache 的 Roller 把重心放在博客场景,内容模型简单、部署轻便,但缺少 Lenya 这种"多出版站点 + 双区仓库 + 角色化工作流"的结构化能力;彼时商业化的 Hippo(后并入 Bloomreach)与 Magnolia 走的也是"仓库加渲染"路线,Hippo 后演化为基于 Apache Jackrabbit 的 JCR 生态,而 Lenya 坚持文件系统加 XML 的直读仓库,省去了 JCR 规范的复杂度,代价是并发写与集群能力薄弱;与同期主流的 PHP 系 CMS(Drupal 等)相比,Lenya 的 XML 管线让内容的结构化程度与可转换性更高,但开发门槛与运维成本也明显更高。放回今天看,语义化的内容仓库、发布工作流、多出版站点隔离这些概念在无头 CMS 与静态站点生成器中又以新形态回归,Lenya 因此更像一面可供对照的镜子——它验证过哪些设计经得起时间检验(UUID 锚定、双区隔离),哪些设计被时代淘汰(Avalon 组件池、XSLT 重度渲染)。

九、常见问题解答(FAQ)

Q1:Lenya 退役后还能从官方渠道下载吗? 可以。归档地址 https://archive.apache.org/dist/lenya/ 下仍保留 SOURCES 与 BINARIES 目录,实测下载 apache-lenya-2.0.4-src.zip 的 MD5 与官方归档页一致(0e125533edaf35187c025bc01849f285)。

Q2:Lenya 2.0.4 需要什么 Java 版本? 官方声明为 JDK 1.5/1.6 时代,但实测 openjdk 8u504 可完成构建并稳定运行(内置 Jetty 与 Tomcat 9 两种方式均通过)。构建期对老版 ICU4J 的兼容问题需要小处理,详见安装部署篇。

Q3:Publication 和传统 CMS 的"站点"有什么区别? Publication 是"站点 + 权限域 + 工作流域"的组合体,多 Publication 之间完全隔离,各自拥有独立的内容仓库与用户体系,类似多租户的最小单元。

Q4:authoring 与 live 是物理隔离还是逻辑隔离? 物理隔离。两者是 content/ 下的不同目录树,各自有独立的 sitetree.xml 与内容文件,只有工作流发布动作会把 authoring 内容复制/推进到 live,实测发布前 live 页面 404、发布后 200。

Q5:为什么 admin 账号不能直接发布? Lenya 的角色按 URL 策略授予组而非按用户等级。authoring 子树策略中 admin 组只有 admin 角色,而 publish 转换要求 edit + review 双角色,二者分别授予 editor 与 reviewer 组。实测把 lenya 用户加入 reviewer 组后发布才成功。

Q6:页面内容为什么按 UUID 目录存放? UUID 是文档的稳定标识,重命名、移动只改 sitetree 中的 id 与 URL,内部链接与修订历史都以 UUID 为锚,避免断链。实测新建页面立即获得形如 12a85db0-c80d-11f1-b628-e0673eff98e1 的 UUID 目录。

Q7:Lenya 支持所见即所得编辑吗? 支持。编辑区提供 BXE(Bitflux Editor)与 Kupu 两个基于 JavaScript 的 WYSIWYG 编辑器菜单项,实测页面菜单中存在 bxe.edit 与 kupu.edit 用例;编辑内容最终经 WebDAV PUT 保存(实测 PUT 返回 204)。

Q8:修订控制在文件层面如何体现? 每个文档目录下有 .rcml 文件记录 CheckOut/CheckIn 历史,含操作者、会话与时间戳;CheckIn 带 backup="true" 时会生成历史版本文件(如 en.1792009389851.bak)。异常退出会留下悬挂的 CheckOut 记录,需按安装部署篇的方法清理。

Q9:Lenya 2.0.4 可以部署到 Tomcat 9 吗? 实测可以。关键动作有三步:把 WEB-INF/lib/endorsed 目录下的 jar(jakarta-regexp、xalan、xerces 等)上移到 WEB-INF/lib;在 JRE 的 lib/endorsed 放置 xalan-2.7.1、xercesImpl-2.9.1、xml-apis;以 ROOT 应用部署。堆内存按 -Xms256M -Xmx512M 配置并经 ps 确认。

Q10:还在维护吗?有新版本计划吗? 没有。项目已于 2015 年迁入 Attic,官网与源码仓库只读,Bugzilla 与邮件列表归档。任何"新版本"均非 Apache 官方产物。

Q11:构建横幅显示 Java version 1.6,是不是真的用了 JDK 6? 不是。横幅中的版本号由 Ant 1.7.1 打印,该版 Ant 发布早于 Java 7/8,版本探测表里没有登记新版本号,运行在更高版本 JDK 上时会按 1.6 上限显示。实测构建与运行均在 OpenJDK 8 上完成,产物可正常运行,横幅数字只反映 Ant 的探测局限,与实际 JDK 无关。

Q12:8889 端口能当管理接口用吗? 不能直接用。属性文件虽预留 web.app.server.jetty.admin.port=8889,构建横幅也会提示该端口,但默认启动只装配 tools/jetty/conf/main.xml,admin.xml 中的管理监听器未启用,实测 8889 连接被拒绝。需要时须自行修改启动配置把 admin.xml 装配进来。

十、参考链接

  1. Apache Attic 项目页(退役信息):https://attic.apache.org/projects/lenya.html
  2. Lenya 归档下载(源码与二进制):https://archive.apache.org/dist/lenya/
  3. Lenya 网站归档:https://lenya.apache.org/
  4. Lenya 源码仓库(只读 SVN):https://svn.apache.org/repos/asf/lenya/
  5. Lenya 邮件列表归档:https://mail-archives.apache.org/mod_mbox/lenya-dev/
  6. Lenya Wiki 归档:https://cwiki.apache.org/confluence/spaces/LENYA/
  7. Apache Cocoon 项目:https://cocoon.apache.org/
  8. Lenya Bugzilla 归档:https://bz.apache.org/bugzilla/buglist.cgi?product=Lenya