2 * Copyright (C) 2007-2012 Argeo GmbH
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package org
.argeo
.jackrabbit
;
19 import java
.io
.IOException
;
20 import java
.io
.InputStream
;
21 import java
.util
.HashSet
;
23 import java
.util
.Properties
;
25 import java
.util
.TreeSet
;
26 import java
.util
.UUID
;
28 import javax
.jcr
.Node
;
29 import javax
.jcr
.Repository
;
30 import javax
.jcr
.RepositoryException
;
31 import javax
.jcr
.Session
;
33 import org
.apache
.commons
.io
.FileUtils
;
34 import org
.apache
.commons
.io
.IOUtils
;
35 import org
.apache
.commons
.logging
.Log
;
36 import org
.apache
.commons
.logging
.LogFactory
;
37 import org
.apache
.jackrabbit
.api
.JackrabbitRepository
;
38 import org
.apache
.jackrabbit
.core
.RepositoryImpl
;
39 import org
.apache
.jackrabbit
.core
.config
.RepositoryConfig
;
40 import org
.apache
.jackrabbit
.core
.config
.RepositoryConfigurationParser
;
41 import org
.argeo
.jcr
.ArgeoJcrException
;
42 import org
.argeo
.jcr
.ArgeoNames
;
43 import org
.argeo
.jcr
.JcrUtils
;
44 import org
.springframework
.core
.io
.Resource
;
45 import org
.springframework
.util
.SystemPropertyUtils
;
46 import org
.xml
.sax
.InputSource
;
49 * Wrapper around a Jackrabbit repository which allows to configure it in Spring
50 * and expose it as a {@link Repository}.
53 public class JackrabbitContainer
extends JackrabbitWrapper
{
54 private final static Log log
= LogFactory
.getLog(JackrabbitContainer
.class);
57 private Resource configuration
;
59 private Resource variables
;
61 private RepositoryConfig repositoryConfig
;
62 private File homeDirectory
;
63 private Boolean inMemory
= false;
65 /** Migrations to execute (if not already done) */
66 private Set
<JackrabbitDataModelMigration
> dataModelMigrations
= new HashSet
<JackrabbitDataModelMigration
>();
68 /** Straight (non spring) values */
69 private Properties configurationProperties
;
70 private InputSource configurationXml
;
73 * Empty constructor, {@link #init()} should be called after properties have
76 public JackrabbitContainer() {
80 // long begin = System.currentTimeMillis();
82 if (getRepository() != null)
83 throw new ArgeoJcrException("Cannot be used to wrap another repository");
84 Repository repository
= createJackrabbitRepository();
85 super.setRepository(repository
);
90 // apply new CND files after migration
93 // double duration = ((double) (System.currentTimeMillis() - begin)) /
95 // if (log.isDebugEnabled())
96 // log.debug("Initialized JCR repository wrapper in " + duration
100 /** Actually creates the new repository. */
101 protected Repository
createJackrabbitRepository() {
102 long begin
= System
.currentTimeMillis();
103 InputStream configurationIn
= null;
104 Repository repository
;
107 if (inMemory
&& getHomeDirectory().exists()) {
108 FileUtils
.deleteDirectory(getHomeDirectory());
109 log
.warn("Deleted Jackrabbit home directory " + getHomeDirectory());
112 // process configuration file
113 Properties vars
= getConfigurationProperties();
114 vars
.put(RepositoryConfigurationParser
.REPOSITORY_HOME_VARIABLE
, getHomeDirectory().getCanonicalPath());
116 if (configurationXml
!= null)
117 is
= configurationXml
;
119 configurationIn
= readConfiguration();
120 is
= new InputSource(configurationIn
);
122 repositoryConfig
= RepositoryConfig
.create(is
, vars
);
125 // Actual repository creation
127 repository
= RepositoryImpl
.create(repositoryConfig
);
129 double duration
= ((double) (System
.currentTimeMillis() - begin
)) / 1000;
130 if (log
.isTraceEnabled())
131 log
.trace("Created Jackrabbit repository in " + duration
+ " s, home: " + getHomeDirectory());
134 } catch (Exception e
) {
135 throw new ArgeoJcrException("Cannot create Jackrabbit repository " + getHomeDirectory(), e
);
137 IOUtils
.closeQuietly(configurationIn
);
142 protected File
getHomeDirectory() {
144 if (homeDirectory
== null) {
146 homeDirectory
= new File(System
.getProperty("java.io.tmpdir") + File
.separator
147 + System
.getProperty("user.name") + File
.separator
+ "jackrabbit-" + UUID
.randomUUID());
148 homeDirectory
.mkdirs();
149 // will it work if directory is not empty??
150 homeDirectory
.deleteOnExit();
154 return homeDirectory
.getCanonicalFile();
155 } catch (IOException e
) {
156 throw new ArgeoJcrException("Cannot get canonical file for " + homeDirectory
, e
);
160 /** Executes migrations, if needed. */
161 protected void migrate() {
162 // No migration to perform
163 if (dataModelMigrations
.size() == 0)
166 Boolean restartAndClearCaches
= false;
169 Session session
= null;
172 for (JackrabbitDataModelMigration dataModelMigration
: new TreeSet
<JackrabbitDataModelMigration
>(
173 dataModelMigrations
)) {
174 if (dataModelMigration
.migrate(session
)) {
175 restartAndClearCaches
= true;
178 } catch (ArgeoJcrException e
) {
180 } catch (Exception e
) {
181 throw new ArgeoJcrException("Cannot migrate", e
);
183 JcrUtils
.logoutQuietly(session
);
186 // restart repository
187 if (restartAndClearCaches
) {
188 Repository repository
= getRepository();
189 if (repository
instanceof RepositoryImpl
) {
190 JackrabbitDataModelMigration
.clearRepositoryCaches(((RepositoryImpl
) repository
).getConfig());
192 ((JackrabbitRepository
) repository
).shutdown();
193 createJackrabbitRepository();
196 // set data model version
199 } catch (RepositoryException e
) {
200 throw new ArgeoJcrException("Cannot login to migrated repository", e
);
203 for (JackrabbitDataModelMigration dataModelMigration
: new TreeSet
<JackrabbitDataModelMigration
>(
204 dataModelMigrations
)) {
206 if (session
.itemExists(dataModelMigration
.getDataModelNodePath())) {
207 Node dataModelNode
= session
.getNode(dataModelMigration
.getDataModelNodePath());
208 dataModelNode
.setProperty(ArgeoNames
.ARGEO_DATA_MODEL_VERSION
,
209 dataModelMigration
.getTargetVersion());
212 } catch (Exception e
) {
213 log
.error("Cannot set model version", e
);
216 JcrUtils
.logoutQuietly(session
);
220 /** Shutdown the repository */
221 public void destroy() throws Exception
{
222 Repository repository
= getRepository();
223 if (repository
!= null && repository
instanceof RepositoryImpl
) {
224 long begin
= System
.currentTimeMillis();
225 ((RepositoryImpl
) repository
).shutdown();
227 if (getHomeDirectory().exists()) {
228 FileUtils
.deleteDirectory(getHomeDirectory());
229 if (log
.isDebugEnabled())
230 log
.debug("Deleted Jackrabbit home directory " + getHomeDirectory());
232 double duration
= ((double) (System
.currentTimeMillis() - begin
)) / 1000;
233 if (log
.isTraceEnabled())
234 log
.trace("Destroyed Jackrabbit repository in " + duration
+ " s, home: " + getHomeDirectory());
239 public void dispose() {
240 throw new IllegalArgumentException("Call destroy() method instead of dispose()");
247 * Reads the configuration which will initialize a {@link RepositoryConfig}.
249 protected InputStream
readConfiguration() {
251 return configuration
!= null ? configuration
.getInputStream() : null;
252 } catch (IOException e
) {
253 throw new ArgeoJcrException("Cannot read Jackrabbit configuration " + configuration
, e
);
258 * Reads the variables which will initialize a {@link Properties}. Returns
259 * null by default, to be overridden.
261 * @return a new stream or null if no variables available
263 protected InputStream
readVariables() {
265 return variables
!= null ? variables
.getInputStream() : null;
266 } catch (IOException e
) {
267 throw new ArgeoJcrException("Cannot read Jackrabbit variables " + variables
, e
);
272 * Resolves ${} placeholders in the provided string. Based on system
273 * properties if no map is provided.
275 protected String
resolvePlaceholders(String string
, Map
<String
, String
> variables
) {
276 return SystemPropertyUtils
.resolvePlaceholders(string
);
279 /** Generates the properties to use in the configuration. */
280 protected Properties
getConfigurationProperties() {
281 if (configurationProperties
!= null)
282 return configurationProperties
;
284 InputStream propsIn
= null;
287 vars
= new Properties();
288 propsIn
= readVariables();
289 if (propsIn
!= null) {
292 // resolve system properties
293 for (Object key
: vars
.keySet()) {
294 // TODO: implement a smarter mechanism to resolve nested ${}
295 String newValue
= resolvePlaceholders(vars
.getProperty(key
.toString()), null);
296 vars
.put(key
, newValue
);
298 // override with system properties
299 vars
.putAll(System
.getProperties());
301 if (log
.isTraceEnabled()) {
302 log
.trace("Jackrabbit config variables:");
303 for (Object key
: new TreeSet
<Object
>(vars
.keySet()))
304 log
.trace(key
+ "=" + vars
.getProperty(key
.toString()));
307 } catch (IOException e
) {
308 throw new ArgeoJcrException("Cannot read configuration properties", e
);
310 IOUtils
.closeQuietly(propsIn
);
319 public void setHomeDirectory(File homeDirectory
) {
320 this.homeDirectory
= homeDirectory
;
323 public void setInMemory(Boolean inMemory
) {
324 this.inMemory
= inMemory
;
327 public void setRepository(Repository repository
) {
328 throw new ArgeoJcrException("Cannot be used to wrap another repository");
331 public void setDataModelMigrations(Set
<JackrabbitDataModelMigration
> dataModelMigrations
) {
332 this.dataModelMigrations
= dataModelMigrations
;
335 public void setVariables(Resource variables
) {
336 this.variables
= variables
;
339 public void setConfiguration(Resource configuration
) {
340 this.configuration
= configuration
;
343 public void setConfigurationProperties(Properties configurationProperties
) {
344 this.configurationProperties
= configurationProperties
;
347 public void setConfigurationXml(InputSource configurationXml
) {
348 this.configurationXml
= configurationXml
;