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
.IOException
;
20 import java
.io
.InputStream
;
21 import java
.io
.InputStreamReader
;
22 import java
.io
.Reader
;
24 import java
.util
.ArrayList
;
25 import java
.util
.HashMap
;
26 import java
.util
.HashSet
;
27 import java
.util
.List
;
30 import java
.util
.TreeSet
;
32 import javax
.jcr
.Credentials
;
33 import javax
.jcr
.LoginException
;
34 import javax
.jcr
.NoSuchWorkspaceException
;
35 import javax
.jcr
.Node
;
36 import javax
.jcr
.NodeIterator
;
37 import javax
.jcr
.Repository
;
38 import javax
.jcr
.RepositoryException
;
39 import javax
.jcr
.Session
;
40 import javax
.jcr
.SimpleCredentials
;
42 import org
.apache
.commons
.io
.FilenameUtils
;
43 import org
.apache
.commons
.io
.IOUtils
;
44 import org
.apache
.commons
.logging
.Log
;
45 import org
.apache
.commons
.logging
.LogFactory
;
46 import org
.apache
.jackrabbit
.api
.JackrabbitRepository
;
47 import org
.apache
.jackrabbit
.commons
.NamespaceHelper
;
48 import org
.apache
.jackrabbit
.commons
.cnd
.CndImporter
;
49 import org
.apache
.jackrabbit
.core
.RepositoryImpl
;
50 import org
.argeo
.ArgeoException
;
51 import org
.argeo
.jcr
.ArgeoJcrConstants
;
52 import org
.argeo
.jcr
.ArgeoNames
;
53 import org
.argeo
.jcr
.ArgeoTypes
;
54 import org
.argeo
.jcr
.JcrUtils
;
55 import org
.argeo
.security
.SystemAuthentication
;
56 import org
.osgi
.framework
.Bundle
;
57 import org
.osgi
.framework
.BundleContext
;
58 import org
.osgi
.framework
.ServiceReference
;
59 import org
.osgi
.service
.packageadmin
.ExportedPackage
;
60 import org
.osgi
.service
.packageadmin
.PackageAdmin
;
61 import org
.springframework
.core
.io
.Resource
;
62 import org
.springframework
.security
.Authentication
;
63 import org
.springframework
.security
.context
.SecurityContextHolder
;
64 import org
.springframework
.security
.providers
.UsernamePasswordAuthenticationToken
;
65 import org
.springframework
.util
.SystemPropertyUtils
;
68 * Wrapper around a Jackrabbit repository which allows to configure it in Spring
69 * and expose it as a {@link Repository}.
71 public class JackrabbitContainer
extends JackrabbitWrapper
{
72 private Log log
= LogFactory
.getLog(JackrabbitContainer
.class);
75 private Credentials remoteSystemCredentials
= null;
78 private Resource configuration
;
79 private Resource variables
;
82 /** Node type definitions in CND format */
83 private List
<String
> cndFiles
= new ArrayList
<String
>();
85 * Always import CNDs. Useful during development of new data models. In
86 * production, explicit migration processes should be used.
88 private Boolean forceCndImport
= false;
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 BundleContext bundleContext
;
99 * Empty constructor, {@link #init()} should be called after properties have
102 public JackrabbitContainer() {
106 * Convenience constructor for remote, {@link #init()} is called in the
109 public JackrabbitContainer(String uri
, Credentials remoteSystemCredentials
) {
111 setRemoteSystemCredentials(remoteSystemCredentials
);
116 protected void postInitWrapped() {
121 protected void postInitNew() {
125 // apply new CND files after migration
126 if (cndFiles
!= null && cndFiles
.size() > 0)
135 * Import declared node type definitions and register namespaces. Tries to
136 * update the node definitions if they have changed. In case of failures an
137 * error will be logged but no exception will be thrown.
139 protected void prepareDataModel() {
140 // importing node def on remote si currently not supported
144 Session session
= null;
147 // register namespaces
148 if (namespaces
.size() > 0) {
149 NamespaceHelper namespaceHelper
= new NamespaceHelper(session
);
150 namespaceHelper
.registerNamespaces(namespaces
);
152 // load CND files from classpath or as URL
153 for (String resUrl
: cndFiles
) {
156 if (resUrl
.startsWith("classpath:")) {
157 resUrl
= resUrl
.substring("classpath:".length());
159 } else if (resUrl
.indexOf(':') < 0) {
160 if (!resUrl
.startsWith("/")) {
161 resUrl
= "/" + resUrl
;
162 log
.warn("Classpath should start with '/'");
164 // resUrl = "classpath:" + resUrl;
171 Bundle dataModelBundle
= null;
173 if (bundleContext
!= null) {
174 Bundle currentBundle
= bundleContext
.getBundle();
175 url
= currentBundle
.getResource(resUrl
);
176 if (url
!= null) {// found
177 dataModelBundle
= findDataModelBundle(resUrl
);
180 url
= getClass().getClassLoader().getResource(resUrl
);
183 throw new ArgeoException("No " + resUrl
184 + " in the classpath,"
185 + " make sure the containing"
186 + " package is visible.");
189 url
= new URL(resUrl
);
192 // check existing data model nodes
193 new NamespaceHelper(session
).registerNamespace(
194 ArgeoNames
.ARGEO
, ArgeoNames
.ARGEO_NAMESPACE
);
196 .itemExists(ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
))
197 JcrUtils
.mkdirs(session
,
198 ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
);
199 Node dataModels
= session
200 .getNode(ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
);
201 NodeIterator it
= dataModels
.getNodes();
202 Node dataModel
= null;
203 while (it
.hasNext()) {
204 Node node
= it
.nextNode();
205 if (node
.getProperty(ArgeoNames
.ARGEO_URI
).getString()
212 // does nothing if data model already registered
213 if (dataModel
!= null && !forceCndImport
) {
214 if (dataModelBundle
!= null) {
215 String version
= dataModel
.getProperty(
216 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
)
218 String dataModelBundleVersion
= dataModelBundle
219 .getVersion().toString();
220 if (!version
.equals(dataModelBundleVersion
)) {
221 log
.warn("Data model with version "
222 + dataModelBundleVersion
223 + " available, current version is "
227 // do not implicitly update
231 Reader reader
= null;
233 reader
= new InputStreamReader(url
.openStream());
234 // actually imports the CND
235 CndImporter
.registerNodeTypes(reader
, session
, true);
237 // FIXME: what if argeo.cnd would not be the first called on
238 // a new repo? argeo:dataModel would not be found
239 String fileName
= FilenameUtils
.getName(url
.getPath());
240 if (dataModel
== null) {
241 dataModel
= dataModels
.addNode(fileName
);
242 dataModel
.addMixin(ArgeoTypes
.ARGEO_DATA_MODEL
);
243 dataModel
.setProperty(ArgeoNames
.ARGEO_URI
, resUrl
);
245 session
.getWorkspace().getVersionManager()
246 .checkout(dataModel
.getPath());
248 if (dataModelBundle
!= null)
249 dataModel
.setProperty(
250 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
,
251 dataModelBundle
.getVersion().toString());
253 dataModel
.setProperty(
254 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
, "0.0.0");
255 JcrUtils
.updateLastModified(dataModel
);
257 session
.getWorkspace().getVersionManager()
258 .checkin(dataModel
.getPath());
260 IOUtils
.closeQuietly(reader
);
263 if (log
.isDebugEnabled())
264 log
.debug("Data model "
266 + (dataModelBundle
!= null ?
", version "
267 + dataModelBundle
.getVersion()
269 + dataModelBundle
.getSymbolicName() : ""));
271 } catch (Exception e
) {
272 JcrUtils
.discardQuietly(session
);
273 throw new ArgeoException("Cannot import node type definitions "
276 JcrUtils
.logoutQuietly(session
);
281 /** Executes migrations, if needed. */
282 protected void migrate() {
283 // Remote migration not supported
287 // No migration to perform
288 if (dataModelMigrations
.size() == 0)
291 Boolean restartAndClearCaches
= false;
294 Session session
= null;
297 for (JackrabbitDataModelMigration dataModelMigration
: new TreeSet
<JackrabbitDataModelMigration
>(
298 dataModelMigrations
)) {
299 if (dataModelMigration
.migrate(session
)) {
300 restartAndClearCaches
= true;
303 } catch (ArgeoException e
) {
305 } catch (Exception e
) {
306 throw new ArgeoException("Cannot migrate", e
);
308 JcrUtils
.logoutQuietly(session
);
311 // restart repository
312 if (restartAndClearCaches
) {
313 Repository repository
= getRepository();
314 if (repository
instanceof RepositoryImpl
) {
315 JackrabbitDataModelMigration
316 .clearRepositoryCaches(((RepositoryImpl
) repository
)
319 ((JackrabbitRepository
) repository
).shutdown();
320 createJackrabbitRepository();
323 // set data model version
326 } catch (RepositoryException e
) {
327 throw new ArgeoException("Cannot login to migrated repository", e
);
330 for (JackrabbitDataModelMigration dataModelMigration
: new TreeSet
<JackrabbitDataModelMigration
>(
331 dataModelMigrations
)) {
333 if (session
.itemExists(dataModelMigration
334 .getDataModelNodePath())) {
335 Node dataModelNode
= session
.getNode(dataModelMigration
336 .getDataModelNodePath());
337 dataModelNode
.setProperty(
338 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
,
339 dataModelMigration
.getTargetVersion());
342 } catch (Exception e
) {
343 log
.error("Cannot set model version", e
);
346 JcrUtils
.logoutQuietly(session
);
351 * REPOSITORY INTERCEPTOR
353 /** Central login method */
354 public Session
login(Credentials credentials
, String workspaceName
)
355 throws LoginException
, NoSuchWorkspaceException
,
356 RepositoryException
{
358 // retrieve credentials for remote
359 if (credentials
== null && isRemote()) {
360 Authentication authentication
= SecurityContextHolder
.getContext()
361 .getAuthentication();
362 if (authentication
!= null) {
363 if (authentication
instanceof UsernamePasswordAuthenticationToken
) {
364 UsernamePasswordAuthenticationToken upat
= (UsernamePasswordAuthenticationToken
) authentication
;
365 credentials
= new SimpleCredentials(upat
.getName(), upat
366 .getCredentials().toString().toCharArray());
367 } else if ((authentication
instanceof SystemAuthentication
)
368 && remoteSystemCredentials
!= null) {
369 credentials
= remoteSystemCredentials
;
374 return super.login(credentials
, workspaceName
);
382 protected InputStream
readConfiguration() {
384 return configuration
!= null ? configuration
.getInputStream()
386 } catch (IOException e
) {
387 throw new ArgeoException("Cannot read Jackrabbit configuration "
393 protected InputStream
readVariables() {
395 return variables
!= null ? variables
.getInputStream() : null;
396 } catch (IOException e
) {
397 throw new ArgeoException("Cannot read Jackrabbit variables "
403 protected String
resolvePlaceholders(String string
,
404 Map
<String
, String
> variables
) {
405 return SystemPropertyUtils
.resolvePlaceholders(string
);
408 /** Find which OSGi bundle provided the data model resource */
409 protected Bundle
findDataModelBundle(String resUrl
) {
410 if (resUrl
.startsWith("/"))
411 resUrl
= resUrl
.substring(1);
412 String pkg
= resUrl
.substring(0, resUrl
.lastIndexOf('/')).replace('/',
414 ServiceReference paSr
= bundleContext
415 .getServiceReference(PackageAdmin
.class.getName());
416 PackageAdmin packageAdmin
= (PackageAdmin
) bundleContext
419 // find exported package
420 ExportedPackage exportedPackage
= null;
421 ExportedPackage
[] exportedPackages
= packageAdmin
422 .getExportedPackages(pkg
);
423 if (exportedPackages
== null)
424 throw new ArgeoException("No exported package found for " + pkg
);
425 for (ExportedPackage ep
: exportedPackages
) {
426 for (Bundle b
: ep
.getImportingBundles()) {
427 if (b
.getBundleId() == bundleContext
.getBundle().getBundleId()) {
428 exportedPackage
= ep
;
434 Bundle exportingBundle
= null;
435 if (exportedPackage
!= null) {
436 exportingBundle
= exportedPackage
.getExportingBundle();
438 throw new ArgeoException("No OSGi exporting package found for "
441 return exportingBundle
;
447 public void setConfiguration(Resource configuration
) {
448 this.configuration
= configuration
;
451 public void setNamespaces(Map
<String
, String
> namespaces
) {
452 this.namespaces
= namespaces
;
455 public void setCndFiles(List
<String
> cndFiles
) {
456 this.cndFiles
= cndFiles
;
459 public void setVariables(Resource variables
) {
460 this.variables
= variables
;
463 public void setRemoteSystemCredentials(Credentials remoteSystemCredentials
) {
464 this.remoteSystemCredentials
= remoteSystemCredentials
;
467 public void setDataModelMigrations(
468 Set
<JackrabbitDataModelMigration
> dataModelMigrations
) {
469 this.dataModelMigrations
= dataModelMigrations
;
472 public void setBundleContext(BundleContext bundleContext
) {
473 this.bundleContext
= bundleContext
;
476 public void setForceCndImport(Boolean forceCndUpdate
) {
477 this.forceCndImport
= forceCndUpdate
;