/*
- * Copyright (C) 2010 Mathieu Baudier <mbaudier@argeo.org>
+ * Copyright (C) 2007-2012 Argeo GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
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.List;
+import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
-import java.util.concurrent.Executor;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.UUID;
-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;
-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.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.TransientRepository;
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;
-import org.springframework.beans.factory.DisposableBean;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.context.ResourceLoaderAware;
+import org.argeo.jcr.MaintainedRepository;
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 {
- private Log log = LogFactory.getLog(JackrabbitContainer.class);
+public class JackrabbitContainer extends JackrabbitWrapper implements
+ MaintainedRepository {
+ private final static Log log = LogFactory.getLog(JackrabbitContainer.class);
+ // local
private Resource configuration;
- private File homeDirectory;
private Resource variables;
-
+ private RepositoryConfig repositoryConfig;
+ private File homeDirectory;
private Boolean inMemory = false;
- private String uri = null;
-
- private Repository repository;
- private ResourceLoader resourceLoader;
+ /** Migrations to execute (if not already done) */
+ private Set<JackrabbitDataModelMigration> dataModelMigrations = new HashSet<JackrabbitDataModelMigration>();
- /** Node type definitions in CND format */
- private List<String> cndFiles = new ArrayList<String>();
+ /**
+ * Empty constructor, {@link #init()} should be called after properties have
+ * been set
+ */
+ public JackrabbitContainer() {
+ }
- /** Namespaces to register: key is prefix, value namespace */
- private Map<String, String> namespaces = new HashMap<String, String>();
+ public void init() {
+ long begin = System.currentTimeMillis();
- private Boolean autocreateWorkspaces = false;
+ if (getRepository() != null)
+ throw new ArgeoException(
+ "Cannot be used to wrap another repository");
+ Repository repository = createJackrabbitRepository();
+ super.setRepository(repository);
- private Executor systemExecutor;
- private Credentials adminCredentials;
+ // migrate if needed
+ migrate();
- public void afterPropertiesSet() throws Exception {
- // remote repository
- if (uri != null && !uri.trim().equals("")) {
- 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);
- // do not perform further initialization since we assume that the
- // remote repository has been properly configured
- return;
- }
+ // apply new CND files after migration
+ prepareDataModel();
- // local repository
- if (inMemory && homeDirectory.exists()) {
- FileUtils.deleteDirectory(homeDirectory);
- log.warn("Deleted Jackrabbit home directory " + homeDirectory);
- }
+ double duration = ((double) (System.currentTimeMillis() - begin)) / 1000;
+ if (log.isDebugEnabled())
+ log.debug("Initialized JCR repository wrapper in " + duration
+ + " s");
+ }
- RepositoryConfig config;
- InputStream in = configuration.getInputStream();
- InputStream propsIn = null;
+ /** Actually creates the new repository. */
+ protected Repository createJackrabbitRepository() {
+ long begin = System.currentTimeMillis();
+ InputStream configurationIn = null;
+ Repository repository;
try {
- Properties vars = new Properties();
- if (variables != null) {
- propsIn = variables.getInputStream();
- vars.load(propsIn);
+ // temporary
+ if (inMemory && getHomeDirectory().exists()) {
+ FileUtils.deleteDirectory(getHomeDirectory());
+ log.warn("Deleted Jackrabbit home directory "
+ + getHomeDirectory());
}
- // override with system properties
- vars.putAll(System.getProperties());
+
+ // process configuration file
+ Properties vars = getConfigurationProperties();
+ configurationIn = readConfiguration();
vars.put(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE,
- homeDirectory.getCanonicalPath());
- config = RepositoryConfig.create(new InputSource(in), vars);
+ 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.isTraceEnabled())
+ log.trace("Created Jackrabbit repository in " + duration
+ + " s, home: " + getHomeDirectory());
+
+ return repository;
} catch (Exception e) {
- throw new RuntimeException("Cannot read configuration", e);
+ throw new ArgeoException("Cannot create Jackrabbit repository "
+ + getHomeDirectory(), e);
} finally {
- IOUtils.closeQuietly(in);
- IOUtils.closeQuietly(propsIn);
+ IOUtils.closeQuietly(configurationIn);
}
+ }
- if (inMemory)
- repository = new TransientRepository(config);
- else
- repository = RepositoryImpl.create(config);
-
- if (cndFiles != null && cndFiles.size() > 0)
- importNodeTypeDefinitions(repository);
+ /** 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();
+ }
+ }
- log.info("Initialized Jackrabbit repository " + repository + " in "
- + homeDirectory + " with config " + configuration);
+ return homeDirectory.getCanonicalFile();
+ } catch (IOException e) {
+ throw new ArgeoException("Cannot get canonical file for "
+ + homeDirectory, e);
+ }
}
- /**
- * 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) {
- final Credentials credentialsToUse;
- if (systemExecutor == null) {
- if (adminCredentials == null)
- throw new ArgeoException(
- "No system executor or admin credentials found");
- credentialsToUse = adminCredentials;
- } else {
- credentialsToUse = null;
- }
+ /** Executes migrations, if needed. */
+ protected void migrate() {
+ // No migration to perform
+ if (dataModelMigrations.size() == 0)
+ return;
- Runnable action = new Runnable() {
- public void run() {
- Reader reader = null;
- Session session = null;
- try {
- session = repository.login(credentialsToUse);
- 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);
+ Boolean restartAndClearCaches = false;
+
+ // migrate data
+ Session session = null;
+ try {
+ session = login();
+ for (JackrabbitDataModelMigration dataModelMigration : new TreeSet<JackrabbitDataModelMigration>(
+ dataModelMigrations)) {
+ if (dataModelMigration.migrate(session)) {
+ restartAndClearCaches = true;
}
}
- };
-
- if (systemExecutor != null)
- systemExecutor.execute(action);
- else
- action.run();
- }
-
- public void destroy() throws Exception {
- if (repository != null) {
- if (repository instanceof JackrabbitRepository)
- ((JackrabbitRepository) repository).shutdown();
- else if (repository instanceof RepositoryImpl)
- ((RepositoryImpl) repository).shutdown();
- else if (repository instanceof TransientRepository)
- ((TransientRepository) repository).shutdown();
+ } catch (ArgeoException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ArgeoException("Cannot migrate", e);
+ } finally {
+ JcrUtils.logoutQuietly(session);
}
- if (inMemory)
- if (homeDirectory.exists()) {
- FileUtils.deleteDirectory(homeDirectory);
- if (log.isDebugEnabled())
- log.debug("Deleted Jackrabbit home directory "
- + homeDirectory);
+ // restart repository
+ if (restartAndClearCaches) {
+ Repository repository = getRepository();
+ if (repository instanceof RepositoryImpl) {
+ JackrabbitDataModelMigration
+ .clearRepositoryCaches(((RepositoryImpl) repository)
+ .getConfig());
}
+ ((JackrabbitRepository) repository).shutdown();
+ createJackrabbitRepository();
+ }
- 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);
- }
-
- // JCR REPOSITORY (delegated)
- public String getDescriptor(String key) {
- return repository.getDescriptor(key);
- }
+ // set data model version
+ try {
+ session = login();
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot login to migrated repository", e);
+ }
- public String[] getDescriptorKeys() {
- return repository.getDescriptorKeys();
- }
+ 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 Session login() throws LoginException, RepositoryException {
- Session session = repository.login();
- processNewSession(session);
- return session;
}
- public Session login(Credentials credentials, String workspaceName)
- throws LoginException, NoSuchWorkspaceException,
- RepositoryException {
- Session session;
- try {
- session = repository.login(credentials, workspaceName);
- } catch (NoSuchWorkspaceException e) {
- if (autocreateWorkspaces)
- session = createWorkspaceAndLogsIn(credentials, workspaceName);
- else
- throw e;
+ /** Shutdown the repository */
+ public void destroy() throws Exception {
+ Repository repository = getRepository();
+ 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());
}
- processNewSession(session);
- return session;
+ repository = null;
}
- public Session login(Credentials credentials) throws LoginException,
- RepositoryException {
- Session session = repository.login(credentials);
- processNewSession(session);
- return session;
+ public void dispose() {
+ throw new IllegalArgumentException(
+ "Call destroy() method instead of dispose()");
}
- public Session login(String workspaceName) throws LoginException,
- NoSuchWorkspaceException, RepositoryException {
- Session session;
+ /*
+ * UTILITIES
+ */
+ /**
+ * Reads the configuration which will initialize a {@link RepositoryConfig}.
+ */
+ protected InputStream readConfiguration() {
try {
- session = repository.login(workspaceName);
- } catch (NoSuchWorkspaceException e) {
- if (autocreateWorkspaces)
- session = createWorkspaceAndLogsIn(null, workspaceName);
- else
- throw e;
+ return configuration != null ? configuration.getInputStream()
+ : null;
+ } catch (IOException e) {
+ throw new ArgeoException("Cannot read Jackrabbit configuration "
+ + configuration, e);
}
- processNewSession(session);
- return session;
}
- protected synchronized void processNewSession(Session session) {
+ /**
+ * 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() {
try {
- NamespaceHelper namespaceHelper = new NamespaceHelper(session);
- namespaceHelper.registerNamespaces(namespaces);
-
- } catch (Exception e) {
- throw new ArgeoException("Cannot process new session", e);
+ return variables != null ? variables.getInputStream() : null;
+ } catch (IOException e) {
+ throw new ArgeoException("Cannot read Jackrabbit variables "
+ + variables, e);
}
}
/**
- * Logs in to the default workspace, creates the required workspace, logs
- * out, logs in to the required workspace.
+ * Resolves ${} placeholders in the provided string. Based on system
+ * properties if no map is provided.
*/
- protected Session createWorkspaceAndLogsIn(Credentials credentials,
- String workspaceName) throws RepositoryException {
- if (workspaceName == null)
- throw new ArgeoException("No workspace specified.");
- Session session = repository.login(credentials);
- session.getWorkspace().createWorkspace(workspaceName);
- session.logout();
- return repository.login(credentials, workspaceName);
+ protected String resolvePlaceholders(String string,
+ Map<String, String> variables) {
+ return SystemPropertyUtils.resolvePlaceholders(string);
}
- public void setResourceLoader(ResourceLoader resourceLoader) {
- this.resourceLoader = resourceLoader;
- }
-
- public boolean isStandardDescriptor(String key) {
- return repository.isStandardDescriptor(key);
- }
+ /** 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());
- public boolean isSingleValueDescriptor(String key) {
- return repository.isSingleValueDescriptor(key);
- }
+ if (log.isTraceEnabled()) {
+ log.trace("Jackrabbit config variables:");
+ for (Object key : new TreeSet<Object>(vars.keySet()))
+ log.trace(key + "=" + vars.getProperty(key.toString()));
+ }
- public Value getDescriptorValue(String key) {
- return repository.getDescriptorValue(key);
+ } catch (IOException e) {
+ throw new ArgeoException("Cannot read configuration properties", e);
+ } finally {
+ IOUtils.closeQuietly(propsIn);
+ }
+ return vars;
}
- public Value[] getDescriptorValues(String key) {
- return repository.getDescriptorValues(key);
- }
+ /*
+ * FIELDS ACCESS
+ */
- // BEANS METHODS
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;
+ public void setRepository(Repository repository) {
+ throw new ArgeoException("Cannot be used to wrap another repository");
}
- public void setCndFiles(List<String> cndFiles) {
- this.cndFiles = cndFiles;
+ public void setDataModelMigrations(
+ Set<JackrabbitDataModelMigration> dataModelMigrations) {
+ this.dataModelMigrations = dataModelMigrations;
}
public void setVariables(Resource variables) {
this.variables = variables;
}
- public void setUri(String uri) {
- this.uri = uri;
- }
-
- public void setSystemExecutor(Executor systemExecutor) {
- this.systemExecutor = systemExecutor;
- }
-
- public void setAdminCredentials(Credentials adminCredentials) {
- this.adminCredentials = adminCredentials;
+ public void setConfiguration(Resource configuration) {
+ this.configuration = configuration;
}
}