2 * Copyright (C) 2010 Mathieu Baudier <mbaudier@argeo.org>
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.
17 package org
.argeo
.jackrabbit
;
19 import java
.io
.ByteArrayInputStream
;
21 import java
.io
.IOException
;
22 import java
.io
.InputStream
;
23 import java
.io
.InputStreamReader
;
24 import java
.io
.Reader
;
25 import java
.util
.ArrayList
;
26 import java
.util
.HashMap
;
27 import java
.util
.HashSet
;
28 import java
.util
.List
;
30 import java
.util
.Properties
;
32 import java
.util
.TreeSet
;
33 import java
.util
.UUID
;
34 import java
.util
.concurrent
.Executor
;
36 import javax
.jcr
.Credentials
;
37 import javax
.jcr
.LoginException
;
38 import javax
.jcr
.NoSuchWorkspaceException
;
39 import javax
.jcr
.Node
;
40 import javax
.jcr
.Repository
;
41 import javax
.jcr
.RepositoryException
;
42 import javax
.jcr
.Session
;
43 import javax
.jcr
.Value
;
45 import org
.apache
.commons
.io
.FileUtils
;
46 import org
.apache
.commons
.io
.IOUtils
;
47 import org
.apache
.commons
.logging
.Log
;
48 import org
.apache
.commons
.logging
.LogFactory
;
49 import org
.apache
.jackrabbit
.api
.JackrabbitRepository
;
50 import org
.apache
.jackrabbit
.commons
.NamespaceHelper
;
51 import org
.apache
.jackrabbit
.commons
.cnd
.CndImporter
;
52 import org
.apache
.jackrabbit
.core
.RepositoryImpl
;
53 import org
.apache
.jackrabbit
.core
.TransientRepository
;
54 import org
.apache
.jackrabbit
.core
.config
.RepositoryConfig
;
55 import org
.apache
.jackrabbit
.core
.config
.RepositoryConfigurationParser
;
56 import org
.apache
.jackrabbit
.jcr2dav
.Jcr2davRepositoryFactory
;
57 import org
.argeo
.ArgeoException
;
58 import org
.argeo
.jcr
.ArgeoNames
;
59 import org
.argeo
.jcr
.JcrUtils
;
60 import org
.springframework
.context
.ResourceLoaderAware
;
61 import org
.springframework
.core
.io
.Resource
;
62 import org
.springframework
.core
.io
.ResourceLoader
;
63 import org
.springframework
.util
.SystemPropertyUtils
;
64 import org
.xml
.sax
.InputSource
;
67 * Wrapper around a Jackrabbit repository which allows to configure it in Spring
68 * and expose it as a {@link Repository}.
70 public class JackrabbitContainer
implements Repository
, ResourceLoaderAware
{
71 private Log log
= LogFactory
.getLog(JackrabbitContainer
.class);
73 private Resource configuration
;
74 private File homeDirectory
;
75 private Resource variables
;
77 private Boolean inMemory
= false;
78 private String uri
= null;
81 private Repository repository
;
82 private RepositoryConfig repositoryConfig
;
85 private ResourceLoader resourceLoader
;
87 /** Node type definitions in CND format */
88 private List
<String
> cndFiles
= new ArrayList
<String
>();
90 /** Migrations to execute (if not already done) */
91 private Set
<JackrabbitDataModelMigration
> dataModelMigrations
= new HashSet
<JackrabbitDataModelMigration
>();
93 /** Namespaces to register: key is prefix, value namespace */
94 private Map
<String
, String
> namespaces
= new HashMap
<String
, String
>();
96 private Boolean autocreateWorkspaces
= false;
98 private Executor systemExecutor
;
100 public void init() throws Exception
{
101 if (repository
!= null) {
102 // we are just wrapping another repository
103 importNodeTypeDefinitions(repository
);
107 createJackrabbitRepository();
112 // apply new CND files after migration
113 if (cndFiles
!= null && cndFiles
.size() > 0)
114 importNodeTypeDefinitions(repository
);
117 /** Actually creates a new repository. */
118 protected void createJackrabbitRepository() {
119 long begin
= System
.currentTimeMillis();
122 if (uri
!= null && !uri
.trim().equals("")) {
123 Map
<String
, String
> params
= new HashMap
<String
, String
>();
125 org
.apache
.jackrabbit
.commons
.JcrUtils
.REPOSITORY_URI
,
127 repository
= new Jcr2davRepositoryFactory()
128 .getRepository(params
);
129 if (repository
== null)
130 throw new ArgeoException("Remote Davex repository " + uri
132 log
.info("Initialized Jackrabbit repository " + repository
133 + " from URI " + uri
);
134 // do not perform further initialization since we assume that
136 // remote repository has been properly configured
141 if (inMemory
&& getHomeDirectory().exists()) {
142 FileUtils
.deleteDirectory(getHomeDirectory());
143 log
.warn("Deleted Jackrabbit home directory "
144 + getHomeDirectory());
147 Properties vars
= getConfigurationProperties();
148 InputStream in
= configuration
.getInputStream();
151 RepositoryConfigurationParser
.REPOSITORY_HOME_VARIABLE
,
152 getHomeDirectory().getCanonicalPath());
153 repositoryConfig
= RepositoryConfig
.create(new InputSource(in
),
155 } catch (Exception e
) {
156 throw new RuntimeException("Cannot read configuration", e
);
158 IOUtils
.closeQuietly(in
);
162 repository
= new TransientRepository(repositoryConfig
);
164 repository
= RepositoryImpl
.create(repositoryConfig
);
166 double duration
= ((double) (System
.currentTimeMillis() - begin
)) / 1000;
167 log
.info("Initialized Jackrabbit repository in " + duration
168 + " s, home: " + getHomeDirectory() + ", config: "
170 } catch (Exception e
) {
171 throw new ArgeoException("Cannot create Jackrabbit repository "
172 + getHomeDirectory(), e
);
176 /** Executes migrations, if needed. */
177 protected void migrate() {
178 // No migration to perform
179 if (dataModelMigrations
.size() == 0)
182 Boolean restartAndClearCaches
= false;
185 Session session
= null;
188 for (JackrabbitDataModelMigration dataModelMigration
: new TreeSet
<JackrabbitDataModelMigration
>(
189 dataModelMigrations
)) {
190 if (dataModelMigration
.migrate(session
)) {
191 restartAndClearCaches
= true;
194 } catch (ArgeoException e
) {
196 } catch (Exception e
) {
197 throw new ArgeoException("Cannot migrate", e
);
199 JcrUtils
.logoutQuietly(session
);
202 // restart repository
203 if (restartAndClearCaches
) {
204 JackrabbitDataModelMigration
205 .clearRepositoryCaches(repositoryConfig
);
206 ((JackrabbitRepository
) repository
).shutdown();
207 createJackrabbitRepository();
210 // set data model version
213 } catch (RepositoryException e
) {
214 throw new ArgeoException("Cannot login to migrated repository", e
);
217 for (JackrabbitDataModelMigration dataModelMigration
: new TreeSet
<JackrabbitDataModelMigration
>(
218 dataModelMigrations
)) {
220 if (session
.itemExists(dataModelMigration
221 .getDataModelNodePath())) {
222 Node dataModelNode
= session
.getNode(dataModelMigration
223 .getDataModelNodePath());
224 dataModelNode
.setProperty(
225 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
,
226 dataModelMigration
.getTargetVersion());
229 } catch (Exception e
) {
230 log
.error("Cannot set model version", e
);
233 JcrUtils
.logoutQuietly(session
);
238 protected File
getHomeDirectory() {
240 if (homeDirectory
== null) {
242 homeDirectory
= new File(
243 System
.getProperty("java.io.tmpdir")
245 + System
.getProperty("user.name")
246 + File
.separator
+ "jackrabbit-"
247 + UUID
.randomUUID());
248 homeDirectory
.mkdirs();
249 // will it work if directory is not empty?
250 homeDirectory
.deleteOnExit();
254 return homeDirectory
.getCanonicalFile();
255 } catch (IOException e
) {
256 throw new ArgeoException("Cannot get canonical file for "
261 public void dispose() throws Exception
{
262 long begin
= System
.currentTimeMillis();
263 if (repository
!= null) {
264 if (repository
instanceof JackrabbitRepository
)
265 ((JackrabbitRepository
) repository
).shutdown();
266 else if (repository
instanceof RepositoryImpl
)
267 ((RepositoryImpl
) repository
).shutdown();
268 else if (repository
instanceof TransientRepository
)
269 ((TransientRepository
) repository
).shutdown();
273 if (getHomeDirectory().exists()) {
274 FileUtils
.deleteDirectory(getHomeDirectory());
275 if (log
.isDebugEnabled())
276 log
.debug("Deleted Jackrabbit home directory "
277 + getHomeDirectory());
280 double duration
= ((double) (System
.currentTimeMillis() - begin
)) / 1000;
281 if (uri
!= null && !uri
.trim().equals(""))
282 log
.info("Destroyed Jackrabbit repository with uri " + uri
);
284 log
.info("Destroyed Jackrabbit repository in " + duration
285 + " s, home: " + getHomeDirectory() + ", config "
290 * @deprecated explicitly declare {@link #dispose()} as destroy-method
293 public void destroy() throws Exception
{
294 log
.error("## Declare destroy-method=\"dispose\". in the Jackrabbit container bean");
297 /** @deprecated explicitly declare {@link #init()} as init-method instead. */
298 public void afterPropertiesSet() throws Exception
{
299 log
.error("## Declare init-method=\"init\". in the Jackrabbit container bean");
302 protected Properties
getConfigurationProperties() {
303 InputStream propsIn
= null;
306 vars
= new Properties();
307 if (variables
!= null) {
308 propsIn
= variables
.getInputStream();
311 // resolve system properties
312 for (Object key
: vars
.keySet()) {
313 // TODO: implement a smarter mechanism to resolve nested ${}
314 String newValue
= SystemPropertyUtils
.resolvePlaceholders(vars
315 .getProperty(key
.toString()));
316 vars
.put(key
, newValue
);
318 // override with system properties
319 vars
.putAll(System
.getProperties());
321 if (log
.isTraceEnabled()) {
322 log
.trace("Jackrabbit config variables:");
323 for (Object key
: new TreeSet
<Object
>(vars
.keySet()))
324 log
.trace(key
+ "=" + vars
.getProperty(key
.toString()));
327 } catch (IOException e
) {
328 throw new ArgeoException("Cannot read configuration properties", e
);
330 IOUtils
.closeQuietly(propsIn
);
336 * Import declared node type definitions, trying to update them if they have
337 * changed. In case of failures an error will be logged but no exception
340 protected void importNodeTypeDefinitions(final Repository repository
) {
341 Runnable action
= new Runnable() {
343 Reader reader
= null;
344 Session session
= null;
346 session
= repository
.login();
347 processNewSession(session
);
348 // Load cnds as resources
349 for (String resUrl
: cndFiles
) {
350 Resource res
= resourceLoader
.getResource(resUrl
);
351 byte[] arr
= IOUtils
.toByteArray(res
.getInputStream());
352 reader
= new InputStreamReader(
353 new ByteArrayInputStream(arr
));
354 CndImporter
.registerNodeTypes(reader
, session
, true);
357 } catch (Exception e
) {
359 "Cannot import node type definitions " + cndFiles
,
361 JcrUtils
.discardQuietly(session
);
363 IOUtils
.closeQuietly(reader
);
364 JcrUtils
.logoutQuietly(session
);
369 if (systemExecutor
!= null)
370 systemExecutor
.execute(action
);
375 // JCR REPOSITORY (delegated)
376 public String
getDescriptor(String key
) {
377 return getRepository().getDescriptor(key
);
380 public String
[] getDescriptorKeys() {
381 return getRepository().getDescriptorKeys();
384 public Session
login() throws LoginException
, RepositoryException
{
385 Session session
= getRepository().login();
386 processNewSession(session
);
390 public Session
login(Credentials credentials
, String workspaceName
)
391 throws LoginException
, NoSuchWorkspaceException
,
392 RepositoryException
{
395 session
= getRepository().login(credentials
, workspaceName
);
396 } catch (NoSuchWorkspaceException e
) {
397 if (autocreateWorkspaces
)
398 session
= createWorkspaceAndLogsIn(credentials
, workspaceName
);
402 processNewSession(session
);
406 public Session
login(Credentials credentials
) throws LoginException
,
407 RepositoryException
{
408 Session session
= getRepository().login(credentials
);
409 processNewSession(session
);
413 public Session
login(String workspaceName
) throws LoginException
,
414 NoSuchWorkspaceException
, RepositoryException
{
417 session
= getRepository().login(workspaceName
);
418 } catch (NoSuchWorkspaceException e
) {
419 if (autocreateWorkspaces
)
420 session
= createWorkspaceAndLogsIn(null, workspaceName
);
424 processNewSession(session
);
428 /** Wraps access to the repository, making sure it is available. */
429 protected Repository
getRepository() {
430 if (repository
== null) {
431 throw new ArgeoException(
432 "No repository initialized."
433 + " Was the init() method called?"
434 + " The dispose() method should also be called on shutdown.");
439 protected synchronized void processNewSession(Session session
) {
441 NamespaceHelper namespaceHelper
= new NamespaceHelper(session
);
442 namespaceHelper
.registerNamespaces(namespaces
);
443 } catch (Exception e
) {
444 throw new ArgeoException("Cannot process new session", e
);
449 * Logs in to the default workspace, creates the required workspace, logs
450 * out, logs in to the required workspace.
452 protected Session
createWorkspaceAndLogsIn(Credentials credentials
,
453 String workspaceName
) throws RepositoryException
{
454 if (workspaceName
== null)
455 throw new ArgeoException("No workspace specified.");
456 Session session
= getRepository().login(credentials
);
457 session
.getWorkspace().createWorkspace(workspaceName
);
459 return getRepository().login(credentials
, workspaceName
);
462 public void setResourceLoader(ResourceLoader resourceLoader
) {
463 this.resourceLoader
= resourceLoader
;
466 public boolean isStandardDescriptor(String key
) {
467 return getRepository().isStandardDescriptor(key
);
470 public boolean isSingleValueDescriptor(String key
) {
471 return getRepository().isSingleValueDescriptor(key
);
474 public Value
getDescriptorValue(String key
) {
475 return getRepository().getDescriptorValue(key
);
478 public Value
[] getDescriptorValues(String key
) {
479 return getRepository().getDescriptorValues(key
);
483 public void setHomeDirectory(File homeDirectory
) {
484 this.homeDirectory
= homeDirectory
;
487 public void setConfiguration(Resource configuration
) {
488 this.configuration
= configuration
;
491 public void setInMemory(Boolean inMemory
) {
492 this.inMemory
= inMemory
;
495 public void setNamespaces(Map
<String
, String
> namespaces
) {
496 this.namespaces
= namespaces
;
499 public void setCndFiles(List
<String
> cndFiles
) {
500 this.cndFiles
= cndFiles
;
503 public void setVariables(Resource variables
) {
504 this.variables
= variables
;
507 public void setUri(String uri
) {
511 public void setSystemExecutor(Executor systemExecutor
) {
512 this.systemExecutor
= systemExecutor
;
515 public void setRepository(Repository repository
) {
516 this.repository
= repository
;
519 public void setDataModelMigrations(
520 Set
<JackrabbitDataModelMigration
> dataModelMigrations
) {
521 this.dataModelMigrations
= dataModelMigrations
;