Repository plugin

This plugin provides low-level modeling and storing of any kind of information (content, document, page, user, ...) as a light layer above JCR (JSR 170).
Apache Jackrabbit is used for the JCR implementation.

  1. Plugin informations
  2. Description
    1. Changelog
  3. Components
    1. AmetysObjectResolver component
    2. UnlockScheduler component
  4. Extension point
    1. JCR Repository single extension point
    2. SessionFactory single extension point
    3. AmetysObjectFactory extension point
  5. Usage
    1. Accessing the JCR Repository
    2. Access and manage AmetysObject
      1. Retrieve root AmetysObject
      2. Access feeds and items
      3. Create and modify an item
      4. Query and child navigation
      5. Using a MetadataAwareAmetysObject
      6. Using a VersionableAmetysObject
      7. Using a LockableAmetysObject

Plugin informations


Repository plugin allows to read, write, browse and query AmetysObject.

An AmetysObject is a hierarchical entity defined by the following mandatory properties:

  • an id in the following syntax scheme://uri.
  • a name.
  • a path.
  • a parent (null for the parent's root).
  • a parent path (for performance purpose).

On top of that, an AmetysObject can be a:

  • TraversableAmetysObject for creating and accessing children.
  • MetadataAwareAmetysObject for providing metadata support.
  • VersionableAmetysObject for supporting versioning features.
  • LockableAmetysObject for supporting locking features.
  • JCRAmetysObject which is a MetadataAwareAmetysObject with JCR node access.

Most of the time an AmetysObject is backed by one and only one JCR node with a specific node type but it can also be virtual (typically used for dynamic Pages).

By default, the Ametys repository contains only one node which is the Ametys root node and is by definition traversable for accessing and creating children.
You must distinguish JCR repository from Ametys repository (the former is a sub-set of the JCR repository with virtual feature).

Only a few node type are registered (ametys:root by default) for exposing node as AmetysObject.
Child nodes with unregistered node types are therefore not returned when calling TraversableAmetysObject#getChildren(), they are simply ignored.

Theses two repositories can be browsed using the repository workspace accessible with the following URL:

Because access to this data must be secured, a HTTP BASIC authentication with the same credentials as in the admin workspace is used.

CMS massively uses this plugin for managing CMS business AmetysObject like Content or Page.


TODO insert here changelog from JIRA.


Repository plugin comes with two Avalon components.

AmetysObjectResolver component

AmetysObjectResolver is a Avalon component which provides:

  • Ametys namespaces and nodetypes registration.
  • AmetysObject access by path or id.
  • AmetysObject search using JCR XPath query ( only for static AmetysObject, not virtual ones).

Resolving an AmetysObject or querying the repository will open and provide one JCR session.
Therefore, saving changes must be done manually using the wrapped node or the provided session.

See #usage for seeing it in action.

UnlockScheduler component

UnlockScheduler is an Avalon component for automatically unlocking LockableAmetysObject after a given period.
The thread can be disable using a configuration parameter and the period can be tweaked using also a configuration parameter expressed in hours.

Extension point

JCR Repository single extension point

This extension point provides the javax.jcr.Repository instance as an Avalon component.

Default implementation creates a JackrabbitRepository instance using as the repository home a configuration parameter and as the repository configuration the file WEB-INF/param/repository.xml.

org.ametys.plugins.repository.provider.JNDIRepository can also be used if the Repository instance is created by the sevlet engine and shared between multiple contexts via JNDI.

SessionFactory single extension point

This extension point is responsible for managing javax.jcr.Session objects.

Default implementation uses the JCR Repository extension point for creating sessions and also an Apache commons pool in order to reuse session for performance purposes.
Sessions are returned to the pool when logout() method is called or at the end of an HTTP request.
Calling getSession() multiple times returns a new Session each time.

AmetysObjectFactory extension point

This extension point allows to register many kind of AmetysObjectFactory in order to create and manage AmetysObject.

See AmetysObjectFactory for creating your own AmetysObject and AmetysObjectFactory.


Accessing the JCR Repository
Repository repository = (Repository) manager.lookup(Repository.class.getName());
Session session = repository.login(new SimpleCredentials("ametys", new char[0]);

But the right way to retrieve a JCR Session is to use the SessionFactory (for benefit from pool implementation):

SessionFactory sessionFactory = (SessionFactory) manager.lookup(SessionFactory.ROLE);
Session session = sessionFactory.getSession();

  // Returns the session to the pool

Ensure that the session is logout when it is not needed anymore for avoiding pool exhaustion when your are in a thread environment.
Indeed in a HTTP request environment sessions opened are returned to the pool at the end of the request thanks to a J2EE request listener.

Access and manage AmetysObject

In the following examples, four AmetysObjectFactory are already registered:

  • ametys:root node type with root id (built-in).
  • feed:feeds node type with feeds id (for creating TraversableAmetysObject for feeds root).
  • feed:feed node type with feed id (for creating TraversableAmetysObject for feed).
  • feed:item node type with item id (for creating MyItem instances which implements JCRAmetysObject).

See AmetysObjectFactory for more information on creating and registering AmetysObjectFactory.

Retrieve root AmetysObject
AmetysObjectResolver resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
// By path
AmetysObject ametysObjectRoot = resolver.resolve("/");
// By a different path
ametysObjectRoot = resolver.resolve("");
// By id
ametysObjectRoot = resolver.resolveById("root://d6d2e760-55d3-11de-8f6c-0002a5d5c51b");
Access feeds and items
// Automatic cast
TraversableAmetysObject myFeed = resolver.resolve("/myfeeds/myfeed");
// Child direct access by path
MyItem myItem01 = resolver.resolve("/myfeeds/myfeed/item01");
// Child direct access by id
myItem01 = resolver.resolve("myitem://d40843c0-55d5-11de-927f-0002a5d5c51b");
// Child access by parent navigation
myItem01 = myFeed.getChild("item01");
// Retrieve parent
myFeed = myItem01.getParent();
Create and modify an item
TraversableAmetysObject myFeed = resolver.resolve("/myfeeds/myfeed");

// Create a new item
MyItem myItem02 = myFeed.createChild("item02", "feed:item");
// Save changes (use the session because a newly created node cannot be save directly)

MyItem myItem01 = myFeed.getChild("item01");

// Access JCR node and change title
myItem01.getNode().setProperty("title", "Item 01");
// Save changes
Query and child navigation
// Query example
AmetysObjectIterable<MyItem> myItemIterator = resolver.query("//element(*, feed:item)");

for (MyItem myItem : myItemIterator)

// The same with children browsing
myItemIterator = resolver.resolve("/myfeeds/myfeed").getChildren();

for (MyItem myItem : myItemIterator)
Using a MetadataAwareAmetysObject
MetadataAwareAmetysObject myObject = resolver.resolve("/myobject");

// Retrieve composite metadata holder
CompositeMetadata metadata = myObject.getMetadataHolder();

// Set a date property
metadata.setMetadata("updatedAt", new Date());

// Set a multiple string property
metadata.setMetadata("authors", new String[] {"bob", "john"});

// Creating a binary metadata
BinaryMetadata binaryMetadata = metadata.getBinaryMetadata("thumbnail", true);
binaryMetadata.setLastModified(new Date());
binaryMetadata.setInputStream(new FileInputStream("/tmp/thumb.jpg"));

// Creating a rich text metadata
RichText richText = metadata.getRichText("body", true);
richText.setLastModified(new Date());
richText.setInputStream(new ByteArrayInputStream("<?xml version='1.0' encoding='UTF-8'?><document><para>body</para></document>".getBytes("UTF-8")));

// Creating a composite metadata
CompositeMetadata videoMetadata = metadata.getCompositeMetadata("address", true);
videoMetadata.setMetadata("street", "Beverly Drive");
videoMetadata.setMetadata("zipcode", "90210");
videoMetadata.setMetadata("city", "Beverly Hills");
videoMetadata.setMetadata("state", "California"); 
Using a VersionableAmetysObject
VersionableAmetysObject myObject = resolver.resolve("/myobject");

// Create a new version

// Mark current version as 'validated' label
myObject.addLabel("validated", true);

// Access the 1.2 version

// Go back to the last version ('validated' label in our example)
Using a LockableAmetysObject
LockableAmetysObject myObject = resolver.resolve("/myobject");

// Test if current object is locked
boolean isLocked = myObject.isLocked();

if (isLocked)
  // Lock current object
  // Unlock current object
  // Retrieve current lock owner
  System.out.println("Current lock owner: " + myObject.getLockOwner());