X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=server%2Fruntime%2Forg.argeo.server.jackrabbit%2Fsrc%2Fmain%2Fjava%2Forg%2Fargeo%2Fjackrabbit%2FJackrabbitWrapper.java;h=669da15a12cb3c7631b892cbb3c464fa7a9d37b3;hb=cd50e3711d3b86921f11d9e021fc6a43bef0d400;hp=ad8f5f8a0fac1a5be89da3def497692fe9afd554;hpb=1d5afdce3e91054f07ddd3c98309c363b4cf1d46;p=lgpl%2Fargeo-commons.git diff --git a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitWrapper.java b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitWrapper.java index ad8f5f8a0..669da15a1 100644 --- a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitWrapper.java +++ b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007-2012 Mathieu Baudier + * 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. @@ -15,374 +15,327 @@ */ package org.argeo.jackrabbit; -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.List; import java.util.Map; -import java.util.Properties; -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.NodeIterator; 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.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.jackrabbit.core.RepositoryImpl; -import org.apache.jackrabbit.core.config.RepositoryConfig; -import org.apache.jackrabbit.core.config.RepositoryConfigurationParser; -import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory; +import org.apache.jackrabbit.commons.NamespaceHelper; +import org.apache.jackrabbit.commons.cnd.CndImporter; import org.argeo.ArgeoException; -import org.xml.sax.InputSource; +import org.argeo.jcr.ArgeoJcrConstants; +import org.argeo.jcr.ArgeoNames; +import org.argeo.jcr.ArgeoTypes; +import org.argeo.jcr.JcrRepositoryWrapper; +import org.argeo.jcr.JcrUtils; +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.context.ResourceLoaderAware; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; /** * Wrapper around a Jackrabbit repository which allows to simplify configuration * and intercept some actions. It exposes itself as a {@link Repository}. */ -public abstract class JackrabbitWrapper implements Repository { +public class JackrabbitWrapper extends JcrRepositoryWrapper implements + ResourceLoaderAware { private Log log = LogFactory.getLog(JackrabbitWrapper.class); - // remote - private String uri = null; - // local - private RepositoryConfig repositoryConfig; - private File homeDirectory; - private Boolean inMemory = false; - - // wrapped repository - private Repository repository; - - private Boolean autocreateWorkspaces = false; + private ResourceLoader resourceLoader; + // data model + /** Node type definitions in CND format */ + private List cndFiles = new ArrayList(); /** - * Empty constructor, {@link #init()} should be called after properties have - * been set + * Always import CNDs. Useful during development of new data models. In + * production, explicit migration processes should be used. */ - public JackrabbitWrapper() { - } + private Boolean forceCndImport = false; - /** - * Reads the configuration which will initialize a {@link RepositoryConfig}. - */ - protected abstract InputStream readConfiguration(); + /** Namespaces to register: key is prefix, value namespace */ + private Map namespaces = new HashMap(); + + private BundleContext bundleContext; /** - * 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 + * Explicitly set admin credentials used in initialization. Useful for + * testing, in real applications authentication is rather dealt with + * externally */ - protected InputStream readVariables() { - return null; - } + private Credentials adminCredentials = null; /** - * Resolves ${} placeholders in the provided string. Based on system - * properties if no map is provided. + * Empty constructor, {@link #init()} should be called after properties have + * been set */ - protected abstract String resolvePlaceholders(String string, - Map variables); + public JackrabbitWrapper() { + } - /** Initializes */ + @Override public void init() { - long begin = System.currentTimeMillis(); - - if (repository != null) { - // we are just wrapping another repository - postInitWrapped(); - } else { - createJackrabbitRepository(); - postInitNew(); - } - - double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; - if (log.isTraceEnabled()) - log.trace("Initialized Jackrabbit wrapper in " + duration + " s"); + prepareDataModel(); } - /** - * Called after initialization of an already existing {@link Repository} - * which is being wrapped (e.g. in order to impact its data model). To be - * overridden, does nothing by default. + /* + * DATA MODEL */ - protected void postInitWrapped() { - - } /** - * Called after initialization of a new {@link Repository} either local or - * remote. To be overridden, does nothing by default. + * 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 postInitNew() { + protected void prepareDataModel() { + if ((cndFiles == null || cndFiles.size() == 0) + && (namespaces == null || namespaces.size() == 0)) + return; - } - - /** Actually creates the new repository. */ - protected void createJackrabbitRepository() { - long begin = System.currentTimeMillis(); - InputStream configurationIn = null; + Session session = null; try { - if (uri != null && !uri.trim().equals("")) {// remote - 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"); - double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; - log.info("Created Jackrabbit repository in " + duration - + " s from URI " + uri); - // we assume that the data model of the remote repository has - // been properly initialized - } else {// local - // force 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()); - } - - // process configuration file - Properties vars = getConfigurationProperties(); - configurationIn = readConfiguration(); - vars.put( - RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE, - 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()); + session = login(adminCredentials); + // register namespaces + if (namespaces.size() > 0) { + NamespaceHelper namespaceHelper = new NamespaceHelper(session); + namespaceHelper.registerNamespaces(namespaces); } - } catch (Exception e) { - throw new ArgeoException("Cannot create Jackrabbit repository " - + getHomeDirectory(), e); - } finally { - IOUtils.closeQuietly(configurationIn); - } - } - /** 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(); + // load CND files from classpath or as URL + for (String resUrl : cndFiles) { + URL url; + Bundle dataModelBundle = null; + + {// TODO put this in a separate method + boolean classpath; + // normalize URL + if (bundleContext != null + && 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 (classpath) { + if (bundleContext != null) { + Bundle currentBundle = bundleContext.getBundle(); + url = currentBundle.getResource(resUrl); + if (url != null) {// found + dataModelBundle = findDataModelBundle(resUrl); + } + } else { + resUrl = "classpath:" + resUrl; + url = null; + // url = + // getClass().getClassLoader().getResource(resUrl); + // if (url == null) + // url = Thread.currentThread() + // .getContextClassLoader() + // .getResource(resUrl); + } + } else if (!resUrl.startsWith("classpath:")) { + url = new URL(resUrl); + } else { + url = null; + } } - } - - return homeDirectory.getCanonicalFile(); - } catch (IOException e) { - throw new ArgeoException("Cannot get canonical file for " - + homeDirectory, e); - } - } - - /** 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()); + // 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; + } } - double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; - log.info("Destroyed Jackrabbit repository in " + duration - + " s, home: " + getHomeDirectory()); - } - } - /** - * @deprecated explicitly declare {@link #destroy()} as destroy-method - * instead. - */ - public void dispose() throws Exception { - log.error("## Declare destroy-method=\"destroy\". in the Jackrabbit container bean"); - destroy(); - } - - /* - * UTILITIES - */ + // 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; + } - /** 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()); + InputStream in = null; + Reader reader = null; + try { + if (url != null) { + in = url.openStream(); + } else if (resourceLoader != null) { + Resource res = resourceLoader.getResource(resUrl); + in = res.getInputStream(); + url = res.getURL(); + } else { + throw new ArgeoException("No " + resUrl + + " in the classpath," + + " make sure the containing" + + " package is visible."); + } + + reader = new InputStreamReader(in); + // 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(); + session.getWorkspace().getVersionManager() + .checkin(dataModel.getPath()); + } finally { + IOUtils.closeQuietly(in); + IOUtils.closeQuietly(reader); + } - if (log.isTraceEnabled()) { - log.trace("Jackrabbit config variables:"); - for (Object key : new TreeSet(vars.keySet())) - log.trace(key + "=" + vars.getProperty(key.toString())); + if (log.isDebugEnabled()) + log.debug("Data model " + + resUrl + + (dataModelBundle != null ? ", version " + + dataModelBundle.getVersion() + + ", bundle " + + dataModelBundle.getSymbolicName() : "")); } - - } catch (IOException e) { - throw new ArgeoException("Cannot read configuration properties", e); + } catch (Exception e) { + JcrUtils.discardQuietly(session); + throw new ArgeoException("Cannot import node type definitions " + + cndFiles, e); } finally { - IOUtils.closeQuietly(propsIn); + JcrUtils.logoutQuietly(session); } - return vars; + } /* - * DELEGATED JCR REPOSITORY METHODS + * REPOSITORY INTERCEPTOR */ - public String getDescriptor(String key) { - return getRepository().getDescriptor(key); - } - - public String[] getDescriptorKeys() { - return getRepository().getDescriptorKeys(); - } - - /** Central login method */ - public Session login(Credentials credentials, String workspaceName) - throws LoginException, NoSuchWorkspaceException, - RepositoryException { - Session session; - try { - session = getRepository().login(credentials, workspaceName); - } catch (NoSuchWorkspaceException e) { - if (autocreateWorkspaces && workspaceName != null) - session = createWorkspaceAndLogsIn(credentials, workspaceName); - else - throw e; + /* + * UTILITIES + */ + /** 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; + } + } } - processNewSession(session); - return session; - } - - public Session login() throws LoginException, RepositoryException { - return login(null, null); - } - public Session login(Credentials credentials) throws LoginException, - RepositoryException { - return login(credentials, null); - } - - public Session login(String workspaceName) throws LoginException, - NoSuchWorkspaceException, RepositoryException { - 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. */ - 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."); + Bundle exportingBundle = null; + if (exportedPackage != null) { + exportingBundle = exportedPackage.getExportingBundle(); + } else { + throw new ArgeoException("No OSGi exporting package found for " + + resUrl); } - return repository; + return exportingBundle; } - /** - * Logs in to the default workspace, creates the required workspace, logs - * out, logs in to the required workspace. + /* + * FIELDS ACCESS */ - protected Session createWorkspaceAndLogsIn(Credentials credentials, - String workspaceName) throws RepositoryException { - if (workspaceName == null) - throw new ArgeoException("No workspace specified."); - Session session = getRepository().login(credentials); - session.getWorkspace().createWorkspace(workspaceName); - session.logout(); - return getRepository().login(credentials, workspaceName); - } - - public boolean isStandardDescriptor(String key) { - return getRepository().isStandardDescriptor(key); + public void setNamespaces(Map namespaces) { + this.namespaces = namespaces; } - public boolean isSingleValueDescriptor(String key) { - return getRepository().isSingleValueDescriptor(key); + public void setCndFiles(List cndFiles) { + this.cndFiles = cndFiles; } - public Value getDescriptorValue(String key) { - return getRepository().getDescriptorValue(key); - } - - public Value[] getDescriptorValues(String key) { - return getRepository().getDescriptorValues(key); - } - - /* - * FIELDS ACCESS - */ - - public void setHomeDirectory(File homeDirectory) { - this.homeDirectory = homeDirectory; + public void setBundleContext(BundleContext bundleContext) { + this.bundleContext = bundleContext; } - public void setInMemory(Boolean inMemory) { - this.inMemory = inMemory; + public void setForceCndImport(Boolean forceCndUpdate) { + this.forceCndImport = forceCndUpdate; } - public void setUri(String uri) { - this.uri = uri; + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; } - public void setRepository(Repository repository) { - this.repository = repository; + public void setAdminCredentials(Credentials adminCredentials) { + this.adminCredentials = adminCredentials; } }