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
.concurrent
.Executor
;
35 import javax
.jcr
.Credentials
;
36 import javax
.jcr
.LoginException
;
37 import javax
.jcr
.NoSuchWorkspaceException
;
38 import javax
.jcr
.Node
;
39 import javax
.jcr
.Repository
;
40 import javax
.jcr
.RepositoryException
;
41 import javax
.jcr
.Session
;
42 import javax
.jcr
.Value
;
44 import org
.apache
.commons
.io
.FileUtils
;
45 import org
.apache
.commons
.io
.IOUtils
;
46 import org
.apache
.commons
.logging
.Log
;
47 import org
.apache
.commons
.logging
.LogFactory
;
48 import org
.apache
.jackrabbit
.api
.JackrabbitRepository
;
49 import org
.apache
.jackrabbit
.commons
.NamespaceHelper
;
50 import org
.apache
.jackrabbit
.commons
.cnd
.CndImporter
;
51 import org
.apache
.jackrabbit
.core
.RepositoryImpl
;
52 import org
.apache
.jackrabbit
.core
.TransientRepository
;
53 import org
.apache
.jackrabbit
.core
.config
.RepositoryConfig
;
54 import org
.apache
.jackrabbit
.core
.config
.RepositoryConfigurationParser
;
55 import org
.apache
.jackrabbit
.jcr2dav
.Jcr2davRepositoryFactory
;
56 import org
.argeo
.ArgeoException
;
57 import org
.argeo
.jcr
.ArgeoNames
;
58 import org
.argeo
.jcr
.JcrUtils
;
59 import org
.springframework
.context
.ResourceLoaderAware
;
60 import org
.springframework
.core
.io
.Resource
;
61 import org
.springframework
.core
.io
.ResourceLoader
;
62 import org
.springframework
.util
.SystemPropertyUtils
;
63 import org
.xml
.sax
.InputSource
;
66 * Wrapper around a Jackrabbit repository which allows to configure it in Spring
67 * and expose it as a {@link Repository}.
69 public class JackrabbitContainer
implements Repository
, ResourceLoaderAware
{
70 private Log log
= LogFactory
.getLog(JackrabbitContainer
.class);
72 private Resource configuration
;
73 private File homeDirectory
;
74 private Resource variables
;
76 private Boolean inMemory
= false;
77 private String uri
= null;
80 private Repository repository
;
81 private RepositoryConfig repositoryConfig
;
84 private ResourceLoader resourceLoader
;
86 /** Node type definitions in CND format */
87 private List
<String
> cndFiles
= new ArrayList
<String
>();
89 /** Migrations to execute (if not already done) */
90 private Set
<JackrabbitDataModelMigration
> dataModelMigrations
= new HashSet
<JackrabbitDataModelMigration
>();
92 /** Namespaces to register: key is prefix, value namespace */
93 private Map
<String
, String
> namespaces
= new HashMap
<String
, String
>();
95 private Boolean autocreateWorkspaces
= false;
97 private Executor systemExecutor
;
99 public void init() throws Exception
{
100 if (repository
!= null) {
101 // we are just wrapping another repository
102 importNodeTypeDefinitions(repository
);
106 createJackrabbitRepository();
111 // apply new CND files after migration
112 if (cndFiles
!= null && cndFiles
.size() > 0)
113 importNodeTypeDefinitions(repository
);
116 /** Actually creates a new repository. */
117 protected void createJackrabbitRepository() {
118 long begin
= System
.currentTimeMillis();
121 if (uri
!= null && !uri
.trim().equals("")) {
122 Map
<String
, String
> params
= new HashMap
<String
, String
>();
124 org
.apache
.jackrabbit
.commons
.JcrUtils
.REPOSITORY_URI
,
126 repository
= new Jcr2davRepositoryFactory()
127 .getRepository(params
);
128 if (repository
== null)
129 throw new ArgeoException("Remote Davex repository " + uri
131 log
.info("Initialized Jackrabbit repository " + repository
132 + " from URI " + uri
);
133 // do not perform further initialization since we assume that
135 // remote repository has been properly configured
140 if (inMemory
&& getHomeDirectory().exists()) {
141 FileUtils
.deleteDirectory(getHomeDirectory());
142 log
.warn("Deleted Jackrabbit home directory "
143 + getHomeDirectory());
146 Properties vars
= getConfigurationProperties();
147 InputStream in
= configuration
.getInputStream();
150 RepositoryConfigurationParser
.REPOSITORY_HOME_VARIABLE
,
151 getHomeDirectory().getCanonicalPath());
152 repositoryConfig
= RepositoryConfig
.create(new InputSource(in
),
154 } catch (Exception e
) {
155 throw new RuntimeException("Cannot read configuration", e
);
157 IOUtils
.closeQuietly(in
);
161 repository
= new TransientRepository(repositoryConfig
);
163 repository
= RepositoryImpl
.create(repositoryConfig
);
165 double duration
= ((double) (System
.currentTimeMillis() - begin
)) / 1000;
166 log
.info("Initialized Jackrabbit repository in " + duration
167 + " s, home: " + getHomeDirectory() + ", config: "
169 } catch (Exception e
) {
170 throw new ArgeoException("Cannot create Jackrabbit repository "
171 + getHomeDirectory(), e
);
175 /** Executes migrations, if needed. */
176 protected void migrate() {
177 // No migration to perform
178 if (dataModelMigrations
.size() == 0)
181 Boolean restartAndClearCaches
= false;
184 Session session
= null;
187 for (JackrabbitDataModelMigration dataModelMigration
: new TreeSet
<JackrabbitDataModelMigration
>(
188 dataModelMigrations
)) {
189 if (dataModelMigration
.migrate(session
)) {
190 restartAndClearCaches
= true;
193 } catch (ArgeoException e
) {
195 } catch (Exception e
) {
196 throw new ArgeoException("Cannot migrate", e
);
198 JcrUtils
.logoutQuietly(session
);
201 // restart repository
202 if (restartAndClearCaches
) {
203 JackrabbitDataModelMigration
204 .clearRepositoryCaches(repositoryConfig
);
205 ((JackrabbitRepository
) repository
).shutdown();
206 createJackrabbitRepository();
209 // set data model version
212 } catch (RepositoryException e
) {
213 throw new ArgeoException("Cannot login to migrated repository", e
);
216 for (JackrabbitDataModelMigration dataModelMigration
: new TreeSet
<JackrabbitDataModelMigration
>(
217 dataModelMigrations
)) {
219 if (session
.itemExists(dataModelMigration
220 .getDataModelNodePath())) {
221 Node dataModelNode
= session
.getNode(dataModelMigration
222 .getDataModelNodePath());
223 dataModelNode
.setProperty(
224 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
,
225 dataModelMigration
.getTargetVersion());
228 } catch (Exception e
) {
229 log
.error("Cannot set model version", e
);
232 JcrUtils
.logoutQuietly(session
);
237 protected File
getHomeDirectory() {
239 return homeDirectory
.getCanonicalFile();
240 } catch (IOException e
) {
241 throw new ArgeoException("Cannot get canonical file for "
246 public void dispose() throws Exception
{
247 long begin
= System
.currentTimeMillis();
248 if (repository
!= null) {
249 if (repository
instanceof JackrabbitRepository
)
250 ((JackrabbitRepository
) repository
).shutdown();
251 else if (repository
instanceof RepositoryImpl
)
252 ((RepositoryImpl
) repository
).shutdown();
253 else if (repository
instanceof TransientRepository
)
254 ((TransientRepository
) repository
).shutdown();
258 if (getHomeDirectory().exists()) {
259 FileUtils
.deleteDirectory(getHomeDirectory());
260 if (log
.isDebugEnabled())
261 log
.debug("Deleted Jackrabbit home directory "
262 + getHomeDirectory());
265 double duration
= ((double) (System
.currentTimeMillis() - begin
)) / 1000;
266 if (uri
!= null && !uri
.trim().equals(""))
267 log
.info("Destroyed Jackrabbit repository with uri " + uri
);
269 log
.info("Destroyed Jackrabbit repository in " + duration
270 + " s, home: " + getHomeDirectory() + ", config "
275 * @deprecated explicitly declare {@link #dispose()} as destroy-method
278 public void destroy() throws Exception
{
279 log
.error("## Declare destroy-method=\"dispose\". in the Jackrabbit container bean");
282 /** @deprecated explicitly declare {@link #init()} as init-method instead. */
283 public void afterPropertiesSet() throws Exception
{
284 log
.error("## Declare init-method=\"init\". in the Jackrabbit container bean");
287 protected Properties
getConfigurationProperties() {
288 InputStream propsIn
= null;
291 vars
= new Properties();
292 if (variables
!= null) {
293 propsIn
= variables
.getInputStream();
296 // resolve system properties
297 for (Object key
: vars
.keySet()) {
298 // TODO: implement a smarter mechanism to resolve nested ${}
299 String newValue
= SystemPropertyUtils
.resolvePlaceholders(vars
300 .getProperty(key
.toString()));
301 vars
.put(key
, newValue
);
303 // override with system properties
304 vars
.putAll(System
.getProperties());
306 if (log
.isTraceEnabled()) {
307 log
.trace("Jackrabbit config variables:");
308 for (Object key
: new TreeSet
<Object
>(vars
.keySet()))
309 log
.trace(key
+ "=" + vars
.getProperty(key
.toString()));
312 } catch (IOException e
) {
313 throw new ArgeoException("Cannot read configuration properties", e
);
315 IOUtils
.closeQuietly(propsIn
);
321 * Import declared node type definitions, trying to update them if they have
322 * changed. In case of failures an error will be logged but no exception
325 protected void importNodeTypeDefinitions(final Repository repository
) {
326 Runnable action
= new Runnable() {
328 Reader reader
= null;
329 Session session
= null;
331 session
= repository
.login();
332 processNewSession(session
);
333 // Load cnds as resources
334 for (String resUrl
: cndFiles
) {
335 Resource res
= resourceLoader
.getResource(resUrl
);
336 byte[] arr
= IOUtils
.toByteArray(res
.getInputStream());
337 reader
= new InputStreamReader(
338 new ByteArrayInputStream(arr
));
339 CndImporter
.registerNodeTypes(reader
, session
, true);
342 } catch (Exception e
) {
344 "Cannot import node type definitions " + cndFiles
,
346 JcrUtils
.discardQuietly(session
);
348 IOUtils
.closeQuietly(reader
);
349 JcrUtils
.logoutQuietly(session
);
354 if (systemExecutor
!= null)
355 systemExecutor
.execute(action
);
360 // JCR REPOSITORY (delegated)
361 public String
getDescriptor(String key
) {
362 return getRepository().getDescriptor(key
);
365 public String
[] getDescriptorKeys() {
366 return getRepository().getDescriptorKeys();
369 public Session
login() throws LoginException
, RepositoryException
{
370 Session session
= getRepository().login();
371 processNewSession(session
);
375 public Session
login(Credentials credentials
, String workspaceName
)
376 throws LoginException
, NoSuchWorkspaceException
,
377 RepositoryException
{
380 session
= getRepository().login(credentials
, workspaceName
);
381 } catch (NoSuchWorkspaceException e
) {
382 if (autocreateWorkspaces
)
383 session
= createWorkspaceAndLogsIn(credentials
, workspaceName
);
387 processNewSession(session
);
391 public Session
login(Credentials credentials
) throws LoginException
,
392 RepositoryException
{
393 Session session
= getRepository().login(credentials
);
394 processNewSession(session
);
398 public Session
login(String workspaceName
) throws LoginException
,
399 NoSuchWorkspaceException
, RepositoryException
{
402 session
= getRepository().login(workspaceName
);
403 } catch (NoSuchWorkspaceException e
) {
404 if (autocreateWorkspaces
)
405 session
= createWorkspaceAndLogsIn(null, workspaceName
);
409 processNewSession(session
);
413 /** Wraps access to the repository, making sure it is available. */
414 protected Repository
getRepository() {
415 if (repository
== null) {
416 throw new ArgeoException(
417 "No repository initialized."
418 + " Was the init() method called?"
419 + " The dispose() method should also be called on shutdown.");
424 protected synchronized void processNewSession(Session session
) {
426 NamespaceHelper namespaceHelper
= new NamespaceHelper(session
);
427 namespaceHelper
.registerNamespaces(namespaces
);
428 } catch (Exception e
) {
429 throw new ArgeoException("Cannot process new session", e
);
434 * Logs in to the default workspace, creates the required workspace, logs
435 * out, logs in to the required workspace.
437 protected Session
createWorkspaceAndLogsIn(Credentials credentials
,
438 String workspaceName
) throws RepositoryException
{
439 if (workspaceName
== null)
440 throw new ArgeoException("No workspace specified.");
441 Session session
= getRepository().login(credentials
);
442 session
.getWorkspace().createWorkspace(workspaceName
);
444 return getRepository().login(credentials
, workspaceName
);
447 public void setResourceLoader(ResourceLoader resourceLoader
) {
448 this.resourceLoader
= resourceLoader
;
451 public boolean isStandardDescriptor(String key
) {
452 return getRepository().isStandardDescriptor(key
);
455 public boolean isSingleValueDescriptor(String key
) {
456 return getRepository().isSingleValueDescriptor(key
);
459 public Value
getDescriptorValue(String key
) {
460 return getRepository().getDescriptorValue(key
);
463 public Value
[] getDescriptorValues(String key
) {
464 return getRepository().getDescriptorValues(key
);
468 public void setHomeDirectory(File homeDirectory
) {
469 this.homeDirectory
= homeDirectory
;
472 public void setConfiguration(Resource configuration
) {
473 this.configuration
= configuration
;
476 public void setInMemory(Boolean inMemory
) {
477 this.inMemory
= inMemory
;
480 public void setNamespaces(Map
<String
, String
> namespaces
) {
481 this.namespaces
= namespaces
;
484 public void setCndFiles(List
<String
> cndFiles
) {
485 this.cndFiles
= cndFiles
;
488 public void setVariables(Resource variables
) {
489 this.variables
= variables
;
492 public void setUri(String uri
) {
496 public void setSystemExecutor(Executor systemExecutor
) {
497 this.systemExecutor
= systemExecutor
;
500 public void setRepository(Repository repository
) {
501 this.repository
= repository
;
504 public void setDataModelMigrations(
505 Set
<JackrabbitDataModelMigration
> dataModelMigrations
) {
506 this.dataModelMigrations
= dataModelMigrations
;