このコンテンツは選択した言語では利用できません。
15.4. Custom Connectors
15.4.1. The Connector Framework リンクのコピーリンクがクリップボードにコピーされました!
リンクのコピーリンクがクリップボードにコピーされました!
A connector is actually a plain old Java object (POJO). To create a connector, create a Java class that extends one of the following abstract classes:
ReadOnlyConnector
- extend this class when the hierarchical database clients will never be able to manipulate, create or remove any content exposed by the connector.WritableConnector
- extend this class when the hierarchical database clients may be able to manipulate, create and/or remove content exposed by the connector. Note that each time this connector is configured, it can still be made to be read-only.
A connector operates by accessing an external system and dynamically creating nodes that represent information in that external system. The nodes must form a single tree, although how that tree is structured and what the nodes actually look like is completely up to the connector implementation.
15.4.2. Documents リンクのコピーリンクがクリップボードにコピーされました!
リンクのコピーリンクがクリップボードにコピーされました!
While a connector conceptually exposes nodes, technically it exchanges representations of nodes (and other information, like sublists of children). These representations take the form of Java Document objects that are semantically like JSON and BSON documents. The connector SPI does this for a number of reasons:
- Firstly, the hierarchical database actually stores its own internal (non-federated) nodes as Documents, so connectors are actually working with the same kind of internal Document instances that the hierarchical database uses.
- Secondly, a Document is easily converted to and from JSON (and BSON), making it potentially very easy to write a connector that accesses a remote system.
- Thirdly, constructs other than nodes can be represented as documents; for example, a connector can be pageable, meaning it breaks the list of child node references into multiple pages that are read with separate requests, allowing the connector to efficiently expose large numbers of children under a single node.
- Finally, the node's identifier, properties, child node references, and other information specific to the hierarchical database are stored in specific fields within a Document, but additional fields can be used by the connector and hidden from hierarchical database clients. Though this makes little sense for a read-only connector, a writable connector might include such hidden fields when reading nodes so that when the document comes back to the connector those hidden fields are still available.
Before studying the Documents you shall study the methods your connector implementation will need to implement.
15.4.3. Read Only Connector リンクのコピーリンクがクリップボードにコピーされました!
リンクのコピーリンクがクリップボードにコピーされました!
The following code fragment shows the methods that a
ReadOnlyConnector
subclass must implement.
Not shown are fields, getters, and other implemented methods that your methods will almost certainly use. For example, a
Document
is a read-only representation of a JSON document, and they can be created by calling the newDocument(id)
method with the document's identifier, using the resulting DocumentWriter
to set/remove/add fields (and nested documents), and calling the writer's document()
method to obtain the read-only Document
instance.
The
DocumentWriter
interface provides dozens of methods for getting and setting node properties and child node references. Here's some code that uses a document writer to construct a node representation with a few properties:
As you can see, creating documents is pretty straightforward.
Identifiers of documents are simple strings that are expected to uniquely and durably identify a document. However, the content of that string is entirely up to the connector implementations. If the external system already has the notion of unique identifiers, it might be easiest to reuse a string representation of those identifiers. For example, a database might have a unique key within a given table, whereas a Git repository uses SHA-1 hashes for identifiers of commits, branches, tags, etc. Some external systems (like file systems) do not have a concept of unique identifiers, and in such cases the connector should devise its own identifier mechanism is durable and reliable.
15.4.4. Properties, Paths, Names, and Values リンクのコピーリンクがクリップボードにコピーされました!
リンクのコピーリンクがクリップボードにコピーされました!
Most of the time, you can use string property names and property values that are String, Calendar, URL, or Numeric instances, and the hierarchical database will convert to an internal object representation. However, the hierarchical database provides object definitions of JCR names, paths, values, and properties. These classes are often much easier to work with than the String names and paths, and they're easy to create using namespace-aware factories. The
ValueFactories
interface is a container for type-specific factories accessible with various getter methods. Here's an example of creating a Path
value from a string and then using the Path
methods to get at the already-parsed segments of the path:
The process of using a factory to create
Name
, Binary
, DateTime
, and all other JCR-compliant values is similar.
Properties are slightly different, since they are a bit more structured. The hierarchical database provides a
PropertyFactory
that can create single- or multi-valued Property
instances given a name and one or more values. Here's some simple code that shows how to create a single-valued property:
PropertyFactory propFactory = propertyFactory(); Name propName = nameFactory().create("lib:title"); String propValue = factories().stringFactory("The Adventures of Huckleberry Finn"); Property prop = propFactory.create(propName,propValue);
PropertyFactory propFactory = propertyFactory();
Name propName = nameFactory().create("lib:title");
String propValue = factories().stringFactory("The Adventures of Huckleberry Finn");
Property prop = propFactory.create(propName,propValue);
All
Property
, Name
, Path
, DateTime
, and Binary
instances are immutable, meaning you can pass them around without worrying about whether the receiver might modify them. Also, the factories will often pick implementation classes that are tailored for the specific value. For example, there are separate implementations for the root path, single-segment paths, paths created from a parent path, single-valued properties, empty properties, and multi-valued properties.
15.4.5. Writable Connector リンクのコピーリンクがクリップボードにコピーされました!
リンクのコピーリンクがクリップボードにコピーされました!
The following code fragment shows the methods that a
WritableConnector
subclass must implement.
A
WritableConnector
has to implement all of the read-related methods that a ReadOnlyConnector
must implement and a handful of write-related methods for removing, updating, and storing new documents (nodes).
In the same way that the hierarchical database provides a
DocumentWriter
, there is also a DocumentReader
that has methods to easily read properties, primary type, mixin types, and child references:
15.4.6. Extra Properties リンクのコピーリンクがクリップボードにコピーされました!
リンクのコピーリンクがクリップボードにコピーされました!
The hierarchical database provides a framework for storing "extra properties" that cannot be stored in the external system. For example, the "file system connector" cannot naturally map arbitrary properties to file attributes, and instead uses a variety of techniques to stores these extra properties.
By default, the hierarchical database can store the extra properties inside the same Infinispan cache where the repository's own internal (non-federated) content is stored. However, this may not be ideal, and so a connector can provide its own implementation of the
ExtraPropertiesStore
interface:
Then to use the extra properties store, simple call in the connector's
initialize(...)
method the setEtraPropertiesStore(ExtraPropertiesStore store)
method with an instance of your custom store. Then, in your store(Document)
and update(Document)
methods, record these extra properties. There are multiple ways of doing this, but here are a few:
15.4.7. Pageable Connectors リンクのコピーリンクがクリップボードにコピーされました!
リンクのコピーリンクがクリップボードにコピーされました!
A
Document
that represents a node will contain references to all the children of that node. These references are relatively small (the ID and name of the child), and for many connectors this is sufficient and fast enough. However, when the number of children under a node starts to increase, building the list of child references for a parent node can become noticeable and even burdensome, especially when few (if any) of the child references may ultimately be resolved into their node representations.
A pageable connector is one that exposes the children of nodes in a "page by page" fashion, where the parent node only contains the first page of child references and subsequent pages are loaded only if needed. This turns out to be quite effective, since when clients navigate a specific path (or ask for a specific child of a parent by its name) the hierarchical database does not need to use the child references in a node's document and can instead have the connector resolve such (relative or absolute external) paths into an identifier and then ask for the document with that ID.
Therefore, the only time the child references are needed are when clients iterate over the children of a node. A pageable connector will only be asked for as many pages as needed to handle the client's iteration, making it very efficient for exposing a node structure that can contain nodes with numerous children.
To make your
ReadOnlyConnector
or WritableConnector
support paging, implement the Pageable
interface:
The hierarchical database then knows that the document for the parent will contain only some of the children and how to access each page of children as needed.
For example, here's an example of code that might be used in a connector's
getDocumentById(...)
method to include some of the children in the parent node's document and to include a reference to a second page of children. This uses an imaginary Book
class that is presumed to represent information about a book in a library:
Then, the connector's
getPage(...)
method would implement getting the child references for a particular page:
As you can see, the logic of
getPage(...)
is actually very similar to the logic that adds children in the getDocumentById(...)
method, and your connector might find it useful to abstract this into a single helper method.