X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;ds=inline;f=server%2Fruntime%2Forg.argeo.server.jackrabbit%2Fsrc%2Fmain%2Fjava%2Forg%2Fargeo%2Fjackrabbit%2FJackrabbitContainer.java;h=3b83941cfade149844b768a15dc56e1c6d4e4269;hb=ada93169e85b906c38407f02514edde1dc193503;hp=2dcb1a9cf911f0569ebd79e0d51ca6e1e0f00fa1;hpb=977f272245dd7ec958747a20e21c4bfe0a00020d;p=lgpl%2Fargeo-commons.git diff --git a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java index 2dcb1a9cf..3b83941cf 100644 --- a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java +++ b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java @@ -18,17 +18,24 @@ package org.argeo.jackrabbit; import java.io.ByteArrayInputStream; import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.Reader; import java.util.ArrayList; 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.concurrent.Executor; import javax.jcr.Credentials; import javax.jcr.LoginException; import javax.jcr.NoSuchWorkspaceException; +import javax.jcr.Node; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; @@ -39,7 +46,6 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.jackrabbit.api.JackrabbitRepository; -import org.apache.jackrabbit.commons.JcrUtils; import org.apache.jackrabbit.commons.NamespaceHelper; import org.apache.jackrabbit.commons.cnd.CndImporter; import org.apache.jackrabbit.core.RepositoryImpl; @@ -48,19 +54,19 @@ 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.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; +import org.argeo.jcr.ArgeoNames; +import org.argeo.jcr.JcrUtils; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; +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 InitializingBean, DisposableBean, - Repository, ResourceLoaderAware { +public class JackrabbitContainer implements Repository, ResourceLoaderAware { private Log log = LogFactory.getLog(JackrabbitContainer.class); private Resource configuration; @@ -70,75 +76,175 @@ public class JackrabbitContainer implements InitializingBean, DisposableBean, private Boolean inMemory = false; private String uri = null; + // wrapped repository private Repository repository; + private RepositoryConfig repositoryConfig; + // CND private ResourceLoader resourceLoader; /** Node type definitions in CND format */ - private List cnds = new ArrayList(); private List cndFiles = new ArrayList(); + /** Migrations to execute (if not already done) */ + private Set dataModelMigrations = new HashSet(); + /** Namespaces to register: key is prefix, value namespace */ private Map namespaces = new HashMap(); private Boolean autocreateWorkspaces = false; - public void afterPropertiesSet() throws Exception { - // Load cnds as resources - for (String resUrl : cndFiles) { - Resource res = resourceLoader.getResource(resUrl); - byte[] arr = IOUtils.toByteArray(res.getInputStream()); - cnds.add(arr); + private Executor systemExecutor; + + public void init() throws Exception { + if (repository != null) { + // we are just wrapping another repository + importNodeTypeDefinitions(repository); + return; } - if (uri != null && !uri.trim().equals("")) { - Map params = new HashMap(); - params.put(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); - } else { - if (inMemory && homeDirectory.exists()) { - FileUtils.deleteDirectory(homeDirectory); - log.warn("Deleted Jackrabbit home directory " + homeDirectory); + createJackrabbitRepository(); + + // migrate if needed + migrate(); + + // apply new CND files after migration + if (cndFiles != null && cndFiles.size() > 0) + importNodeTypeDefinitions(repository); + } + + /** Actually creates a new repository. */ + protected void createJackrabbitRepository() { + long begin = System.currentTimeMillis(); + try { + // remote repository + if (uri != null && !uri.trim().equals("")) { + Map params = new HashMap(); + 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); + // do not perform further initialization since we assume that + // the + // remote repository has been properly configured + return; } - RepositoryConfig config; + // local repository + if (inMemory && getHomeDirectory().exists()) { + FileUtils.deleteDirectory(getHomeDirectory()); + log.warn("Deleted Jackrabbit home directory " + + getHomeDirectory()); + } + + Properties vars = getConfigurationProperties(); InputStream in = configuration.getInputStream(); - InputStream propsIn = null; try { - Properties vars = new Properties(); - if (variables != null) { - propsIn = variables.getInputStream(); - vars.load(propsIn); - } - // override with system properties - vars.putAll(System.getProperties()); vars.put( RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE, - homeDirectory.getCanonicalPath()); - config = RepositoryConfig.create(new InputSource(in), vars); + getHomeDirectory().getCanonicalPath()); + repositoryConfig = RepositoryConfig.create(new InputSource(in), + vars); } catch (Exception e) { throw new RuntimeException("Cannot read configuration", e); } finally { IOUtils.closeQuietly(in); - IOUtils.closeQuietly(propsIn); } if (inMemory) - repository = new TransientRepository(config); + repository = new TransientRepository(repositoryConfig); else - repository = RepositoryImpl.create(config); + repository = RepositoryImpl.create(repositoryConfig); - log.info("Initialized Jackrabbit repository " + repository + " in " - + homeDirectory + " with config " + configuration); + 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); } } - public void destroy() throws Exception { + /** Executes migrations, if needed. */ + protected void migrate() { + // 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( + 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( + 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 { + return homeDirectory.getCanonicalFile(); + } catch (IOException e) { + throw new ArgeoException("Cannot get canonical file for " + + homeDirectory, e); + } + } + + public void dispose() throws Exception { + long begin = System.currentTimeMillis(); if (repository != null) { if (repository instanceof JackrabbitRepository) ((JackrabbitRepository) repository).shutdown(); @@ -149,31 +255,119 @@ public class JackrabbitContainer implements InitializingBean, DisposableBean, } if (inMemory) - if (homeDirectory.exists()) { - FileUtils.deleteDirectory(homeDirectory); + if (getHomeDirectory().exists()) { + FileUtils.deleteDirectory(getHomeDirectory()); if (log.isDebugEnabled()) log.debug("Deleted Jackrabbit home directory " - + homeDirectory); + + getHomeDirectory()); } + double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; if (uri != null && !uri.trim().equals("")) log.info("Destroyed Jackrabbit repository with uri " + uri); else - log.info("Destroyed Jackrabbit repository " + repository + " in " - + homeDirectory + " with config " + configuration); + log.info("Destroyed Jackrabbit repository in " + duration + + " s, home: " + getHomeDirectory() + ", config " + + configuration); + } + + /** + * @deprecated explicitly declare {@link #dispose()} as destroy-method + * instead. + */ + public void destroy() throws Exception { + log.error("## Declare destroy-method=\"dispose\". in the Jackrabbit container bean"); + } + + /** @deprecated explicitly declare {@link #init()} as init-method instead. */ + public void afterPropertiesSet() throws Exception { + log.error("## Declare init-method=\"init\". in the Jackrabbit container bean"); + } + + 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(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, trying to update them if they have + * changed. In case of failures an error will be logged but no exception + * will be thrown. + */ + protected void importNodeTypeDefinitions(final Repository repository) { + Runnable action = new Runnable() { + public void run() { + Reader reader = null; + Session session = null; + try { + session = repository.login(); + processNewSession(session); + // Load cnds as resources + for (String resUrl : cndFiles) { + Resource res = resourceLoader.getResource(resUrl); + byte[] arr = IOUtils.toByteArray(res.getInputStream()); + reader = new InputStreamReader( + new ByteArrayInputStream(arr)); + CndImporter.registerNodeTypes(reader, session, true); + } + session.save(); + } catch (Exception e) { + log.error( + "Cannot import node type definitions " + cndFiles, + e); + JcrUtils.discardQuietly(session); + } finally { + IOUtils.closeQuietly(reader); + JcrUtils.logoutQuietly(session); + } + } + }; + + if (systemExecutor != null) + systemExecutor.execute(action); + else + action.run(); } // JCR REPOSITORY (delegated) public String getDescriptor(String key) { - return repository.getDescriptor(key); + return getRepository().getDescriptor(key); } public String[] getDescriptorKeys() { - return repository.getDescriptorKeys(); + return getRepository().getDescriptorKeys(); } public Session login() throws LoginException, RepositoryException { - Session session = repository.login(); + Session session = getRepository().login(); processNewSession(session); return session; } @@ -183,7 +377,7 @@ public class JackrabbitContainer implements InitializingBean, DisposableBean, RepositoryException { Session session; try { - session = repository.login(credentials, workspaceName); + session = getRepository().login(credentials, workspaceName); } catch (NoSuchWorkspaceException e) { if (autocreateWorkspaces) session = createWorkspaceAndLogsIn(credentials, workspaceName); @@ -196,7 +390,7 @@ public class JackrabbitContainer implements InitializingBean, DisposableBean, public Session login(Credentials credentials) throws LoginException, RepositoryException { - Session session = repository.login(credentials); + Session session = getRepository().login(credentials); processNewSession(session); return session; } @@ -205,7 +399,7 @@ public class JackrabbitContainer implements InitializingBean, DisposableBean, NoSuchWorkspaceException, RepositoryException { Session session; try { - session = repository.login(workspaceName); + session = getRepository().login(workspaceName); } catch (NoSuchWorkspaceException e) { if (autocreateWorkspaces) session = createWorkspaceAndLogsIn(null, workspaceName); @@ -216,14 +410,21 @@ public class JackrabbitContainer implements InitializingBean, DisposableBean, return session; } + /** 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; + } + protected synchronized void processNewSession(Session session) { try { NamespaceHelper namespaceHelper = new NamespaceHelper(session); namespaceHelper.registerNamespaces(namespaces); - - for (byte[] arr : cnds) - CndImporter.registerNodeTypes(new InputStreamReader( - new ByteArrayInputStream(arr)), session, true); } catch (Exception e) { throw new ArgeoException("Cannot process new session", e); } @@ -237,10 +438,10 @@ public class JackrabbitContainer implements InitializingBean, DisposableBean, String workspaceName) throws RepositoryException { if (workspaceName == null) throw new ArgeoException("No workspace specified."); - Session session = repository.login(credentials); + Session session = getRepository().login(credentials); session.getWorkspace().createWorkspace(workspaceName); session.logout(); - return repository.login(credentials, workspaceName); + return getRepository().login(credentials, workspaceName); } public void setResourceLoader(ResourceLoader resourceLoader) { @@ -248,19 +449,19 @@ public class JackrabbitContainer implements InitializingBean, DisposableBean, } public boolean isStandardDescriptor(String key) { - return repository.isStandardDescriptor(key); + return getRepository().isStandardDescriptor(key); } public boolean isSingleValueDescriptor(String key) { - return repository.isSingleValueDescriptor(key); + return getRepository().isSingleValueDescriptor(key); } public Value getDescriptorValue(String key) { - return repository.getDescriptorValue(key); + return getRepository().getDescriptorValue(key); } public Value[] getDescriptorValues(String key) { - return repository.getDescriptorValues(key); + return getRepository().getDescriptorValues(key); } // BEANS METHODS @@ -292,4 +493,17 @@ public class JackrabbitContainer implements InitializingBean, DisposableBean, this.uri = uri; } + public void setSystemExecutor(Executor systemExecutor) { + this.systemExecutor = systemExecutor; + } + + public void setRepository(Repository repository) { + this.repository = repository; + } + + public void setDataModelMigrations( + Set dataModelMigrations) { + this.dataModelMigrations = dataModelMigrations; + } + }