In
this post I described how an Jackrabbit content repository can be set up. I will show now how it can be accessed via JNDI. I dont' want to write an JNDI lookup although it's not difficult. I would like to use
Google Guice, a dependency injection framework. At first we need a Guice configuration module.
public class DefaultConfigurationGuiceModule extends AbstractModule
{
protected void configure()
{
bind(String.class).annotatedWith(Names.named("repository name")).toInstance("jcr/repository");
// bind naming context to the default InitialContext
bind(Context.class).to(InitialContext.class);
// bind to the repository from JNDI
bind(Repository.class).toProvider(JndiIntegration.fromJndi(Repository.class, "jcr/repository"));
// bind to the factory class for the creation of repository accessor
// see http://code.google.com/docreader/#p=google-guice&s=google-guice&t=AssistedInject
bind(RepositoryAccessorFactory.class).toProvider(FactoryProvider.newFactory(RepositoryAccessorFactory.class,
JackrabbitRepositoryAccessor.class)).in(Singleton.class);
}
}
Guice has a helpful class
JndiIntegration to create a provider which looks up objects in JNDI using the given name. Furthermore I use Guice's
AssistedInject and define a factory interface to create an instance for repository access (
JackrabbitRepositoryAccessor). The real factory will be created by
AssistedInject.
public interface RepositoryAccessorFactory
{
/**
* Greates an instance of {@link RepositoryAccessor}. Sets an input stream of XML file which describes custom node
* types and appropriated custom namespace mapping. Custom node types and namespace will be registered one-time if
* the JCR session is requested and they were not registered yet.
*
* @param nodeTypeConfigs configurations of custom node types to be registered
* @return RepositoryAccessor the instance of {@link RepositoryAccessor}
*/
RepositoryAccessor create(@Assisted final NodeTypeConfig[] nodeTypeConfigs);
}
The class
NodeTypeConfig is used for the registration of custom node types. Node types are described
here. More about custom node types in XML notation see in
my previous post.
/**
* Configuration infos about a node type to be registered.
*/
public class NodeTypeConfig
{
/** input stream of XML file which describes node types */
private InputStream inputStream;
/** namespace prefix of the node type */
private String namespacePrefix;
/** namespace uri of the node type */
private String namespaceUri;
/**
* Creates a new NodeTypeConfig object.
*
* @param inputStream input stream of XML file which describes node types
* @param namespacePrefix namespace prefix of the node type
* @param namespaceUri namespace uri of the node type
*/
public NodeTypeConfig(final InputStream inputStream, final String namespacePrefix, final String namespaceUri)
{
this.inputStream = inputStream;
this.namespacePrefix = namespacePrefix;
this.namespaceUri = namespaceUri;
}
setter / getter ...
/**
* Loads node type configuration from XML file in classpath.
*
* @param fileName file name
* @param namespacePrefix namespace prefix of the node type
* @param namespaceUri namespace uri of the node type
* @return NodeTypeConfig configuration
*/
public static NodeTypeConfig getNodeTypeConfig(final String fileName, final String namespacePrefix, final String namespaceUri)
{
InputStream inputStream = getInputStreamConfig(fileName);
return new NodeTypeConfig(inputStream, namespacePrefix, namespaceUri);
}
/**
* Gets input stream from XML file in classpath.
*
* @param fileName file name
* @return NodeTypeConfig configuration
*/
public static InputStream getInputStreamConfig(final String fileName)
{
Validate.notNull(fileName, "XML file with node type configuration is null");
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
if (classloader == null) {
classloader = NodeTypeConfig.class.getClassLoader();
}
return classloader.getResourceAsStream(fileName);
}
}
The most important class is
JackrabbitRepositoryAccessor. This is an entry point into the content repository. This class implements an interface
RepositoryAccessor. This interface looks as follows
public interface RepositoryAccessor
{
/**
* Gets the content repository. If no repository has been yet created it will be created.
*
* @see #startRepository()
* @return Repository repository {@link Repository}
* @throws RepositoryException if the repository could be not acquired
*/
Repository getRepository() throws RepositoryException;
/**
* Starts and initializes the content repository by the configured repository name via JNDI.
*
* @throws RepositoryException if the repository could be not acquired or an error occured
*/
void startRepository() throws RepositoryException;
/**
* Retrieves the current JCR Session local to the thread which it is tied to one workspase. If no JCR Session is
* open, opens a new JCR Session for the running thread.
*
* @param workspaceName name of the workspace (<code>null</code> is not allowed)
* @param viewerId viewer id from {@link SecurityToken}
* @return Session JCR session {@link Session}
* @throws LoginException if the login fails
* @throws NoSuchWorkspaceException if a specific workspace is not found
* @throws AccessDeniedException if the session associated with the workspace object does not have sufficient
* permissions to register core / custom namespaces, to create a new workspace or
* some access-related methods failed
* @throws NamespaceException if an illegal attempt is made to register a mapping
* @throws RegisterNodeTypeException if registration of core / custom node type(s) failed
* @throws RepositoryException if the repository could be not acquired or an error occured
*/
Session getSession(final String workspaceName, final String viewerId)
throws LoginException, NoSuchWorkspaceException, AccessDeniedException, NamespaceException,
RegisterNodeTypeException, RepositoryException;
/**
* Closes all JCR Sessions local to the thread.
*/
void releaseSession();
/**
* Releases the content repository
*/
void releaseRepository();
}
And the implementation (a little bit big code) looks as follows
public class JackrabbitRepositoryAccessor implements RepositoryAccessor
{
private static final Logger LOG = LoggerFactory.getLogger(RepositoryAccessor.class);
private static final ThreadLocal<Map<String, Session>> THREAD_SESSION = new ThreadLocal<Map<String, Session>>();
/** repository instance */
private Repository repository;
/** defauilt workspace */
private Workspace defaultWorkspace;
/** repository name (not mandatory) */
@Inject(optional = true)
@Named("repository name")
private String repositoryName;
/** flag whether the core namespace mapping and node types were already registered */
private boolean isRegistered = false;
/** flag whether the custom namespace mapping and node types were already registered */
private boolean isCustomRegistered = false;
/** input stream of XML file which describes custom node types (not mandatory) */
private NodeTypeConfig[] customNodeTypeConfigs;
/** provider for repository */
private Provider<Repository> repositoryProvider;
/**
* Creates a new <code>JackrabbitRepositoryAccessor</code> object and sets repository providers.
* Note: Custom node types and namespace will be registered one-time if the JCR session is
* requested and they were not registered yet.
*
* @param repositoryProvider repository provider to get an access to the configured repository
* {@link Repository}
* @param customNodeTypeConfigs custom node types configurations (if <code>null</code> no custom node
* types will be registered)
*/
@Inject
public JackrabbitRepositoryAccessor(final Provider<Repository> repositoryProvider,
@Assisted
@Nullable
final NodeTypeConfig[] customNodeTypeConfigs)
{
// set repository provider
this.repositoryProvider = repositoryProvider;
this.customNodeTypeConfigs = customNodeTypeConfigs;
}
//~ Methods ----------------------------------------------------------------
/**
* Gets the default workspace. If no default workspace has been yet created it will be created.
*
* @see #startRepository()
* @return Workspace default workspace {@link Workspace}
* @throws RepositoryException if the repository or workspace could be not acquired
*/
protected Workspace getDefaultWorkspace() throws RepositoryException
{
if (defaultWorkspace == null) {
synchronized (JackrabbitRepositoryAccessor.class) {
Repository repository = getRepository();
if (defaultWorkspace == null) {
defaultWorkspace = repository.login().getWorkspace();
if (LOG.isDebugEnabled()) {
LOG.debug("==> Default workspace '"
+ (defaultWorkspace != null ? defaultWorkspace.getName() : "null")
+ "' acquired.");
}
}
}
}
return defaultWorkspace;
}
/**
* Registers a node type.
*
* @param jcrSession current JCR session
* @param inputStream input stream of XML file which describes node types
* @throws RegisterNodeTypeException if registration of core / custom node type failed
* @throws RepositoryException if an error occured
*/
@SuppressWarnings("unchecked")
protected void registerNodeType(final Session jcrSession, final InputStream inputStream)
throws RegisterNodeTypeException, RepositoryException
{
try {
NodeTypeManagerImpl ntManager = (NodeTypeManagerImpl) jcrSession.getWorkspace().getNodeTypeManager();
NodeTypeRegistry ntRegistry = ntManager.getNodeTypeRegistry();
NodeTypeDefStore ntDefStore = new NodeTypeDefStore();
ntDefStore.load(inputStream);
Collection<NodeTypeDef> ntDefs = ntDefStore.all();
Iterator<NodeTypeDef> iter = ntDefs.iterator();
while (iter.hasNext()) {
NodeTypeDef ntDef = iter.next();
if (!ntRegistry.isRegistered(ntDef.getName())) {
ntRegistry.registerNodeType(ntDef);
}
}
} catch (IOException e) {
throw new RegisterNodeTypeException(e);
} catch (InvalidNodeTypeDefException e) {
throw new RegisterNodeTypeException(e);
} finally {
IOUtils.closeQuietly(inputStream);
}
}
/**
* {@inheritDoc}
*/
public Repository getRepository() throws RepositoryException
{
if (repository == null) {
synchronized (JackrabbitRepositoryAccessor.class) {
if (repository == null) {
startRepository();
}
}
}
return repository;
}
/**
* {@inheritDoc}
*/
public void startRepository() throws RepositoryException
{
try {
repository = repositoryProvider.get();
if (repository == null) {
throw new RepositoryException("Unable to acquire Repository '" + repositoryName
+ "' via JNDI");
}
if (LOG.isDebugEnabled()) {
LOG.debug("==> Repository started.");
}
// get default workspace (it's always available)
defaultWorkspace = repository.login().getWorkspace();
if (LOG.isDebugEnabled()) {
LOG.debug("==> Default workspace '" + (defaultWorkspace != null ? defaultWorkspace.getName() : "null")
+ "' acquired.");
}
} catch (Throwable t) {
throw new RepositoryException("Unable to acquire Repository '" + repositoryName
+ "' via JNDI", t);
}
}
/**
* {@inheritDoc}
*/
public Session getSession(final String workspaceName, final String viewerId)
throws LoginException, NoSuchWorkspaceException, AccessDeniedException, NamespaceException,
RegisterNodeTypeException, RepositoryException
{
if (workspaceName == null) {
throw new NoSuchWorkspaceException("Workspace name is null. JCR Session can be not opened.");
}
Session jcrSession = null;
Map<String, Session> workspace2Session = THREAD_SESSION.get();
if (workspace2Session == null) {
workspace2Session = new HashMap<String, Session>();
} else {
jcrSession = workspace2Session.get(workspaceName);
}
if (jcrSession != null && !jcrSession.isLive()) {
jcrSession = null;
}
if (jcrSession == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("==> Opening new JCR Session for the current thread.");
}
SimpleCredentials credentials = new SimpleCredentials(viewerId, "".toCharArray());
try {
// authentication to get jcr session
jcrSession = getRepository().login(credentials, workspaceName);
} catch (NoSuchWorkspaceException e) {
// try to create new workspace with the given name because it doesn't exist yet
Workspace workspace = getDefaultWorkspace();
if (workspace == null) {
throw new NoSuchWorkspaceException("Default workspace could be not created. JCR Session can be not opened.");
}
if (LOG.isDebugEnabled()) {
LOG.debug("==> Try to create workspace '" + workspaceName + "'.");
}
// create new workspace
((JackrabbitWorkspace) workspace).createWorkspace(workspaceName);
if (LOG.isDebugEnabled()) {
LOG.debug("==> Workspace '" + workspaceName + "' has been created.");
}
// authentication again to get jcr session
jcrSession = getRepository().login(credentials, workspaceName);
}
if (jcrSession == null) {
throw new LoginException("JCR Session could be not opened (null).");
}
workspace2Session.put(workspaceName, jcrSession);
THREAD_SESSION.set(workspace2Session);
}
// register core namespace mapping and node types if they were not registered yet
if (!isRegistered) {
synchronized (JackrabbitRepositoryAccessor.class) {
if (!isRegistered) {
NamespaceRegistry namespaceRegistry = jcrSession.getWorkspace().getNamespaceRegistry();
// check whether the namespace prefix or uri already exist
if (!ArrayUtils.contains(namespaceRegistry.getPrefixes(), Constants.NAMESPACE_PREFIX)
|| !ArrayUtils.contains(namespaceRegistry.getURIs(), Constants.NAMESPACE_URI)) {
// register namespace
namespaceRegistry.registerNamespace(Constants.NAMESPACE_PREFIX, Constants.NAMESPACE_URI);
if (LOG.isDebugEnabled()) {
LOG.debug("Namespace prefix '" + Constants.NAMESPACE_PREFIX
+ "' has been registered to the uri '"
+ Constants.NAMESPACE_URI + "'");
}
}
// register core node types!
InputStream inputStream = NodeTypeConfig.getInputStreamConfig("core_node_types.xml");
if (inputStream == null) {
LOG.error("Node type definition 'core_node_types.xml' was not found");
throw new RegisterNodeTypeException("Node type definition 'core_node_types.xml' was not found");
}
registerNodeType(jcrSession, inputStream);
if (LOG.isDebugEnabled()) {
LOG.debug("Register of core node types is ensured");
}
isRegistered = true;
}
}
}
// register core namespace mapping and node types if they were not registered yet
if (!isCustomRegistered) {
synchronized (JackrabbitRepositoryAccessor.class) {
if (!isCustomRegistered) {
if (!ArrayUtils.isEmpty(customNodeTypeConfigs)) {
NamespaceRegistry namespaceRegistry = jcrSession.getWorkspace().getNamespaceRegistry();
for (NodeTypeConfig ndc : customNodeTypeConfigs) {
if (ndc.getNamespacePrefix() != null && ndc.getNamespaceUri() != null) {
// check whether the namespace prefix or uri already exist
if (!ArrayUtils.contains(namespaceRegistry.getPrefixes(), ndc.getNamespacePrefix())
|| !ArrayUtils.contains(namespaceRegistry.getURIs(), ndc.getNamespaceUri())) {
// register namespace
namespaceRegistry.registerNamespace(ndc.getNamespacePrefix(),
ndc.getNamespaceUri());
if (LOG.isDebugEnabled()) {
LOG.debug("Custom namespace prefix '" + ndc.getNamespacePrefix()
+ "' has been registered to the custom uri '"
+ ndc.getNamespaceUri() + "'");
}
}
}
if (ndc.getInputStream() != null) {
registerNodeType(jcrSession, ndc.getInputStream());
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("Register of " + customNodeTypeConfigs.length + " custom node types is ensured");
}
}
isCustomRegistered = true;
}
}
}
return jcrSession;
}
/**
* {@inheritDoc}
*/
public void releaseSession()
{
Map<String, Session> workspace2Session = THREAD_SESSION.get();
if (workspace2Session != null) {
Collection<Session> sessions = workspace2Session.values();
for (Session jcrSession : sessions) {
if (jcrSession != null && jcrSession.isLive()) {
if (LOG.isDebugEnabled()) {
LOG.debug("==> Closing JCR Session for the current thread.");
}
jcrSession.logout();
}
}
}
THREAD_SESSION.set(null);
}
/**
* {@inheritDoc}
*/
public void releaseRepository()
{
// Jackrabbit specific
if (repository instanceof JackrabbitRepository) {
((JackrabbitRepository) repository).shutdown();
}
repository = null;
}
}
RepositoryAccessor should be accessible from application scope and can be instantiated during application startup (e.g. in ServletContextListener's contextInitialized() or in an JSF managed bean's method annotated with @PostConstruct). Well. Let's put all classes together! I would like to show typically steps to get an instance of
JackrabbitRepositoryAccessor.
// create a google guice injector for the configuration module
Injector injector = Guice.createInjector(new DefaultConfigurationGuiceModule());
// create the factory instance to create a repository accessor instance
RepositoryAccessorFactory repositoryAccessorFactory = injector.getInstance(RepositoryAccessorFactory.class);
// create custom node type configurations from describing XML file and given namespace prefix / URI
NodeTypeConfig[] nodeTypeConfigs = new NodeTypeConfig[1];
nodeTypeConfigs[0] = NodeTypeConfig.getNodeTypeConfig("custom_node.xml", "xyz", "http://mysite.net/xyz");
// create an instance of repository accessor (parameter can be null if no custom node types are available)
repositoryAccessor = repositoryAccessorFactory.create(nodeTypeConfigs);
// method and field injection
injector.injectMembers(repositoryAccessor);
// start and initialize the content repository
repositoryAccessor.startRepository();
Now you can access both - Repository and JCR Session somewhere you want
javax.jcr.Repository repository = repositoryAccessor.getRepository();
javax.jcr.Session session = repositoryAccessor.getSession(workspaceName, viewerId);
Not forget to release repository when the application goes down (e.g. in ServletContextListener's contextDestroyed() or in an JSF managed bean's method annotated with @PreDestroy).
repositoryAccessor.releaseRepository();
repositoryAccessor = null;
That's all :-)