X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=server%2Fruntime%2Forg.argeo.server.jackrabbit%2Fsrc%2Fmain%2Fjava%2Forg%2Fargeo%2Fjackrabbit%2FJackrabbitContainer.java;h=c8d18698f9c2ddbc546299edf8d1eb473ed25c7c;hb=1d5afdce3e91054f07ddd3c98309c363b4cf1d46;hp=eb090514f425aa8f3ead1045ec072e78b38a7eda;hpb=149023e5969377045847bbecf24b0898b18a67a9;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 eb090514f..c8d18698f 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Mathieu Baudier + * Copyright (C) 2007-2012 Mathieu Baudier * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,31 +13,32 @@ * 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.net.URL; 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.concurrent.Executor; +import java.util.Set; +import java.util.TreeSet; import javax.jcr.Credentials; import javax.jcr.LoginException; import javax.jcr.NoSuchWorkspaceException; +import javax.jcr.Node; +import javax.jcr.NodeIterator; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; -import javax.jcr.Value; +import javax.jcr.SimpleCredentials; -import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -45,309 +46,407 @@ 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.ArgeoJcrConstants; +import org.argeo.jcr.ArgeoNames; +import org.argeo.jcr.ArgeoTypes; 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.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 InitializingBean, DisposableBean, - Repository, ResourceLoaderAware { +public class JackrabbitContainer extends JackrabbitWrapper { private Log log = LogFactory.getLog(JackrabbitContainer.class); + // remote + private Credentials remoteSystemCredentials = null; + + // local private Resource configuration; - private File homeDirectory; private Resource variables; - private Boolean inMemory = false; - private String uri = null; - - private Repository repository; - - private ResourceLoader resourceLoader; - + // data model /** Node type definitions in CND format */ private List cndFiles = new ArrayList(); + /** + * Always import CNDs. Useful during development of new data models. In + * production, explicit migration processes should be used. + */ + private Boolean forceCndImport = false; + + /** 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; - - private Executor systemExecutor; - private Credentials adminCredentials; - - public void afterPropertiesSet() throws Exception { - // 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; - } + private BundleContext bundleContext; - // local repository - if (inMemory && homeDirectory.exists()) { - FileUtils.deleteDirectory(homeDirectory); - log.warn("Deleted Jackrabbit home directory " + homeDirectory); - } + /** + * Empty constructor, {@link #init()} should be called after properties have + * been set + */ + public JackrabbitContainer() { + } - RepositoryConfig config; - Properties vars = getConfigurationProperties(); - InputStream in = configuration.getInputStream(); - try { - 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); - } finally { - IOUtils.closeQuietly(in); - } + /** + * Convenience constructor for remote, {@link #init()} is called in the + * constructor. + */ + public JackrabbitContainer(String uri, Credentials remoteSystemCredentials) { + setUri(uri); + setRemoteSystemCredentials(remoteSystemCredentials); + init(); + } - if (inMemory) - repository = new TransientRepository(config); - else - repository = RepositoryImpl.create(config); + @Override + protected void postInitWrapped() { + prepareDataModel(); + } - if (cndFiles != null && cndFiles.size() > 0) - importNodeTypeDefinitions(repository); + @Override + protected void postInitNew() { + // migrate if needed + migrate(); - log.info("Initialized Jackrabbit repository " + repository + " in " - + homeDirectory + " with config " + configuration); + // apply new CND files after migration + if (cndFiles != null && cndFiles.size() > 0) + prepareDataModel(); } - 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()); - } catch (IOException e) { - throw new ArgeoException("Cannot read configuration properties", e); - } finally { - IOUtils.closeQuietly(propsIn); - } - return vars; - } + /* + * DATA MODEL + */ /** - * 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) { - 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 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; + // normalize URL + 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; + } + + 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); + } + + // check existing data model nodes + new NamespaceHelper(session).registerNamespace( + ArgeoNames.ARGEO, ArgeoNames.ARGEO_NAMESPACE); + if (!session + .itemExists(ArgeoJcrConstants.DATA_MODELS_BASE_PATH)) + JcrUtils.mkdirs(session, + ArgeoJcrConstants.DATA_MODELS_BASE_PATH); + Node dataModels = session + .getNode(ArgeoJcrConstants.DATA_MODELS_BASE_PATH); + NodeIterator it = dataModels.getNodes(); + Node dataModel = null; + while (it.hasNext()) { + Node node = it.nextNode(); + if (node.getProperty(ArgeoNames.ARGEO_URI).getString() + .equals(resUrl)) { + dataModel = node; + break; + } + } + + // does nothing if data model already registered + if (dataModel != null && !forceCndImport) { + if (dataModelBundle != null) { + String version = dataModel.getProperty( + ArgeoNames.ARGEO_DATA_MODEL_VERSION) + .getString(); + String dataModelBundleVersion = dataModelBundle + .getVersion().toString(); + if (!version.equals(dataModelBundleVersion)) { + log.warn("Data model with version " + + dataModelBundleVersion + + " available, current version is " + + version); + } + } + // do not implicitly update + 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); + reader = new InputStreamReader(url.openStream()); + // actually imports the CND + CndImporter.registerNodeTypes(reader, session, true); + + // FIXME: what if argeo.cnd would not be the first called on + // a new repo? argeo:dataModel would not be found + String fileName = FilenameUtils.getName(url.getPath()); + if (dataModel == null) { + dataModel = dataModels.addNode(fileName); + dataModel.addMixin(ArgeoTypes.ARGEO_DATA_MODEL); + dataModel.setProperty(ArgeoNames.ARGEO_URI, resUrl); + } else { + session.getWorkspace().getVersionManager() + .checkout(dataModel.getPath()); } + if (dataModelBundle != null) + dataModel.setProperty( + ArgeoNames.ARGEO_DATA_MODEL_VERSION, + dataModelBundle.getVersion().toString()); + else + dataModel.setProperty( + ArgeoNames.ARGEO_DATA_MODEL_VERSION, "0.0.0"); + JcrUtils.updateLastModified(dataModel); session.save(); - } catch (Exception e) { - log.error( - "Cannot import node type definitions " + cndFiles, - e); - JcrUtils.discardQuietly(session); + session.getWorkspace().getVersionManager() + .checkin(dataModel.getPath()); } finally { IOUtils.closeQuietly(reader); - JcrUtils.logoutQuietly(session); } - } - }; - - 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(); - } - - if (inMemory) - if (homeDirectory.exists()) { - FileUtils.deleteDirectory(homeDirectory); if (log.isDebugEnabled()) - log.debug("Deleted Jackrabbit home directory " - + homeDirectory); + 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 (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); - } + /** Executes migrations, if needed. */ + protected void migrate() { + // Remote migration not supported + if (isRemote()) + return; - public String[] getDescriptorKeys() { - return repository.getDescriptorKeys(); - } + // No migration to perform + if (dataModelMigrations.size() == 0) + return; - public Session login() throws LoginException, RepositoryException { - Session session = repository.login(); - processNewSession(session); - return session; - } + Boolean restartAndClearCaches = false; - public Session login(Credentials credentials, String workspaceName) - throws LoginException, NoSuchWorkspaceException, - RepositoryException { - Session session; + // migrate data + Session session = null; try { - session = repository.login(credentials, workspaceName); - } catch (NoSuchWorkspaceException e) { - if (autocreateWorkspaces) - session = createWorkspaceAndLogsIn(credentials, workspaceName); - else - throw e; + 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); } - processNewSession(session); - return session; - } - - public Session login(Credentials credentials) throws LoginException, - RepositoryException { - Session session = repository.login(credentials); - processNewSession(session); - return session; - } - 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; + // restart repository + if (restartAndClearCaches) { + Repository repository = getRepository(); + if (repository instanceof RepositoryImpl) { + JackrabbitDataModelMigration + .clearRepositoryCaches(((RepositoryImpl) repository) + .getConfig()); + } + ((JackrabbitRepository) repository).shutdown(); + createJackrabbitRepository(); } - processNewSession(session); - return session; - } - protected synchronized void processNewSession(Session session) { + // set data model version try { - NamespaceHelper namespaceHelper = new NamespaceHelper(session); - namespaceHelper.registerNamespaces(namespaces); + session = login(); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot login to migrated repository", e); + } - } catch (Exception e) { - throw new ArgeoException("Cannot process new session", 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); + } - /** - * Logs in to the default workspace, creates the required workspace, logs - * out, logs in to the required workspace. + /* + * REPOSITORY INTERCEPTOR */ - 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); - } + /** Central login method */ + public Session login(Credentials credentials, String workspaceName) + throws LoginException, NoSuchWorkspaceException, + RepositoryException { - public void setResourceLoader(ResourceLoader resourceLoader) { - this.resourceLoader = resourceLoader; - } + // 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; + } + } + } - public boolean isStandardDescriptor(String key) { - return repository.isStandardDescriptor(key); + return super.login(credentials, workspaceName); } - public boolean isSingleValueDescriptor(String key) { - return repository.isSingleValueDescriptor(key); + /* + * UTILITIES + */ + + @Override + protected InputStream readConfiguration() { + try { + return configuration != null ? configuration.getInputStream() + : null; + } catch (IOException e) { + throw new ArgeoException("Cannot read Jackrabbit configuration " + + configuration, e); + } } - public Value getDescriptorValue(String key) { - return repository.getDescriptorValue(key); + @Override + protected InputStream readVariables() { + try { + return variables != null ? variables.getInputStream() : null; + } catch (IOException e) { + throw new ArgeoException("Cannot read Jackrabbit variables " + + variables, e); + } } - public Value[] getDescriptorValues(String key) { - return repository.getDescriptorValues(key); + @Override + protected String resolvePlaceholders(String string, + Map variables) { + return SystemPropertyUtils.resolvePlaceholders(string); } - // BEANS METHODS - public void setHomeDirectory(File homeDirectory) { - this.homeDirectory = homeDirectory; + /** 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; } + /* + * FIELDS ACCESS + */ public void setConfiguration(Resource configuration) { this.configuration = configuration; } - public void setInMemory(Boolean inMemory) { - this.inMemory = inMemory; - } - public void setNamespaces(Map namespaces) { this.namespaces = namespaces; } @@ -360,16 +459,21 @@ public class JackrabbitContainer implements InitializingBean, DisposableBean, this.variables = variables; } - public void setUri(String uri) { - this.uri = uri; + public void setRemoteSystemCredentials(Credentials remoteSystemCredentials) { + this.remoteSystemCredentials = remoteSystemCredentials; + } + + public void setDataModelMigrations( + Set dataModelMigrations) { + this.dataModelMigrations = dataModelMigrations; } - public void setSystemExecutor(Executor systemExecutor) { - this.systemExecutor = systemExecutor; + public void setBundleContext(BundleContext bundleContext) { + this.bundleContext = bundleContext; } - public void setAdminCredentials(Credentials adminCredentials) { - this.adminCredentials = adminCredentials; + public void setForceCndImport(Boolean forceCndUpdate) { + this.forceCndImport = forceCndUpdate; } }