以下内容来自jackrabbit官方文档
官方跳转链接:Jackrabbit First Hops
First Hops
欢迎来到您进入Jackrabbit世界的第一站!
本简介为您提供了使用Jackrabbit和JCR API的动手经验。
一旦完成了本文档的跳转,就应该准备继续使用官方的JCR规范和该站点上的文档。
Hop 0: 入门
开始使用Jackrabbit的最简单方法是下载可运行的Standalone Server jar。
除了运行它,您还可以将其放在类路径中,以快速访问下面需要的所有类和接口。
另外,如果您使用Apache Maven构建系统(我们推荐),则可以使用以下依赖项来设置您的first 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.12.1</version>
</dependency>
<!-- Use Log4J for logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.5.11</version>
</dependency>
</dependencies>
如果在尝试编译或运行以下示例时收到ClassNotFoundException消息,则您的类路径设置中可能存在错误。
Hop 1: 登录到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();
}
}
}
如果已设置类路径,则可以使用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接口表示用于访问存储库的单个登录会话。
需要一个会话来访问存储库中的任何内容。
请注意,不能保证会话实例是线程安全的,因此,如果需要同时从不同的线程访问存储库内容,则应该启动多个会话。
这对于Web应用程序之类的东西尤其重要。
import org.apache.jackrabbit.commons.JcrUtils;
部署Jackrabbit的最佳实践是在容器环境中使用JNDI或其他配置机制,以保持应用程序代码不受Jackrabbit的直接依赖,但是由于我们正在创建一个简单的独立应用程序,因此可以通过使用JcrUtils类从Jackrabbit commons.
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方法
)。
最后一个分支中的Session.logout()方法将关闭该会话,由于这是我们启动的唯一会话,因此TransientRepository将自动关闭。
String user = session.getUserID();
与会话相关联的用户的用户名或标识符可使用Session.getUserID()方法获得。
Jackrabbit默认情况下返回“anonymous”。
String name = repository.getDescriptor(Repository.REP_NAME_DESC);
每个内容存储库实现都会发布许多描述各种实现属性的字符串描述符,例如实现级别和受支持的可选JCR功能。
有关标准存储库描述符的列表,请参见Repository 接口。
REP_NAME_DESC描述符包含存储库实现的名称,在这种情况下为“ Jackrabbit”。
Hop 2: 处理内容
内容存储库的主要功能是允许应用程序存储和检索内容。
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();
}
}
}
像在 first hop中一样,该示例源也可以从SecondHop.java中获得。
您也可以像在第一跳示例中所做的那样编译并运行此类。
运行此示例将产生以下输出:
/hello/world
Hello, World!
该示例应用程序的基本结构与“First Hop”示例中的相同,因此让我们简单地了解一下这些差异:
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) 方法
默认的Jackrabbit登录机制仅接受用户名和密码作为已知用户的有效凭据。
具有默认配置的Jackrabbit存储库将在首次初始化时创建一个admin用户。
因此,我们需要使用管理员用户名和初始默认密码(在本例中为“ admin”和“ admin”)来构造和使用SimpleCredentials实例。
SimpleCredentials构造函数遵循JAAS约定,将用户名表示为常规String,但将密码表示为字符数组,因此我们需要使用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)方法向“ world”节点添加一个名为“ message”的字符串属性。
该属性的值为字符串“ Hello,World!”。
像添加的节点一样,该属性也首先在与当前会话关联的临时存储中创建。
如果命名属性已经存在,则此方法将更改该属性的值。
session.save();
即使仅使用单个会话的临时存储,示例的其余部分也可以正常工作,但我们仍要保留到目前为止所做的更改。
这样,其他会话也可以访问我们刚刚创建的示例内容。
如果愿意,您甚至可以将示例应用程序分为三部分,分别用于存储,检索和删除示例内容。
除非我们坚持所做的更改,否则这种分裂是行不通的。
Session.save()方法将所有暂挂的更改持久保存在临时存储中。
所做的更改将写入持久性存储库存储中,并且所有访问同一工作空间的会话都可以看到这些更改。
没有此调用,所有更改将在会话关闭时永久丢失。
Node node = root.getNode("hello/world");
由于我们仍在使用相同的会话,因此我们可以使用现有的hello和world节点引用来访问存储的内容,但我们假设我们已经开始了另一个会话,并希望检索以前存储的内容。
Node.getNode(String relPath)方法返回相对于该节点的给定路径上该节点的引用。
路径语法遵循通用的文件系统约定:正斜杠分隔节点名称,单点代表当前节点,双点代表父节点。
因此,路径“ hello / world”标识当前节点的“ hello”子节点的“ world”子节点-在这种情况下为根节点。
最终结果是该方法返回一个节点实例,该实例代表与之前几行创建的world实例相同的内容节点。
System.out.println(node.getPath());
每个内容节点和属性均由其在工作空间中的绝对路径唯一标识。
绝对路径以正斜杠开头,并按顺序包含当前节点或属性名称之前的所有祖先节点名称。
可以使用Item.getPath()方法检索节点或属性的路径。
Item接口是Node和Property的超级接口,包含Node和Properties共享的所有功能。
node变量引用“ world”节点,因此此语句将输出“ / hello / world”行。
System.out.println(node.getProperty("message").getString());
可以使用Node.getProperty(String relPath)方法访问属性,该方法返回Property接口的实例,该实例表示相对于当前节点的给定路径上的属性。
在这种情况下,“ message”属性是我们之前创建的几行。
JCR属性可以包含给定类型的单个或多个值。
有用于存储字符串,数字,日期,二进制流,节点引用等的属性类型。我们只需要单个字符串值,因此我们使用Property.getString()方法。
该语句的结果是“ Hello,World!”行。
正在输出。
root.getNode("hello").remove();
可以使用theItem.remove()方法删除节点和属性。
该方法将删除整个内容子树,因此我们只需要删除最上面的“ hello”节点即可摆脱之前添加的所有内容。
删除内容首先存储在会话本地临时存储中,就像添加和更改的内容一样。
像以前一样,需要将临时更改显式保存,以便将内容从持久性存储中删除。
Hop 3: 导入内容
TODO:更新以匹配上一hop的样式。
为了更有效地添加内容,您可能需要尝试JCR的导入工具,例如Session.importXML。
以下Elliotte Rusty Harold的XML文档提供了一个有趣的示例,演示了存储库的命名空间功能:
test.xml
<xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:mathml="http://www.w3.org/1998/Math/MathML">
<xhtml:head><xhtml:title>Three Namespaces</xhtml:title></xhtml:head>
<xhtml:body>
<xhtml:h1 align="center">An Ellipse and a Rectangle</xhtml:h1>
<svg:svg xmlns:svg="http://www.w3.org/2000/svg"
width="12cm" height="10cm">
<svg:ellipse rx="110" ry="130" />
<svg:rect x="4cm" y="1cm" width="3cm" height="6cm" />
</svg:svg>
<xhtml:p>The equation for ellipses</xhtml:p>
<mathml:math>
<mathml:apply>
<mathml:eq/>
<mathml:cn> 1 </mathml:cn>
<mathml:apply>
<mathml:plus/>
<mathml:apply>
<mathml:divide/>
<mathml:apply>
<mathml:power/>
<mathml:ci> x </mathml:ci>
<mathml:cn> 2 </mathml:cn>
</mathml:apply>
<mathml:apply>
<mathml:power/>
<mathml:ci> a </mathml:ci>
<mathml:cn> 2 </mathml:cn>
</mathml:apply>
</mathml:apply>
<mathml:apply>
<mathml:divide/>
<mathml:apply>
<mathml:power/>
<mathml:ci> y </mathml:ci>
<mathml:cn> 2 </mathml:cn>
</mathml:apply>
<mathml:apply>
<mathml:power/>
<mathml:ci> b </mathml:ci>
<mathml:cn> 2 </mathml:cn>
</mathml:apply>
</mathml:apply>
</mathml:apply>
</mathml:apply>
</mathml:math>
<xhtml:hr/>
<xhtml:p>Last Modified January 10, 2002</xhtml:p>
</xhtml:body>
</xhtml:html>
下面显示的第三个示例应用程序将从当前目录中导入名为test.xml的XML文件到名为importxml的新内容存储库节点中。
导入XML内容后,应用程序将使用简单的dump()方法以递归方式转储整个工作区的内容。
import javax.jcr.*;
import java.io.FileInputStream;
import org.apache.jackrabbit.commons.JcrUtils;
/**
* Third Jackrabbit example application. Imports an example XML file
* and outputs the contents of the entire workspace.
*/
public class ThirdHop {
/**
* 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();
// Import the XML file unless already imported
if (!root.hasNode("importxml")) {
System.out.print("Importing xml... ");
// Create an unstructured node under which to import the XML
Node node = root.addNode("importxml", "nt:unstructured");
// Import the file "test.xml" under the created node
FileInputStream xml = new FileInputStream("test.xml");
session.importXML(
node.getPath(), xml, ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW);
xml.close();
session.save();
System.out.println("done.");
}
//output the repository content
dump(root);
} finally {
session.logout();
}
}
/** Recursively outputs the contents of the given node. */
private static void dump(Node node) throws RepositoryException {
// First output the node path
System.out.println(node.getPath());
// Skip the virtual (and large!) jcr:system subtree
if (node.getName().equals("jcr:system")) {
return;
}
// Then output the properties
PropertyIterator properties = node.getProperties();
while (properties.hasNext()) {
Property property = properties.nextProperty();
if (property.getDefinition().isMultiple()) {
// A multi-valued property, print all values
Value[] values = property.getValues();
for (int i = 0; i < values.length; i++) {
System.out.println(
property.getPath() + " = " + values[i] .getString());
}
} else {
// A single-valued property
System.out.println(
property.getPath() + " = " + property.getString());
}
}
// Finally output all the child nodes recursively
NodeIterator nodes = node.getNodes();
while (nodes.hasNext()) {
dump(nodes.nextNode());
}
}
}
运行ThirdHop类应产生如下输出:
Importing XML... done.
/
/jcr:primaryType=rep:root
/jcr:system
/importxml
/importxml/jcr:primaryType=nt:unstructured
/importxml/xhtml:html
/importxml/xhtml:html/jcr:primaryType=nt:unstructured
/importxml/xhtml:html/xhtml:head
/importxml/xhtml:html/xhtml:head/jcr:primaryType=nt:unstructured
/importxml/xhtml:html/xhtml:head/xhtml:title
/importxml/xhtml:html/xhtml:head/xhtml:title/jcr:primaryType=nt:unstructured
/importxml/xhtml:html/xhtml:head/xhtml:title/jcr:xmltext
/importxml/xhtml:html/xhtml:head/xhtml:title/jcr:xmltext/jcr:primaryType=nt:unstructured
/importxml/xhtml:html/xhtml:head/xhtml:title/jcr:xmltext/jcr:xmlcharacters=Three Namespaces
/importxml/xhtml:html/xhtml:body
/importxml/xhtml:html/xhtml:body/jcr:primaryType=nt:unstructured
/importxml/xhtml:html/xhtml:body/xhtml:h1
/importxml/xhtml:html/xhtml:body/xhtml:h1/jcr:primaryType=nt:unstructured
/importxml/xhtml:html/xhtml:body/xhtml:h1/align=center
/importxml/xhtml:html/xhtml:body/xhtml:h1/jcr:xmltext
/importxml/xhtml:html/xhtml:body/xhtml:h1/jcr:xmltext/jcr:primaryType=nt:unstructured
/importxml/xhtml:html/xhtml:body/xhtml:h1/jcr:xmltext/jcr:xmlcharacters=An Ellipse and a Rectangle
/importxml/xhtml:html/xhtml:body/svg:svg
/importxml/xhtml:html/xhtml:body/svg:svg/jcr:primaryType=nt:unstructured
/importxml/xhtml:html/xhtml:body/svg:svg/width=12cm
/importxml/xhtml:html/xhtml:body/svg:svg/height=10cm
.
.
.
此Hop与Second Hop示例有很多相似之处:我们通过登录来创建一个具有写访问权的新会话,接下来我们将数据插入到存储库中,这次是通过导入xml文件,最后输出整个存储库内容。
到目前为止,您应该熟悉登录到存储库(Repository.login),在存储库根目录(Session.getRootNode)下创建一个新节点(Node.addNode)并保存会话,以便我们的更改得以保留(Session.save) 。
让我们看一下此示例中使用的新方法,即如何导入xml内容:
session.importXML(node.getPath(), xml, ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW);
这反序列化XML文档,并在提供的路径上将结果项子图添加为节点的子级。
标志ImportUUIDBehavior控制如何处理传入节点的标识符。
有四个选项:
- ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW:
传入节点的添加方式与使用Node.addNode添加新节点的方式相同。
即,在添加或保存时,将为它们分配新创建的标识符。
无论哪种情况,都不会发生标识符冲突。 - ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING:
- 如果传入节点的标识符与工作空间中已经存在的节点的标识符相同,则在添加传入节点之前,将已存在的节点(及其子图)从工作空间中可能存在的任何位置中删除。
请注意,这可能导致节点从工作空间中远离要写入传入子图的位置的位置“消失”。
删除和新添加都会在保存时分派。 - ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING:
- 如果传入的节点具有与工作空间中已经存在的节点相同的标识符,则已存在的节点将被位于与现有节点相同位置的传入节点替换。
请注意,这可能会导致传入的子图被分解并“散布”到工作空间中的不同位置。
在最极端的情况下,此行为可能导致根本没有任何节点被添加为parentAbsPath的子代。
如果传入XML的最顶层元素具有与工作空间中其他位置的现有节点相同的标识符,则会发生这种情况。
更改将在保存时分派。 - ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW:
如果传入的节点具有与工作空间中已经存在的节点相同的标识符,则将引发ItemExistsException。
另一个有趣的方法是
void dump(Node node)
这可以用作如何对所有存储库进行递归遍历并检查每个节点的属性的示例。
注意,我们必须特别注意多值属性,因为这会影响我们使用它们的方式:
property.getDefinition().isMultiple()
如果结果为true,那么我们正在处理一个值数组:
Value[] values = property.getValues();
否则,我们可以使用提供的api快捷方式来获得所需的值:
property.getString()
相当于
property.getValue().getString()
与实用程序相关的代码示例的一个很好的入口点是JcrUtils。
(完)