Anyway, see https://gist.github.com/1977909 (and below) for my latest
draft of these interfaces. Note also the javadocs.
This draft is fairly close to the model already present in
o.a.j.mk.model, with the most crucial difference being that a
ChildNodeEntry returns a NodeState reference instead of just a
content
id. In other words, the underlying addressing mechanism is hidden
below this interface.
Note that since we considered it best to decouple the methods for
accessing properties and child nodes, i.e. have getProperty(String)
and getNode(String) instead of just a getItem(String), it actually
makes sense to have a getName() method on the PropertyState instance.
Otherwise a separate PropertyEntry interface would be needed in order
to avoid unnecessary extra string lookups when iterating over all
properties. The desire to avoid extra lookups is also why I'd rather
use a separate interface for properties instead of adding extra
methods to NodeState.
BR,
Jukka Zitting
/**
* A content tree consists of nodes and properties, each of which
* evolves through different states during its lifecycle. This
interface
* represents a specific, immutable state of a node in a content tree.
* Depending on context, a NodeState instance can be interpreted as
* representing the state of just that node, of the subtree starting
at
* that node, or of an entire tree in case it's a root node.
* <p>
* The crucial difference between this interface and the similarly
named
* class in Jackrabbit 2.x is that this interface represents a
specific,
* immutable state of a node, whereas the Jackrabbit 2.x class
represented
* the "current" state of a node.
*
* <h2>Properties and child nodes</h2>
* <p>
* A node consists of an unordered set of properties, and an ordered
set
* of child nodes. Each property and child node is uniquely named
and a
* single name can only refer to a property or a child node, not
both at
* the same time.
*
* <h2>Immutability and thread-safety</h2>
* <p>
* As mentioned above, all node and property states are always
immutable.
* Thus repeating a method call is always guaranteed to produce the
same
* result as before unless some internal error occurs (see below).
Note
* however that this immutability only applies to a specific state
instance.
* Different states of a node can obviously be different, and in some
cases
* even different instances of the same state may behave slightly
differently.
* For example due to performance optimization or other similar
changes
the
* iteration order of properties may be different for two instances
of the
* same node state. However, all such changes must file
* <p>
* In addition to being immutable, a specific state instance
guaranteed to
* be fully thread-safe. Possible caching or other internal changes
need
to
* be properly synchronized so that any number of concurrent clients
can
* safely access a state instance.
*
* <h2>Persistence and error-handling</h2>
* <p>
* A node state can be (and often is) backed by local files or network
* resources. All IO operations or related concerns like caching
should be
* handled transparently below this interface. Potential IO problems
and
* recovery attempts like retrying a timed-out network access need
to be
* handled below this interface, and only hard errors should be
thrown up
* as {@link RuntimeException unchecked exceptions} that higher
level code
* is not expected to be able to recover from.
* <p>
* Since this interface exposes no higher level constructs like access
* controls, locking, node types or even path parsing, there's no way
* for content access to fail because of such concerns. Such
functionality
* and related checked exceptions or other control flow constructs
should
* be implemented on a higher level above this interface.
*
* <h2>Decoration and virtual content</h2>
* <p>
* Not all content exposed by this interface needs to be backed by
actual
* persisted data. An implementation may want to provide provide
derived
* data like for example the aggregate size of the entire subtree as
an
* extra virtual property. A virtualization, sharding or caching layer
* could provide a composite view over multiple underlying content
trees.
* Or a basic access control layer could decide to hide certain
content
* based on specific rules. All such features need to be implemented
* according to the API contract of this interface. A separate higher
level
* interface needs to be used if an implementation can't for example
* guarantee immutability of exposed content as discussed above.
*/
public interface NodeState {
/**
* Returns the named property. The name is an opaque string and
* is not parsed or otherwise interpreted by this method.
* <p>
* The namespace of properties and child nodes is shared, so if
* this method returns a non-<code>null</code> value for a given
* name, then {@link #getChildNode(String)} is guaranteed to return
* <code>null</code> for the same name.
*
* @param name name of the property to return
* @return named property, or <code>null</code> if not found
*/
PropertyState getProperty(String name);
/**
* Returns an iterable of the properties of this node. Multiple
* iterations are guaranteed to return the properties in the same
* order, but the specific order used is implementation-dependent
* and may change across different states of the same node.
*
* @return properties in some stable order
*/
Iterable<PropertyState> getProperties();
/**
* Returns the named child node. The name is an opaque string and
* is not parsed or otherwise interpreted by this method.
* <p>
* The namespace of properties and child nodes is shared, so if
* this method returns a non-<code>null</code> value for a given
* name, then {@link #getProperty(String)} is guaranteed to return
* <code>null</code> for the same name.
*
* @param name name of the child node to return
* @return named child node, or <code>null</code> if not found
*/
NodeState getChildNode(String name);
/**
* Returns the number of child nodes of this node.
*
* @return number of child nodes
*/
int getChildNodeCount();
/**
* Returns an iterable of the child node entries starting from the
* given offset and containing the given number of entries. The
order
* of child nodes is normally as specified by the client that
created
* or reordered them.
* <p>
* The order of child nodes is by default as specified by the
* client that created or reordered them, but the caller can also
* ask the underlying implementation to return nodes in their
* native order that may be more efficient to iterate over.
* To request such native ordering, the caller should specify
* the offset parameter in ones' complement form
* (i.e. <code>~offset</code>).
* <p>
* If the requested range is completely or partially beyond the
number
* of child nodes of this node, then only those child nodes that
match
* the range are returned. Thus the returned iterable may contain
less
* than the requested number of entries.
*
* @param offset start offset from which to return entries;
* with <code>0</code> being the offset of the first
entry,
* and negative offsets interpreted as described
above
* @param length maximum number of entries to return;
* use <code>-1</code> to return all remaining
entries
* @return requested child node entries
*/
Iterable<ChildNodeEntry> getChildNodeEntries(int offset, int
length);
}
/**
* TODO: document
*/
public interface PropertyState {
/**
* TODO: document
*/
String getName();
/**
* FIXME: replace with type-specific accessors
*/
String getEncodedValue();
}
/**
* TODO: document
*/
public interface ChildNodeEntry {
/**
* TODO: document
*/
String getName();
/**
* TODO: document
*/
NodeState getNode();
}