X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=org.argeo.server.jackrabbit%2Fsrc%2Forg%2Fargeo%2Fjackrabbit%2FJackrabbitContainer.java;fp=org.argeo.server.jackrabbit%2Fsrc%2Forg%2Fargeo%2Fjackrabbit%2FJackrabbitContainer.java;h=9060b585a59bee58a8615b910a11cdfd9934e5cf;hb=d33e8191813f561cee96fbbbd3f74737070140d0;hp=0000000000000000000000000000000000000000;hpb=959ea5e8b3cc27eaf6cb31c37d7fc28f2719f6f3;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.server.jackrabbit/src/org/argeo/jackrabbit/JackrabbitContainer.java b/org.argeo.server.jackrabbit/src/org/argeo/jackrabbit/JackrabbitContainer.java new file mode 100644 index 000000000..9060b585a --- /dev/null +++ b/org.argeo.server.jackrabbit/src/org/argeo/jackrabbit/JackrabbitContainer.java @@ -0,0 +1,352 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.jackrabbit; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; +import java.util.UUID; + +import javax.jcr.Node; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +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.core.RepositoryImpl; +import org.apache.jackrabbit.core.config.RepositoryConfig; +import org.apache.jackrabbit.core.config.RepositoryConfigurationParser; +import org.argeo.ArgeoException; +import org.argeo.jcr.ArgeoNames; +import org.argeo.jcr.JcrUtils; +import org.argeo.jcr.MaintainedRepository; +import org.springframework.core.io.Resource; +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 extends JackrabbitWrapper implements + MaintainedRepository { + private final static Log log = LogFactory.getLog(JackrabbitContainer.class); + + // local + private Resource configuration; + private Resource variables; + private RepositoryConfig repositoryConfig; + private File homeDirectory; + private Boolean inMemory = false; + + /** Migrations to execute (if not already done) */ + private Set dataModelMigrations = new HashSet(); + + /** + * Empty constructor, {@link #init()} should be called after properties have + * been set + */ + public JackrabbitContainer() { + } + + public void init() { + long begin = System.currentTimeMillis(); + + if (getRepository() != null) + throw new ArgeoException( + "Cannot be used to wrap another repository"); + Repository repository = createJackrabbitRepository(); + super.setRepository(repository); + + // migrate if needed + migrate(); + + // apply new CND files after migration + prepareDataModel(); + + double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; + if (log.isDebugEnabled()) + log.debug("Initialized JCR repository wrapper in " + duration + + " s"); + } + + /** Actually creates the new repository. */ + protected Repository createJackrabbitRepository() { + long begin = System.currentTimeMillis(); + InputStream configurationIn = null; + Repository repository; + try { + // 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()); + + return repository; + } 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(); + } + } + + return homeDirectory.getCanonicalFile(); + } catch (IOException e) { + throw new ArgeoException("Cannot get canonical file for " + + homeDirectory, 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( + 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) { + Repository repository = getRepository(); + if (repository instanceof RepositoryImpl) { + JackrabbitDataModelMigration + .clearRepositoryCaches(((RepositoryImpl) repository) + .getConfig()); + } + ((JackrabbitRepository) repository).shutdown(); + createJackrabbitRepository(); + } + + // set data model version + try { + session = login(); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot login to migrated repository", 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); + + } + + /** 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()); + } + repository = null; + } + + public void dispose() { + throw new IllegalArgumentException( + "Call destroy() method instead of dispose()"); + } + + /* + * UTILITIES + */ + /** + * Reads the configuration which will initialize a {@link RepositoryConfig}. + */ + protected InputStream readConfiguration() { + try { + return configuration != null ? configuration.getInputStream() + : null; + } catch (IOException e) { + throw new ArgeoException("Cannot read Jackrabbit configuration " + + configuration, e); + } + } + + /** + * 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 { + return variables != null ? variables.getInputStream() : null; + } catch (IOException e) { + throw new ArgeoException("Cannot read Jackrabbit variables " + + variables, e); + } + } + + /** + * Resolves ${} placeholders in the provided string. Based on system + * properties if no map is provided. + */ + protected String resolvePlaceholders(String string, + Map variables) { + return SystemPropertyUtils.resolvePlaceholders(string); + } + + /** 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()); + + if (log.isTraceEnabled()) { + log.trace("Jackrabbit config variables:"); + for (Object key : new TreeSet(vars.keySet())) + log.trace(key + "=" + vars.getProperty(key.toString())); + } + + } catch (IOException e) { + throw new ArgeoException("Cannot read configuration properties", e); + } finally { + IOUtils.closeQuietly(propsIn); + } + return vars; + } + + /* + * FIELDS ACCESS + */ + + public void setHomeDirectory(File homeDirectory) { + this.homeDirectory = homeDirectory; + } + + public void setInMemory(Boolean inMemory) { + this.inMemory = inMemory; + } + + public void setRepository(Repository repository) { + throw new ArgeoException("Cannot be used to wrap another repository"); + } + + public void setDataModelMigrations( + Set dataModelMigrations) { + this.dataModelMigrations = dataModelMigrations; + } + + public void setVariables(Resource variables) { + this.variables = variables; + } + + public void setConfiguration(Resource configuration) { + this.configuration = configuration; + } + +}