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
.ArgeoException
;
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}.
52 public class JackrabbitContainer
extends JackrabbitWrapper
{
53 private Log log
= LogFactory
.getLog(JackrabbitContainer
.class);
56 private Resource configuration
;
57 private Resource variables
;
58 private RepositoryConfig repositoryConfig
;
59 private File homeDirectory
;
60 private Boolean inMemory
= false;
62 /** Migrations to execute (if not already done) */
63 private Set
<JackrabbitDataModelMigration
> dataModelMigrations
= new HashSet
<JackrabbitDataModelMigration
>();
66 * Empty constructor, {@link #init()} should be called after properties have
69 public JackrabbitContainer() {
73 long begin
= System
.currentTimeMillis();
75 if (getRepository() != null)
76 throw new ArgeoException(
77 "Cannot be used to wrap another repository");
78 Repository repository
= createJackrabbitRepository();
79 super.setRepository(repository
);
84 // apply new CND files after migration
87 double duration
= ((double) (System
.currentTimeMillis() - begin
)) / 1000;
88 if (log
.isDebugEnabled())
89 log
.debug("Initialized JCR repository wrapper in " + duration
93 /** Actually creates the new repository. */
94 protected Repository
createJackrabbitRepository() {
95 long begin
= System
.currentTimeMillis();
96 InputStream configurationIn
= null;
97 Repository repository
;
100 if (inMemory
&& getHomeDirectory().exists()) {
101 FileUtils
.deleteDirectory(getHomeDirectory());
102 log
.warn("Deleted Jackrabbit home directory "
103 + getHomeDirectory());
106 // process configuration file
107 Properties vars
= getConfigurationProperties();
108 configurationIn
= readConfiguration();
109 vars
.put(RepositoryConfigurationParser
.REPOSITORY_HOME_VARIABLE
,
110 getHomeDirectory().getCanonicalPath());
111 repositoryConfig
= RepositoryConfig
.create(new InputSource(
112 configurationIn
), vars
);
115 // Actual repository creation
117 repository
= RepositoryImpl
.create(repositoryConfig
);
119 double duration
= ((double) (System
.currentTimeMillis() - begin
)) / 1000;
120 if (log
.isTraceEnabled())
121 log
.trace("Created Jackrabbit repository in " + duration
122 + " s, home: " + getHomeDirectory());
125 } catch (Exception e
) {
126 throw new ArgeoException("Cannot create Jackrabbit repository "
127 + getHomeDirectory(), e
);
129 IOUtils
.closeQuietly(configurationIn
);
134 protected File
getHomeDirectory() {
136 if (homeDirectory
== null) {
138 homeDirectory
= new File(
139 System
.getProperty("java.io.tmpdir")
141 + System
.getProperty("user.name")
142 + File
.separator
+ "jackrabbit-"
143 + UUID
.randomUUID());
144 homeDirectory
.mkdirs();
145 // will it work if directory is not empty??
146 homeDirectory
.deleteOnExit();
150 return homeDirectory
.getCanonicalFile();
151 } catch (IOException e
) {
152 throw new ArgeoException("Cannot get canonical file for "
157 /** Executes migrations, if needed. */
158 protected void migrate() {
159 // No migration to perform
160 if (dataModelMigrations
.size() == 0)
163 Boolean restartAndClearCaches
= false;
166 Session session
= null;
169 for (JackrabbitDataModelMigration dataModelMigration
: new TreeSet
<JackrabbitDataModelMigration
>(
170 dataModelMigrations
)) {
171 if (dataModelMigration
.migrate(session
)) {
172 restartAndClearCaches
= true;
175 } catch (ArgeoException e
) {
177 } catch (Exception e
) {
178 throw new ArgeoException("Cannot migrate", e
);
180 JcrUtils
.logoutQuietly(session
);
183 // restart repository
184 if (restartAndClearCaches
) {
185 Repository repository
= getRepository();
186 if (repository
instanceof RepositoryImpl
) {
187 JackrabbitDataModelMigration
188 .clearRepositoryCaches(((RepositoryImpl
) repository
)
191 ((JackrabbitRepository
) repository
).shutdown();
192 createJackrabbitRepository();
195 // set data model version
198 } catch (RepositoryException e
) {
199 throw new ArgeoException("Cannot login to migrated repository", e
);
202 for (JackrabbitDataModelMigration dataModelMigration
: new TreeSet
<JackrabbitDataModelMigration
>(
203 dataModelMigrations
)) {
205 if (session
.itemExists(dataModelMigration
206 .getDataModelNodePath())) {
207 Node dataModelNode
= session
.getNode(dataModelMigration
208 .getDataModelNodePath());
209 dataModelNode
.setProperty(
210 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
,
211 dataModelMigration
.getTargetVersion());
214 } catch (Exception e
) {
215 log
.error("Cannot set model version", e
);
218 JcrUtils
.logoutQuietly(session
);
222 /** Shutdown the repository */
223 public void destroy() throws Exception
{
224 Repository repository
= getRepository();
225 if (repository
!= null && repository
instanceof RepositoryImpl
) {
226 long begin
= System
.currentTimeMillis();
227 ((RepositoryImpl
) repository
).shutdown();
229 if (getHomeDirectory().exists()) {
230 FileUtils
.deleteDirectory(getHomeDirectory());
231 if (log
.isDebugEnabled())
232 log
.debug("Deleted Jackrabbit home directory "
233 + getHomeDirectory());
235 double duration
= ((double) (System
.currentTimeMillis() - begin
)) / 1000;
236 log
.info("Destroyed Jackrabbit repository in " + duration
237 + " s, home: " + getHomeDirectory());
242 public void dispose() {
243 throw new IllegalArgumentException(
244 "Call destroy() method instead of dispose()");
251 * Reads the configuration which will initialize a {@link RepositoryConfig}.
253 protected InputStream
readConfiguration() {
255 return configuration
!= null ? configuration
.getInputStream()
257 } catch (IOException e
) {
258 throw new ArgeoException("Cannot read Jackrabbit configuration "
264 * Reads the variables which will initialize a {@link Properties}. Returns
265 * null by default, to be overridden.
267 * @return a new stream or null if no variables available
269 protected InputStream
readVariables() {
271 return variables
!= null ? variables
.getInputStream() : null;
272 } catch (IOException e
) {
273 throw new ArgeoException("Cannot read Jackrabbit variables "
279 * Resolves ${} placeholders in the provided string. Based on system
280 * properties if no map is provided.
282 protected String
resolvePlaceholders(String string
,
283 Map
<String
, String
> variables
) {
284 return SystemPropertyUtils
.resolvePlaceholders(string
);
287 /** Generates the properties to use in the configuration. */
288 protected Properties
getConfigurationProperties() {
289 InputStream propsIn
= null;
292 vars
= new Properties();
293 propsIn
= readVariables();
294 if (propsIn
!= null) {
297 // resolve system properties
298 for (Object key
: vars
.keySet()) {
299 // TODO: implement a smarter mechanism to resolve nested ${}
300 String newValue
= resolvePlaceholders(
301 vars
.getProperty(key
.toString()), null);
302 vars
.put(key
, newValue
);
304 // override with system properties
305 vars
.putAll(System
.getProperties());
307 if (log
.isTraceEnabled()) {
308 log
.trace("Jackrabbit config variables:");
309 for (Object key
: new TreeSet
<Object
>(vars
.keySet()))
310 log
.trace(key
+ "=" + vars
.getProperty(key
.toString()));
313 } catch (IOException e
) {
314 throw new ArgeoException("Cannot read configuration properties", e
);
316 IOUtils
.closeQuietly(propsIn
);
325 public void setHomeDirectory(File homeDirectory
) {
326 this.homeDirectory
= homeDirectory
;
329 public void setInMemory(Boolean inMemory
) {
330 this.inMemory
= inMemory
;
333 public void setRepository(Repository repository
) {
334 throw new ArgeoException("Cannot be used to wrap another repository");
337 public void setDataModelMigrations(
338 Set
<JackrabbitDataModelMigration
> dataModelMigrations
) {
339 this.dataModelMigrations
= dataModelMigrations
;
342 public void setVariables(Resource variables
) {
343 this.variables
= variables
;
346 public void setConfiguration(Resource configuration
) {
347 this.configuration
= configuration
;