系列文章目录
第一章 初见 Apache Jackrabbit
第二章 Apache Jackrabbit 入门
第三章 Repository 配置文件
第四章 Apache Jackrabbit 文件存储
第五章 Apache Jackrabbit 版本管理
欢迎第一次进入 Jackrabbit 的世界!本介绍让您亲身体验 Jackrabbit 和 JCR API。
一、环境搭建
开始使用 Jackrabbit 的最简单方法是 下载 可运行的独立服务器jar。除了运行它之外,您还可以将其放入您的类路径中,以快速访问下面您需要的所有类和接口。或者,如果您使用Apache Maven 构建系统(我们推荐),您可以使用以下依赖项设置您的第一个 hops 项目。
<dependencies>
<!-- The JCR API -->
<dependency>
<groupId>javax.jcr</groupId>
<artifactId>jcr</artifactId>
<version>2.0</version>
</dependency>
<!-- Jackrabbit content repository -->
<dependency>
<groupId>org.apache.jackrabbit</groupId>
<artifactId>jackrabbit-core</artifactId>
<version>2.20.10</version>
</dependency>
<!-- Use Log4J for logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.4</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.14.2.0</version>
</dependency>
</dependencies>
二、登录 Jackrabbit
那么让我们开始吧。作为热身,我们将创建一个 Jackrabbit 内容存储库并启动登录会话来访问它。执行此操作的完整示例应用程序如下所示,稍后将逐行说明。
import javax.jcr.GuestCredentials;
import javax.jcr.Repository;
import javax.jcr.Session;
import org.apache.jackrabbit.commons.JcrUtils;
/**
* First hop example. Logs in to a content repository and prints a
* status message.
*/
public class FirstHop {
/**
* The main entry point of the example application.
*
* @param args command line arguments (ignored)
* @throws Exception if an error occurs
*/
public static void main(String[] args) throws Exception {
Repository repository = JcrUtils.getRepository();
Session session = repository.login(new GuestCredentials());
try {
String user = session.getUserID();
String name = repository.getDescriptor(Repository.REP_NAME_DESC);
System.out.println(
"Logged in as " + user + " to a " + name + " repository.");
} finally {
session.logout();
}
}
}
您还可以将源文件下载为FirstHop.java. 如果您设置了类路径,则可以使用 javac 编译应用程序FirstHop.java 并使用 java FirstHop 运行它以获得以下输出。
Logged in as anonymous to a Jackrabbit repository.
除了生成上述状态行之外,应用程序还将默认存储库配置文件复制到子目录repository.xml中并创建初始 Jackrabbit 内容存储库jackrabbit。您可以使用系统属性org.apache.jackrabbit.repository.conf来 org.apache.jackrabbit.repository.home设置备用配置文件和存储库目录位置。
请继续阅读 FirstHop 应用程序的详细分类:
import javax.jcr.Repository;
import javax.jcr.Session;
JCR API 接口位于 jcr-2.0.jar 库中的 javax.jcr 包中。JCR API 的承诺是,如果您仅在内容应用程序中使用这些接口,那么它应该在很大程度上独立于底层内容存储库实现。
Repository 接口代表给定的内容存储库实例,Session接口代表用于访问存储库的 单个登录会话。访问存储库中的任何内容都需要会话。
请注意,Session 实例不能保证线程安全,因此如果您需要从不同线程同时访问存储库内容,则应该启动多个会话。这对于 Web 应用程序等事物尤其重要。
import org.apache.jackrabbit.commons.JcrUtils;
部署 Jackrabbit 的最佳实践是在容器环境中使用 JNDI 或其他一些配置机制,以使应用程序代码不受 Jackrabbit 的直接依赖,但由于我们正在创建一个简单的独立应用程序,因此我们可以通过使用JcrUtils 类来走捷径长耳大野兔的共同点。
public class FirstHop
public static void main(String[] args) throws Exception
FirstHop 示例是一个简单的独立应用程序,非常适合该main()方法,并让 JVM 处理可能的异常。更丰富的内容应用程序还可以编写为具有不同设置和错误处理模式的 Web 应用程序或 EJB 组件。
Repository repository = JcrUtils.getRepository();
该JcrUtils.getRepository()方法返回一个实现 JCR Repository 接口的对象。实际的实现取决于类路径上可用的 jar 文件,在本示例中是TransientRepository。该实现包含一个实用程序功能,该功能将在第一个会话启动时处理初始配置和存储库构建。因此,目前不需要手动配置,除非您想直接控制存储库设置。
该TransientRepository实现将在第一个会话启动时自动初始化内容存储库,并在最后一个会话关闭时将其关闭。因此,只要正确关闭所有会话,就无需显式关闭存储库。请注意,Jackrabbit 存储库目录包含一个锁定文件,可防止多个进程同时访问它。如果您在离开访问存储库的进程之前未能正确关闭所有会话或以其他方式关闭存储库,您将看到由锁定文件引起的存储库启动异常。通常,在这种情况下,您可以手动删除锁定文件,但这种情况总是存在存储库损坏的可能性,特别是在您使用非事务性持久性管理器时。
Session session = repository.login(new GuestCredentials());
该Repository.login(Credentials)方法使用默认工作区和一些用户凭据启动存储库会话。FirstHop 示例使用GuestCredentials ,Jackrabbit 将其映射到只读匿名用户。
由于我们使用该类TransientRepository作为 Repository 实现,因此这一步也会导致存储库被初始化。
try { ... } finally { session.logout(); }
正确释放所有获取的资源是一个很好的做法,JCR 会话也不例外。try-finally 习惯用法是确保资源真正被释放的好方法,因为即使中间代码抛出异常或以其他方式跳转到范围之外(例如使用 return、break 或 continue 语句),release 方法也会被调用)。
finally 分支中的方法Session.logout()关闭会话,并且由于这是我们启动的唯一会话,因此 TransientRepository 会自动关闭。
String user = session.getUserID();
使用该方法可以获得与会话相关联的用户的用户名或标识符Session.getUserID()。"anonymous"长耳大野兔默认返回 。
String name = repository.getDescriptor(Repository.REP_NAME_DESC);
每个内容存储库实现都会发布许多字符串描述符,这些描述符描述各种实现属性,例如实现级别和支持的可选 JCR 功能。 有关标准存储库描述符的列表,请参阅存储库接口。描述 REP_NAME_DESC符包含存储库实现的名称,在本例中为"Jackrabbit"。
三、处理内容
内容存储库的主要功能是允许应用程序存储和检索内容。JCR 内容存储库中的内容由结构化或非结构化数据组成,这些数据被建模为具有包含实际数据的属性的节点层次结构。
以下示例应用程序首先将一些内容存储到最初为空的内容存储库,然后检索存储的内容并将其输出,最后删除存储的内容。
import javax.jcr.Repository;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.Node;
import org.apache.jackrabbit.commons.JcrUtils;
/**
* Second hop example. Stores, retrieves, and removes example content.
*/
public class SecondHop {
/**
* The main entry point of the example application.
*
* @param args command line arguments (ignored)
* @throws Exception if an error occurs
*/
public static void main(String[] args) throws Exception {
Repository repository = JcrUtils.getRepository();
Session session = repository.login(
new SimpleCredentials("admin", "admin".toCharArray()));
try {
Node root = session.getRootNode();
// Store content
Node hello = root.addNode("hello");
Node world = hello.addNode("world");
world.setProperty("message", "Hello, World!");
session.save();
// Retrieve content
Node node = root.getNode("hello/world");
System.out.println(node.getPath());
System.out.println(node.getProperty("message").getString());
// Remove content
root.getNode("hello").remove();
session.save();
} finally {
session.logout();
}
}
}
此示例源代码也可以作为 SecondHop.java 提供。运行此示例应产生以下输出:
/hello/world
Hello, World!
此示例应用程序的基本结构与登录示例中的相同,因此让我们来看看差异:
import javax.jcr.SimpleCredentials;
import javax.jcr.Node;
这是本示例中我们需要的两个新类。SimpleCredentials类是Credentials接口的 简单实现, 用于将显式用户凭据传递给该 方法。Repository.login(Credentials)
Node接口 用于管理存储库中的内容节点。有一个名为Property的相关接口 用于管理内容属性,但在本例中我们仅间接使用 Property 接口。
new SimpleCredentials("admin", "admin".toCharArray())
正如登录示例中所讨论的,登录会GuestCredentials 在 Jackrabbit 默认配置中返回匿名只读会话。为了能够存储和删除内容,我们需要创建一个具有写访问权限的会话,为此,我们需要将带有用户名和密码的凭据传递给
Repository.login(Credentials credentials) method.
默认的 Jackrabbit 登录机制仅接受用户名和密码作为已知用户的有效凭据。具有默认配置的 Jackrabbit 存储库将在首次初始化时创建一个管理员用户。因此,我们需要使用管理员用户的用户名和初始默认密码(在本例中为"admin"和 )构建并使用 SimpleCredentials 实例"admin"。
SimpleCredentials 构造函数遵循 JAAS 约定,将用户名表示为普通字符串,但将密码表示为字符数组,因此我们需要使用该方法String.toCharArray()来满足构造函数。
Node root = session.getRootNode();
每个 JCR 会话都与包含单个节点树的工作区相关联。访问根节点的一个简单方法是调用该 Session.getRootNode()方法。拥有对根节点的引用使我们能够轻松地存储和检索当前工作区中的内容。
Node hello = root.addNode("hello");
Node world = hello.addNode("world");
可以使用该方法添加新的内容节点Node.addNode(String relPath) 。该方法采用要添加的节点的名称(或相对路径),并在与当前会话关联的临时存储中创建命名节点。在持久存储临时存储之前,添加的节点仅在当前会话中可见,而在并发访问内容存储库的任何其他会话中不可见。
此代码片段创建两个新节点,称为"hello"和"world",分别 “hello"是根节点的子节点和"world"节点的子节点"hello” 。
world.setProperty("message", "Hello, World!");
“hello"为了向使用和节点创建的结构添加一些内容"world” ,我们使用该Node.setProperty(String name, String value)方法添加一个调用节点的字符串"message"属性"world"。属性的值是字符串"Hello, World!"。
与添加的节点一样,该属性也首先在与当前会话关联的临时存储中创建。如果指定的属性已存在,则此方法将更改该属性的值。
session.save();
尽管我们示例的其余部分仅使用单个会话的临时存储就可以正常工作,但我们希望保留迄今为止所做的更改。这样其他会话也可以访问我们刚刚创建的示例内容。如果您愿意,您甚至可以将示例应用程序分成三部分,分别用于存储、检索和删除示例内容。除非我们坚持所做的改变,否则这种分裂是行不通的。
该Session.save()方法将所有挂起的更改保留在临时存储中。更改将写入持久存储库存储,并且对访问同一工作区的所有会话都可见。如果没有此调用,当会话关闭时,所有更改都将永远丢失。
Node node = root.getNode("hello/world");
由于我们仍在使用相同的会话,因此我们可以使用现有的 hello 和 world 节点引用来访问存储的内容,但让我们假设我们已经启动了另一个会话并想要检索之前存储的内容。
该Node.getNode(String relPath)方法返回对相对于该节点的给定路径处的节点的引用。路径语法遵循常见的文件系统约定:正斜杠分隔节点名称,单点表示当前节点,双点表示父节点。因此,该路径"hello/world"标识当前节点的子节点"world"的子节点"hello"- 在本例中为根节点。最终结果是该方法返回一个节点实例,该实例表示与前面几行创建的世界实例相同的内容节点。
System.out.println(node.getPath());
每个内容节点和属性都由其在工作区中的绝对路径唯一标识。绝对路径以正斜杠开头,并在当前节点或属性的名称之前按顺序包含所有祖先节点的名称。
可以使用 Item.getPath() 方法检索节点或属性的路径。Item接口是Node和Property的超接口,包含节点和属性共享的 所有功能。
节点变量引用"world"节点,因此该语句将输出行"/hello/world"。
System.out.println(node.getProperty("message").getString());
可以使用返回PropertyNode.getProperty(String relPath)接口 实例的方法来访问属性 ,该实例表示相对于当前节点的给定路径处的属性。在本例中,“message”属性是我们之前创建的几行属性。
JCR 属性可以包含给定类型的单个或多个值。有用于存储字符串、数字、日期、二进制流、节点引用等的属性类型。我们只需要单个字符串值,因此我们使用该Property.getString()方法。该语句的结果是"Hello, World!"正在输出的行。
root.getNode("hello").remove();
可以使用该Item.remove()方法删除节点和属性。该方法会删除整个内容子树,因此我们只需要删除最顶层的"hello"节点即可删除之前添加的所有内容。
删除首先存储在会话本地临时存储中,就像添加和更改的内容一样。与以前一样,需要显式保存瞬态更改才能从持久存储中删除内容。