Author: rhauch
Date: 2009-06-08 11:47:40 -0400 (Mon, 08 Jun 2009)
New Revision: 997
Added:
trunk/docs/reference/src/main/docbook/en-US/content/core/connector.xml
Removed:
trunk/docs/reference/src/main/docbook/en-US/content/core/repositories.xml
Modified:
trunk/docs/reference/src/main/docbook/en-US/content/core/sequencing.xml
trunk/docs/reference/src/main/docbook/en-US/master.xml
Log:
More revisions to the master Reference Guide document.
Copied: trunk/docs/reference/src/main/docbook/en-US/content/core/connector.xml (from rev
996, trunk/docs/reference/src/main/docbook/en-US/content/core/repositories.xml)
===================================================================
--- trunk/docs/reference/src/main/docbook/en-US/content/core/connector.xml
(rev 0)
+++ trunk/docs/reference/src/main/docbook/en-US/content/core/connector.xml 2009-06-08
15:47:40 UTC (rev 997)
@@ -0,0 +1,1468 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ JBoss DNA (
http://www.jboss.org/dna)
+ ~
+ ~ See the COPYRIGHT.txt file distributed with this work for information
+ ~ regarding copyright ownership. Some portions may be licensed
+ ~ to Red Hat, Inc. under one or more contributor license agreements.
+ ~ See the AUTHORS.txt file in the distribution for a full listing of
+ ~ individual contributors.
+ ~
+ ~ JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ ~ is licensed to you under the terms of the GNU Lesser General Public License as
+ ~ published by the Free Software Foundation; either version 2.1 of
+ ~ the License, or (at your option) any later version.
+ ~
+ ~ JBoss DNA is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ ~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ ~ for more details.
+ ~
+ ~ You should have received a copy of the GNU Lesser General Public License
+ ~ along with this distribution; if not, write to:
+ ~ Free Software Foundation, Inc.
+ ~ 51 Franklin Street, Fifth Floor
+ ~ Boston, MA 02110-1301 USA
+ -->
+<!DOCTYPE preface PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
+<!ENTITY % CustomDTD SYSTEM "../../custom.dtd">
+%CustomDTD;
+]>
+<chapter id="connector_framework">
+ <title>Connector Framework</title>
+ <para></para>
+ <para>There is a lot of information stored in many of different places:
databases, repositories, SCM systems,
+ registries, file systems, services, etc. The purpose of the federation engine is to
allow applications to use the JCR API
+ to access that information as if it were all stored in a single JCR repository, but
to really leave the information where
+ it is.</para>
+ <para>Why not just copy or move the information into a JCR repository? Moving it
is probably pretty difficult, since most
+ likely there are existing applications that rely upon that information being where it
is. All of those applications
+ would break or have to change. And copying the information means that we'd have to
continually synchronize the changes.
+ This not only is a lot of work, but it often makes it difficult to know whether
information is accurate and "the master" data.
+ </para>
+ <para>JBoss DNA lets us leave information where it, yet access it through the JCR
API as if it were in one big repository.
+ One major benefit is that existing applications that use the information in the
original locations don't break, since they
+ can keep using the information. But now our JCR clients can also access all the
information, too. And if our federating JBoss DNA repository is
+ configured to allow updates, JCR client applications can change the information in
the repository and JBoss DNA will propagate
+ those changes down to the original source, making those changes visible to all the
other applications.
+ </para>
+ <para>
+ In short, all clients see the correct information, even when it changes in the
underlying systems. But the JCR clients can get to all of the information
+ in one spot, using one powerful standard API.
+ </para>
+ <sect1 id="connectors">
+ <title>Repository connectors</title>
+ <para>
+ With JBoss DNA, your applications use the <ulink
url="&JSR170;">JCR API</ulink> to work with the repository,
+ but the DNA repository transparently fetches the information from different kinds of
repositories and storage systems,
+ not just a single purpose-built store. This is fundamentally what makes JBoss DNA
different.
+ </para>
+ <para>How does JBoss DNA do this? At the heart of JBoss DNA and it's JCR
implementation is a simple graph-based
+ <emphasis>repository connector</emphasis> system. Essentially, JBoss
DNA's JCR implementation uses a single
+ repository connector to access all content:
+ <figure id="dnajcr-and-connector">
+ <title>JBoss DNA's JCR implementation delegates to a repository
connector</title>
+ <graphic align="center" scale="100"
fileref="dnajcr-and-connector.png"/>
+ </figure>
+ That single repository connector could use an in-memory repository, a JBoss Cache
instance (including those that are clustered and replicated),
+ or a federated repository where content from multiple sources is unified.
+ <figure id="dna-connectors-0.2">
+ <title>JBoss DNA can put JCR on top of multiple kinds of systems</title>
+ <graphic align="center" scale="100"
fileref="dna-connectors-0.2.png"/>
+ </figure>
+ Really, the federated connector gives us all kinds of possibilities, since we can use
that connector on top of lots of connectors
+ to other individual sources. This simple connector architecture is fundamentally what
makes JBoss DNA so powerful and flexible.
+ Along with a good library of connectors, which is what we're planning to create.
+ </para>
+ <para>
+ For instance, we want to build a connector to <ulink
url="&JIRA-39;">other JCR repositories</ulink>, and another that
accesses
+ the <ulink url="&JIRA-34;">local file system</ulink>.
We've already started on a <ulink url="&JIRA-36;">Subversion
connector</ulink>,
+ which will allow JCR to access the files in a SVN repository (and perhaps push changes
into SVN through a commit).
+ And of course we want to create a connector that accesses <ulink
url="&JIRA-199;">data</ulink>
+ and <ulink url="&JIRA-37;">metadata</ulink> from relational
databases. For more information, check out our
+ <ulink
url="&JIRA;?report=com.atlassian.jira.plugin.system.project:roadmap-panel">roadmap</ulink>.
+ Of course, if we don't have a connector to suit your needs, you can <link
linkend="custom-connectors">write your own</link>.
+ <figure id="dna-connectors-future">
+ <title>Future JBoss DNA connectors</title>
+ <graphic align="center" scale="100"
fileref="dna-connectors-future.png"/>
+ </figure>
+ </para>
+ <para>
+ </para>
+ <para>
+ It's even possible to put a different API layer on top of the connectors. For
example, the new <ulink url="&JSR203;">New I/O
(JSR-203)</ulink>
+ API offers the opportunity to build new file system providers. This would be very
straightforward to put on top of a JCR implementation,
+ but it could be made even simpler by putting it on top of a DNA connector. In both
cases, it'd be a trivial mapping from nodes that represent
+ files and folders into JSR-203 files and directories, and events on those nodes could
easily be translated into JSR-203 watch events.
+ Then, simply choose a DNA connector and configure it to use the source you want to
use.
+ <figure id="dna-connectors-vfs">
+ <title>Virtual File System with JBoss DNA</title>
+ <graphic align="center" scale="100"
fileref="vfs-and-connector.png"/>
+ </figure>
+ </para>
+ <para>Before we go further, let's define some terminology regarding
connectors.</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ A <emphasis role="strong">connector</emphasis> is the runnable
code packaged in one or more JAR files that
+ contains implementations of several interfaces (described below). A Java developer
<emphasis>writes</emphasis>
+ a connector to a type of source, such as a particular database management system,
LDAP directory, source code
+ management system, etc. It is then packaged into one or more JAR files (including
dependent JARs) and deployed
+ for use in applications that use JBoss DNA repositories.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ The description of a particular source system (e.g., the "Customer"
database, or the company LDAP system)
+ is called a <emphasis role="strong">repository
source</emphasis>. JBoss DNA defines a &RepositorySource; interface
+ that defines methods describing the behavior and supported features and a method for
establishing connections.
+ A connector will have a class that implements this interface and that has JavaBean
properties for
+ all of the connector-specific properties required to fully describe an instance of
the system. Use of JavaBean
+ properties is not required, but it is highly recommended, as it enables reflective
configuration and administration.
+ Applications that use JBoss DNA create an instance of the connector's
&RepositorySource; implementation and set
+ the properties for the external source that the application wants to access with
that connector.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ A repository source instance is then used to establish <emphasis
role="strong">connections</emphasis> to
+ that source. A connector provides an implementation of the
&RepositoryConnection; interface, which
+ defines methods for interacting with the external system. In particular, the
<code>execute(...)</code> method
+ takes an &ExecutionContext; instance and a &Request; object. The
&ExectuionContext; object defines the
+ environment in which the processing is occurring, including information about the
JAAS &Subject; and &LoginContext;.
+ The &Request; object describes the requested operations on the content, with
different concrete subclasses
+ representing each type of activity. Examples of commands include (but not limited
to) getting a node, moving a node, creating a node,
+ changing a node, and deleting a node. And, if the repository source is able to
participate in JTA/JTS distributed transactions, then the
+ &RepositoryConnection; must implement the
<code>getXaResource()</code> method by returning
+ a valid <code>javax.transaction.xa.XAResource</code> object that can be
used by the transaction monitor.
+ </para>
+ </listitem>
+ </itemizedlist>
+ <para>As an example, consider that we want JBoss DNA to give us access through
JCR to the schema information contained in a
+ relational databases. We first have to develop a connector that allows us to interact
with relational databases using JDBC.
+ That connector would contain a <code>JdbcRepositorySource</code> Java
class that implements &RepositorySource;,
+ and that has all of the various JavaBean properties for setting the name of the driver
class, URL, username, password,
+ and other properties. (Or we might have a JavaBean property that defines the JNDI
name where we can find a JDBC
+ <code>DataSource</code> instance pointing to our JDBC database.)
+ </para>
+ <para>
+ Our new connector would also have a <code>JdbcRepositoryConnection</code>
Java class that implements the
+ &RepositoryConnection; interface. This class would probably wrap a JDBC database
connection,
+ and would implement the <code>execute(...)</code> method such that the
nodes exposed by the connector
+ describe the database schema of the database. For example, the connector might
represent each database table
+ as a node with the table's name, with properties that describe the table (e.g.,
the description, whether it's a
+ temporary table), and with child nodes that represent each of the columns, keys and
constraints.
+ </para>
+ <para>
+ To use our connector in an application that uses JBoss DNA, we need to create an
instance of the
+ <classname>JdbcRepositorySource</classname> for each database instance
that we want to access. If we have 3 MySQL databases,
+ 9 Oracle databases, and 4 PostgreSQL databases, then we'd need to create a total
of 16 <classname>JdbcRepositorySource</classname>
+ instances, each with the properties describing a single database instance. Those
sources are then available for use by
+ JBoss DNA components, including the <link
linkend="jcr">JCR</link> implementation.
+ </para>
+ <para>
+ So, we've so far learned what a repository connector is and how they're used
to establish connections to the underlying sources
+ and access the content in those sources. In the <link
linkend="repository-service">next section</link>, we'll show how
these
+ source instances can be configured, managed, and their connections pooled. After
that, we'll look review JBoss DNA's
+ <link linkend="connector-library">existing connectors</link> and
show how to <link linkend="custom-connectors">create your own
connectors</link>.
+ </para>
+ </sect1>
+ <sect1 id="repository-workspaces">
+ <title>Workspaces</title>
+ <para>The previous section talked about how connector expose their information
through the graph language of JBoss DNA.
+ This is true, except that we didn't dive into too much of the detail. JBoss DNA
graphs have the notion of <emphasis>workspaces</emphasis>
+ in which the content appears, and its very easy for clients using the graph to switch
between workspaces. In fact,
+ workspaces differ from each other in that they provide different views of the same
information.
+ </para>
+ <para>Consider a source control system, like SVN or CVS. These systems provide
different views of the source code:
+ a mainline development branch as well as other branches (or tags) commonly used for
releases. So, just like one source
+ file might appear in the mainline branch as well as the previous two release branches,
a node in a repository source
+ might appear in multiple workspaces.
+ </para>
+ <para>
+ However, each connector can kind of decide how (or whether) it uses workspaces. For
example, there may be no overlap
+ in the content between workspaces. Or a connector might only expose a single
workspace (in other words, there's only one
+ "default" workspace).
+ </para>
+ </sect1>
+ <sect1 id="repository-service">
+ <title>Repository Service</title>
+ <para>The JBoss DNA &RepositoryService; is the component that manages the
<emphasis>repository sources</emphasis>
+ and the connections to them. &RepositorySource; instances can be
programmatically added to the service, but
+ the service can actually read its configuration from a configuration repository
(which, by the way, is represented by a
+ just another &RepositorySource; instance that's usually added programmatically
to the service). The service connects to
+ the configuration repository, reads the content in a particular area, and
automatically sets up the &RepositorySource; instances
+ per the information found in the configuration repository.
+ </para>
+ <para>
+ The &RepositoryService; also transparently maintains for each source a pool of
reusable connections. The pooling properties
+ can be controlled via the configuration repository, or adjusted programmatically.
+ </para>
+ <para>
+ Using a repository, then, involves simply asking the &RepositoryService; for a
&RepositoryConnection;
+ to the repository given the repository's name. If a source exists with that name,
the service checks out a connection from
+ the source's pool. The resulting connection is actually a wrapper around the
underlying pooled connection, so the
+ component that requested the connection can simply close it, and under the covers the
actual connection is simply returned
+ to the pool.
+ </para>
+ <para>To instantiate the &RepositoryService;, we need to first have a few
other objects:
+ <itemizedlist>
+ <listitem>
+ <para>
+ A &ExecutionContext; instance, as discussed <link
linkend="execution-context">earlier</link>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ A &RepositoryLibrary; instance that manages the list of &RepositorySource;
instances,
+ properly injects the execution contexts into each repository source, and provides a
configurable pool of connections
+ for each source.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ A <emphasis>configuration repository</emphasis> that contains
descriptions of all of the repository sources
+ as well as any information those sources need. Because this is a regular
repository, this could be a simple
+ repository with content loaded from an XML file, or it could be a shared
+ central repository with information about all of the JBoss DNA repositories used
across your organization.
+ </para>
+ </listitem>
+ </itemizedlist>
+ With these components in place, we can then instantiate the &RepositoryService;
and start it (using its
+ &ServiceAdministrator;). During startup, the service reads the configuration
repository and loads any
+ defined &RepositorySource; instances into the repository library, using the class
loader factory
+ (available in the &ExecutionContext;) to obtain.
+ </para>
+ <para>
+ Here's sample code that shows how to set up and start the repository service. You
can see something similar
+ in the example application in the <code>startRepositories()</code> method
of the
+ <code>org.jboss.example.dna.repository.RepositoryClient</code> class.
+ </para>
+ <programlisting>
+ // Create the top-level execution context with all standard components ...
+ &ExecutionContext; context = new ExecutionContext();
+
+ // Create the library for the RepositorySource instances ...
+ &RepositoryLibrary; sources = new &RepositoryLibrary;(context);
+
+ // Load into the source manager the repository source for the configuration repository
...
+ &InMemoryRepositorySource; configSource = new &InMemoryRepositorySource;();
+ configSource.setName("Configuration");
+ sources.addSource(configSource);
+
+ // Now instantiate the Repository Service ...
+ &RepositoryService; service = new &RepositoryService;(sources,
configSource.getName(), context);
+ service.getAdministrator().start();
+ </programlisting>
+
+ <para>After startup completes, the repositories are ready to be used. The client
application obtains the list of repositories
+ and presents them to the user. When the user selects one, the client application
starts navigating that repository
+ starting at its root node (e.g., the "/" path). As you type a command to
list the contents of the current node or to
+ "change directories" to a different node, the client application obtains the
information for the node using a simple
+ procedure:
+ <orderedlist>
+ <listitem>
+ <para>Get a connection to the repository.</para>
+ </listitem>
+ <listitem>
+ <para>Using the connection, find the current node and read its properties and
children, putting the information
+ into a simple Java plain old Java object (POJO).</para>
+ </listitem>
+ <listitem>
+ <para>Close the connection to the repository (in a finally block to ensure it
always happens).</para>
+ </listitem>
+ </orderedlist>
+ </para>
+ </sect1>
+ <sect1 id="connector-library">
+ <title>Out-of-the-box repository connectors</title>
+ <para>
+ A number of repository connectors are already available in JBoss DNA, and are
outlined in the following sections.
+ Note that we do want to build <ulink
url="https://jira.jboss.org/jira/secure/IssueNavigator.jspa?reset=tr...
connectors</ulink>
+ in the upcoming releases.
+ </para>
+ <sect2 id="dna-connector-inmemory">
+ <title>In-memory connector</title>
+ <para>
+ The in-memory repository connector is a simple connector that creates a transient,
in-memory repository.
+ This repository is used as a very simple in-memory cache or as a standalone transient
repository.
+ </para>
+ <para>
+ The &InMemoryRepositorySource; class provides a number of JavaBean properties
that control its behavior:
+ </para>
+ <table frame='all'>
+ <title>&InMemoryRepositorySource; properties</title>
+ <tgroup cols='2' align='left' colsep='1'
rowsep='1'>
+ <colspec colname='c1' colwidth="1*"/>
+ <colspec colname='c2' colwidth="1*"/>
+ <thead>
+ <row>
+ <entry>Property</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>name</entry>
+ <entry>The name of the repository source, which is used by the
&RepositoryService; when obtaining a &RepositoryConnection; by
name.</entry>
+ </row>
+ <row>
+ <entry>jndiName</entry>
+ <entry>Optional property that, if used, specifies the name in JNDI where an
&InMemoryRepository; instance can be found.
+ This is an advanced property that is infrequently used.</entry>
+ </row>
+ <row>
+ <entry>rootNodeUuid</entry>
+ <entry>Optional property that, if used, defines the UUID of the root node in
the in-memory repository. If not used,
+ then a new UUID is generated.</entry>
+ </row>
+ <row>
+ <entry>retryLimit</entry>
+ <entry>Optional property that, if used, defines the number of times that any
single operation on a &RepositoryConnection; to this source should be retried
+ following a communication failure. The default value is
'0'.</entry>
+ </row>
+ <row>
+ <entry>defaultCachePolicy</entry>
+ <entry>Optional property that, if used, defines the default for how long
this information provided by this source may to be
+ cached by other, higher-level components. The default value of null implies that
this source does not define a specific
+ duration for caching information provided by this repository
source.</entry>
+ </row>
+ <row>
+ <entry>defaultWorkspaceName</entry>
+ <entry>Optional property that is initialized to an empty string and which
defines the name for the workspace that will be used by default
+ if none is specified.</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect2>
+ <sect2 id="dna-connector-jbosscache">
+ <title>JBoss Cache connector</title>
+ <para>
+ The JBoss Cache repository connector allows a <ulink
url="http://www.jboss.org/jbosscache/">JBoss Cache</ulink> instance to
be
+ used as a JBoss DNA (and thus JCR) repository. This provides a repository that is an
effective, scalable, and distributed cache,
+ and is often paired with other repository sources to provide a local or <link
linkend="dna-connector-federation">federated</link>
+ repository.
+ </para>
+ <para>
+ The &JBossCacheSource; class provides a number of JavaBean properties that
control its behavior:
+ </para>
+ <table frame='all'>
+ <title>&JBossCacheSource; properties</title>
+ <tgroup cols='2' align='left' colsep='1'
rowsep='1'>
+ <colspec colname='c1' colwidth="1*"/>
+ <colspec colname='c2' colwidth="1*"/>
+ <thead>
+ <row>
+ <entry>Property</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>name</entry>
+ <entry>The name of the repository source, which is used by the
&RepositoryService; when obtaining a &RepositoryConnection; by
name.</entry>
+ </row>
+ <row>
+ <entry>cacheFactoryJndiName</entry>
+ <entry>Optional property that, if used, specifies the name in JNDI where an
existing JBoss Cache Factory instance can be found.
+ That factory would then be used if needed to create a JBoss Cache instance. If
no value is provided, then the
+ JBoss Cache <code>DefaultCacheFactory</code> class is
used.</entry>
+ </row>
+ <row>
+ <entry>cacheConfigurationName</entry>
+ <entry>Optional property that, if used, specifies the name of the
configuration that is supplied to the cache factory
+ when creating a new JBoss Cache instance.</entry>
+ </row>
+ <row>
+ <entry>cacheJndiName</entry>
+ <entry>Optional property that, if used, specifies the name in JNDI where an
existing JBoss Cache instance can be found.
+ This should be used if your application already has a cache that is used, or if
you need to configure the cache in
+ a special way.</entry>
+ </row>
+ <row>
+ <entry>uuidPropertyName</entry>
+ <entry>Optional property that, if used, defines the property that should be
used to find the UUID value for each node
+ in the cache. "<code>dna:uuid</code>" is the
default.</entry>
+ </row>
+ <row>
+ <entry>retryLimit</entry>
+ <entry>Optional property that, if used, defines the number of times that any
single operation on a &RepositoryConnection; to this source should be retried
+ following a communication failure. The default value is
'0'.</entry>
+ </row>
+ <row>
+ <entry>defaultCachePolicy</entry>
+ <entry>Optional property that, if used, defines the default for how long
this information provided by this source may to be
+ cached by other, higher-level components. The default value of null implies that
this source does not define a specific
+ duration for caching information provided by this repository
source.</entry>
+ </row>
+ <row>
+ <entry>nameOfDefaultWorkspace</entry>
+ <entry>Optional property that is initialized to an empty string and which
defines the name for the workspace that will be used by default
+ if none is specified.</entry>
+ </row>
+ <row>
+ <entry>predefinedWorkspaceNames</entry>
+ <entry>Optional property that defines the names of the workspaces that exist
and that are available for use without having to create them.</entry>
+ </row>
+ <row>
+ <entry>creatingWorkspacesAllowed</entry>
+ <entry>Optional property that is by default 'true' that defines
whether clients can create new workspaces.</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect2>
+ <sect2 id="dna-connector-federation">
+ <title>Federating connector</title>
+ <para>
+ The federated repository source provides a unified repository consisting of
information that is dynamically federated from multiple other
+ &RepositorySource; instances. This is a very powerful repository source that
appears to be a single repository, when in
+ fact the content is stored and managed in multiple other systems. Each
&FederatedRepositorySource; is typically configured
+ with the name of another &RepositorySource; that should be used as the local,
unified cache of the federated content.
+ The configuration also contains the names of the other &RepositorySource;
instances that are to be federated along with
+ the &Projection; definition describing where in the unified repository the
content is to appear.
+ </para>
+ <figure id="dna-connector-federation-image">
+ <title>Federating multiple sources using the Federated Repository
Connector</title>
+ <graphic align="center" scale="100"
fileref="dna-connector-federation.png"/>
+ </figure>
+ <para> The federation connector works by effectively building up a single
graph by querying each source and merging or
+ unifying the responses. This information is cached, which improves performance,
reduces the number of (potentially
+ expensive) remote calls, reduces the load on the sources, and helps mitigate
problems with source availability. As
+ clients interact with the repository, this cache is consulted first. When the
requested portion of the graph (or
+ "subgraph") is contained completely in the cache, it is retuned
immediately. However, if any part of the requested
+ subgraph is not in the cache, each source is consulted for their contributions to
that subgraph, and any results are
+ cached.</para>
+ <para> This basic flow makes it possible for the federated repository to
build up a local cache of the integrated graph
+ (or at least the portions that are used by clients). In fact, the federated
repository caches information in a manner
+ that is similar to that of the Domain Name System (DNS). As sources are consulted
for their contributions, the source
+ also specifies whether it is the authoritative source for this information (some
sources that are themselves federated
+ may not be the information's authority), whether the information may be
modified, the time-to-live (TTL) value (the time
+ after which the cached information should be refreshed), and the expiration time
(the time after which the cached
+ information is no longer valid). In effect, the source has complete control over
how the information it contributes is
+ cached and used.</para>
+ <para>
+ The federated repository also needs to incorporate <emphasis>negative
caching</emphasis>, which is storage of the knowledge
+ that something does <emphasis>not</emphasis> exist. Sources can be
configured to contribute information
+ only below certain paths (e.g., <code>/A/B/C</code>), and the
federation engine can take advantage of this by never
+ consulting that source for contributions to information on other paths. However,
below that path, any negative responses
+ must also be cached (with appropriate TTL and expiry parameters) to prevent the
exclusion of that source (in case the source
+ has information to contribute at a later time) or the frequent checking with the
source.
+ </para>
+ <para>
+ The federated repository uses other &RepositorySource;s that are to be federated
and a &RepositorySource; that is to be used as the
+ cache of the unified contents. These are configured in another
&RepositorySource; that is treated as a configuration repository.
+ The &FederatedRepositorySource; class uses JavaBean properties to define the name
of the configuration repository and
+ the path to the "<code>dna:federation</code>" node in that
configuration repository containing the information about the
+ cache and federated sources. This graph structure that is expected at this location
is as follows:
+ </para>
+ <programlisting><![CDATA[<!-- Define the federation configuration. -->
+<dna:federatedRepository
xmlns:dna="http://www.jboss.org/dna"
+
xmlns:jcr="http://www.jcp.org/jcr/1.0"
+ dna:timeToCache="100000" >
+ <dna:workspaces>
+ <dna:workspace jcr:name="default">
+ <!-- Define how the content in the 'Cache' source is to map to the
federated cache -->
+ <dna:cache dna:sourceName="Cache"
dna:workspaceName="default" dna:projectionRules="/a => /" />
+
+ <!-- Define how the content in the two sources maps to the federated/unified
repository.
+ This example puts the 'Cars' and 'Aircraft' content underneath
'/vehicles', but the
+ 'Configuration' content (which is defined by this file) will appear
under '/'. -->
+ <dna:projections>
+ <dna:projection jcr:name="Cars" dna:projectionRules="/Vehicles
=> /" />
+ <dna:projection jcr:name="Aircraft"
dna:projectionRules="/Vehicles => /" />
+ <dna:projection jcr:name="Configuration" dna:projectionRules="/
=> /" />
+ </dna:projections>
+ </dna:workspace>
+ </dna:workspaces>
+</dna:federatedRepository>
+]]></programlisting>
+ <note>
+ <para>
+ We're using XML to represent a graph structure, since the two map pretty well.
Each XML element represents
+ a node and XML attributes represent properties on a node. The name of the node is
defined by either the
+ <code>jcr:name</code> attribute (if it exists) or the name of the XML
element. And we use XML namespaces
+ to define the namespaces used in the node and property names. BTW, this is exactly
how the XML graph importer
+ works.
+ </para>
+ </note>
+ <para>
+ Notice that there is a cache projection and three source projections, and each
projection defines
+ one or more <emphasis>projection rules</emphasis> that are of the form:
+ </para>
+ <programlisting>pathInFederatedRepository =>
pathInSourceRepository</programlisting>
+ <para>
+ So, a projection rule <code>/Vehicles => /</code> projects the
entire contents of the source so that
+ it appears in the federated repository under the
"<code>/Vehicles</code>" node.
+ </para>
+ <para>
+ The &FederatedRepositorySource; class provides a number of JavaBean properties
that control its behavior:
+ </para>
+ <table frame='all'>
+ <title>&FederatedRepositorySource; properties</title>
+ <tgroup cols='2' align='left' colsep='1'
rowsep='1'>
+ <colspec colname='c1' colwidth="1*"/>
+ <colspec colname='c2' colwidth="1*"/>
+ <thead>
+ <row>
+ <entry>Property</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>name</entry>
+ <entry>The name of the repository source, which is used by the
&RepositoryService; when obtaining a &RepositoryConnection; by
name.</entry>
+ </row>
+ <row>
+ <entry>repositoryName</entry>
+ <entry>The name for the federated repository.</entry>
+ </row>
+ <row>
+ <entry>configurationSourceName</entry>
+ <entry>The name of the &RepositorySource; that should be used as the
configuration repository, and in which is defined
+ how this federated repository is to be set up and configured.
+ This name is supplied to the &RepositoryConnectionFactory; that is provided
to this instance when added to the
+ &RepositoryLibrary;.</entry>
+ </row>
+ <row>
+ <entry>configurationWorkspaceName</entry>
+ <entry>The name of the workspace in the configuration &RepositorySource;
with the content defining
+ how this federated repository is to be set up and configured.</entry>
+ </row>
+ <row>
+ <entry>configurationSourcePath</entry>
+ <entry>The path to the node in the configuration repository below which a
"dna:federation" node exists with the
+ graph structure describing how this federated repository is to be
configured.</entry>
+ </row>
+ <row>
+ <entry>securityDomain</entry>
+ <entry>Optional property that, if used, specifies the name of the JAAS
application context that should be used
+ to establish the <link linkend="execution-contenxt">execution
context</link> for this repository.
+ This should correspond to the JAAS login configuration located within the JAAS
login configuration file,
+ and should be used only if a "<code>username</code>"
property is defined.</entry>
+ </row>
+ <row>
+ <entry>username</entry>
+ <entry>Optional property that, if used, defines the name of the JAAS subject
that should be used
+ to establish the <link linkend="execution-contenxt">execution
context</link> for this repository.
+ This should be used if a "<code>securityDomain</code>"
property is defined.</entry>
+ </row>
+ <row>
+ <entry>password</entry>
+ <entry>Optional property that, if used, defines the password of the JAAS
subject that should be used
+ to establish the <link linkend="execution-contenxt">execution
context</link> for this repository.
+ If the password is not provided but values for the
"<code>securityDomain</code>" and
"<code>username</code>" properties are,
+ then authentication will use the default JAAS callback handlers.</entry>
+ </row>
+ <row>
+ <entry>retryLimit</entry>
+ <entry>Optional property that, if used, defines the number of times that any
single operation on a &RepositoryConnection; to this source should be retried
+ following a communication failure. The default value is
'0'.</entry>
+ </row>
+ <row>
+ <entry>defaultCachePolicy</entry>
+ <entry>Optional property that, if used, defines the default for how long
this information provided by this source may to be
+ cached by other, higher-level components. The default value of null implies that
this source does not define a specific
+ duration for caching information provided by this repository
source.</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect2>
+ </sect1>
+ <sect1 id="custom-connectors">
+ <title>Writing custom connectors</title>
+ <para>
+ There may come a time when you want to tackle creating your own repository connector.
Maybe the connectors we provide out-of-the-box
+ don't work with your source. Maybe you want to use a different cache system.
+ Maybe you have a system that you want to make available through a JBoss DNA
repository. Or, maybe you're
+ a contributor and want to help us round out our library with a new connector. No
matter what the reason, creating a new connector
+ is pretty straightforward, as we'll see in this section.
+ </para>
+ <para>
+ Creating a custom connector involves the following steps:
+ <orderedlist>
+ <listitem>
+ <para>Create a Maven 2 project for your connector;</para>
+ </listitem>
+ <listitem>
+ <para>
+ Implement the &RepositorySource; interface, using JavaBean properties for each
bit of information the implementation will
+ need to establish a connection to the source system.
+ </para>
+ <para>
+ Then, implement the &RepositoryConnection; interface with a class that
represents a connection to the source. The
+ <code>execute(&ExecutionContext;, &Request;)</code> method
should process any and all requests that may come down the pike,
+ and the results of each request can be put directly on that request.
+ </para>
+ <para>
+ Don't forget unit tests that verify that the connector is doing what it's
expected to do. (If you'll be committing the connector
+ code to the JBoss DNA project, please ensure that the unit tests can be run by
others that may not have access to the
+ source system. In this case, consider writing integration tests that can be easily
configured to use different sources
+ in different environments, and try to make the failure messages clear when the
tests can't connect to the underlying source.)
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Configure JBoss DNA to use your connector. This may involve just registering the
source with the &RepositoryService;,
+ or it may involve adding a source to a configuration repository used by the
<link linkend="dna-connector-federation">federated
repository</link>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Deploy the JAR file with your connector (as well as any dependencies), and make
them available to JBoss DNA
+ in your application.
+ </para>
+ </listitem>
+ </orderedlist>
+ Let's go through each one of these steps in more detail.
+ </para>
+ <sect2 id="custom_connector_project">
+ <title>Creating the Maven 2 project</title>
+ <para>
+ The first step is to create the Maven 2 project that you can use to compile your code
and build the JARs.
+ Maven 2 automates a lot of the work, and since you're already <link
linkend="maven">set up to use Maven</link>,
+ using Maven for your project will save you a lot of time and effort. Of course,
you don't have to use Maven 2, but then you'll
+ have to get the required libraries and manage the compiling and building process
yourself.</para>
+ <note>
+ <para>JBoss DNA may provide in the future a Maven archetype for creating
connector projects. If you'd find this useful
+ and would like to help create it, please <link
linkend="preface">join the community</link>.
+ </para>
+ <para>In lieu of a Maven archetype, you may find it easier to start with a
small existing connector project.
+ The <emphasis
role="strong">dna-connector-filesystem</emphasis> project is small, but
it may be tough to separate
+ the stuff that every connector needs from the extra code and data structures that
manage the content.
+ See the subversion repository: <ulink
url="&Subversion;trunk/extensions/dna-connector-filesystem/">&Subversion;trunk/extensions/dna-connector-filesystem/</ulink>
+ </para>
+ </note>
+ <para>
+ You can create your Maven project any way you'd like. For examples, see the
+ <ulink
url="http://maven.apache.org/guides/getting-started/index.html#How_d...
2 documentation</ulink>.
+ Once you've done that, just add the dependencies in your project's
<code>pom.xml</code> dependencies section:
+ </para>
+ <programlisting role="XML"><![CDATA[
+<dependency>
+ <groupId>org.jboss.dna</groupId>
+ <artifactId>dna-graph</artifactId>
+ <version>0.4</version>
+</dependency>
+ ]]></programlisting>
+ <para>
+ This is the only dependency required for compiling a connector - Maven pulls in all
of the dependencies needed by
+ the 'dna-graph' artifact. Of course, you'll still have to add
dependencies for any library your connector needs
+ to talk to its underlying system.
+ </para>
+ <para>
+ As for testing, you probably will want to add more dependencies, such as those listed
here:
+ </para>
+ <programlisting role="XML"><![CDATA[
+<dependency>
+ <groupId>org.jboss.dna</groupId>
+ <artifactId>dna-graph</artifactId>
+ <version>0.4</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+</dependency>
+<dependency>
+ <groupId>org.jboss.dna</groupId>
+ <artifactId>dna-common</artifactId>
+ <version>0.4</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+</dependency>
+<dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.4</version>
+ <scope>test</scope>
+</dependency>
+<dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-library</artifactId>
+ <version>1.1</version>
+ <scope>test</scope>
+</dependency>
+<!-- Logging with Log4J -->
+<dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ <version>1.4.3</version>
+ <scope>test</scope>
+</dependency>
+<dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.14</version>
+ <scope>test</scope>
+</dependency>
+ ]]></programlisting>
+ <para>
+ Testing JBoss DNA connectors does not require a JCR repository or the JBoss DNA
services. (For more detail,
+ see the <link linkend="testing_custom_connectors">testing
section</link>.) However, if you want to do
+ integration testing with a JCR repository and the JBoss DNA services, you'll
need additional dependencies
+ (e.g., <code>dna-repository</code> and any other extensions).
+ </para>
+ <para>
+ At this point, your project should be set up correctly, and you're ready to move
on to
+ <link linkend="implementing_repository_source">writing the Java
implementation</link> for your connector.
+ </para>
+ </sect2>
+ <sect2 id="implementing_repository_source">
+ <title>Implementing a <code>RepositorySource</code></title>
+ <para>
+ As mentioned earlier, a <emphasis>connector</emphasis> consists of the
Java code that is used to access content
+ from a system. Perhaps the most important class that makes up a connector is the
implementation of the
+ &RepositorySource;. This class is analogous to JDBC's DataSource in that it
is instantiated to represent
+ a single instance of a system that will be accessed, and it contains enough
information (in the form of JavaBean properties)
+ so that it can create connections to the source.
+ </para>
+ <para>
+ Why is the &RepositorySource; implementation a JavaBean? Well, this is the class
that is instantiated, usually
+ reflectively, and so a no-arg constructor is required. Using JavaBean properties
makes it possible
+ to reflect upon the object's class to determine the properties that can be set
(using setters) and read
+ (using getters). This means that an administrative application can instantiate,
configure, and manage
+ the objects that represent the actual sources, without having to know anything about
the actual implementation.
+ </para>
+ <para>
+ So, your connector will need a public class that implements &RepositorySource;
and provides JavaBean properties
+ for any kind of inputs or options required to establish a connection to and interact
with the underlying source.
+ Most of the semantics of the class are defined by the &RepositorySource; and
inherited interface.
+ However, there are a few characteristics that are worth mentioning here.
+ </para>
+ <sect3 id="connector_cache_policy">
+ <title>Cache policy</title>
+ <para>
+ Each connector is responsible for determining whether and how long DNA is to cache
the
+ content made available by the connector. This is referred to as the
<emphasis>caching policy</emphasis>,
+ and consists of a <emphasis>time to live</emphasis> value representing
the number of milliseconds that
+ a piece of data may be cached. After the TTL has passed, the information is no
longer used.
+ </para>
+ <para>
+ DNA allows a connector to use a flexible and powerful caching policy. First, each
connection returns the
+ <emphasis>default</emphasis> caching policy for all information returned
by that connection.
+ Often this policy can be configured via properties on the &RepositorySource;
implementation.
+ This is optional, meaning the connector can return <code>null</code> if
it does not wish to
+ have a default caching policy.
+ </para>
+ <para>
+ Second, the connector is able to override its default caching policy on individual
requests
+ (which we'll cover in the <link
linkend="implementing_repository_connection">next section</link>).
+ Again, this is optional, meaning that a null caching policy on a request implies
that the
+ request has no overridden caching policy.
+ </para>
+ <para>
+ Third, if the connector has no default caching policy and none is set on the
individual requests,
+ DNA uses whatever caching policy is set up for that component using the connector.
For example, the federating
+ connector allows a default caching policy to be specified, and this policy is used
should the sources
+ being federated not define their own caching policy.
+ </para>
+ <para>
+ In summary, a connector has total control over whether and for how long the
information it provides
+ is cached.
+ </para>
+ </sect3>
+ <sect3 id="repository_source_jndi">
+ <title>Leveraging JNDI</title>
+ <para>
+ Sometimes it is necessary (or easier) for a &RepositorySource; implementation to
look up an object in JNDI.
+ One example of this is the JBoss Cache connector: while the connector can
+ instantiate a new JBoss Cache instance, more interesting use cases involve JBoss
Cache instances that are
+ set up for clustering and replication, something that is generally difficult to
configure in a single JavaBean.
+ Therefore the &JBossCacheSource; has optional JavaBean properties that define
how it is to look up a
+ JBoss Cache instance in JNDI.
+ </para>
+ <para>
+ This is a simple pattern that you may find useful in your connector. Basically, if
your source implementation
+ can look up an object in JNDI, simply use a single JavaBean String property that
defines the
+ full name that should be used to locate that object in JNDI. Usually it's best
to include "Jndi" in the
+ JavaBean property name so that administrative users understand the purpose of the
property.
+ (And some may suggest that any optional property also use the word
"optional" in the property name.)
+ </para>
+ </sect3>
+ <sect3 id="repository_source_capabilities">
+ <title>Capabilities</title>
+ <para>
+ Another characteristic of a &RepositorySource; implementation is that it
provides some hint as to whether
+ it supports several features. This is defined on the interface as a method that
returns a
+ &RepositorySourceCapabilities; object. This class currently provides methods
that say whether the connector supports
+ updates, whether it supports same-name-siblings (SNS), and whether the connector
supports listeners and events.
+ </para>
+ <para>
+ Note that these may be hard-coded values, or the connector's response may be
determined at runtime by various factors.
+ For example, a connector may interrogate the underlying system to decide whether it
can support updates.
+ </para>
+ <para>
+ The &RepositorySourceCapabilities; can be used as is (the class is immutable),
or it can be subclassed
+ to provide more complex behavior. It is important, however, that the capabilities
remain constant
+ throughout the lifetime of the &RepositorySource; instance.
+ </para>
+ <note>
+ <para>
+ Why a concrete class and not an interface? By using a concrete class, connectors
inherit the default
+ behavior. If additional capabilities need to be added to the class in future
releases, connectors may
+ not have to override the defaults. This provides some insulation against future
enhancements to the connector framework.
+ </para>
+ </note>
+ </sect3>
+ <sect3 id="repository_source_security">
+ <title>Security and authentication</title>
+ <para>
+ As we'll see in the next section, the main method connectors have to process
requests takes an &ExecutionContext;,
+ which contains the JAAS security information of the subject performing the request.
This means that the connector
+ can use this to determine authentication and authorization information for each
request.
+ </para>
+ <para>
+ Sometimes that is not sufficient. For example, it may be that the connector needs
its own authorization information
+ so that it can establish a connection (even if user-level privileges still use the
&ExecutionContext; provided with
+ each request). In this case, the &RepositorySource; implementation will
probably need JavaBean properties
+ that represent the connector's authentication information. This may take the
form of a username and password,
+ or it may be properties that are used to delegate authentication to JAAS.
+ Either way, just realize that it's perfectly acceptable for the connector to
require its own security properties.
+ </para>
+ </sect3>
+ </sect2>
+ <sect2 id="implementing_repository_connection">
+ <title>Implementing a
<code>RepositoryConnection</code></title>
+ <para>
+ One job of the &RepositorySource; implementation is to create connections to the
underlying sources.
+ Connections are represented by classes that implement the &RepositoryConnection;
interface, and creating this
+ class is the next step in writing a repository connector. This is what we'll
cover in this section.
+ </para>
+ <para>
+ The &RepositoryConnection; interface is pretty straightforward:
+ </para>
+ <programlisting>
+/**
+ * A connection to a repository source.
+ * <p>
+ * These connections need not support concurrent operations by multiple threads.
+ * </p>
+ */
+@NotThreadSafe
+public interface &RepositoryConnection; {
+
+ /**
+ * Get the name for this repository source. This value should be the same as that
returned
+ * by the same &RepositorySource; that created this connection.
+ *
+ * @return the identifier; never null or empty
+ */
+ String getSourceName();
+
+ /**
+ * Return the transactional resource associated with this connection. The transaction
manager
+ * will use this resource to manage the participation of this connection in a
distributed transaction.
+ *
+ * @return the XA resource, or null if this connection is not aware of distributed
transactions
+ */
+ XAResource getXAResource();
+
+ /**
+ * Ping the underlying system to determine if the connection is still valid and
alive.
+ *
+ * @param time the length of time to wait before timing out
+ * @param unit the time unit to use; may not be null
+ * @return true if this connection is still valid and can still be used, or false
otherwise
+ * @throws InterruptedException if the thread has been interrupted during the
operation
+ */
+ boolean ping( long time, &TimeUnit; unit ) throws InterruptedException;
+
+ /**
+ * Set the listener that is to receive notifications to changes to content within
this source.
+ *
+ * @param listener the new listener, or null if no component is interested in the
change notifications
+ */
+ void setListener( &RepositorySourceListener; listener );
+
+ /**
+ * Get the default cache policy for this repository. If none is provided, a global
cache policy
+ * will be used.
+ *
+ * @return the default cache policy
+ */
+ &CachePolicy; getDefaultCachePolicy();
+
+ /**
+ * Execute the supplied commands against this repository source.
+ *
+ * @param context the environment in which the commands are being executed; never
null
+ * @param request the request to be executed; never null
+ * @throws RepositorySourceException if there is a problem loading the node data
+ */
+ void execute( &ExecutionContext; context,
+ &Request; request ) throws &RepositorySourceException;;
+
+ /**
+ * Close this connection to signal that it is no longer needed and that any
accumulated
+ * resources are to be released.
+ */
+ void close();
+}</programlisting>
+ <para>
+ While most of these methods are straightforward, a few warrant additional
information.
+ The <code>ping(...)</code> allows DNA to check the connection to see if
it is
+ alive. This method can be used in a variety of situations, ranging from verifying
that a &RepositorySource;'s
+ JavaBean properties are correct to ensuring that a connection is still alive before
returning the connection from
+ a connection pool.
+ </para>
+ <para>
+ DNA hasn't yet defined the event mechanism, so connectors don't have any
methods to invoke on the &RepositorySourceListener;.
+ This will be defined in the next release, so feel free to manage the listeners now.
Note that by default the &RepositorySourceCapabilities; returns
+ <code>false</code> for <code>supportsEvents()</code>.
+ </para>
+ <para>
+ The most important method on this interface, though, is the
<code>execute(...)</code> method, which serves as the
+ mechanism by which the component using the connector access and manipulates the
content exposed by the connector.
+ The first parameter to this method is the &ExecutionContext;, which contains the
information about environment
+ as well as the subject performing the request. This was discussed <link
linkend="execution-context">earlier</link>.
+ </para>
+ <para>
+ The second parameter, however, represents a request that is to be processed by the
connector. Request objects can
+ take many different forms, as there are different classes for each kind of request
(see the table below).
+ Each request contains the information a connector needs to do the processing, and it
also is the place
+ where the connector places the results (or the error, if one occurs).
+ </para>
+ <para>
+ How do the requests reference a node (or nodes)? Since requests are coming from a
client, the client
+ may identify a particular node using a &Location; object that is created with:
+ <itemizedlist>
+ <listitem>
+ <para>the &Path; to the node; or</para>
+ </listitem>
+ <listitem>
+ <para>one or more <emphasis>identification
properties</emphasis> that are likely source=specific
+ and that are represented with &Property; objects; or</para>
+ </listitem>
+ <listitem>
+ <para>a combination of both.</para>
+ </listitem>
+ </itemizedlist>
+ So, when a client knows the path or the identification properties, they can create a
&Location;. However,
+ all of the requests return &Location; objects, so often times the client simply
uses the location
+ from a previous request. Since &Location; is an immutable class, it is perfectly
safe to reuse them.
+ </para>
+ <para>
+ One more thing about locations: while the request may have an incomplete location
(e.g., a path but no
+ identification properties), the connector is expected to set on the request the
<emphasis>actual</emphasis>
+ location that contains the path and all identification properties. So as long as the
client
+ reuses the actual locations in subsequent requests, the connectors will have the
benefit of having
+ both the path and identification properties. Connectors can then be written to
leverage this
+ information, although the connector should still perform as expected when requests
have incomplete locations.
+ </para>
+ <table frame='all'>
+ <title>Types of Node Operation Requests</title>
+ <tgroup cols='2' align='left' colsep='1'
rowsep='1'>
+ <colspec colname='c1' colwidth="1*"/>
+ <colspec colname='c2' colwidth="1*"/>
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>ReadNodeRequest</entry>
+ <entry>
+ A request to read from the named workspace in the source a node's properties
and children.
+ The node may be specified by path and/or by identification properties.
+ The connector returns all properties and the locations for all children,
+ or sets a &PathNotFoundException; error on the request if the node did not
exist in the workspace.
+ If the node is found, the connector sets on the request the actual location of
the node (including the path and identification properties).
+ The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
+ </entry>
+ </row>
+ <row>
+ <entry>VerifyNodeExistsRequest</entry>
+ <entry>
+ A request to verify the existance of a node at the specified location in the
named workspace of the source.
+ The connector returns all the actual location for the node if it exists, or
+ sets a &PathNotFoundException; error on the request if the node does not
exist in the workspace.
+ The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
+ </entry>
+ </row>
+ <row>
+ <entry>ReadAllPropertiesRequest</entry>
+ <entry>
+ A request to read from the named workspace in the source all of the properties of
a node.
+ The node may be specified by path and/or by identification properties.
+ The connector returns all properties that were found on the node,
+ or sets a &PathNotFoundException; error on the request if the node did not
exist in the workspace.
+ If the node is found, the connector sets on the request the actual location of
the node (including the path and identification properties).
+ The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
+ </entry>
+ </row>
+ <row>
+ <entry>ReadPropertyRequest</entry>
+ <entry>
+ A request to read from the named workspace in the source a single property of a
node.
+ The node may be specified by path and/or by identification properties,
+ and the property is specified by name.
+ The connector returns the property if found on the node,
+ or sets a &PathNotFoundException; error on the request if the node or
property did not exist in the workspace.
+ If the node is found, the connector sets on the request the actual location of
the node (including the path and identification properties).
+ The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
+ </entry>
+ </row>
+ <row>
+ <entry>ReadAllChildrenRequest</entry>
+ <entry>
+ A request to read from the named workspace in the source all of the children of a
node.
+ The node may be specified by path and/or by identification properties.
+ The connector returns an ordered list of locations for each child found on the
node,
+ an empty list if the node had no children,
+ or sets a &PathNotFoundException; error on the request if the node did not
exist in the workspace.
+ If the node is found, the connector sets on the request the actual location of
the parent node (including the path and identification properties).
+ The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
+ </entry>
+ </row>
+ <row>
+ <entry>ReadBlockOfChildrenRequest</entry>
+ <entry>
+ A request to read from the named workspace in the source a block of children of a
node, starting with the n<superscript>th</superscript> children.
+ This is designed to allow paging through the children, which is much more
efficient for large numbers of children.
+ The node may be specified by path and/or by identification properties, and the
block
+ is defined by a starting index and a count (i.e., the block size).
+ The connector returns an ordered list of locations for each of the node's
children found in the block,
+ or an empty list if there are no children in that range.
+ The connector also sets on the request the actual location of the parent node
(including the path and identification properties)
+ or sets a &PathNotFoundException; error on the request if the parent node did
not exist in the workspace.
+ The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
+ </entry>
+ </row>
+ <row>
+ <entry>ReadNextBlockOfChildrenRequest</entry>
+ <entry>
+ A request to read from the named workspace in the source a block of children of a
node, starting with the children that immediately follow
+ a previously-returned child.
+ This is designed to allow paging through the children, which is much more
efficient for large numbers of children.
+ The node may be specified by path and/or by identification properties, and the
block
+ is defined by the location of the node immediately preceding the block and a
count (i.e., the block size).
+ The connector returns an ordered list of locations for each of the node's
children found in the block,
+ or an empty list if there are no children in that range.
+ The connector also sets on the request the actual location of the parent node
(including the path and identification properties)
+ or sets a &PathNotFoundException; error on the request if the parent node did
not exist in the workspace.
+ The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
+ </entry>
+ </row>
+ <row>
+ <entry>ReadBranchRequest</entry>
+ <entry>
+ A request to read a portion of a subgraph that has as its root a particular node,
up to a maximum depth.
+ This request is an efficient mechanism when a branch (or part of a branch) is to
be navigated and processed,
+ and replaces some non-trivial code to read the branch iteratively using multiple
<code>ReadNodeRequest</code>s.
+ The connector reads the branch to the specified maximum depth, returning the
properties and children for all
+ nodes found in the branch.
+ The connector also sets on the request the actual location of the branch's
root node (including the path and identification properties).
+ The connector sets a &PathNotFoundException; error on the request if the node
at
+ the top of the branch does not exist in the workspace.
+ The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
+ </entry>
+ </row>
+ <row>
+ <entry>CreateNodeRequest</entry>
+ <entry>
+ A request to create a node at the specified location and setting on the new node
the properties included in the request.
+ The connector creates the node at the desired location, adjusting any
same-name-sibling indexes as required.
+ (If an SNS index is provided in the new node's location, existing children
with the same name after that SNS index
+ will have their SNS indexes adjusted. However, if the requested location does
not include a SNS index, the new
+ node is added after all existing children, and it's SNS index is set
accordingly.)
+ The connector also sets on the request the actual location of the new node
(including the path and identification properties)..
+ The connector sets a &PathNotFoundException; error on the request if the
parent node does not exist in the workspace.
+ The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
+ </entry>
+ </row>
+ <row>
+ <entry>RemovePropertiesRequest</entry>
+ <entry>
+ A request to remove a set of properties on an existing node. The request
contains the location of the node as well as the
+ names of the properties to be removed. The connector performs these changes and
sets on the request the
+ actual location (including the path and identification properties) of the node.
+ The connector sets a &PathNotFoundException; error on the request if the node
does not exist in the workspace.
+ The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
+ </entry>
+ </row>
+ <row>
+ <entry>UpdatePropertiesRequest</entry>
+ <entry>
+ A request to set or update properties on an existing node. The request contains
the location of the node as well as the
+ properties to be set and those to be deleted. The connector performs these
changes and sets on the request the
+ actual location (including the path and identification properties) of the node.
+ The connector sets a &PathNotFoundException; error on the request if the node
does not exist in the workspace.
+ The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
+ </entry>
+ </row>
+ <row>
+ <entry>RenameNodeRequest</entry>
+ <entry>
+ A request to change the name of a node. The connector changes the node's
name, adjusts all SNS indexes
+ accordingly, and returns the actual locations (including the path and
identification properties) of both the original
+ location and the new location.
+ The connector sets a &PathNotFoundException; error on the request if the node
does not exist in the workspace.
+ The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
+ </entry>
+ </row>
+ <row>
+ <entry>CopyBranchRequest</entry>
+ <entry>
+ A request to copy a portion of a subgraph that has as its root a particular node,
up to a maximum depth.
+ The request includes the name of the workspace where the original node is located
as well as the name of the
+ workspace where the copy is to be placed (these may be the same, but may be
different).
+ The connector copies the branch from the original location, up to the specified
maximum depth, and places a copy
+ of the node as a child of the new location.
+ The connector also sets on the request the actual location (including the path
and identification properties)
+ of the original location as well as the location of the new copy.
+ The connector sets a &PathNotFoundException; error on the request if the node
at
+ the top of the branch does not exist in the workspace.
+ The connector sets a &InvalidWorkspaceException; error on the request if one
of the named workspaces does not exist.
+ </entry>
+ </row>
+ <row>
+ <entry>MoveBranchRequest</entry>
+ <entry>
+ A request to move a subgraph that has a particular node as its root.
+ The connector moves the branch from the original location and places it as child
of the specified new location.
+ The connector also sets on the request the actual location (including the path
and identification properties)
+ of the original and new locations. The connector will adjust SNS indexes
accordingly.
+ The connector sets a &PathNotFoundException; error on the request if the node
that is to be moved or the
+ new location do not exist in the workspace.
+ The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
+ </entry>
+ </row>
+ <row>
+ <entry>DeleteBranchRequest</entry>
+ <entry>
+ A request to delete an entire branch specified by a single node's location.
+ The connector deletes the specified node and all nodes below it, and sets the
actual location,
+ including the path and identification properties, of the node that was deleted.
+ The connector sets a &PathNotFoundException; error on the request if the node
being deleted does not exist in the workspace.
+ The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
+ </entry>
+ </row>
+ <row>
+ <entry>CompositeRequest</entry>
+ <entry>
+ A request that actually comprises multiple requests (none of which will be a
composite).
+ The connector simply processes all of the requests in the composite request, but
should set on the composite
+ request any error (usually the first error) that occurs during processing of the
contained requests.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ <para>There are also requests that deal with workspaces:</para>
+ <table frame='all'>
+ <title>Types of Workspace Requests</title>
+ <tgroup cols='2' align='left' colsep='1'
rowsep='1'>
+ <colspec colname='c1' colwidth="1*"/>
+ <colspec colname='c2' colwidth="1*"/>
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>GetWorkspacesRequest</entry>
+ <entry>
+ A request to obtain the names of the existing workspaces that are accessible to
the caller.
+ </entry>
+ </row>
+ <row>
+ <entry>VerifyWorkspaceRequest</entry>
+ <entry>
+ A request to verify that a workspace with a particular name exists.
+ The connector returns the actual location for the root node if the workspace
exists, as well as the actual name of the workspace
+ (e.g., the default workspace name if a null name is supplied).
+ </entry>
+ </row>
+ <row>
+ <entry>CreateWorkspaceRequest</entry>
+ <entry>
+ A request to create a workspace with a particular name.
+ The connector returns the actual location for the root node if the workspace
exists, as well as the actual name of the workspace
+ (e.g., the default workspace name if a null name is supplied).
+ The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace already exists.
+ </entry>
+ </row>
+ <row>
+ <entry>DestroyWorkspaceRequest</entry>
+ <entry>
+ A request to destroy a workspace with a particular name.
+ The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
+ </entry>
+ </row>
+ <row>
+ <entry>CloneWorkspaceRequest</entry>
+ <entry>
+ A request to clone one named workspace as another new named workspace.
+ The connector sets a &InvalidWorkspaceException; error on the request if the
original workspace does not exist,
+ or if the new workspace already exists.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ <para>
+ Although there are over a dozen different kinds of requests, we do anticipate adding
more in future releases.
+ For example, DNA will likely support searching repository content in sources through
an additional subclass of &Request;.
+ Getting the version history for a node will likely be another kind of request added
in an upcoming release.
+ </para>
+ <para>
+ A connector is technically free to implement the
<code>execute(...)</code> method in any way, as long as the semantics
+ are maintained. But DNA provides a &RequestProcessor; class that can simplify
writing your own connector and at the
+ same time help insulate your connector from new kinds of requests that may be added
in the future. The &RequestProcessor;
+ is an abstract class that defines a <code>process(...)</code> method for
each concrete &Request; subclass.
+ In other words, there is a <code>process(CompositeRequest)</code> method,
a <code>process(ReadNodeRequest)</code> method,
+ and so on.
+ </para>
+ <para>
+ To use this in your connector, simply create a subclass of &RequestProcessor;,
overriding all of the abstract methods and optionally
+ overriding any of the other methods that have a default implementation.
+ </para>
+ <note>
+ <para>
+ The &RequestProcessor; abstract class contains default implementations for quite
a few of the <code>process(...)</code> methods,
+ and these will be <emphasis>sufficient</emphasis> but probably not
efficient or optimum. If you can provide a more efficient
+ implementation given your source, feel free to do so. However, if performance is
not a big issue, all of the concrete methods
+ will provide the correct behavior. Keep things simple to start out - you can always
provide better implementations later.
+ </para>
+ </note>
+ <para>
+ Then, in your connector's <code>execute(&ExecutionContext;,
&Request;)</code> method, instantiate your &RequestProcessor; subclass
+ and call its <code>process(&Request;) method, passing in the
<code>execute(...)</code> method's &Request; parameter.</code>
+ The &RequestProcessor; will determine the appropriate method given the actual
&Request; object and will then invoke that method:
+ </para>
+ <programlisting>
+public void execute( final &ExecutionContext; context,
+ final &Request; request ) throws RepositorySourceException {
+ RequestProcessor processor = new RequestProcessor(context);
+ try {
+ processor.process(request);
+ } finally {
+ processor.close();
+ }
+}</programlisting>
+ <para>
+ If you do this, the bulk of your connector implementation may be in the
&RequestProcessor; implementation methods.
+ This not only is pretty maintainable, it also lends itself to easier testing. And
should any new request types be added
+ in the future, your connector may work just fine without any changes. In fact, if
the &RequestProcessor; class
+ can implement meaningful methods for those new request types, your connector may
"just work". Or, at least
+ your connector will still be binary compatible, even if your connector won't
support any of the new features.
+ </para>
+ <para>
+ Finally, how should the connector handle exceptions? As mentioned above, each
&Request; object has a slot where the connector
+ can set any exception encountered during processing. This not only handles the
exception, but in the case of &CompositeRequest;s
+ it also correctly associates the problem with the request. However, it is perfectly
acceptable to throw an exception
+ if the connection becomes invalid (e.g., there is a communication failure) or if a
fatal error would prevent subsequent
+ requests from being processed.
+ </para>
+ </sect2>
+ <sect2 id="testing_custom_connectors">
+ <title>Testing custom connectors</title>
+ <para>
+ Testing connectors is not really that much different than testing other classes.
Using mocks may help to isolate your
+ instances so you can create more unit tests that don't require the underlying
source system.
+ </para>
+ <para>
+ However, there may be times when you have to use the underlying source system in your
tests. If this is the case,
+ we recommend using Maven integration tests, which run at a different point in the
Maven lifecycle. The benefit of
+ using integration tests is that by convention they're able to rely upon external
systems. Plus, your unit tests
+ don't become polluted with slow-running tests that break if the external system
is not available.
+ </para>
+ </sect2>
+ <sect2 id="deploying_custom_connectors">
+ <title>Configuring and deploying custom connectors</title>
+ <para>
+ After building your connector project, you need to configure the JBoss DNA components
your application is using so
+ that they use your connector. In a lot of cases, this will entail instantiating your
connector's &RepositorySource; class,
+ setting the various properties, and registering it with a &RepositoryLibrary;.
Or, it will entail using a configuration
+ repository to use your source and letting &RepositoryService; instantiate and set
up your &RepositorySource; instance.
+ Or, you can just instantiate and set it up manually, passing the instance to whatever
component needs it.
+ </para>
+ <para>
+ And of course you have to make the JAR file containing your connector (as well as any
dependency JARs) available to
+ your application's classpath.
+ </para>
+ </sect2>
+ </sect1>
+ <sect1 id="dna_graph_api">
+ <title>Graph API for using connectors</title>
+ <para>
+ So far we've talked about repositories, repository connectors, and how connectors
respond to the different kinds of requests.
+ Normally you'd code to the JCR API and use our JCR implementation. However, what
does your code look like if you want
+ to use the connectors directly, without using our JCR implementation? After all, you
may be a contributor to JBoss DNA,
+ or you may want to take advantage of our connectors without all the overhead of JCR.
+ </para>
+ <para>
+ One option, of course, is to explicitly create the different requests and pass them to
the connector's <code>execute(...)</code> method.
+ While this is the most efficient approach (and one taken in some key DNA components),
you probably want something that
+ is much less verbose and much easier to use. This is where the DNA graph API comes
in.
+ </para>
+ <para>
+ JBoss DNA's <emphasis>Graph API</emphasis> was designed as a
lightweight public API for working with graph information,
+ and it insulates components from the underlying requests and interacting with
connectors.
+ The &Graph; class is the primary class in API, and each instance represents a
single, independent
+ view of the graph of content from a single connector. &Graph; instances return
snapshots of state, and those snapshots
+ never change after they're retrieved. To obtain a &Graph; instance, use the
static <code>create(...)</code>
+ method, supplying the name of the source, a &RepositoryConnectionFactory; from
which a &RepositoryConnection; can be obtained,
+ and the &ExecutionContext;.
+ </para>
+ <para>
+ The &Graph; class basically represents an <ulink
url="http://www.martinfowler.com/bliki/DomainSpecificLanguage.html&q...
domain specific language (DSL)</ulink>,
+ designed to be easy to use in an application.
+ The Graph API makes extensive use of interfaces and method chaining, so that methods
return a concise interface that has only those
+ methods that make sense at that point. In fact, this should be really easy if your
IDE has code completion.
+ Just remember that under the covers, a &Graph; is just building &Request;
objects, submitting them to the connector,
+ and then exposing the results.
+ </para>
+ <sect2 id="dna_graph_api_workspaces">
+ <title>Using workspaces</title>
+ <para>
+ Let's look at some examples of how the Graph API works. This first example shows
how to obtain the names of the available workspaces:
+ </para>
+ <programlisting>
+&Set;<&String;> workspaceNames = graph.getWorkspaces();
+</programlisting>
+ <para>Once you know the name of the workspace, you can specify that the graph
should use it:
+ </para>
+ <programlisting>
+graph.useWorkspace("myWorkspace");
+</programlisting>
+ <para>
+ From this point forward, all requests will apply to the workspace named
"myWorkspace". At any time, you can use a different workspace,
+ which will affect all subsequent requests made using the graph. Of course, creating a
new workspace is just as easy:
+ </para>
+ <programlisting>
+graph.createWorkspace().named("newWorkspace");
+</programlisting>
+ <para>This will attempt to create a workspace named "newWorkspace",
which will fail if that workspace already exists. You may
+ want to create a new workspace with a name that should be altered if the name you
supply is already used. The following code shows
+ how you can do this:
+ </para>
+ <programlisting>
+graph.createWorkspace().namedSomethingLike("newWorkspace");
+</programlisting>
+ <para>If there is no existing workspace named "newWorkspace", a new
one will be created with this name. However, if "newWorkspace" already
+ exists, this call will create a workspace with a name that is some alteration of the
supplied name.
+ </para>
+ <para>
+ You can also clone workspaces, too:
+ </para>
+ <programlisting>
+graph.createWorkspace().clonedFrom("original").named("something");
+</programlisting>
+ <para>
+ or
+ </para>
+ <programlisting>
+graph.createWorkspace().clonedFrom("original").namedSomethingLike("something");
+</programlisting>
+ <para>
+ As you can see, it's very easy to specify which workspace you want to use or to
create new workspaces. You can also find out which workspace
+ the graph is currently using:
+ </para>
+ <programlisting>
+&String; current = graph.getCurrentWorkspaceName();
+</programlisting>
+ <para>or, if you want, you can get more information about the workspace:
+ </para>
+ <programlisting>
+&Workspace; current = graph.getCurrentWorkspace();
+&String; name = current.getName();
+&Location; rootLocation = current.getRoot();
+</programlisting>
+ </sect2>
+ <sect2 id="dna_graph_api_nodes">
+ <title>Working with nodes</title>
+ <para>
+ Now let's switch to working with nodes. This first example returns a map of
properties (keyed by property name)
+ for a node at a specific &Path;:
+ </para>
+ <programlisting>
+&Path; path = ...
+Map<&Name;,&Property;> propertiesByName =
graph.getPropertiesByName().on(path);
+</programlisting>
+ <para>
+ This next example shows how the graph can be used to obtain and loop over the
properties of a node:
+ </para>
+ <programlisting>
+&Path; path = ...
+for ( &Property; property : graph.getProperties().on(path) ) {
+ ...
+}
+</programlisting>
+ <para>
+ Likewise, the next example shows how the graph can be used to obtain and loop over the
children of a node:
+ </para>
+ <programlisting>
+&Path; path = ...
+for ( &Location; child : graph.getChildren().of(path) ) {
+ &Path; childPath = child.getPath();
+ ...
+}
+</programlisting>
+ <para>
+ Notice that the examples pass a &Path; instance to the
<code>on(...)</code> and <code>of(...)</code> methods. Many
+ of the Graph API methods take a variety of parameter types, including String,
&Path;s, &Location;s, &UUID;, or &Property; parameters.
+ This should make it easy to use in many different situations.
+ </para>
+ <para>
+ Of course, changing content is more interesting and offers more interesting
possibilities. Here are a few examples:
+ </para>
+ <programlisting>
+&Path; path = ...
+&Location; location = ...
+&Property; idProp1 = ...
+&Property; idProp2 = ...
+&UUID; uuid = ...
+graph.move(path).into(idProp1, idProp2);
+graph.copy(path).into(location);
+graph.delete(uuid);
+graph.delete(idProp1,idProp2);
+</programlisting>
+ <para>
+ The methods shown above work immediately, as soon as each request is built. However,
there is another way to use
+ the &Graph; object, and that is in a <emphasis>batch</emphasis> mode.
Simply create a &GraphBatch; object using the
+ <code>batch()</code> method, create the requests on that batch object, and
then execute all of the commands on the
+ batch by calling its <code>execute()</code> method. That
<code>execute()</code> method returns a &Results; interface
+ that can be used to read the node information retrieved by the batched requests.
+ </para>
+ <para>
+ Method chaining works really well with the batch mode, since multiple commands can be
assembled together very easily:
+ </para>
+ <programlisting>
+&Path; path = ...
+String path2 = ...
+&Location; location = ...
+&Property; idProp1 = ...
+&Property; idProp2 = ...
+&UUID; uuid = ...
+graph.batch().move(path).into(idProp1,
idProp2).and().copy(path2).into(location).and().delete(uuid).execute();
+&Results; results = graph.batch().read(path2).
+ and().readChildren().of(idProp1,idProp2).
+ and().readSugraphOfDepth(3).at(uuid2).
+ execute();
+for ( &Location; child : results.getNode(path2) ) {
+ ...
+}
+</programlisting>
+ <para>
+ Of course, this section provided just a hint of the Graph API.
+ The &Graph; interface is actually quite complete and offers a full-featured
approach for reading and updating a graph.
+ For more information, see the &Graph; JavaDocs.
+ </para>
+ </sect2>
+ </sect1>
+ <sect1>
+ <title>Summary</title>
+ <para>
+ In this chapter, we covered all the aspects of JBoss DNA repositories, including the
connector framework,
+ how DNA's JCR implementation works with connectors, what connectors are available
(and how to use them),
+ and how to write your own connector. So now that you know how to set up and use JBoss
DNA repositories,
+ the <link linkend="jcr">next chapter</link> describes how you
can leverage JBoss DNA's JCR implementation.
+ </para>
+ </sect1>
+</chapter>
Property changes on:
trunk/docs/reference/src/main/docbook/en-US/content/core/connector.xml
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Deleted: trunk/docs/reference/src/main/docbook/en-US/content/core/repositories.xml
===================================================================
--- trunk/docs/reference/src/main/docbook/en-US/content/core/repositories.xml 2009-06-08
15:21:29 UTC (rev 996)
+++ trunk/docs/reference/src/main/docbook/en-US/content/core/repositories.xml 2009-06-08
15:47:40 UTC (rev 997)
@@ -1,1468 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- ~ JBoss DNA (
http://www.jboss.org/dna)
- ~
- ~ See the COPYRIGHT.txt file distributed with this work for information
- ~ regarding copyright ownership. Some portions may be licensed
- ~ to Red Hat, Inc. under one or more contributor license agreements.
- ~ See the AUTHORS.txt file in the distribution for a full listing of
- ~ individual contributors.
- ~
- ~ JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
- ~ is licensed to you under the terms of the GNU Lesser General Public License as
- ~ published by the Free Software Foundation; either version 2.1 of
- ~ the License, or (at your option) any later version.
- ~
- ~ JBoss DNA is distributed in the hope that it will be useful,
- ~ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- ~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- ~ for more details.
- ~
- ~ You should have received a copy of the GNU Lesser General Public License
- ~ along with this distribution; if not, write to:
- ~ Free Software Foundation, Inc.
- ~ 51 Franklin Street, Fifth Floor
- ~ Boston, MA 02110-1301 USA
- -->
-<!DOCTYPE preface PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
-<!ENTITY % CustomDTD SYSTEM "../../custom.dtd">
-%CustomDTD;
-]>
-<chapter id="repositories">
- <title>Repositories</title>
- <para></para>
- <para>There is a lot of information stored in many of different places:
databases, repositories, SCM systems,
- registries, file systems, services, etc. The purpose of the federation engine is to
allow applications to use the JCR API
- to access that information as if it were all stored in a single JCR repository, but
to really leave the information where
- it is.</para>
- <para>Why not just copy or move the information into a JCR repository? Moving it
is probably pretty difficult, since most
- likely there are existing applications that rely upon that information being where it
is. All of those applications
- would break or have to change. And copying the information means that we'd have to
continually synchronize the changes.
- This not only is a lot of work, but it often makes it difficult to know whether
information is accurate and "the master" data.
- </para>
- <para>JBoss DNA lets us leave information where it, yet access it through the JCR
API as if it were in one big repository.
- One major benefit is that existing applications that use the information in the
original locations don't break, since they
- can keep using the information. But now our JCR clients can also access all the
information, too. And if our federating JBoss DNA repository is
- configured to allow updates, JCR client applications can change the information in
the repository and JBoss DNA will propagate
- those changes down to the original source, making those changes visible to all the
other applications.
- </para>
- <para>
- In short, all clients see the correct information, even when it changes in the
underlying systems. But the JCR clients can get to all of the information
- in one spot, using one powerful standard API.
- </para>
- <sect1 id="connectors">
- <title>Repository connectors</title>
- <para>
- With JBoss DNA, your applications use the <ulink
url="&JSR170;">JCR API</ulink> to work with the repository,
- but the DNA repository transparently fetches the information from different kinds of
repositories and storage systems,
- not just a single purpose-built store. This is fundamentally what makes JBoss DNA
different.
- </para>
- <para>How does JBoss DNA do this? At the heart of JBoss DNA and it's JCR
implementation is a simple graph-based
- <emphasis>repository connector</emphasis> system. Essentially, JBoss
DNA's JCR implementation uses a single
- repository connector to access all content:
- <figure id="dnajcr-and-connector">
- <title>JBoss DNA's JCR implementation delegates to a repository
connector</title>
- <graphic align="center" scale="100"
fileref="dnajcr-and-connector.png"/>
- </figure>
- That single repository connector could use an in-memory repository, a JBoss Cache
instance (including those that are clustered and replicated),
- or a federated repository where content from multiple sources is unified.
- <figure id="dna-connectors-0.2">
- <title>JBoss DNA can put JCR on top of multiple kinds of systems</title>
- <graphic align="center" scale="100"
fileref="dna-connectors-0.2.png"/>
- </figure>
- Really, the federated connector gives us all kinds of possibilities, since we can use
that connector on top of lots of connectors
- to other individual sources. This simple connector architecture is fundamentally what
makes JBoss DNA so powerful and flexible.
- Along with a good library of connectors, which is what we're planning to create.
- </para>
- <para>
- For instance, we want to build a connector to <ulink
url="&JIRA-39;">other JCR repositories</ulink>, and another that
accesses
- the <ulink url="&JIRA-34;">local file system</ulink>.
We've already started on a <ulink url="&JIRA-36;">Subversion
connector</ulink>,
- which will allow JCR to access the files in a SVN repository (and perhaps push changes
into SVN through a commit).
- And of course we want to create a connector that accesses <ulink
url="&JIRA-199;">data</ulink>
- and <ulink url="&JIRA-37;">metadata</ulink> from relational
databases. For more information, check out our
- <ulink
url="&JIRA;?report=com.atlassian.jira.plugin.system.project:roadmap-panel">roadmap</ulink>.
- Of course, if we don't have a connector to suit your needs, you can <link
linkend="custom-connectors">write your own</link>.
- <figure id="dna-connectors-future">
- <title>Future JBoss DNA connectors</title>
- <graphic align="center" scale="100"
fileref="dna-connectors-future.png"/>
- </figure>
- </para>
- <para>
- </para>
- <para>
- It's even possible to put a different API layer on top of the connectors. For
example, the new <ulink url="&JSR203;">New I/O
(JSR-203)</ulink>
- API offers the opportunity to build new file system providers. This would be very
straightforward to put on top of a JCR implementation,
- but it could be made even simpler by putting it on top of a DNA connector. In both
cases, it'd be a trivial mapping from nodes that represent
- files and folders into JSR-203 files and directories, and events on those nodes could
easily be translated into JSR-203 watch events.
- Then, simply choose a DNA connector and configure it to use the source you want to
use.
- <figure id="dna-connectors-vfs">
- <title>Virtual File System with JBoss DNA</title>
- <graphic align="center" scale="100"
fileref="vfs-and-connector.png"/>
- </figure>
- </para>
- <para>Before we go further, let's define some terminology regarding
connectors.</para>
- <itemizedlist>
- <listitem>
- <para>
- A <emphasis role="strong">connector</emphasis> is the runnable
code packaged in one or more JAR files that
- contains implementations of several interfaces (described below). A Java developer
<emphasis>writes</emphasis>
- a connector to a type of source, such as a particular database management system,
LDAP directory, source code
- management system, etc. It is then packaged into one or more JAR files (including
dependent JARs) and deployed
- for use in applications that use JBoss DNA repositories.
- </para>
- </listitem>
- <listitem>
- <para>
- The description of a particular source system (e.g., the "Customer"
database, or the company LDAP system)
- is called a <emphasis role="strong">repository
source</emphasis>. JBoss DNA defines a &RepositorySource; interface
- that defines methods describing the behavior and supported features and a method for
establishing connections.
- A connector will have a class that implements this interface and that has JavaBean
properties for
- all of the connector-specific properties required to fully describe an instance of
the system. Use of JavaBean
- properties is not required, but it is highly recommended, as it enables reflective
configuration and administration.
- Applications that use JBoss DNA create an instance of the connector's
&RepositorySource; implementation and set
- the properties for the external source that the application wants to access with
that connector.
- </para>
- </listitem>
- <listitem>
- <para>
- A repository source instance is then used to establish <emphasis
role="strong">connections</emphasis> to
- that source. A connector provides an implementation of the
&RepositoryConnection; interface, which
- defines methods for interacting with the external system. In particular, the
<code>execute(...)</code> method
- takes an &ExecutionContext; instance and a &Request; object. The
&ExectuionContext; object defines the
- environment in which the processing is occurring, including information about the
JAAS &Subject; and &LoginContext;.
- The &Request; object describes the requested operations on the content, with
different concrete subclasses
- representing each type of activity. Examples of commands include (but not limited
to) getting a node, moving a node, creating a node,
- changing a node, and deleting a node. And, if the repository source is able to
participate in JTA/JTS distributed transactions, then the
- &RepositoryConnection; must implement the
<code>getXaResource()</code> method by returning
- a valid <code>javax.transaction.xa.XAResource</code> object that can be
used by the transaction monitor.
- </para>
- </listitem>
- </itemizedlist>
- <para>As an example, consider that we want JBoss DNA to give us access through
JCR to the schema information contained in a
- relational databases. We first have to develop a connector that allows us to interact
with relational databases using JDBC.
- That connector would contain a <code>JdbcRepositorySource</code> Java
class that implements &RepositorySource;,
- and that has all of the various JavaBean properties for setting the name of the driver
class, URL, username, password,
- and other properties. (Or we might have a JavaBean property that defines the JNDI
name where we can find a JDBC
- <code>DataSource</code> instance pointing to our JDBC database.)
- </para>
- <para>
- Our new connector would also have a <code>JdbcRepositoryConnection</code>
Java class that implements the
- &RepositoryConnection; interface. This class would probably wrap a JDBC database
connection,
- and would implement the <code>execute(...)</code> method such that the
nodes exposed by the connector
- describe the database schema of the database. For example, the connector might
represent each database table
- as a node with the table's name, with properties that describe the table (e.g.,
the description, whether it's a
- temporary table), and with child nodes that represent each of the columns, keys and
constraints.
- </para>
- <para>
- To use our connector in an application that uses JBoss DNA, we need to create an
instance of the
- <classname>JdbcRepositorySource</classname> for each database instance
that we want to access. If we have 3 MySQL databases,
- 9 Oracle databases, and 4 PostgreSQL databases, then we'd need to create a total
of 16 <classname>JdbcRepositorySource</classname>
- instances, each with the properties describing a single database instance. Those
sources are then available for use by
- JBoss DNA components, including the <link
linkend="jcr">JCR</link> implementation.
- </para>
- <para>
- So, we've so far learned what a repository connector is and how they're used
to establish connections to the underlying sources
- and access the content in those sources. In the <link
linkend="repository-service">next section</link>, we'll show how
these
- source instances can be configured, managed, and their connections pooled. After
that, we'll look review JBoss DNA's
- <link linkend="connector-library">existing connectors</link> and
show how to <link linkend="custom-connectors">create your own
connectors</link>.
- </para>
- </sect1>
- <sect1 id="repository-workspaces">
- <title>Workspaces</title>
- <para>The previous section talked about how connector expose their information
through the graph language of JBoss DNA.
- This is true, except that we didn't dive into too much of the detail. JBoss DNA
graphs have the notion of <emphasis>workspaces</emphasis>
- in which the content appears, and its very easy for clients using the graph to switch
between workspaces. In fact,
- workspaces differ from each other in that they provide different views of the same
information.
- </para>
- <para>Consider a source control system, like SVN or CVS. These systems provide
different views of the source code:
- a mainline development branch as well as other branches (or tags) commonly used for
releases. So, just like one source
- file might appear in the mainline branch as well as the previous two release branches,
a node in a repository source
- might appear in multiple workspaces.
- </para>
- <para>
- However, each connector can kind of decide how (or whether) it uses workspaces. For
example, there may be no overlap
- in the content between workspaces. Or a connector might only expose a single
workspace (in other words, there's only one
- "default" workspace).
- </para>
- </sect1>
- <sect1 id="repository-service">
- <title>Repository Service</title>
- <para>The JBoss DNA &RepositoryService; is the component that manages the
<emphasis>repository sources</emphasis>
- and the connections to them. &RepositorySource; instances can be
programmatically added to the service, but
- the service can actually read its configuration from a configuration repository
(which, by the way, is represented by a
- just another &RepositorySource; instance that's usually added programmatically
to the service). The service connects to
- the configuration repository, reads the content in a particular area, and
automatically sets up the &RepositorySource; instances
- per the information found in the configuration repository.
- </para>
- <para>
- The &RepositoryService; also transparently maintains for each source a pool of
reusable connections. The pooling properties
- can be controlled via the configuration repository, or adjusted programmatically.
- </para>
- <para>
- Using a repository, then, involves simply asking the &RepositoryService; for a
&RepositoryConnection;
- to the repository given the repository's name. If a source exists with that name,
the service checks out a connection from
- the source's pool. The resulting connection is actually a wrapper around the
underlying pooled connection, so the
- component that requested the connection can simply close it, and under the covers the
actual connection is simply returned
- to the pool.
- </para>
- <para>To instantiate the &RepositoryService;, we need to first have a few
other objects:
- <itemizedlist>
- <listitem>
- <para>
- A &ExecutionContext; instance, as discussed <link
linkend="execution-context">earlier</link>.
- </para>
- </listitem>
- <listitem>
- <para>
- A &RepositoryLibrary; instance that manages the list of &RepositorySource;
instances,
- properly injects the execution contexts into each repository source, and provides a
configurable pool of connections
- for each source.
- </para>
- </listitem>
- <listitem>
- <para>
- A <emphasis>configuration repository</emphasis> that contains
descriptions of all of the repository sources
- as well as any information those sources need. Because this is a regular
repository, this could be a simple
- repository with content loaded from an XML file, or it could be a shared
- central repository with information about all of the JBoss DNA repositories used
across your organization.
- </para>
- </listitem>
- </itemizedlist>
- With these components in place, we can then instantiate the &RepositoryService;
and start it (using its
- &ServiceAdministrator;). During startup, the service reads the configuration
repository and loads any
- defined &RepositorySource; instances into the repository library, using the class
loader factory
- (available in the &ExecutionContext;) to obtain.
- </para>
- <para>
- Here's sample code that shows how to set up and start the repository service. You
can see something similar
- in the example application in the <code>startRepositories()</code> method
of the
- <code>org.jboss.example.dna.repository.RepositoryClient</code> class.
- </para>
- <programlisting>
- // Create the top-level execution context with all standard components ...
- &ExecutionContext; context = new ExecutionContext();
-
- // Create the library for the RepositorySource instances ...
- &RepositoryLibrary; sources = new &RepositoryLibrary;(context);
-
- // Load into the source manager the repository source for the configuration repository
...
- &InMemoryRepositorySource; configSource = new &InMemoryRepositorySource;();
- configSource.setName("Configuration");
- sources.addSource(configSource);
-
- // Now instantiate the Repository Service ...
- &RepositoryService; service = new &RepositoryService;(sources,
configSource.getName(), context);
- service.getAdministrator().start();
- </programlisting>
-
- <para>After startup completes, the repositories are ready to be used. The client
application obtains the list of repositories
- and presents them to the user. When the user selects one, the client application
starts navigating that repository
- starting at its root node (e.g., the "/" path). As you type a command to
list the contents of the current node or to
- "change directories" to a different node, the client application obtains the
information for the node using a simple
- procedure:
- <orderedlist>
- <listitem>
- <para>Get a connection to the repository.</para>
- </listitem>
- <listitem>
- <para>Using the connection, find the current node and read its properties and
children, putting the information
- into a simple Java plain old Java object (POJO).</para>
- </listitem>
- <listitem>
- <para>Close the connection to the repository (in a finally block to ensure it
always happens).</para>
- </listitem>
- </orderedlist>
- </para>
- </sect1>
- <sect1 id="connector-library">
- <title>Out-of-the-box repository connectors</title>
- <para>
- A number of repository connectors are already available in JBoss DNA, and are
outlined in the following sections.
- Note that we do want to build <ulink
url="https://jira.jboss.org/jira/secure/IssueNavigator.jspa?reset=tr...
connectors</ulink>
- in the upcoming releases.
- </para>
- <sect2 id="dna-connector-inmemory">
- <title>In-memory connector</title>
- <para>
- The in-memory repository connector is a simple connector that creates a transient,
in-memory repository.
- This repository is used as a very simple in-memory cache or as a standalone transient
repository.
- </para>
- <para>
- The &InMemoryRepositorySource; class provides a number of JavaBean properties
that control its behavior:
- </para>
- <table frame='all'>
- <title>&InMemoryRepositorySource; properties</title>
- <tgroup cols='2' align='left' colsep='1'
rowsep='1'>
- <colspec colname='c1' colwidth="1*"/>
- <colspec colname='c2' colwidth="1*"/>
- <thead>
- <row>
- <entry>Property</entry>
- <entry>Description</entry>
- </row>
- </thead>
- <tbody>
- <row>
- <entry>name</entry>
- <entry>The name of the repository source, which is used by the
&RepositoryService; when obtaining a &RepositoryConnection; by
name.</entry>
- </row>
- <row>
- <entry>jndiName</entry>
- <entry>Optional property that, if used, specifies the name in JNDI where an
&InMemoryRepository; instance can be found.
- This is an advanced property that is infrequently used.</entry>
- </row>
- <row>
- <entry>rootNodeUuid</entry>
- <entry>Optional property that, if used, defines the UUID of the root node in
the in-memory repository. If not used,
- then a new UUID is generated.</entry>
- </row>
- <row>
- <entry>retryLimit</entry>
- <entry>Optional property that, if used, defines the number of times that any
single operation on a &RepositoryConnection; to this source should be retried
- following a communication failure. The default value is
'0'.</entry>
- </row>
- <row>
- <entry>defaultCachePolicy</entry>
- <entry>Optional property that, if used, defines the default for how long
this information provided by this source may to be
- cached by other, higher-level components. The default value of null implies that
this source does not define a specific
- duration for caching information provided by this repository
source.</entry>
- </row>
- <row>
- <entry>defaultWorkspaceName</entry>
- <entry>Optional property that is initialized to an empty string and which
defines the name for the workspace that will be used by default
- if none is specified.</entry>
- </row>
- </tbody>
- </tgroup>
- </table>
- </sect2>
- <sect2 id="dna-connector-jbosscache">
- <title>JBoss Cache connector</title>
- <para>
- The JBoss Cache repository connector allows a <ulink
url="http://www.jboss.org/jbosscache/">JBoss Cache</ulink> instance to
be
- used as a JBoss DNA (and thus JCR) repository. This provides a repository that is an
effective, scalable, and distributed cache,
- and is often paired with other repository sources to provide a local or <link
linkend="dna-connector-federation">federated</link>
- repository.
- </para>
- <para>
- The &JBossCacheSource; class provides a number of JavaBean properties that
control its behavior:
- </para>
- <table frame='all'>
- <title>&JBossCacheSource; properties</title>
- <tgroup cols='2' align='left' colsep='1'
rowsep='1'>
- <colspec colname='c1' colwidth="1*"/>
- <colspec colname='c2' colwidth="1*"/>
- <thead>
- <row>
- <entry>Property</entry>
- <entry>Description</entry>
- </row>
- </thead>
- <tbody>
- <row>
- <entry>name</entry>
- <entry>The name of the repository source, which is used by the
&RepositoryService; when obtaining a &RepositoryConnection; by
name.</entry>
- </row>
- <row>
- <entry>cacheFactoryJndiName</entry>
- <entry>Optional property that, if used, specifies the name in JNDI where an
existing JBoss Cache Factory instance can be found.
- That factory would then be used if needed to create a JBoss Cache instance. If
no value is provided, then the
- JBoss Cache <code>DefaultCacheFactory</code> class is
used.</entry>
- </row>
- <row>
- <entry>cacheConfigurationName</entry>
- <entry>Optional property that, if used, specifies the name of the
configuration that is supplied to the cache factory
- when creating a new JBoss Cache instance.</entry>
- </row>
- <row>
- <entry>cacheJndiName</entry>
- <entry>Optional property that, if used, specifies the name in JNDI where an
existing JBoss Cache instance can be found.
- This should be used if your application already has a cache that is used, or if
you need to configure the cache in
- a special way.</entry>
- </row>
- <row>
- <entry>uuidPropertyName</entry>
- <entry>Optional property that, if used, defines the property that should be
used to find the UUID value for each node
- in the cache. "<code>dna:uuid</code>" is the
default.</entry>
- </row>
- <row>
- <entry>retryLimit</entry>
- <entry>Optional property that, if used, defines the number of times that any
single operation on a &RepositoryConnection; to this source should be retried
- following a communication failure. The default value is
'0'.</entry>
- </row>
- <row>
- <entry>defaultCachePolicy</entry>
- <entry>Optional property that, if used, defines the default for how long
this information provided by this source may to be
- cached by other, higher-level components. The default value of null implies that
this source does not define a specific
- duration for caching information provided by this repository
source.</entry>
- </row>
- <row>
- <entry>nameOfDefaultWorkspace</entry>
- <entry>Optional property that is initialized to an empty string and which
defines the name for the workspace that will be used by default
- if none is specified.</entry>
- </row>
- <row>
- <entry>predefinedWorkspaceNames</entry>
- <entry>Optional property that defines the names of the workspaces that exist
and that are available for use without having to create them.</entry>
- </row>
- <row>
- <entry>creatingWorkspacesAllowed</entry>
- <entry>Optional property that is by default 'true' that defines
whether clients can create new workspaces.</entry>
- </row>
- </tbody>
- </tgroup>
- </table>
- </sect2>
- <sect2 id="dna-connector-federation">
- <title>Federating connector</title>
- <para>
- The federated repository source provides a unified repository consisting of
information that is dynamically federated from multiple other
- &RepositorySource; instances. This is a very powerful repository source that
appears to be a single repository, when in
- fact the content is stored and managed in multiple other systems. Each
&FederatedRepositorySource; is typically configured
- with the name of another &RepositorySource; that should be used as the local,
unified cache of the federated content.
- The configuration also contains the names of the other &RepositorySource;
instances that are to be federated along with
- the &Projection; definition describing where in the unified repository the
content is to appear.
- </para>
- <figure id="dna-connector-federation-image">
- <title>Federating multiple sources using the Federated Repository
Connector</title>
- <graphic align="center" scale="100"
fileref="dna-connector-federation.png"/>
- </figure>
- <para> The federation connector works by effectively building up a single
graph by querying each source and merging or
- unifying the responses. This information is cached, which improves performance,
reduces the number of (potentially
- expensive) remote calls, reduces the load on the sources, and helps mitigate
problems with source availability. As
- clients interact with the repository, this cache is consulted first. When the
requested portion of the graph (or
- "subgraph") is contained completely in the cache, it is retuned
immediately. However, if any part of the requested
- subgraph is not in the cache, each source is consulted for their contributions to
that subgraph, and any results are
- cached.</para>
- <para> This basic flow makes it possible for the federated repository to
build up a local cache of the integrated graph
- (or at least the portions that are used by clients). In fact, the federated
repository caches information in a manner
- that is similar to that of the Domain Name System (DNS). As sources are consulted
for their contributions, the source
- also specifies whether it is the authoritative source for this information (some
sources that are themselves federated
- may not be the information's authority), whether the information may be
modified, the time-to-live (TTL) value (the time
- after which the cached information should be refreshed), and the expiration time
(the time after which the cached
- information is no longer valid). In effect, the source has complete control over
how the information it contributes is
- cached and used.</para>
- <para>
- The federated repository also needs to incorporate <emphasis>negative
caching</emphasis>, which is storage of the knowledge
- that something does <emphasis>not</emphasis> exist. Sources can be
configured to contribute information
- only below certain paths (e.g., <code>/A/B/C</code>), and the
federation engine can take advantage of this by never
- consulting that source for contributions to information on other paths. However,
below that path, any negative responses
- must also be cached (with appropriate TTL and expiry parameters) to prevent the
exclusion of that source (in case the source
- has information to contribute at a later time) or the frequent checking with the
source.
- </para>
- <para>
- The federated repository uses other &RepositorySource;s that are to be federated
and a &RepositorySource; that is to be used as the
- cache of the unified contents. These are configured in another
&RepositorySource; that is treated as a configuration repository.
- The &FederatedRepositorySource; class uses JavaBean properties to define the name
of the configuration repository and
- the path to the "<code>dna:federation</code>" node in that
configuration repository containing the information about the
- cache and federated sources. This graph structure that is expected at this location
is as follows:
- </para>
- <programlisting><![CDATA[<!-- Define the federation configuration. -->
-<dna:federatedRepository
xmlns:dna="http://www.jboss.org/dna"
-
xmlns:jcr="http://www.jcp.org/jcr/1.0"
- dna:timeToCache="100000" >
- <dna:workspaces>
- <dna:workspace jcr:name="default">
- <!-- Define how the content in the 'Cache' source is to map to the
federated cache -->
- <dna:cache dna:sourceName="Cache"
dna:workspaceName="default" dna:projectionRules="/a => /" />
-
- <!-- Define how the content in the two sources maps to the federated/unified
repository.
- This example puts the 'Cars' and 'Aircraft' content underneath
'/vehicles', but the
- 'Configuration' content (which is defined by this file) will appear
under '/'. -->
- <dna:projections>
- <dna:projection jcr:name="Cars" dna:projectionRules="/Vehicles
=> /" />
- <dna:projection jcr:name="Aircraft"
dna:projectionRules="/Vehicles => /" />
- <dna:projection jcr:name="Configuration" dna:projectionRules="/
=> /" />
- </dna:projections>
- </dna:workspace>
- </dna:workspaces>
-</dna:federatedRepository>
-]]></programlisting>
- <note>
- <para>
- We're using XML to represent a graph structure, since the two map pretty well.
Each XML element represents
- a node and XML attributes represent properties on a node. The name of the node is
defined by either the
- <code>jcr:name</code> attribute (if it exists) or the name of the XML
element. And we use XML namespaces
- to define the namespaces used in the node and property names. BTW, this is exactly
how the XML graph importer
- works.
- </para>
- </note>
- <para>
- Notice that there is a cache projection and three source projections, and each
projection defines
- one or more <emphasis>projection rules</emphasis> that are of the form:
- </para>
- <programlisting>pathInFederatedRepository =>
pathInSourceRepository</programlisting>
- <para>
- So, a projection rule <code>/Vehicles => /</code> projects the
entire contents of the source so that
- it appears in the federated repository under the
"<code>/Vehicles</code>" node.
- </para>
- <para>
- The &FederatedRepositorySource; class provides a number of JavaBean properties
that control its behavior:
- </para>
- <table frame='all'>
- <title>&FederatedRepositorySource; properties</title>
- <tgroup cols='2' align='left' colsep='1'
rowsep='1'>
- <colspec colname='c1' colwidth="1*"/>
- <colspec colname='c2' colwidth="1*"/>
- <thead>
- <row>
- <entry>Property</entry>
- <entry>Description</entry>
- </row>
- </thead>
- <tbody>
- <row>
- <entry>name</entry>
- <entry>The name of the repository source, which is used by the
&RepositoryService; when obtaining a &RepositoryConnection; by
name.</entry>
- </row>
- <row>
- <entry>repositoryName</entry>
- <entry>The name for the federated repository.</entry>
- </row>
- <row>
- <entry>configurationSourceName</entry>
- <entry>The name of the &RepositorySource; that should be used as the
configuration repository, and in which is defined
- how this federated repository is to be set up and configured.
- This name is supplied to the &RepositoryConnectionFactory; that is provided
to this instance when added to the
- &RepositoryLibrary;.</entry>
- </row>
- <row>
- <entry>configurationWorkspaceName</entry>
- <entry>The name of the workspace in the configuration &RepositorySource;
with the content defining
- how this federated repository is to be set up and configured.</entry>
- </row>
- <row>
- <entry>configurationSourcePath</entry>
- <entry>The path to the node in the configuration repository below which a
"dna:federation" node exists with the
- graph structure describing how this federated repository is to be
configured.</entry>
- </row>
- <row>
- <entry>securityDomain</entry>
- <entry>Optional property that, if used, specifies the name of the JAAS
application context that should be used
- to establish the <link linkend="execution-contenxt">execution
context</link> for this repository.
- This should correspond to the JAAS login configuration located within the JAAS
login configuration file,
- and should be used only if a "<code>username</code>"
property is defined.</entry>
- </row>
- <row>
- <entry>username</entry>
- <entry>Optional property that, if used, defines the name of the JAAS subject
that should be used
- to establish the <link linkend="execution-contenxt">execution
context</link> for this repository.
- This should be used if a "<code>securityDomain</code>"
property is defined.</entry>
- </row>
- <row>
- <entry>password</entry>
- <entry>Optional property that, if used, defines the password of the JAAS
subject that should be used
- to establish the <link linkend="execution-contenxt">execution
context</link> for this repository.
- If the password is not provided but values for the
"<code>securityDomain</code>" and
"<code>username</code>" properties are,
- then authentication will use the default JAAS callback handlers.</entry>
- </row>
- <row>
- <entry>retryLimit</entry>
- <entry>Optional property that, if used, defines the number of times that any
single operation on a &RepositoryConnection; to this source should be retried
- following a communication failure. The default value is
'0'.</entry>
- </row>
- <row>
- <entry>defaultCachePolicy</entry>
- <entry>Optional property that, if used, defines the default for how long
this information provided by this source may to be
- cached by other, higher-level components. The default value of null implies that
this source does not define a specific
- duration for caching information provided by this repository
source.</entry>
- </row>
- </tbody>
- </tgroup>
- </table>
- </sect2>
- </sect1>
- <sect1 id="custom-connectors">
- <title>Writing custom connectors</title>
- <para>
- There may come a time when you want to tackle creating your own repository connector.
Maybe the connectors we provide out-of-the-box
- don't work with your source. Maybe you want to use a different cache system.
- Maybe you have a system that you want to make available through a JBoss DNA
repository. Or, maybe you're
- a contributor and want to help us round out our library with a new connector. No
matter what the reason, creating a new connector
- is pretty straightforward, as we'll see in this section.
- </para>
- <para>
- Creating a custom connector involves the following steps:
- <orderedlist>
- <listitem>
- <para>Create a Maven 2 project for your connector;</para>
- </listitem>
- <listitem>
- <para>
- Implement the &RepositorySource; interface, using JavaBean properties for each
bit of information the implementation will
- need to establish a connection to the source system.
- </para>
- <para>
- Then, implement the &RepositoryConnection; interface with a class that
represents a connection to the source. The
- <code>execute(&ExecutionContext;, &Request;)</code> method
should process any and all requests that may come down the pike,
- and the results of each request can be put directly on that request.
- </para>
- <para>
- Don't forget unit tests that verify that the connector is doing what it's
expected to do. (If you'll be committing the connector
- code to the JBoss DNA project, please ensure that the unit tests can be run by
others that may not have access to the
- source system. In this case, consider writing integration tests that can be easily
configured to use different sources
- in different environments, and try to make the failure messages clear when the
tests can't connect to the underlying source.)
- </para>
- </listitem>
- <listitem>
- <para>
- Configure JBoss DNA to use your connector. This may involve just registering the
source with the &RepositoryService;,
- or it may involve adding a source to a configuration repository used by the
<link linkend="dna-connector-federation">federated
repository</link>.
- </para>
- </listitem>
- <listitem>
- <para>
- Deploy the JAR file with your connector (as well as any dependencies), and make
them available to JBoss DNA
- in your application.
- </para>
- </listitem>
- </orderedlist>
- Let's go through each one of these steps in more detail.
- </para>
- <sect2 id="custom_connector_project">
- <title>Creating the Maven 2 project</title>
- <para>
- The first step is to create the Maven 2 project that you can use to compile your code
and build the JARs.
- Maven 2 automates a lot of the work, and since you're already <link
linkend="maven">set up to use Maven</link>,
- using Maven for your project will save you a lot of time and effort. Of course,
you don't have to use Maven 2, but then you'll
- have to get the required libraries and manage the compiling and building process
yourself.</para>
- <note>
- <para>JBoss DNA may provide in the future a Maven archetype for creating
connector projects. If you'd find this useful
- and would like to help create it, please <link
linkend="preface">join the community</link>.
- </para>
- <para>In lieu of a Maven archetype, you may find it easier to start with a
small existing connector project.
- The <emphasis
role="strong">dna-connector-filesystem</emphasis> project is small, but
it may be tough to separate
- the stuff that every connector needs from the extra code and data structures that
manage the content.
- See the subversion repository: <ulink
url="&Subversion;trunk/extensions/dna-connector-filesystem/">&Subversion;trunk/extensions/dna-connector-filesystem/</ulink>
- </para>
- </note>
- <para>
- You can create your Maven project any way you'd like. For examples, see the
- <ulink
url="http://maven.apache.org/guides/getting-started/index.html#How_d...
2 documentation</ulink>.
- Once you've done that, just add the dependencies in your project's
<code>pom.xml</code> dependencies section:
- </para>
- <programlisting role="XML"><![CDATA[
-<dependency>
- <groupId>org.jboss.dna</groupId>
- <artifactId>dna-graph</artifactId>
- <version>0.4</version>
-</dependency>
- ]]></programlisting>
- <para>
- This is the only dependency required for compiling a connector - Maven pulls in all
of the dependencies needed by
- the 'dna-graph' artifact. Of course, you'll still have to add
dependencies for any library your connector needs
- to talk to its underlying system.
- </para>
- <para>
- As for testing, you probably will want to add more dependencies, such as those listed
here:
- </para>
- <programlisting role="XML"><![CDATA[
-<dependency>
- <groupId>org.jboss.dna</groupId>
- <artifactId>dna-graph</artifactId>
- <version>0.4</version>
- <type>test-jar</type>
- <scope>test</scope>
-</dependency>
-<dependency>
- <groupId>org.jboss.dna</groupId>
- <artifactId>dna-common</artifactId>
- <version>0.4</version>
- <type>test-jar</type>
- <scope>test</scope>
-</dependency>
-<dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.4</version>
- <scope>test</scope>
-</dependency>
-<dependency>
- <groupId>org.hamcrest</groupId>
- <artifactId>hamcrest-library</artifactId>
- <version>1.1</version>
- <scope>test</scope>
-</dependency>
-<!-- Logging with Log4J -->
-<dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-log4j12</artifactId>
- <version>1.4.3</version>
- <scope>test</scope>
-</dependency>
-<dependency>
- <groupId>log4j</groupId>
- <artifactId>log4j</artifactId>
- <version>1.2.14</version>
- <scope>test</scope>
-</dependency>
- ]]></programlisting>
- <para>
- Testing JBoss DNA connectors does not require a JCR repository or the JBoss DNA
services. (For more detail,
- see the <link linkend="testing_custom_connectors">testing
section</link>.) However, if you want to do
- integration testing with a JCR repository and the JBoss DNA services, you'll
need additional dependencies
- (e.g., <code>dna-repository</code> and any other extensions).
- </para>
- <para>
- At this point, your project should be set up correctly, and you're ready to move
on to
- <link linkend="implementing_repository_source">writing the Java
implementation</link> for your connector.
- </para>
- </sect2>
- <sect2 id="implementing_repository_source">
- <title>Implementing a <code>RepositorySource</code></title>
- <para>
- As mentioned earlier, a <emphasis>connector</emphasis> consists of the
Java code that is used to access content
- from a system. Perhaps the most important class that makes up a connector is the
implementation of the
- &RepositorySource;. This class is analogous to JDBC's DataSource in that it
is instantiated to represent
- a single instance of a system that will be accessed, and it contains enough
information (in the form of JavaBean properties)
- so that it can create connections to the source.
- </para>
- <para>
- Why is the &RepositorySource; implementation a JavaBean? Well, this is the class
that is instantiated, usually
- reflectively, and so a no-arg constructor is required. Using JavaBean properties
makes it possible
- to reflect upon the object's class to determine the properties that can be set
(using setters) and read
- (using getters). This means that an administrative application can instantiate,
configure, and manage
- the objects that represent the actual sources, without having to know anything about
the actual implementation.
- </para>
- <para>
- So, your connector will need a public class that implements &RepositorySource;
and provides JavaBean properties
- for any kind of inputs or options required to establish a connection to and interact
with the underlying source.
- Most of the semantics of the class are defined by the &RepositorySource; and
inherited interface.
- However, there are a few characteristics that are worth mentioning here.
- </para>
- <sect3 id="connector_cache_policy">
- <title>Cache policy</title>
- <para>
- Each connector is responsible for determining whether and how long DNA is to cache
the
- content made available by the connector. This is referred to as the
<emphasis>caching policy</emphasis>,
- and consists of a <emphasis>time to live</emphasis> value representing
the number of milliseconds that
- a piece of data may be cached. After the TTL has passed, the information is no
longer used.
- </para>
- <para>
- DNA allows a connector to use a flexible and powerful caching policy. First, each
connection returns the
- <emphasis>default</emphasis> caching policy for all information returned
by that connection.
- Often this policy can be configured via properties on the &RepositorySource;
implementation.
- This is optional, meaning the connector can return <code>null</code> if
it does not wish to
- have a default caching policy.
- </para>
- <para>
- Second, the connector is able to override its default caching policy on individual
requests
- (which we'll cover in the <link
linkend="implementing_repository_connection">next section</link>).
- Again, this is optional, meaning that a null caching policy on a request implies
that the
- request has no overridden caching policy.
- </para>
- <para>
- Third, if the connector has no default caching policy and none is set on the
individual requests,
- DNA uses whatever caching policy is set up for that component using the connector.
For example, the federating
- connector allows a default caching policy to be specified, and this policy is used
should the sources
- being federated not define their own caching policy.
- </para>
- <para>
- In summary, a connector has total control over whether and for how long the
information it provides
- is cached.
- </para>
- </sect3>
- <sect3 id="repository_source_jndi">
- <title>Leveraging JNDI</title>
- <para>
- Sometimes it is necessary (or easier) for a &RepositorySource; implementation to
look up an object in JNDI.
- One example of this is the JBoss Cache connector: while the connector can
- instantiate a new JBoss Cache instance, more interesting use cases involve JBoss
Cache instances that are
- set up for clustering and replication, something that is generally difficult to
configure in a single JavaBean.
- Therefore the &JBossCacheSource; has optional JavaBean properties that define
how it is to look up a
- JBoss Cache instance in JNDI.
- </para>
- <para>
- This is a simple pattern that you may find useful in your connector. Basically, if
your source implementation
- can look up an object in JNDI, simply use a single JavaBean String property that
defines the
- full name that should be used to locate that object in JNDI. Usually it's best
to include "Jndi" in the
- JavaBean property name so that administrative users understand the purpose of the
property.
- (And some may suggest that any optional property also use the word
"optional" in the property name.)
- </para>
- </sect3>
- <sect3 id="repository_source_capabilities">
- <title>Capabilities</title>
- <para>
- Another characteristic of a &RepositorySource; implementation is that it
provides some hint as to whether
- it supports several features. This is defined on the interface as a method that
returns a
- &RepositorySourceCapabilities; object. This class currently provides methods
that say whether the connector supports
- updates, whether it supports same-name-siblings (SNS), and whether the connector
supports listeners and events.
- </para>
- <para>
- Note that these may be hard-coded values, or the connector's response may be
determined at runtime by various factors.
- For example, a connector may interrogate the underlying system to decide whether it
can support updates.
- </para>
- <para>
- The &RepositorySourceCapabilities; can be used as is (the class is immutable),
or it can be subclassed
- to provide more complex behavior. It is important, however, that the capabilities
remain constant
- throughout the lifetime of the &RepositorySource; instance.
- </para>
- <note>
- <para>
- Why a concrete class and not an interface? By using a concrete class, connectors
inherit the default
- behavior. If additional capabilities need to be added to the class in future
releases, connectors may
- not have to override the defaults. This provides some insulation against future
enhancements to the connector framework.
- </para>
- </note>
- </sect3>
- <sect3 id="repository_source_security">
- <title>Security and authentication</title>
- <para>
- As we'll see in the next section, the main method connectors have to process
requests takes an &ExecutionContext;,
- which contains the JAAS security information of the subject performing the request.
This means that the connector
- can use this to determine authentication and authorization information for each
request.
- </para>
- <para>
- Sometimes that is not sufficient. For example, it may be that the connector needs
its own authorization information
- so that it can establish a connection (even if user-level privileges still use the
&ExecutionContext; provided with
- each request). In this case, the &RepositorySource; implementation will
probably need JavaBean properties
- that represent the connector's authentication information. This may take the
form of a username and password,
- or it may be properties that are used to delegate authentication to JAAS.
- Either way, just realize that it's perfectly acceptable for the connector to
require its own security properties.
- </para>
- </sect3>
- </sect2>
- <sect2 id="implementing_repository_connection">
- <title>Implementing a
<code>RepositoryConnection</code></title>
- <para>
- One job of the &RepositorySource; implementation is to create connections to the
underlying sources.
- Connections are represented by classes that implement the &RepositoryConnection;
interface, and creating this
- class is the next step in writing a repository connector. This is what we'll
cover in this section.
- </para>
- <para>
- The &RepositoryConnection; interface is pretty straightforward:
- </para>
- <programlisting>
-/**
- * A connection to a repository source.
- * <p>
- * These connections need not support concurrent operations by multiple threads.
- * </p>
- */
-@NotThreadSafe
-public interface &RepositoryConnection; {
-
- /**
- * Get the name for this repository source. This value should be the same as that
returned
- * by the same &RepositorySource; that created this connection.
- *
- * @return the identifier; never null or empty
- */
- String getSourceName();
-
- /**
- * Return the transactional resource associated with this connection. The transaction
manager
- * will use this resource to manage the participation of this connection in a
distributed transaction.
- *
- * @return the XA resource, or null if this connection is not aware of distributed
transactions
- */
- XAResource getXAResource();
-
- /**
- * Ping the underlying system to determine if the connection is still valid and
alive.
- *
- * @param time the length of time to wait before timing out
- * @param unit the time unit to use; may not be null
- * @return true if this connection is still valid and can still be used, or false
otherwise
- * @throws InterruptedException if the thread has been interrupted during the
operation
- */
- boolean ping( long time, &TimeUnit; unit ) throws InterruptedException;
-
- /**
- * Set the listener that is to receive notifications to changes to content within
this source.
- *
- * @param listener the new listener, or null if no component is interested in the
change notifications
- */
- void setListener( &RepositorySourceListener; listener );
-
- /**
- * Get the default cache policy for this repository. If none is provided, a global
cache policy
- * will be used.
- *
- * @return the default cache policy
- */
- &CachePolicy; getDefaultCachePolicy();
-
- /**
- * Execute the supplied commands against this repository source.
- *
- * @param context the environment in which the commands are being executed; never
null
- * @param request the request to be executed; never null
- * @throws RepositorySourceException if there is a problem loading the node data
- */
- void execute( &ExecutionContext; context,
- &Request; request ) throws &RepositorySourceException;;
-
- /**
- * Close this connection to signal that it is no longer needed and that any
accumulated
- * resources are to be released.
- */
- void close();
-}</programlisting>
- <para>
- While most of these methods are straightforward, a few warrant additional
information.
- The <code>ping(...)</code> allows DNA to check the connection to see if
it is
- alive. This method can be used in a variety of situations, ranging from verifying
that a &RepositorySource;'s
- JavaBean properties are correct to ensuring that a connection is still alive before
returning the connection from
- a connection pool.
- </para>
- <para>
- DNA hasn't yet defined the event mechanism, so connectors don't have any
methods to invoke on the &RepositorySourceListener;.
- This will be defined in the next release, so feel free to manage the listeners now.
Note that by default the &RepositorySourceCapabilities; returns
- <code>false</code> for <code>supportsEvents()</code>.
- </para>
- <para>
- The most important method on this interface, though, is the
<code>execute(...)</code> method, which serves as the
- mechanism by which the component using the connector access and manipulates the
content exposed by the connector.
- The first parameter to this method is the &ExecutionContext;, which contains the
information about environment
- as well as the subject performing the request. This was discussed <link
linkend="execution-context">earlier</link>.
- </para>
- <para>
- The second parameter, however, represents a request that is to be processed by the
connector. Request objects can
- take many different forms, as there are different classes for each kind of request
(see the table below).
- Each request contains the information a connector needs to do the processing, and it
also is the place
- where the connector places the results (or the error, if one occurs).
- </para>
- <para>
- How do the requests reference a node (or nodes)? Since requests are coming from a
client, the client
- may identify a particular node using a &Location; object that is created with:
- <itemizedlist>
- <listitem>
- <para>the &Path; to the node; or</para>
- </listitem>
- <listitem>
- <para>one or more <emphasis>identification
properties</emphasis> that are likely source=specific
- and that are represented with &Property; objects; or</para>
- </listitem>
- <listitem>
- <para>a combination of both.</para>
- </listitem>
- </itemizedlist>
- So, when a client knows the path or the identification properties, they can create a
&Location;. However,
- all of the requests return &Location; objects, so often times the client simply
uses the location
- from a previous request. Since &Location; is an immutable class, it is perfectly
safe to reuse them.
- </para>
- <para>
- One more thing about locations: while the request may have an incomplete location
(e.g., a path but no
- identification properties), the connector is expected to set on the request the
<emphasis>actual</emphasis>
- location that contains the path and all identification properties. So as long as the
client
- reuses the actual locations in subsequent requests, the connectors will have the
benefit of having
- both the path and identification properties. Connectors can then be written to
leverage this
- information, although the connector should still perform as expected when requests
have incomplete locations.
- </para>
- <table frame='all'>
- <title>Types of Node Operation Requests</title>
- <tgroup cols='2' align='left' colsep='1'
rowsep='1'>
- <colspec colname='c1' colwidth="1*"/>
- <colspec colname='c2' colwidth="1*"/>
- <thead>
- <row>
- <entry>Name</entry>
- <entry>Description</entry>
- </row>
- </thead>
- <tbody>
- <row>
- <entry>ReadNodeRequest</entry>
- <entry>
- A request to read from the named workspace in the source a node's properties
and children.
- The node may be specified by path and/or by identification properties.
- The connector returns all properties and the locations for all children,
- or sets a &PathNotFoundException; error on the request if the node did not
exist in the workspace.
- If the node is found, the connector sets on the request the actual location of
the node (including the path and identification properties).
- The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
- </entry>
- </row>
- <row>
- <entry>VerifyNodeExistsRequest</entry>
- <entry>
- A request to verify the existance of a node at the specified location in the
named workspace of the source.
- The connector returns all the actual location for the node if it exists, or
- sets a &PathNotFoundException; error on the request if the node does not
exist in the workspace.
- The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
- </entry>
- </row>
- <row>
- <entry>ReadAllPropertiesRequest</entry>
- <entry>
- A request to read from the named workspace in the source all of the properties of
a node.
- The node may be specified by path and/or by identification properties.
- The connector returns all properties that were found on the node,
- or sets a &PathNotFoundException; error on the request if the node did not
exist in the workspace.
- If the node is found, the connector sets on the request the actual location of
the node (including the path and identification properties).
- The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
- </entry>
- </row>
- <row>
- <entry>ReadPropertyRequest</entry>
- <entry>
- A request to read from the named workspace in the source a single property of a
node.
- The node may be specified by path and/or by identification properties,
- and the property is specified by name.
- The connector returns the property if found on the node,
- or sets a &PathNotFoundException; error on the request if the node or
property did not exist in the workspace.
- If the node is found, the connector sets on the request the actual location of
the node (including the path and identification properties).
- The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
- </entry>
- </row>
- <row>
- <entry>ReadAllChildrenRequest</entry>
- <entry>
- A request to read from the named workspace in the source all of the children of a
node.
- The node may be specified by path and/or by identification properties.
- The connector returns an ordered list of locations for each child found on the
node,
- an empty list if the node had no children,
- or sets a &PathNotFoundException; error on the request if the node did not
exist in the workspace.
- If the node is found, the connector sets on the request the actual location of
the parent node (including the path and identification properties).
- The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
- </entry>
- </row>
- <row>
- <entry>ReadBlockOfChildrenRequest</entry>
- <entry>
- A request to read from the named workspace in the source a block of children of a
node, starting with the n<superscript>th</superscript> children.
- This is designed to allow paging through the children, which is much more
efficient for large numbers of children.
- The node may be specified by path and/or by identification properties, and the
block
- is defined by a starting index and a count (i.e., the block size).
- The connector returns an ordered list of locations for each of the node's
children found in the block,
- or an empty list if there are no children in that range.
- The connector also sets on the request the actual location of the parent node
(including the path and identification properties)
- or sets a &PathNotFoundException; error on the request if the parent node did
not exist in the workspace.
- The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
- </entry>
- </row>
- <row>
- <entry>ReadNextBlockOfChildrenRequest</entry>
- <entry>
- A request to read from the named workspace in the source a block of children of a
node, starting with the children that immediately follow
- a previously-returned child.
- This is designed to allow paging through the children, which is much more
efficient for large numbers of children.
- The node may be specified by path and/or by identification properties, and the
block
- is defined by the location of the node immediately preceding the block and a
count (i.e., the block size).
- The connector returns an ordered list of locations for each of the node's
children found in the block,
- or an empty list if there are no children in that range.
- The connector also sets on the request the actual location of the parent node
(including the path and identification properties)
- or sets a &PathNotFoundException; error on the request if the parent node did
not exist in the workspace.
- The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
- </entry>
- </row>
- <row>
- <entry>ReadBranchRequest</entry>
- <entry>
- A request to read a portion of a subgraph that has as its root a particular node,
up to a maximum depth.
- This request is an efficient mechanism when a branch (or part of a branch) is to
be navigated and processed,
- and replaces some non-trivial code to read the branch iteratively using multiple
<code>ReadNodeRequest</code>s.
- The connector reads the branch to the specified maximum depth, returning the
properties and children for all
- nodes found in the branch.
- The connector also sets on the request the actual location of the branch's
root node (including the path and identification properties).
- The connector sets a &PathNotFoundException; error on the request if the node
at
- the top of the branch does not exist in the workspace.
- The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
- </entry>
- </row>
- <row>
- <entry>CreateNodeRequest</entry>
- <entry>
- A request to create a node at the specified location and setting on the new node
the properties included in the request.
- The connector creates the node at the desired location, adjusting any
same-name-sibling indexes as required.
- (If an SNS index is provided in the new node's location, existing children
with the same name after that SNS index
- will have their SNS indexes adjusted. However, if the requested location does
not include a SNS index, the new
- node is added after all existing children, and it's SNS index is set
accordingly.)
- The connector also sets on the request the actual location of the new node
(including the path and identification properties)..
- The connector sets a &PathNotFoundException; error on the request if the
parent node does not exist in the workspace.
- The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
- </entry>
- </row>
- <row>
- <entry>RemovePropertiesRequest</entry>
- <entry>
- A request to remove a set of properties on an existing node. The request
contains the location of the node as well as the
- names of the properties to be removed. The connector performs these changes and
sets on the request the
- actual location (including the path and identification properties) of the node.
- The connector sets a &PathNotFoundException; error on the request if the node
does not exist in the workspace.
- The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
- </entry>
- </row>
- <row>
- <entry>UpdatePropertiesRequest</entry>
- <entry>
- A request to set or update properties on an existing node. The request contains
the location of the node as well as the
- properties to be set and those to be deleted. The connector performs these
changes and sets on the request the
- actual location (including the path and identification properties) of the node.
- The connector sets a &PathNotFoundException; error on the request if the node
does not exist in the workspace.
- The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
- </entry>
- </row>
- <row>
- <entry>RenameNodeRequest</entry>
- <entry>
- A request to change the name of a node. The connector changes the node's
name, adjusts all SNS indexes
- accordingly, and returns the actual locations (including the path and
identification properties) of both the original
- location and the new location.
- The connector sets a &PathNotFoundException; error on the request if the node
does not exist in the workspace.
- The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
- </entry>
- </row>
- <row>
- <entry>CopyBranchRequest</entry>
- <entry>
- A request to copy a portion of a subgraph that has as its root a particular node,
up to a maximum depth.
- The request includes the name of the workspace where the original node is located
as well as the name of the
- workspace where the copy is to be placed (these may be the same, but may be
different).
- The connector copies the branch from the original location, up to the specified
maximum depth, and places a copy
- of the node as a child of the new location.
- The connector also sets on the request the actual location (including the path
and identification properties)
- of the original location as well as the location of the new copy.
- The connector sets a &PathNotFoundException; error on the request if the node
at
- the top of the branch does not exist in the workspace.
- The connector sets a &InvalidWorkspaceException; error on the request if one
of the named workspaces does not exist.
- </entry>
- </row>
- <row>
- <entry>MoveBranchRequest</entry>
- <entry>
- A request to move a subgraph that has a particular node as its root.
- The connector moves the branch from the original location and places it as child
of the specified new location.
- The connector also sets on the request the actual location (including the path
and identification properties)
- of the original and new locations. The connector will adjust SNS indexes
accordingly.
- The connector sets a &PathNotFoundException; error on the request if the node
that is to be moved or the
- new location do not exist in the workspace.
- The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
- </entry>
- </row>
- <row>
- <entry>DeleteBranchRequest</entry>
- <entry>
- A request to delete an entire branch specified by a single node's location.
- The connector deletes the specified node and all nodes below it, and sets the
actual location,
- including the path and identification properties, of the node that was deleted.
- The connector sets a &PathNotFoundException; error on the request if the node
being deleted does not exist in the workspace.
- The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
- </entry>
- </row>
- <row>
- <entry>CompositeRequest</entry>
- <entry>
- A request that actually comprises multiple requests (none of which will be a
composite).
- The connector simply processes all of the requests in the composite request, but
should set on the composite
- request any error (usually the first error) that occurs during processing of the
contained requests.
- </entry>
- </row>
- </tbody>
- </tgroup>
- </table>
- <para>There are also requests that deal with workspaces:</para>
- <table frame='all'>
- <title>Types of Workspace Requests</title>
- <tgroup cols='2' align='left' colsep='1'
rowsep='1'>
- <colspec colname='c1' colwidth="1*"/>
- <colspec colname='c2' colwidth="1*"/>
- <thead>
- <row>
- <entry>Name</entry>
- <entry>Description</entry>
- </row>
- </thead>
- <tbody>
- <row>
- <entry>GetWorkspacesRequest</entry>
- <entry>
- A request to obtain the names of the existing workspaces that are accessible to
the caller.
- </entry>
- </row>
- <row>
- <entry>VerifyWorkspaceRequest</entry>
- <entry>
- A request to verify that a workspace with a particular name exists.
- The connector returns the actual location for the root node if the workspace
exists, as well as the actual name of the workspace
- (e.g., the default workspace name if a null name is supplied).
- </entry>
- </row>
- <row>
- <entry>CreateWorkspaceRequest</entry>
- <entry>
- A request to create a workspace with a particular name.
- The connector returns the actual location for the root node if the workspace
exists, as well as the actual name of the workspace
- (e.g., the default workspace name if a null name is supplied).
- The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace already exists.
- </entry>
- </row>
- <row>
- <entry>DestroyWorkspaceRequest</entry>
- <entry>
- A request to destroy a workspace with a particular name.
- The connector sets a &InvalidWorkspaceException; error on the request if the
named workspace does not exist.
- </entry>
- </row>
- <row>
- <entry>CloneWorkspaceRequest</entry>
- <entry>
- A request to clone one named workspace as another new named workspace.
- The connector sets a &InvalidWorkspaceException; error on the request if the
original workspace does not exist,
- or if the new workspace already exists.
- </entry>
- </row>
- </tbody>
- </tgroup>
- </table>
- <para>
- Although there are over a dozen different kinds of requests, we do anticipate adding
more in future releases.
- For example, DNA will likely support searching repository content in sources through
an additional subclass of &Request;.
- Getting the version history for a node will likely be another kind of request added
in an upcoming release.
- </para>
- <para>
- A connector is technically free to implement the
<code>execute(...)</code> method in any way, as long as the semantics
- are maintained. But DNA provides a &RequestProcessor; class that can simplify
writing your own connector and at the
- same time help insulate your connector from new kinds of requests that may be added
in the future. The &RequestProcessor;
- is an abstract class that defines a <code>process(...)</code> method for
each concrete &Request; subclass.
- In other words, there is a <code>process(CompositeRequest)</code> method,
a <code>process(ReadNodeRequest)</code> method,
- and so on.
- </para>
- <para>
- To use this in your connector, simply create a subclass of &RequestProcessor;,
overriding all of the abstract methods and optionally
- overriding any of the other methods that have a default implementation.
- </para>
- <note>
- <para>
- The &RequestProcessor; abstract class contains default implementations for quite
a few of the <code>process(...)</code> methods,
- and these will be <emphasis>sufficient</emphasis> but probably not
efficient or optimum. If you can provide a more efficient
- implementation given your source, feel free to do so. However, if performance is
not a big issue, all of the concrete methods
- will provide the correct behavior. Keep things simple to start out - you can always
provide better implementations later.
- </para>
- </note>
- <para>
- Then, in your connector's <code>execute(&ExecutionContext;,
&Request;)</code> method, instantiate your &RequestProcessor; subclass
- and call its <code>process(&Request;) method, passing in the
<code>execute(...)</code> method's &Request; parameter.</code>
- The &RequestProcessor; will determine the appropriate method given the actual
&Request; object and will then invoke that method:
- </para>
- <programlisting>
-public void execute( final &ExecutionContext; context,
- final &Request; request ) throws RepositorySourceException {
- RequestProcessor processor = new RequestProcessor(context);
- try {
- processor.process(request);
- } finally {
- processor.close();
- }
-}</programlisting>
- <para>
- If you do this, the bulk of your connector implementation may be in the
&RequestProcessor; implementation methods.
- This not only is pretty maintainable, it also lends itself to easier testing. And
should any new request types be added
- in the future, your connector may work just fine without any changes. In fact, if
the &RequestProcessor; class
- can implement meaningful methods for those new request types, your connector may
"just work". Or, at least
- your connector will still be binary compatible, even if your connector won't
support any of the new features.
- </para>
- <para>
- Finally, how should the connector handle exceptions? As mentioned above, each
&Request; object has a slot where the connector
- can set any exception encountered during processing. This not only handles the
exception, but in the case of &CompositeRequest;s
- it also correctly associates the problem with the request. However, it is perfectly
acceptable to throw an exception
- if the connection becomes invalid (e.g., there is a communication failure) or if a
fatal error would prevent subsequent
- requests from being processed.
- </para>
- </sect2>
- <sect2 id="testing_custom_connectors">
- <title>Testing custom connectors</title>
- <para>
- Testing connectors is not really that much different than testing other classes.
Using mocks may help to isolate your
- instances so you can create more unit tests that don't require the underlying
source system.
- </para>
- <para>
- However, there may be times when you have to use the underlying source system in your
tests. If this is the case,
- we recommend using Maven integration tests, which run at a different point in the
Maven lifecycle. The benefit of
- using integration tests is that by convention they're able to rely upon external
systems. Plus, your unit tests
- don't become polluted with slow-running tests that break if the external system
is not available.
- </para>
- </sect2>
- <sect2 id="deploying_custom_connectors">
- <title>Configuring and deploying custom connectors</title>
- <para>
- After building your connector project, you need to configure the JBoss DNA components
your application is using so
- that they use your connector. In a lot of cases, this will entail instantiating your
connector's &RepositorySource; class,
- setting the various properties, and registering it with a &RepositoryLibrary;.
Or, it will entail using a configuration
- repository to use your source and letting &RepositoryService; instantiate and set
up your &RepositorySource; instance.
- Or, you can just instantiate and set it up manually, passing the instance to whatever
component needs it.
- </para>
- <para>
- And of course you have to make the JAR file containing your connector (as well as any
dependency JARs) available to
- your application's classpath.
- </para>
- </sect2>
- </sect1>
- <sect1 id="dna_graph_api">
- <title>Graph API for using connectors</title>
- <para>
- So far we've talked about repositories, repository connectors, and how connectors
respond to the different kinds of requests.
- Normally you'd code to the JCR API and use our JCR implementation. However, what
does your code look like if you want
- to use the connectors directly, without using our JCR implementation? After all, you
may be a contributor to JBoss DNA,
- or you may want to take advantage of our connectors without all the overhead of JCR.
- </para>
- <para>
- One option, of course, is to explicitly create the different requests and pass them to
the connector's <code>execute(...)</code> method.
- While this is the most efficient approach (and one taken in some key DNA components),
you probably want something that
- is much less verbose and much easier to use. This is where the DNA graph API comes
in.
- </para>
- <para>
- JBoss DNA's <emphasis>Graph API</emphasis> was designed as a
lightweight public API for working with graph information,
- and it insulates components from the underlying requests and interacting with
connectors.
- The &Graph; class is the primary class in API, and each instance represents a
single, independent
- view of the graph of content from a single connector. &Graph; instances return
snapshots of state, and those snapshots
- never change after they're retrieved. To obtain a &Graph; instance, use the
static <code>create(...)</code>
- method, supplying the name of the source, a &RepositoryConnectionFactory; from
which a &RepositoryConnection; can be obtained,
- and the &ExecutionContext;.
- </para>
- <para>
- The &Graph; class basically represents an <ulink
url="http://www.martinfowler.com/bliki/DomainSpecificLanguage.html&q...
domain specific language (DSL)</ulink>,
- designed to be easy to use in an application.
- The Graph API makes extensive use of interfaces and method chaining, so that methods
return a concise interface that has only those
- methods that make sense at that point. In fact, this should be really easy if your
IDE has code completion.
- Just remember that under the covers, a &Graph; is just building &Request;
objects, submitting them to the connector,
- and then exposing the results.
- </para>
- <sect2 id="dna_graph_api_workspaces">
- <title>Using workspaces</title>
- <para>
- Let's look at some examples of how the Graph API works. This first example shows
how to obtain the names of the available workspaces:
- </para>
- <programlisting>
-&Set;<&String;> workspaceNames = graph.getWorkspaces();
-</programlisting>
- <para>Once you know the name of the workspace, you can specify that the graph
should use it:
- </para>
- <programlisting>
-graph.useWorkspace("myWorkspace");
-</programlisting>
- <para>
- From this point forward, all requests will apply to the workspace named
"myWorkspace". At any time, you can use a different workspace,
- which will affect all subsequent requests made using the graph. Of course, creating a
new workspace is just as easy:
- </para>
- <programlisting>
-graph.createWorkspace().named("newWorkspace");
-</programlisting>
- <para>This will attempt to create a workspace named "newWorkspace",
which will fail if that workspace already exists. You may
- want to create a new workspace with a name that should be altered if the name you
supply is already used. The following code shows
- how you can do this:
- </para>
- <programlisting>
-graph.createWorkspace().namedSomethingLike("newWorkspace");
-</programlisting>
- <para>If there is no existing workspace named "newWorkspace", a new
one will be created with this name. However, if "newWorkspace" already
- exists, this call will create a workspace with a name that is some alteration of the
supplied name.
- </para>
- <para>
- You can also clone workspaces, too:
- </para>
- <programlisting>
-graph.createWorkspace().clonedFrom("original").named("something");
-</programlisting>
- <para>
- or
- </para>
- <programlisting>
-graph.createWorkspace().clonedFrom("original").namedSomethingLike("something");
-</programlisting>
- <para>
- As you can see, it's very easy to specify which workspace you want to use or to
create new workspaces. You can also find out which workspace
- the graph is currently using:
- </para>
- <programlisting>
-&String; current = graph.getCurrentWorkspaceName();
-</programlisting>
- <para>or, if you want, you can get more information about the workspace:
- </para>
- <programlisting>
-&Workspace; current = graph.getCurrentWorkspace();
-&String; name = current.getName();
-&Location; rootLocation = current.getRoot();
-</programlisting>
- </sect2>
- <sect2 id="dna_graph_api_nodes">
- <title>Working with nodes</title>
- <para>
- Now let's switch to working with nodes. This first example returns a map of
properties (keyed by property name)
- for a node at a specific &Path;:
- </para>
- <programlisting>
-&Path; path = ...
-Map<&Name;,&Property;> propertiesByName =
graph.getPropertiesByName().on(path);
-</programlisting>
- <para>
- This next example shows how the graph can be used to obtain and loop over the
properties of a node:
- </para>
- <programlisting>
-&Path; path = ...
-for ( &Property; property : graph.getProperties().on(path) ) {
- ...
-}
-</programlisting>
- <para>
- Likewise, the next example shows how the graph can be used to obtain and loop over the
children of a node:
- </para>
- <programlisting>
-&Path; path = ...
-for ( &Location; child : graph.getChildren().of(path) ) {
- &Path; childPath = child.getPath();
- ...
-}
-</programlisting>
- <para>
- Notice that the examples pass a &Path; instance to the
<code>on(...)</code> and <code>of(...)</code> methods. Many
- of the Graph API methods take a variety of parameter types, including String,
&Path;s, &Location;s, &UUID;, or &Property; parameters.
- This should make it easy to use in many different situations.
- </para>
- <para>
- Of course, changing content is more interesting and offers more interesting
possibilities. Here are a few examples:
- </para>
- <programlisting>
-&Path; path = ...
-&Location; location = ...
-&Property; idProp1 = ...
-&Property; idProp2 = ...
-&UUID; uuid = ...
-graph.move(path).into(idProp1, idProp2);
-graph.copy(path).into(location);
-graph.delete(uuid);
-graph.delete(idProp1,idProp2);
-</programlisting>
- <para>
- The methods shown above work immediately, as soon as each request is built. However,
there is another way to use
- the &Graph; object, and that is in a <emphasis>batch</emphasis> mode.
Simply create a &GraphBatch; object using the
- <code>batch()</code> method, create the requests on that batch object, and
then execute all of the commands on the
- batch by calling its <code>execute()</code> method. That
<code>execute()</code> method returns a &Results; interface
- that can be used to read the node information retrieved by the batched requests.
- </para>
- <para>
- Method chaining works really well with the batch mode, since multiple commands can be
assembled together very easily:
- </para>
- <programlisting>
-&Path; path = ...
-String path2 = ...
-&Location; location = ...
-&Property; idProp1 = ...
-&Property; idProp2 = ...
-&UUID; uuid = ...
-graph.batch().move(path).into(idProp1,
idProp2).and().copy(path2).into(location).and().delete(uuid).execute();
-&Results; results = graph.batch().read(path2).
- and().readChildren().of(idProp1,idProp2).
- and().readSugraphOfDepth(3).at(uuid2).
- execute();
-for ( &Location; child : results.getNode(path2) ) {
- ...
-}
-</programlisting>
- <para>
- Of course, this section provided just a hint of the Graph API.
- The &Graph; interface is actually quite complete and offers a full-featured
approach for reading and updating a graph.
- For more information, see the &Graph; JavaDocs.
- </para>
- </sect2>
- </sect1>
- <sect1>
- <title>Summary</title>
- <para>
- In this chapter, we covered all the aspects of JBoss DNA repositories, including the
connector framework,
- how DNA's JCR implementation works with connectors, what connectors are available
(and how to use them),
- and how to write your own connector. So now that you know how to set up and use JBoss
DNA repositories,
- the <link linkend="jcr">next chapter</link> describes how you
can leverage JBoss DNA's JCR implementation.
- </para>
- </sect1>
-</chapter>
Modified: trunk/docs/reference/src/main/docbook/en-US/content/core/sequencing.xml
===================================================================
--- trunk/docs/reference/src/main/docbook/en-US/content/core/sequencing.xml 2009-06-08
15:21:29 UTC (rev 996)
+++ trunk/docs/reference/src/main/docbook/en-US/content/core/sequencing.xml 2009-06-08
15:47:40 UTC (rev 997)
@@ -28,7 +28,7 @@
<!ENTITY % CustomDTD SYSTEM "../../custom.dtd">
%CustomDTD;
]>
-<chapter id="sequencing">
+<chapter id="sequencing_framework">
<title>Sequencing content</title>
<para>As we've mentioned before, JBoss DNA is able to work with existing JCR
repositories. Your client applications
make changes to the information in those repositories, and JBoss DNA automatically
uses its sequencers to extract
Modified: trunk/docs/reference/src/main/docbook/en-US/master.xml
===================================================================
--- trunk/docs/reference/src/main/docbook/en-US/master.xml 2009-06-08 15:21:29 UTC (rev
996)
+++ trunk/docs/reference/src/main/docbook/en-US/master.xml 2009-06-08 15:47:40 UTC (rev
997)
@@ -52,32 +52,71 @@
</bookinfo>
<xi:include
xmlns:xi="http://www.w3.org/2001/XInclude"
href="content/preface.xml"/>
<xi:include
xmlns:xi="http://www.w3.org/2001/XInclude"
href="content/introduction.xml"/>
- <part>
+ <part id="developers-part">
<title>Developers and Contributors</title>
+ <partintro>
+ <para>
+ The JBoss DNA project uses a number of process, tools, and procedures to assist in
the development of
+ the software. This portion of the document focuses on these aspects and will help
developers and contributors
+ obtain the source code, build locally, and contribute to the project.
+ </para>
+ <para>If you're not contributing to the project but are still developing
+ custom <link linkend="connector_framework">connectors</link> or
<link linkend="sequencing_framework">sequencers</link>.
+ this information may be helpful in establishing your own environment.
+ </para>
+ </partintro>
<xi:include
xmlns:xi="http://www.w3.org/2001/XInclude"
href="content/developers/tools.xml"/>
<xi:include
xmlns:xi="http://www.w3.org/2001/XInclude"
href="content/developers/testing.xml"/>
</part>
- <part>
+ <part id="core-part">
<title>JBoss DNA Core</title>
+ <partintro>
+ <para>
+ The JBoss DNA project organizes the codebase into a number of subprojects. The most
fundamental are those
+ <emphasis>core libraries</emphasis>, including the graph API, connector
framework, sequencing framework,
+ as well as the configuration and engine in which all the components run. These are
all topics covered
+ in this part of the document.
+ </para>
+ <para>The JBoss DNA implementation of the <ulink
url="&JSR170;">JCR API</ulink> as well as some other
+ JCR-related components are covered in the <link
linkend="jcr-part">next part</link>.
+ </para>
+ </partintro>
<xi:include
xmlns:xi="http://www.w3.org/2001/XInclude"
href="content/core/classloaders.xml"/>
<xi:include
xmlns:xi="http://www.w3.org/2001/XInclude"
href="content/core/environment.xml"/>
- <xi:include
xmlns:xi="http://www.w3.org/2001/XInclude"
href="content/core/repositories.xml"/>
+ <xi:include
xmlns:xi="http://www.w3.org/2001/XInclude"
href="content/core/connector.xml"/>
<xi:include
xmlns:xi="http://www.w3.org/2001/XInclude"
href="content/core/sequencing.xml"/>
<xi:include
xmlns:xi="http://www.w3.org/2001/XInclude"
href="content/core/mimetypes.xml"/>
<xi:include
xmlns:xi="http://www.w3.org/2001/XInclude"
href="content/core/configuration.xml"/>
</part>
- <part>
+ <part id="jcr-part">
<title>JBoss DNA JCR</title>
+ <partintro>
+ <para>
+ The JBoss DNA project provides an implementation of the <ulink
url="&JSR170;">JCR API</ulink>, which is
+ built on top of the <link linkend="core-part">core
libraries</link> discussed earlier. This implementation
+ as well as a number of JCR-related components are described in this part of the
document.
+ </para>
+ </partintro>
<xi:include
xmlns:xi="http://www.w3.org/2001/XInclude"
href="content/jcr/jcr.xml"/>
<xi:include
xmlns:xi="http://www.w3.org/2001/XInclude"
href="content/jcr/deploying_dna_jcr.xml"/>
<xi:include
xmlns:xi="http://www.w3.org/2001/XInclude"
href="content/jcr/rest_service.xml"/>
</part>
- <part>
+ <part id="provied-connectors-part">
<title>Provided Connectors</title>
+ <para>
+ The JBoss DNA project provides a number of <link
linkend="connector_framework">connectors</link> out-of-the-box.
+ These are ready to be used by simply including them in the classpath and <link
linkend="configuration">configuring</link>
+ them as a repository source.
+ </para>
<xi:include
xmlns:xi="http://www.w3.org/2001/XInclude"
href="content/connectors/in_memory.xml"/>
</part>
- <part>
+ <part id="provied-sequencers-part">
<title>Provided Sequencers</title>
+ <para>
+ The JBoss DNA project provides a number of <link
linkend="sequencing_framework">sequencers</link> out-of-the-box.
+ These are ready to be used by simply including them in the classpath and <link
linkend="configuration">configuring</link>
+ them appropriately.
+ </para>
<xi:include
xmlns:xi="http://www.w3.org/2001/XInclude"
href="content/sequencers/zip.xml"/>
</part>
<xi:include
xmlns:xi="http://www.w3.org/2001/XInclude"
href="content/future.xml"/>