2 * Copyright (C) 2007-2012 Mathieu Baudier
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
;
18 import java
.io
.IOException
;
19 import java
.io
.InputStream
;
20 import java
.io
.InputStreamReader
;
21 import java
.io
.Reader
;
23 import java
.util
.ArrayList
;
24 import java
.util
.HashMap
;
25 import java
.util
.HashSet
;
26 import java
.util
.List
;
29 import java
.util
.TreeSet
;
31 import javax
.jcr
.Credentials
;
32 import javax
.jcr
.LoginException
;
33 import javax
.jcr
.NoSuchWorkspaceException
;
34 import javax
.jcr
.Node
;
35 import javax
.jcr
.NodeIterator
;
36 import javax
.jcr
.Repository
;
37 import javax
.jcr
.RepositoryException
;
38 import javax
.jcr
.Session
;
39 import javax
.jcr
.SimpleCredentials
;
41 import org
.apache
.commons
.io
.FilenameUtils
;
42 import org
.apache
.commons
.io
.IOUtils
;
43 import org
.apache
.commons
.logging
.Log
;
44 import org
.apache
.commons
.logging
.LogFactory
;
45 import org
.apache
.jackrabbit
.api
.JackrabbitRepository
;
46 import org
.apache
.jackrabbit
.commons
.NamespaceHelper
;
47 import org
.apache
.jackrabbit
.commons
.cnd
.CndImporter
;
48 import org
.apache
.jackrabbit
.core
.RepositoryImpl
;
49 import org
.argeo
.ArgeoException
;
50 import org
.argeo
.jcr
.ArgeoJcrConstants
;
51 import org
.argeo
.jcr
.ArgeoNames
;
52 import org
.argeo
.jcr
.ArgeoTypes
;
53 import org
.argeo
.jcr
.JcrUtils
;
54 import org
.argeo
.security
.SystemAuthentication
;
55 import org
.osgi
.framework
.Bundle
;
56 import org
.osgi
.framework
.BundleContext
;
57 import org
.osgi
.framework
.ServiceReference
;
58 import org
.osgi
.service
.packageadmin
.ExportedPackage
;
59 import org
.osgi
.service
.packageadmin
.PackageAdmin
;
60 import org
.springframework
.core
.io
.Resource
;
61 import org
.springframework
.security
.Authentication
;
62 import org
.springframework
.security
.context
.SecurityContextHolder
;
63 import org
.springframework
.security
.providers
.UsernamePasswordAuthenticationToken
;
64 import org
.springframework
.util
.SystemPropertyUtils
;
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
extends JackrabbitWrapper
{
71 private Log log
= LogFactory
.getLog(JackrabbitContainer
.class);
74 private Credentials remoteSystemCredentials
= null;
77 private Resource configuration
;
78 private Resource variables
;
81 /** Node type definitions in CND format */
82 private List
<String
> cndFiles
= new ArrayList
<String
>();
84 * Always import CNDs. Useful during development of new data models. In
85 * production, explicit migration processes should be used.
87 private Boolean forceCndImport
= false;
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 BundleContext bundleContext
;
98 * Empty constructor, {@link #init()} should be called after properties have
101 public JackrabbitContainer() {
105 * Convenience constructor for remote, {@link #init()} is called in the
108 public JackrabbitContainer(String uri
, Credentials remoteSystemCredentials
) {
110 setRemoteSystemCredentials(remoteSystemCredentials
);
115 protected void postInitWrapped() {
120 protected void postInitNew() {
124 // apply new CND files after migration
125 if (cndFiles
!= null && cndFiles
.size() > 0)
134 * Import declared node type definitions and register namespaces. Tries to
135 * update the node definitions if they have changed. In case of failures an
136 * error will be logged but no exception will be thrown.
138 protected void prepareDataModel() {
139 // importing node def on remote si currently not supported
143 Session session
= null;
146 // register namespaces
147 if (namespaces
.size() > 0) {
148 NamespaceHelper namespaceHelper
= new NamespaceHelper(session
);
149 namespaceHelper
.registerNamespaces(namespaces
);
151 // load CND files from classpath or as URL
152 for (String resUrl
: cndFiles
) {
155 if (resUrl
.startsWith("classpath:")) {
156 resUrl
= resUrl
.substring("classpath:".length());
158 } else if (resUrl
.indexOf(':') < 0) {
159 if (!resUrl
.startsWith("/")) {
160 resUrl
= "/" + resUrl
;
161 log
.warn("Classpath should start with '/'");
163 // resUrl = "classpath:" + resUrl;
170 Bundle dataModelBundle
= null;
172 if (bundleContext
!= null) {
173 Bundle currentBundle
= bundleContext
.getBundle();
174 url
= currentBundle
.getResource(resUrl
);
175 if (url
!= null) {// found
176 dataModelBundle
= findDataModelBundle(resUrl
);
179 url
= getClass().getClassLoader().getResource(resUrl
);
182 throw new ArgeoException("No " + resUrl
183 + " in the classpath,"
184 + " make sure the containing"
185 + " package is visible.");
188 url
= new URL(resUrl
);
191 // check existing data model nodes
192 new NamespaceHelper(session
).registerNamespace(
193 ArgeoNames
.ARGEO
, ArgeoNames
.ARGEO_NAMESPACE
);
195 .itemExists(ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
))
196 JcrUtils
.mkdirs(session
,
197 ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
);
198 Node dataModels
= session
199 .getNode(ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
);
200 NodeIterator it
= dataModels
.getNodes();
201 Node dataModel
= null;
202 while (it
.hasNext()) {
203 Node node
= it
.nextNode();
204 if (node
.getProperty(ArgeoNames
.ARGEO_URI
).getString()
211 // does nothing if data model already registered
212 if (dataModel
!= null && !forceCndImport
) {
213 if (dataModelBundle
!= null) {
214 String version
= dataModel
.getProperty(
215 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
)
217 String dataModelBundleVersion
= dataModelBundle
218 .getVersion().toString();
219 if (!version
.equals(dataModelBundleVersion
)) {
220 log
.warn("Data model with version "
221 + dataModelBundleVersion
222 + " available, current version is "
226 // do not implicitly update
230 Reader reader
= null;
232 reader
= new InputStreamReader(url
.openStream());
233 // actually imports the CND
234 CndImporter
.registerNodeTypes(reader
, session
, true);
236 // FIXME: what if argeo.cnd would not be the first called on
237 // a new repo? argeo:dataModel would not be found
238 String fileName
= FilenameUtils
.getName(url
.getPath());
239 if (dataModel
== null) {
240 dataModel
= dataModels
.addNode(fileName
);
241 dataModel
.addMixin(ArgeoTypes
.ARGEO_DATA_MODEL
);
242 dataModel
.setProperty(ArgeoNames
.ARGEO_URI
, resUrl
);
244 session
.getWorkspace().getVersionManager()
245 .checkout(dataModel
.getPath());
247 if (dataModelBundle
!= null)
248 dataModel
.setProperty(
249 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
,
250 dataModelBundle
.getVersion().toString());
252 dataModel
.setProperty(
253 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
, "0.0.0");
254 JcrUtils
.updateLastModified(dataModel
);
256 session
.getWorkspace().getVersionManager()
257 .checkin(dataModel
.getPath());
259 IOUtils
.closeQuietly(reader
);
262 if (log
.isDebugEnabled())
263 log
.debug("Data model "
265 + (dataModelBundle
!= null ?
", version "
266 + dataModelBundle
.getVersion()
268 + dataModelBundle
.getSymbolicName() : ""));
270 } catch (Exception e
) {
271 JcrUtils
.discardQuietly(session
);
272 throw new ArgeoException("Cannot import node type definitions "
275 JcrUtils
.logoutQuietly(session
);
280 /** Executes migrations, if needed. */
281 protected void migrate() {
282 // Remote migration not supported
286 // No migration to perform
287 if (dataModelMigrations
.size() == 0)
290 Boolean restartAndClearCaches
= false;
293 Session session
= null;
296 for (JackrabbitDataModelMigration dataModelMigration
: new TreeSet
<JackrabbitDataModelMigration
>(
297 dataModelMigrations
)) {
298 if (dataModelMigration
.migrate(session
)) {
299 restartAndClearCaches
= true;
302 } catch (ArgeoException e
) {
304 } catch (Exception e
) {
305 throw new ArgeoException("Cannot migrate", e
);
307 JcrUtils
.logoutQuietly(session
);
310 // restart repository
311 if (restartAndClearCaches
) {
312 Repository repository
= getRepository();
313 if (repository
instanceof RepositoryImpl
) {
314 JackrabbitDataModelMigration
315 .clearRepositoryCaches(((RepositoryImpl
) repository
)
318 ((JackrabbitRepository
) repository
).shutdown();
319 createJackrabbitRepository();
322 // set data model version
325 } catch (RepositoryException e
) {
326 throw new ArgeoException("Cannot login to migrated repository", e
);
329 for (JackrabbitDataModelMigration dataModelMigration
: new TreeSet
<JackrabbitDataModelMigration
>(
330 dataModelMigrations
)) {
332 if (session
.itemExists(dataModelMigration
333 .getDataModelNodePath())) {
334 Node dataModelNode
= session
.getNode(dataModelMigration
335 .getDataModelNodePath());
336 dataModelNode
.setProperty(
337 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
,
338 dataModelMigration
.getTargetVersion());
341 } catch (Exception e
) {
342 log
.error("Cannot set model version", e
);
345 JcrUtils
.logoutQuietly(session
);
350 * REPOSITORY INTERCEPTOR
352 /** Central login method */
353 public Session
login(Credentials credentials
, String workspaceName
)
354 throws LoginException
, NoSuchWorkspaceException
,
355 RepositoryException
{
357 // retrieve credentials for remote
358 if (credentials
== null && isRemote()) {
359 Authentication authentication
= SecurityContextHolder
.getContext()
360 .getAuthentication();
361 if (authentication
!= null) {
362 if (authentication
instanceof UsernamePasswordAuthenticationToken
) {
363 UsernamePasswordAuthenticationToken upat
= (UsernamePasswordAuthenticationToken
) authentication
;
364 credentials
= new SimpleCredentials(upat
.getName(), upat
365 .getCredentials().toString().toCharArray());
366 } else if ((authentication
instanceof SystemAuthentication
)
367 && remoteSystemCredentials
!= null) {
368 credentials
= remoteSystemCredentials
;
373 return super.login(credentials
, workspaceName
);
381 protected InputStream
readConfiguration() {
383 return configuration
!= null ? configuration
.getInputStream()
385 } catch (IOException e
) {
386 throw new ArgeoException("Cannot read Jackrabbit configuration "
392 protected InputStream
readVariables() {
394 return variables
!= null ? variables
.getInputStream() : null;
395 } catch (IOException e
) {
396 throw new ArgeoException("Cannot read Jackrabbit variables "
402 protected String
resolvePlaceholders(String string
,
403 Map
<String
, String
> variables
) {
404 return SystemPropertyUtils
.resolvePlaceholders(string
);
407 /** Find which OSGi bundle provided the data model resource */
408 protected Bundle
findDataModelBundle(String resUrl
) {
409 if (resUrl
.startsWith("/"))
410 resUrl
= resUrl
.substring(1);
411 String pkg
= resUrl
.substring(0, resUrl
.lastIndexOf('/')).replace('/',
413 ServiceReference paSr
= bundleContext
414 .getServiceReference(PackageAdmin
.class.getName());
415 PackageAdmin packageAdmin
= (PackageAdmin
) bundleContext
418 // find exported package
419 ExportedPackage exportedPackage
= null;
420 ExportedPackage
[] exportedPackages
= packageAdmin
421 .getExportedPackages(pkg
);
422 if (exportedPackages
== null)
423 throw new ArgeoException("No exported package found for " + pkg
);
424 for (ExportedPackage ep
: exportedPackages
) {
425 for (Bundle b
: ep
.getImportingBundles()) {
426 if (b
.getBundleId() == bundleContext
.getBundle().getBundleId()) {
427 exportedPackage
= ep
;
433 Bundle exportingBundle
= null;
434 if (exportedPackage
!= null) {
435 exportingBundle
= exportedPackage
.getExportingBundle();
437 throw new ArgeoException("No OSGi exporting package found for "
440 return exportingBundle
;
446 public void setConfiguration(Resource configuration
) {
447 this.configuration
= configuration
;
450 public void setNamespaces(Map
<String
, String
> namespaces
) {
451 this.namespaces
= namespaces
;
454 public void setCndFiles(List
<String
> cndFiles
) {
455 this.cndFiles
= cndFiles
;
458 public void setVariables(Resource variables
) {
459 this.variables
= variables
;
462 public void setRemoteSystemCredentials(Credentials remoteSystemCredentials
) {
463 this.remoteSystemCredentials
= remoteSystemCredentials
;
466 public void setDataModelMigrations(
467 Set
<JackrabbitDataModelMigration
> dataModelMigrations
) {
468 this.dataModelMigrations
= dataModelMigrations
;
471 public void setBundleContext(BundleContext bundleContext
) {
472 this.bundleContext
= bundleContext
;
475 public void setForceCndImport(Boolean forceCndUpdate
) {
476 this.forceCndImport
= forceCndUpdate
;