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.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
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.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.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.context.ResourceLoaderAware;
+import org.argeo.security.SystemAuthentication;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.packageadmin.ExportedPackage;
+import org.osgi.service.packageadmin.PackageAdmin;
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 Repository, ResourceLoaderAware {
+public class JackrabbitContainer implements Repository {
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;
- private String uri = null;
// wrapped repository
private Repository repository;
- private RepositoryConfig repositoryConfig;
-
- // CND
- private ResourceLoader resourceLoader;
+ // data model
/** Node type definitions in CND format */
private List<String> cndFiles = new ArrayList<String>();
private Boolean autocreateWorkspaces = false;
- private Executor systemExecutor;
+ private BundleContext bundleContext;
+
+ /**
+ * 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() throws Exception {
+ /** Initializes */
+ public void init() {
if (repository != null) {
// we are just wrapping another repository
- importNodeTypeDefinitions(repository);
+ prepareDataModel();
return;
}
createJackrabbitRepository();
-
// migrate if needed
migrate();
// apply new CND files after migration
if (cndFiles != null && cndFiles.size() > 0)
- importNodeTypeDefinitions(repository);
+ prepareDataModel();
}
- /** Actually creates a new repository. */
+ /** Actually creates the new repository. */
protected void createJackrabbitRepository() {
long begin = System.currentTimeMillis();
+ InputStream configurationIn = null;
try {
- // remote repository
- if (uri != null && !uri.trim().equals("")) {
+ if (uri != null && !uri.trim().equals("")) {// remote
Map<String, String> params = new HashMap<String, String>();
params.put(
org.apache.jackrabbit.commons.JcrUtils.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;
- }
-
- // local repository
- if (inMemory && getHomeDirectory().exists()) {
- FileUtils.deleteDirectory(getHomeDirectory());
- log.warn("Deleted Jackrabbit home directory "
- + getHomeDirectory());
- }
+ // 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());
+ }
- Properties vars = getConfigurationProperties();
- InputStream in = configuration.getInputStream();
- try {
+ // process configuration file
+ Properties vars = getConfigurationProperties();
+ configurationIn = configuration.getInputStream();
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);
- }
+ repositoryConfig = RepositoryConfig.create(new InputSource(
+ configurationIn), vars);
- if (inMemory)
- repository = new TransientRepository(repositoryConfig);
- else
+ //
+ // 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);
+ 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;
/** 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 "
}
}
- 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
+ /** 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 #dispose()} as destroy-method
+ * @deprecated explicitly declare {@link #destroy()} as destroy-method
* instead.
*/
- public void destroy() throws Exception {
- log.error("## Declare destroy-method=\"dispose\". in the Jackrabbit container bean");
+ public void dispose() throws Exception {
+ log.error("## Declare destroy-method=\"destroy\". in the Jackrabbit container bean");
+ destroy();
}
- /** @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");
- }
+ /*
+ * UTILITIES
+ */
+ /** Generates the properties to use in the configuration. */
protected Properties getConfigurationProperties() {
InputStream propsIn = null;
Properties 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.
+ * Import declared node type definitions and register namespaces. Tries to
+ * update the node definitions 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() {
+ protected void prepareDataModel() {
+ // importing node def on remote si currently not supported
+ if (isRemote())
+ return;
+
+ Session session = null;
+ try {
+ session = login();
+ // register namespaces
+ if (namespaces.size() > 0) {
+ NamespaceHelper namespaceHelper = new NamespaceHelper(session);
+ namespaceHelper.registerNamespaces(namespaces);
+ }
+ // load CND files from classpath or as URL
+ for (String resUrl : cndFiles) {
+ boolean classpath;
+ // if (resUrl.startsWith("classpath:")) {
+ // // resUrl = resUrl.substring("classpath:".length());
+ // classpath = true;
+ // } else if (resUrl.indexOf(':') < 0) {
+ // if (!resUrl.startsWith("/")) {
+ // resUrl = "/" + resUrl;
+ // log.warn("Classpath should start with '/'");
+ // }
+ // resUrl = "classpath:" + resUrl;
+ // classpath = true;
+ // } else {
+ // classpath = false;
+ // }
+
+ if (resUrl.startsWith("classpath:")) {
+ resUrl = resUrl.substring("classpath:".length());
+ classpath = true;
+ } else if (resUrl.indexOf(':') < 0) {
+ if (!resUrl.startsWith("/")) {
+ resUrl = "/" + resUrl;
+ log.warn("Classpath should start with '/'");
+ }
+ // resUrl = "classpath:" + resUrl;
+ classpath = true;
+ } else {
+ classpath = false;
+ }
+
+ // Resource resource =
+ // resourceLoader.getResource(resUrl);
+
+ // = classpath ? new ClassPathResource(resUrl) : new
+ // UrlResource(resUrl);
+
+ URL url;
+ Bundle dataModelBundle = null;
+ if (classpath) {
+ if (bundleContext != null) {
+ Bundle currentBundle = bundleContext.getBundle();
+ url = currentBundle.getResource(resUrl);
+ if (url != null) {// found
+ dataModelBundle = findDataModelBundle(resUrl);
+ }
+ } else {
+ url = getClass().getClassLoader().getResource(resUrl);
+ }
+ if (url == null)
+ throw new ArgeoException("No " + resUrl
+ + " in the classpath,"
+ + " make sure the containing"
+ + " package is visible.");
+
+ } else {
+ url = new URL(resUrl);
+ }
+
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);
+ reader = new InputStreamReader(url.openStream());
+ CndImporter.registerNodeTypes(reader, session, true);
} finally {
IOUtils.closeQuietly(reader);
- JcrUtils.logoutQuietly(session);
}
+
+ if (log.isDebugEnabled())
+ log.debug("Data model "
+ + resUrl
+ + (dataModelBundle != null ? ", version "
+ + dataModelBundle.getVersion()
+ + ", bundle "
+ + dataModelBundle.getSymbolicName() : ""));
}
- };
+ } catch (Exception e) {
+ JcrUtils.discardQuietly(session);
+ throw new ArgeoException("Cannot import node type definitions "
+ + cndFiles, e);
+ } finally {
+ JcrUtils.logoutQuietly(session);
+ }
- if (systemExecutor != null)
- systemExecutor.execute(action);
- else
- action.run();
}
- // JCR REPOSITORY (delegated)
+ /** 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);
+
+ // 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;
+ }
+ }
+ }
+
+ Bundle exportingBundle = null;
+ if (exportedPackage != null) {
+ exportingBundle = exportedPackage.getExportingBundle();
+ } else {
+ throw new ArgeoException("No OSGi exporting package found for "
+ + resUrl);
+ }
+ return exportingBundle;
+ }
+
+ /*
+ * DELEGATED JCR REPOSITORY METHODS
+ */
+
public String getDescriptor(String key) {
return getRepository().getDescriptor(key);
}
return getRepository().getDescriptorKeys();
}
- public Session login() throws LoginException, RepositoryException {
- Session session = getRepository().login();
- processNewSession(session);
- return session;
- }
-
+ /** 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 = 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 = getRepository().login(credentials);
- processNewSession(session);
- return session;
+ return login(credentials, null);
}
public Session login(String workspaceName) throws LoginException,
NoSuchWorkspaceException, RepositoryException {
- Session session;
- try {
- session = getRepository().login(workspaceName);
- } catch (NoSuchWorkspaceException e) {
- if (autocreateWorkspaces)
- session = createWorkspaceAndLogsIn(null, workspaceName);
- else
- throw e;
- }
- processNewSession(session);
- return session;
+ 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. */
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);
- }
- }
-
/**
* Logs in to the default workspace, creates the required workspace, logs
* out, logs in to the required workspace.
return getRepository().login(credentials, workspaceName);
}
- public void setResourceLoader(ResourceLoader resourceLoader) {
- this.resourceLoader = resourceLoader;
- }
-
public boolean isStandardDescriptor(String key) {
return getRepository().isStandardDescriptor(key);
}
return getRepository().getDescriptorValues(key);
}
- // BEANS METHODS
+ /*
+ * FIELDS ACCESS
+ */
+
public void setHomeDirectory(File homeDirectory) {
this.homeDirectory = homeDirectory;
}
this.uri = uri;
}
+ public void setRemoteSystemCredentials(Credentials remoteSystemCredentials) {
+ this.remoteSystemCredentials = remoteSystemCredentials;
+ }
+
public void setSystemExecutor(Executor systemExecutor) {
- this.systemExecutor = 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.dataModelMigrations = dataModelMigrations;
}
+ public void setBundleContext(BundleContext bundleContext) {
+ this.bundleContext = bundleContext;
+ }
}