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.UUID;
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;
+import javax.jcr.SimpleCredentials;
import javax.jcr.Value;
import org.apache.commons.io.FileUtils;
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.argeo.security.SystemAuthentication;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
+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 InitializingBean, DisposableBean,
- Repository, ResourceLoaderAware {
+public class JackrabbitContainer implements Repository, ResourceLoaderAware {
private Log log = LogFactory.getLog(JackrabbitContainer.class);
private Resource configuration;
private Resource variables;
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<String> cndFiles = new ArrayList<String>();
+ /** Migrations to execute (if not already done) */
+ private Set<JackrabbitDataModelMigration> dataModelMigrations = new HashSet<JackrabbitDataModelMigration>();
+
/** Namespaces to register: key is prefix, value namespace */
private Map<String, String> namespaces = new HashMap<String, String>();
private Boolean autocreateWorkspaces = false;
private Executor systemExecutor;
- private Credentials adminCredentials;
- 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
+ // remote
+ private String uri = null;
+ private Credentials remoteSystemCredentials = null;
+
+ /**
+ * Empty constructor, {@link #init()} should be called after properties have
+ * been set
+ */
+ public JackrabbitContainer() {
+ }
+
+ /**
+ * Convenience constructor for remote, {@link #init()} is called in the
+ * constructor.
+ */
+ public JackrabbitContainer(String uri, Credentials remoteSystemCredentials) {
+ setUri(uri);
+ setRemoteSystemCredentials(remoteSystemCredentials);
+ init();
+ }
+
+ public void init() {
+ if (repository != null) {
+ // we are just wrapping another repository
+ importNodeTypeDefinitions();
return;
}
- // local repository
- 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();
+ }
+
+ /** Actually creates a new repository. */
+ protected void createJackrabbitRepository() {
+ long begin = System.currentTimeMillis();
+ try {
+ // 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;
+ } else {
+ // reset uri to null in order to optimize isRemote()
+ uri = null;
+ }
+
+ // local repository
+ if (inMemory && getHomeDirectory().exists()) {
+ FileUtils.deleteDirectory(getHomeDirectory());
+ log.warn("Deleted Jackrabbit home directory "
+ + getHomeDirectory());
+ }
+
+ Properties vars = getConfigurationProperties();
+ InputStream in = configuration.getInputStream();
+ try {
+ vars.put(
+ RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE,
+ getHomeDirectory().getCanonicalPath());
+ repositoryConfig = RepositoryConfig.create(new InputSource(in),
+ vars);
+ } catch (Exception e) {
+ throw new RuntimeException("Cannot read configuration", e);
+ } finally {
+ IOUtils.closeQuietly(in);
+ }
+
+ if (inMemory)
+ repository = new TransientRepository(repositoryConfig);
+ else
+ 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);
+ }
+ }
+
+ /** 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<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);
}
- RepositoryConfig config;
- InputStream in = configuration.getInputStream();
+ 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);
+ }
+ }
+
+ public void dispose() throws Exception {
+ long begin = System.currentTimeMillis();
+ 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();
+ }
+
+ 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;
+ if (uri != null && !uri.trim().equals(""))
+ log.info("Destroyed Jackrabbit repository with uri " + uri);
+ else
+ 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 {
- Properties vars = new Properties();
+ 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());
- vars.put(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE,
- homeDirectory.getCanonicalPath());
- config = RepositoryConfig.create(new InputSource(in), vars);
- } catch (Exception e) {
- throw new RuntimeException("Cannot read configuration", e);
+
+ 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(in);
IOUtils.closeQuietly(propsIn);
}
-
- if (inMemory)
- repository = new TransientRepository(config);
- else
- repository = RepositoryImpl.create(config);
-
- if (cndFiles != null && cndFiles.size() > 0)
- importNodeTypeDefinitions(repository);
-
- log.info("Initialized Jackrabbit repository " + repository + " in "
- + homeDirectory + " with config " + configuration);
+ return vars;
}
/**
* 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;
- }
+ protected void importNodeTypeDefinitions() {
+ // importing node def on remote si currently not supported
+ if (isRemote())
+ return;
Runnable action = new Runnable() {
public void run() {
Reader reader = null;
Session session = null;
try {
- session = repository.login(credentialsToUse);
- processNewSession(session);
+ session = login();
+ // processNewSession(session);
// Load cnds as resources
for (String resUrl : cndFiles) {
Resource res = resourceLoader.getResource(resUrl);
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();
- }
-
- if (inMemory)
- if (homeDirectory.exists()) {
- FileUtils.deleteDirectory(homeDirectory);
- if (log.isDebugEnabled())
- log.debug("Deleted Jackrabbit home directory "
- + homeDirectory);
- }
-
- 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);
+ return getRepository().getDescriptor(key);
}
public String[] getDescriptorKeys() {
- return repository.getDescriptorKeys();
- }
-
- public Session login() throws LoginException, RepositoryException {
- Session session = repository.login();
- processNewSession(session);
- return session;
+ return getRepository().getDescriptorKeys();
}
+ /** Central login method */
public Session login(Credentials credentials, String workspaceName)
throws LoginException, NoSuchWorkspaceException,
RepositoryException {
+
+ // retrieve credentials for remote
+ if (credentials == null && isRemote()) {
+ Authentication authentication = SecurityContextHolder.getContext()
+ .getAuthentication();
+ if (authentication != null) {
+ if (authentication instanceof UsernamePasswordAuthenticationToken) {
+ UsernamePasswordAuthenticationToken upat = (UsernamePasswordAuthenticationToken) authentication;
+ credentials = new SimpleCredentials(upat.getName(), upat
+ .getCredentials().toString().toCharArray());
+ } else if ((authentication instanceof SystemAuthentication)
+ && remoteSystemCredentials != null) {
+ credentials = remoteSystemCredentials;
+ }
+ }
+ }
+
Session session;
try {
- session = repository.login(credentials, workspaceName);
+ session = getRepository().login(credentials, workspaceName);
} catch (NoSuchWorkspaceException e) {
- if (autocreateWorkspaces)
+ if (autocreateWorkspaces && workspaceName != null)
session = createWorkspaceAndLogsIn(credentials, workspaceName);
else
throw e;
return session;
}
+ public Session login() throws LoginException, RepositoryException {
+ return login(null, null);
+ }
+
public Session login(Credentials credentials) throws LoginException,
RepositoryException {
- Session session = repository.login(credentials);
- processNewSession(session);
- return session;
+ return login(credentials, null);
}
public Session login(String workspaceName) throws LoginException,
NoSuchWorkspaceException, RepositoryException {
- Session session;
- try {
- session = repository.login(workspaceName);
- } catch (NoSuchWorkspaceException e) {
- if (autocreateWorkspaces)
- session = createWorkspaceAndLogsIn(null, workspaceName);
- else
- throw e;
+ return login(null, workspaceName);
+ }
+
+ 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.");
}
- processNewSession(session);
- return session;
+ return repository;
}
protected synchronized void processNewSession(Session session) {
try {
NamespaceHelper namespaceHelper = new NamespaceHelper(session);
namespaceHelper.registerNamespaces(namespaces);
-
} catch (Exception e) {
throw new ArgeoException("Cannot process new session", e);
}
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) {
}
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
this.uri = uri;
}
+ public void setRemoteSystemCredentials(Credentials remoteSystemCredentials) {
+ this.remoteSystemCredentials = remoteSystemCredentials;
+ }
+
public void setSystemExecutor(Executor systemExecutor) {
this.systemExecutor = systemExecutor;
}
- public void setAdminCredentials(Credentials adminCredentials) {
- this.adminCredentials = adminCredentials;
+ public void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+ public void setDataModelMigrations(
+ Set<JackrabbitDataModelMigration> dataModelMigrations) {
+ this.dataModelMigrations = dataModelMigrations;
}
}