Start refactoring Jackrabbit container
authorMathieu Baudier <mbaudier@argeo.org>
Fri, 13 Jan 2012 18:21:47 +0000 (18:21 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Fri, 13 Jan 2012 18:21:47 +0000 (18:21 +0000)
git-svn-id: https://svn.argeo.org/commons/trunk@4972 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java
server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitWrapper.java [new file with mode: 0644]

index 70bc300a1b247af9fdd20c846bba0857ecc0e57f..82648998b7c1b5c8c5720abdc88b092f87e85b26 100644 (file)
@@ -16,7 +16,6 @@
 
 package org.argeo.jackrabbit;
 
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -27,11 +26,8 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Properties;
 import java.util.Set;
 import java.util.TreeSet;
-import java.util.UUID;
-import java.util.concurrent.Executor;
 
 import javax.jcr.Credentials;
 import javax.jcr.LoginException;
@@ -41,9 +37,7 @@ import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.SimpleCredentials;
-import javax.jcr.Value;
 
-import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -51,9 +45,6 @@ import org.apache.jackrabbit.api.JackrabbitRepository;
 import org.apache.jackrabbit.commons.NamespaceHelper;
 import org.apache.jackrabbit.commons.cnd.CndImporter;
 import org.apache.jackrabbit.core.RepositoryImpl;
-import org.apache.jackrabbit.core.config.RepositoryConfig;
-import org.apache.jackrabbit.core.config.RepositoryConfigurationParser;
-import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory;
 import org.argeo.ArgeoException;
 import org.argeo.jcr.ArgeoNames;
 import org.argeo.jcr.JcrUtils;
@@ -68,28 +59,20 @@ import org.springframework.security.Authentication;
 import org.springframework.security.context.SecurityContextHolder;
 import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
 import org.springframework.util.SystemPropertyUtils;
-import org.xml.sax.InputSource;
 
 /**
  * Wrapper around a Jackrabbit repository which allows to configure it in Spring
  * and expose it as a {@link Repository}.
  */
-public class JackrabbitContainer implements Repository {
+public class JackrabbitContainer extends JackrabbitWrapper {
        private Log log = LogFactory.getLog(JackrabbitContainer.class);
 
        // remote
-       private String uri = null;
        private Credentials remoteSystemCredentials = null;
 
        // local
        private Resource configuration;
-       private RepositoryConfig repositoryConfig;
-       private File homeDirectory;
        private Resource variables;
-       private Boolean inMemory = false;
-
-       // wrapped repository
-       private Repository repository;
 
        // data model
        /** Node type definitions in CND format */
@@ -101,8 +84,6 @@ public class JackrabbitContainer implements Repository {
        /** Namespaces to register: key is prefix, value namespace */
        private Map<String, String> namespaces = new HashMap<String, String>();
 
-       private Boolean autocreateWorkspaces = false;
-
        private BundleContext bundleContext;
 
        /**
@@ -122,15 +103,13 @@ public class JackrabbitContainer implements Repository {
                init();
        }
 
-       /** Initializes */
-       public void init() {
-               if (repository != null) {
-                       // we are just wrapping another repository
-                       prepareDataModel();
-                       return;
-               }
+       @Override
+       protected void postInitWrapped() {
+               prepareDataModel();
+       }
 
-               createJackrabbitRepository();
+       @Override
+       protected void postInitNew() {
                // migrate if needed
                migrate();
 
@@ -139,218 +118,10 @@ public class JackrabbitContainer implements Repository {
                        prepareDataModel();
        }
 
-       /** Actually creates the new repository. */
-       protected void createJackrabbitRepository() {
-               long begin = System.currentTimeMillis();
-               InputStream configurationIn = null;
-               try {
-                       if (uri != null && !uri.trim().equals("")) {// remote
-                               Map<String, String> params = new HashMap<String, String>();
-                               params.put(
-                                               org.apache.jackrabbit.commons.JcrUtils.REPOSITORY_URI,
-                                               uri);
-                               repository = new Jcr2davRepositoryFactory()
-                                               .getRepository(params);
-                               if (repository == null)
-                                       throw new ArgeoException("Remote Davex repository " + uri
-                                                       + " not found");
-                               log.info("Initialized Jackrabbit repository " + repository
-                                               + " from URI " + uri);
-                               // we assume that the remote repository has been properly
-                               // configured
-                       } else {// local
-                               // reset uri to null in order to optimize isRemote()
-                               uri = null;
-
-                               // temporary
-                               if (inMemory && getHomeDirectory().exists()) {
-                                       FileUtils.deleteDirectory(getHomeDirectory());
-                                       log.warn("Deleted Jackrabbit home directory "
-                                                       + getHomeDirectory());
-                               }
-
-                               // process configuration file
-                               Properties vars = getConfigurationProperties();
-                               configurationIn = configuration.getInputStream();
-                               vars.put(
-                                               RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE,
-                                               getHomeDirectory().getCanonicalPath());
-                               repositoryConfig = RepositoryConfig.create(new InputSource(
-                                               configurationIn), vars);
-
-                               //
-                               // Actual repository creation
-                               //
-                               repository = RepositoryImpl.create(repositoryConfig);
-
-                               double duration = ((double) (System.currentTimeMillis() - begin)) / 1000;
-                               log.info("Initialized Jackrabbit repository in " + duration
-                                               + " s, home: " + getHomeDirectory() + ", config: "
-                                               + configuration);
-                       }
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot create Jackrabbit repository "
-                                       + getHomeDirectory(), e);
-               } finally {
-                       IOUtils.closeQuietly(configurationIn);
-               }
-       }
-
-       /** Executes migrations, if needed. */
-       protected void migrate() {
-               // Remote migration not supported
-               if (isRemote())
-                       return;
-
-               // No migration to perform
-               if (dataModelMigrations.size() == 0)
-                       return;
-
-               Boolean restartAndClearCaches = false;
-
-               // migrate data
-               Session session = null;
-               try {
-                       session = login();
-                       for (JackrabbitDataModelMigration dataModelMigration : new TreeSet<JackrabbitDataModelMigration>(
-                                       dataModelMigrations)) {
-                               if (dataModelMigration.migrate(session)) {
-                                       restartAndClearCaches = true;
-                               }
-                       }
-               } catch (ArgeoException e) {
-                       throw e;
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot migrate", e);
-               } finally {
-                       JcrUtils.logoutQuietly(session);
-               }
-
-               // restart repository
-               if (restartAndClearCaches) {
-                       JackrabbitDataModelMigration
-                                       .clearRepositoryCaches(repositoryConfig);
-                       ((JackrabbitRepository) repository).shutdown();
-                       createJackrabbitRepository();
-               }
-
-               // set data model version
-               try {
-                       session = login();
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot login to migrated repository", e);
-               }
-
-               for (JackrabbitDataModelMigration dataModelMigration : new TreeSet<JackrabbitDataModelMigration>(
-                               dataModelMigrations)) {
-                       try {
-                               if (session.itemExists(dataModelMigration
-                                               .getDataModelNodePath())) {
-                                       Node dataModelNode = session.getNode(dataModelMigration
-                                                       .getDataModelNodePath());
-                                       dataModelNode.setProperty(
-                                                       ArgeoNames.ARGEO_DATA_MODEL_VERSION,
-                                                       dataModelMigration.getTargetVersion());
-                                       session.save();
-                               }
-                       } catch (Exception e) {
-                               log.error("Cannot set model version", e);
-                       }
-               }
-               JcrUtils.logoutQuietly(session);
-
-       }
-
-       /** Lazy init. */
-       protected File getHomeDirectory() {
-               try {
-                       if (homeDirectory == null) {
-                               if (inMemory) {
-                                       homeDirectory = new File(
-                                                       System.getProperty("java.io.tmpdir")
-                                                                       + File.separator
-                                                                       + System.getProperty("user.name")
-                                                                       + File.separator + "jackrabbit-"
-                                                                       + UUID.randomUUID());
-                                       homeDirectory.mkdirs();
-                                       // will it work if directory is not empty??
-                                       homeDirectory.deleteOnExit();
-                               }
-                       }
-
-                       return homeDirectory.getCanonicalFile();
-               } catch (IOException e) {
-                       throw new ArgeoException("Cannot get canonical file for "
-                                       + homeDirectory, e);
-               }
-       }
-
-       /** Shutdown the repository */
-       public void destroy() throws Exception {
-               if (repository != null && repository instanceof RepositoryImpl) {
-                       long begin = System.currentTimeMillis();
-                       ((RepositoryImpl) repository).shutdown();
-                       if (inMemory)
-                               if (getHomeDirectory().exists()) {
-                                       FileUtils.deleteDirectory(getHomeDirectory());
-                                       if (log.isDebugEnabled())
-                                               log.debug("Deleted Jackrabbit home directory "
-                                                               + getHomeDirectory());
-                               }
-                       double duration = ((double) (System.currentTimeMillis() - begin)) / 1000;
-                       log.info("Destroyed Jackrabbit repository in " + duration
-                                       + " s, home: " + getHomeDirectory() + ", config "
-                                       + configuration);
-               }
-       }
-
-       /**
-        * @deprecated explicitly declare {@link #destroy()} as destroy-method
-        *             instead.
-        */
-       public void dispose() throws Exception {
-               log.error("## Declare destroy-method=\"destroy\". in the Jackrabbit container bean");
-               destroy();
-       }
-
        /*
-        * UTILITIES
+        * DATA MODEL
         */
 
-       /** Generates the properties to use in the configuration. */
-       protected Properties getConfigurationProperties() {
-               InputStream propsIn = null;
-               Properties vars;
-               try {
-                       vars = new Properties();
-                       if (variables != null) {
-                               propsIn = variables.getInputStream();
-                               vars.load(propsIn);
-                       }
-                       // resolve system properties
-                       for (Object key : vars.keySet()) {
-                               // TODO: implement a smarter mechanism to resolve nested ${}
-                               String newValue = SystemPropertyUtils.resolvePlaceholders(vars
-                                               .getProperty(key.toString()));
-                               vars.put(key, newValue);
-                       }
-                       // override with system properties
-                       vars.putAll(System.getProperties());
-
-                       if (log.isTraceEnabled()) {
-                               log.trace("Jackrabbit config variables:");
-                               for (Object key : new TreeSet<Object>(vars.keySet()))
-                                       log.trace(key + "=" + vars.getProperty(key.toString()));
-                       }
-
-               } catch (IOException e) {
-                       throw new ArgeoException("Cannot read configuration properties", e);
-               } finally {
-                       IOUtils.closeQuietly(propsIn);
-               }
-               return vars;
-       }
-
        /**
         * Import declared node type definitions and register namespaces. Tries to
         * update the node definitions if they have changed. In case of failures an
@@ -454,54 +225,78 @@ public class JackrabbitContainer implements Repository {
 
        }
 
-       /** Find which OSGi bundle provided the data model resource */
-       protected Bundle findDataModelBundle(String resUrl) {
-               if (resUrl.startsWith("/"))
-                       resUrl = resUrl.substring(1);
-               String pkg = resUrl.substring(0, resUrl.lastIndexOf('/')).replace('/',
-                               '.');
-               ServiceReference paSr = bundleContext
-                               .getServiceReference(PackageAdmin.class.getName());
-               PackageAdmin packageAdmin = (PackageAdmin) bundleContext
-                               .getService(paSr);
+       /** Executes migrations, if needed. */
+       protected void migrate() {
+               // Remote migration not supported
+               if (isRemote())
+                       return;
 
-               // find exported package
-               ExportedPackage exportedPackage = null;
-               ExportedPackage[] exportedPackages = packageAdmin
-                               .getExportedPackages(pkg);
-               if (exportedPackages == null)
-                       throw new ArgeoException("No exported package found for " + pkg);
-               for (ExportedPackage ep : exportedPackages) {
-                       for (Bundle b : ep.getImportingBundles()) {
-                               if (b.getBundleId() == bundleContext.getBundle().getBundleId()) {
-                                       exportedPackage = ep;
-                                       break;
+               // No migration to perform
+               if (dataModelMigrations.size() == 0)
+                       return;
+
+               Boolean restartAndClearCaches = false;
+
+               // migrate data
+               Session session = null;
+               try {
+                       session = login();
+                       for (JackrabbitDataModelMigration dataModelMigration : new TreeSet<JackrabbitDataModelMigration>(
+                                       dataModelMigrations)) {
+                               if (dataModelMigration.migrate(session)) {
+                                       restartAndClearCaches = true;
                                }
                        }
+               } catch (ArgeoException e) {
+                       throw e;
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot migrate", e);
+               } finally {
+                       JcrUtils.logoutQuietly(session);
                }
 
-               Bundle exportingBundle = null;
-               if (exportedPackage != null) {
-                       exportingBundle = exportedPackage.getExportingBundle();
-               } else {
-                       throw new ArgeoException("No OSGi exporting package found for "
-                                       + resUrl);
+               // restart repository
+               if (restartAndClearCaches) {
+                       Repository repository = getRepository();
+                       if (repository instanceof RepositoryImpl) {
+                               JackrabbitDataModelMigration
+                                               .clearRepositoryCaches(((RepositoryImpl) repository)
+                                                               .getConfig());
+                       }
+                       ((JackrabbitRepository) repository).shutdown();
+                       createJackrabbitRepository();
                }
-               return exportingBundle;
-       }
 
-       /*
-        * DELEGATED JCR REPOSITORY METHODS
-        */
+               // set data model version
+               try {
+                       session = login();
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot login to migrated repository", e);
+               }
 
-       public String getDescriptor(String key) {
-               return getRepository().getDescriptor(key);
-       }
+               for (JackrabbitDataModelMigration dataModelMigration : new TreeSet<JackrabbitDataModelMigration>(
+                               dataModelMigrations)) {
+                       try {
+                               if (session.itemExists(dataModelMigration
+                                               .getDataModelNodePath())) {
+                                       Node dataModelNode = session.getNode(dataModelMigration
+                                                       .getDataModelNodePath());
+                                       dataModelNode.setProperty(
+                                                       ArgeoNames.ARGEO_DATA_MODEL_VERSION,
+                                                       dataModelMigration.getTargetVersion());
+                                       session.save();
+                               }
+                       } catch (Exception e) {
+                               log.error("Cannot set model version", e);
+                       }
+               }
+               JcrUtils.logoutQuietly(session);
 
-       public String[] getDescriptorKeys() {
-               return getRepository().getDescriptorKeys();
        }
 
+       /*
+        * REPOSITORY INTERCEPTOR
+        */
        /** Central login method */
        public Session login(Credentials credentials, String workspaceName)
                        throws LoginException, NoSuchWorkspaceException,
@@ -523,98 +318,83 @@ public class JackrabbitContainer implements Repository {
                        }
                }
 
-               Session session;
-               try {
-                       session = getRepository().login(credentials, workspaceName);
-               } catch (NoSuchWorkspaceException e) {
-                       if (autocreateWorkspaces && workspaceName != null)
-                               session = createWorkspaceAndLogsIn(credentials, workspaceName);
-                       else
-                               throw e;
-               }
-               processNewSession(session);
-               return session;
-       }
-
-       public Session login() throws LoginException, RepositoryException {
-               return login(null, null);
-       }
-
-       public Session login(Credentials credentials) throws LoginException,
-                       RepositoryException {
-               return login(credentials, null);
-       }
-
-       public Session login(String workspaceName) throws LoginException,
-                       NoSuchWorkspaceException, RepositoryException {
-               return login(null, workspaceName);
-       }
-
-       /** Called after a session has been created, does nothing by default. */
-       protected void processNewSession(Session session) {
+               return super.login(credentials, workspaceName);
        }
 
-       public Boolean isRemote() {
-               return uri != null;
-       }
+       /*
+        * UTILITIES
+        */
 
-       /** Wraps access to the repository, making sure it is available. */
-       protected Repository getRepository() {
-               if (repository == null) {
-                       throw new ArgeoException(
-                                       "No repository initialized."
-                                                       + " Was the init() method called?"
-                                                       + " The dispose() method should also be called on shutdown.");
+       @Override
+       protected InputStream readConfiguration() {
+               try {
+                       return configuration != null ? configuration.getInputStream()
+                                       : null;
+               } catch (IOException e) {
+                       throw new ArgeoException("Cannot read Jackrabbit configuration "
+                                       + configuration, e);
                }
-               return repository;
        }
 
-       /**
-        * Logs in to the default workspace, creates the required workspace, logs
-        * out, logs in to the required workspace.
-        */
-       protected Session createWorkspaceAndLogsIn(Credentials credentials,
-                       String workspaceName) throws RepositoryException {
-               if (workspaceName == null)
-                       throw new ArgeoException("No workspace specified.");
-               Session session = getRepository().login(credentials);
-               session.getWorkspace().createWorkspace(workspaceName);
-               session.logout();
-               return getRepository().login(credentials, workspaceName);
+       @Override
+       protected InputStream readVariables() {
+               try {
+                       return variables != null ? variables.getInputStream() : null;
+               } catch (IOException e) {
+                       throw new ArgeoException("Cannot read Jackrabbit variables "
+                                       + variables, e);
+               }
        }
 
-       public boolean isStandardDescriptor(String key) {
-               return getRepository().isStandardDescriptor(key);
+       @Override
+       protected String resolvePlaceholders(String string,
+                       Map<String, String> variables) {
+               return SystemPropertyUtils.resolvePlaceholders(string);
        }
 
-       public boolean isSingleValueDescriptor(String key) {
-               return getRepository().isSingleValueDescriptor(key);
-       }
+       /** Find which OSGi bundle provided the data model resource */
+       protected Bundle findDataModelBundle(String resUrl) {
+               if (resUrl.startsWith("/"))
+                       resUrl = resUrl.substring(1);
+               String pkg = resUrl.substring(0, resUrl.lastIndexOf('/')).replace('/',
+                               '.');
+               ServiceReference paSr = bundleContext
+                               .getServiceReference(PackageAdmin.class.getName());
+               PackageAdmin packageAdmin = (PackageAdmin) bundleContext
+                               .getService(paSr);
 
-       public Value getDescriptorValue(String key) {
-               return getRepository().getDescriptorValue(key);
-       }
+               // find exported package
+               ExportedPackage exportedPackage = null;
+               ExportedPackage[] exportedPackages = packageAdmin
+                               .getExportedPackages(pkg);
+               if (exportedPackages == null)
+                       throw new ArgeoException("No exported package found for " + pkg);
+               for (ExportedPackage ep : exportedPackages) {
+                       for (Bundle b : ep.getImportingBundles()) {
+                               if (b.getBundleId() == bundleContext.getBundle().getBundleId()) {
+                                       exportedPackage = ep;
+                                       break;
+                               }
+                       }
+               }
 
-       public Value[] getDescriptorValues(String key) {
-               return getRepository().getDescriptorValues(key);
+               Bundle exportingBundle = null;
+               if (exportedPackage != null) {
+                       exportingBundle = exportedPackage.getExportingBundle();
+               } else {
+                       throw new ArgeoException("No OSGi exporting package found for "
+                                       + resUrl);
+               }
+               return exportingBundle;
        }
 
        /*
         * FIELDS ACCESS
         */
-
-       public void setHomeDirectory(File homeDirectory) {
-               this.homeDirectory = homeDirectory;
-       }
-
        public void setConfiguration(Resource configuration) {
                this.configuration = configuration;
        }
 
-       public void setInMemory(Boolean inMemory) {
-               this.inMemory = inMemory;
-       }
-
        public void setNamespaces(Map<String, String> namespaces) {
                this.namespaces = namespaces;
        }
@@ -627,26 +407,10 @@ public class JackrabbitContainer implements Repository {
                this.variables = variables;
        }
 
-       public void setUri(String uri) {
-               this.uri = uri;
-       }
-
        public void setRemoteSystemCredentials(Credentials remoteSystemCredentials) {
                this.remoteSystemCredentials = remoteSystemCredentials;
        }
 
-       public void setSystemExecutor(Executor systemExecutor) {
-               throw new IllegalArgumentException(
-                               "systemExecutor is not supported anymore, use:\n"
-                                               + "<bean class=\"org.argeo.security.core.AuthenticatedApplicationContextInitialization\">\n"
-                                               + "\t<property name=\"authenticationManager\" ref=\"authenticationManager\" />\n"
-                                               + "</bean>");
-       }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
        public void setDataModelMigrations(
                        Set<JackrabbitDataModelMigration> dataModelMigrations) {
                this.dataModelMigrations = dataModelMigrations;
diff --git a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitWrapper.java b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitWrapper.java
new file mode 100644 (file)
index 0000000..0dd94bc
--- /dev/null
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2010 Mathieu Baudier <mbaudier@argeo.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.argeo.jackrabbit;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import javax.jcr.Credentials;
+import javax.jcr.LoginException;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.apache.jackrabbit.core.config.RepositoryConfigurationParser;
+import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory;
+import org.argeo.ArgeoException;
+import org.xml.sax.InputSource;
+
+/**
+ * Wrapper around a Jackrabbit repository which allows to simplify configuration
+ * and intercept some actions. It exposes itself as a {@link Repository}.
+ */
+public abstract class JackrabbitWrapper implements Repository {
+       private Log log = LogFactory.getLog(JackrabbitWrapper.class);
+
+       // remote
+       private String uri = null;
+
+       // local
+       private RepositoryConfig repositoryConfig;
+       private File homeDirectory;
+       private Boolean inMemory = false;
+
+       // wrapped repository
+       private Repository repository;
+
+       private Boolean autocreateWorkspaces = false;
+
+       /**
+        * Empty constructor, {@link #init()} should be called after properties have
+        * been set
+        */
+       public JackrabbitWrapper() {
+       }
+
+       /**
+        * Reads the configuration which will initialize a {@link RepositoryConfig}.
+        */
+       protected abstract InputStream readConfiguration();
+
+       /**
+        * Reads the variables which will initialize a {@link Properties}. Returns
+        * null by default, to be overridden.
+        * 
+        * @return a new stream or null if no variables available
+        */
+       protected InputStream readVariables() {
+               return null;
+       }
+
+       /**
+        * Resolves ${} placeholders in the provided string. Based on system
+        * properties if no map is provided.
+        */
+       protected abstract String resolvePlaceholders(String string,
+                       Map<String, String> variables);
+
+       /** Initializes */
+       public void init() {
+               long begin = System.currentTimeMillis();
+
+               if (repository != null) {
+                       // we are just wrapping another repository
+                       postInitWrapped();
+               } else {
+                       createJackrabbitRepository();
+                       postInitNew();
+               }
+
+               double duration = ((double) (System.currentTimeMillis() - begin)) / 1000;
+               log.info("Initialized Jackrabbit wrapper in " + duration + " s");
+       }
+
+       /**
+        * Called after initialization of an already existing {@link Repository}
+        * which is being wrapped (e.g. in order to impact its data model). To be
+        * overridden, does nothing by default.
+        */
+       protected void postInitWrapped() {
+
+       }
+
+       /**
+        * Called after initialization of a new {@link Repository} either local or
+        * remote. To be overridden, does nothing by default.
+        */
+       protected void postInitNew() {
+
+       }
+
+       /** Actually creates the new repository. */
+       protected void createJackrabbitRepository() {
+               long begin = System.currentTimeMillis();
+               InputStream configurationIn = null;
+               try {
+                       if (uri != null && !uri.trim().equals("")) {// remote
+                               Map<String, String> params = new HashMap<String, String>();
+                               params.put(
+                                               org.apache.jackrabbit.commons.JcrUtils.REPOSITORY_URI,
+                                               uri);
+                               repository = new Jcr2davRepositoryFactory()
+                                               .getRepository(params);
+                               if (repository == null)
+                                       throw new ArgeoException("Remote Davex repository " + uri
+                                                       + " not found");
+                               double duration = ((double) (System.currentTimeMillis() - begin)) / 1000;
+                               log.info("Created Jackrabbit repository in " + duration
+                                               + " s from URI " + uri);
+                               // we assume that the data model of the remote repository has
+                               // been properly initialized
+                       } else {// local
+                               // force uri to null in order to optimize isRemote()
+                               uri = null;
+
+                               // temporary
+                               if (inMemory && getHomeDirectory().exists()) {
+                                       FileUtils.deleteDirectory(getHomeDirectory());
+                                       log.warn("Deleted Jackrabbit home directory "
+                                                       + getHomeDirectory());
+                               }
+
+                               // process configuration file
+                               Properties vars = getConfigurationProperties();
+                               configurationIn = readConfiguration();
+                               vars.put(
+                                               RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE,
+                                               getHomeDirectory().getCanonicalPath());
+                               repositoryConfig = RepositoryConfig.create(new InputSource(
+                                               configurationIn), vars);
+
+                               //
+                               // Actual repository creation
+                               //
+                               repository = RepositoryImpl.create(repositoryConfig);
+
+                               double duration = ((double) (System.currentTimeMillis() - begin)) / 1000;
+                               if (log.isDebugEnabled())
+                                       log.debug("Created Jackrabbit repository in " + duration
+                                                       + " s, home: " + getHomeDirectory());
+                       }
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot create Jackrabbit repository "
+                                       + getHomeDirectory(), e);
+               } finally {
+                       IOUtils.closeQuietly(configurationIn);
+               }
+       }
+
+       /** Lazy init. */
+       protected File getHomeDirectory() {
+               try {
+                       if (homeDirectory == null) {
+                               if (inMemory) {
+                                       homeDirectory = new File(
+                                                       System.getProperty("java.io.tmpdir")
+                                                                       + File.separator
+                                                                       + System.getProperty("user.name")
+                                                                       + File.separator + "jackrabbit-"
+                                                                       + UUID.randomUUID());
+                                       homeDirectory.mkdirs();
+                                       // will it work if directory is not empty??
+                                       homeDirectory.deleteOnExit();
+                               }
+                       }
+
+                       return homeDirectory.getCanonicalFile();
+               } catch (IOException e) {
+                       throw new ArgeoException("Cannot get canonical file for "
+                                       + homeDirectory, e);
+               }
+       }
+
+       /** Shutdown the repository */
+       public void destroy() throws Exception {
+               if (repository != null && repository instanceof RepositoryImpl) {
+                       long begin = System.currentTimeMillis();
+                       ((RepositoryImpl) repository).shutdown();
+                       if (inMemory)
+                               if (getHomeDirectory().exists()) {
+                                       FileUtils.deleteDirectory(getHomeDirectory());
+                                       if (log.isDebugEnabled())
+                                               log.debug("Deleted Jackrabbit home directory "
+                                                               + getHomeDirectory());
+                               }
+                       double duration = ((double) (System.currentTimeMillis() - begin)) / 1000;
+                       log.info("Destroyed Jackrabbit repository in " + duration
+                                       + " s, home: " + getHomeDirectory());
+               }
+       }
+
+       /**
+        * @deprecated explicitly declare {@link #destroy()} as destroy-method
+        *             instead.
+        */
+       public void dispose() throws Exception {
+               log.error("## Declare destroy-method=\"destroy\". in the Jackrabbit container bean");
+               destroy();
+       }
+
+       /*
+        * UTILITIES
+        */
+
+       /** Generates the properties to use in the configuration. */
+       protected Properties getConfigurationProperties() {
+               InputStream propsIn = null;
+               Properties vars;
+               try {
+                       vars = new Properties();
+                       propsIn = readVariables();
+                       if (propsIn != null) {
+                               vars.load(propsIn);
+                       }
+                       // resolve system properties
+                       for (Object key : vars.keySet()) {
+                               // TODO: implement a smarter mechanism to resolve nested ${}
+                               String newValue = resolvePlaceholders(
+                                               vars.getProperty(key.toString()), null);
+                               vars.put(key, newValue);
+                       }
+                       // override with system properties
+                       vars.putAll(System.getProperties());
+
+                       if (log.isTraceEnabled()) {
+                               log.trace("Jackrabbit config variables:");
+                               for (Object key : new TreeSet<Object>(vars.keySet()))
+                                       log.trace(key + "=" + vars.getProperty(key.toString()));
+                       }
+
+               } catch (IOException e) {
+                       throw new ArgeoException("Cannot read configuration properties", e);
+               } finally {
+                       IOUtils.closeQuietly(propsIn);
+               }
+               return vars;
+       }
+
+       /*
+        * DELEGATED JCR REPOSITORY METHODS
+        */
+
+       public String getDescriptor(String key) {
+               return getRepository().getDescriptor(key);
+       }
+
+       public String[] getDescriptorKeys() {
+               return getRepository().getDescriptorKeys();
+       }
+
+       /** Central login method */
+       public Session login(Credentials credentials, String workspaceName)
+                       throws LoginException, NoSuchWorkspaceException,
+                       RepositoryException {
+               Session session;
+               try {
+                       session = getRepository().login(credentials, workspaceName);
+               } catch (NoSuchWorkspaceException e) {
+                       if (autocreateWorkspaces && workspaceName != null)
+                               session = createWorkspaceAndLogsIn(credentials, workspaceName);
+                       else
+                               throw e;
+               }
+               processNewSession(session);
+               return session;
+       }
+
+       public Session login() throws LoginException, RepositoryException {
+               return login(null, null);
+       }
+
+       public Session login(Credentials credentials) throws LoginException,
+                       RepositoryException {
+               return login(credentials, null);
+       }
+
+       public Session login(String workspaceName) throws LoginException,
+                       NoSuchWorkspaceException, RepositoryException {
+               return login(null, workspaceName);
+       }
+
+       /** Called after a session has been created, does nothing by default. */
+       protected void processNewSession(Session session) {
+       }
+
+       public Boolean isRemote() {
+               return uri != null;
+       }
+
+       /** Wraps access to the repository, making sure it is available. */
+       protected Repository getRepository() {
+               if (repository == null) {
+                       throw new ArgeoException(
+                                       "No repository initialized."
+                                                       + " Was the init() method called?"
+                                                       + " The dispose() method should also be called on shutdown.");
+               }
+               return repository;
+       }
+
+       /**
+        * Logs in to the default workspace, creates the required workspace, logs
+        * out, logs in to the required workspace.
+        */
+       protected Session createWorkspaceAndLogsIn(Credentials credentials,
+                       String workspaceName) throws RepositoryException {
+               if (workspaceName == null)
+                       throw new ArgeoException("No workspace specified.");
+               Session session = getRepository().login(credentials);
+               session.getWorkspace().createWorkspace(workspaceName);
+               session.logout();
+               return getRepository().login(credentials, workspaceName);
+       }
+
+       public boolean isStandardDescriptor(String key) {
+               return getRepository().isStandardDescriptor(key);
+       }
+
+       public boolean isSingleValueDescriptor(String key) {
+               return getRepository().isSingleValueDescriptor(key);
+       }
+
+       public Value getDescriptorValue(String key) {
+               return getRepository().getDescriptorValue(key);
+       }
+
+       public Value[] getDescriptorValues(String key) {
+               return getRepository().getDescriptorValues(key);
+       }
+
+       /*
+        * FIELDS ACCESS
+        */
+
+       public void setHomeDirectory(File homeDirectory) {
+               this.homeDirectory = homeDirectory;
+       }
+
+       public void setInMemory(Boolean inMemory) {
+               this.inMemory = inMemory;
+       }
+
+       public void setUri(String uri) {
+               this.uri = uri;
+       }
+
+       public void setRepository(Repository repository) {
+               this.repository = repository;
+       }
+
+}