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
.context
.ResourceLoaderAware
;
61 import org
.springframework
.core
.io
.Resource
;
62 import org
.springframework
.core
.io
.ResourceLoader
;
63 import org
.springframework
.security
.Authentication
;
64 import org
.springframework
.security
.context
.SecurityContextHolder
;
65 import org
.springframework
.security
.providers
.UsernamePasswordAuthenticationToken
;
66 import org
.springframework
.util
.SystemPropertyUtils
;
69 * Wrapper around a Jackrabbit repository which allows to configure it in Spring
70 * and expose it as a {@link Repository}.
72 public class JackrabbitContainer
extends JackrabbitWrapper
implements
74 private Log log
= LogFactory
.getLog(JackrabbitContainer
.class);
77 private Credentials remoteSystemCredentials
= null;
80 private Resource configuration
;
81 private Resource variables
;
82 private ResourceLoader resourceLoader
;
85 /** Node type definitions in CND format */
86 private List
<String
> cndFiles
= new ArrayList
<String
>();
88 * Always import CNDs. Useful during development of new data models. In
89 * production, explicit migration processes should be used.
91 private Boolean forceCndImport
= false;
93 /** Migrations to execute (if not already done) */
94 private Set
<JackrabbitDataModelMigration
> dataModelMigrations
= new HashSet
<JackrabbitDataModelMigration
>();
96 /** Namespaces to register: key is prefix, value namespace */
97 private Map
<String
, String
> namespaces
= new HashMap
<String
, String
>();
99 private BundleContext bundleContext
;
102 * Empty constructor, {@link #init()} should be called after properties have
105 public JackrabbitContainer() {
109 * Convenience constructor for remote, {@link #init()} is called in the
112 public JackrabbitContainer(String uri
, Credentials remoteSystemCredentials
) {
114 setRemoteSystemCredentials(remoteSystemCredentials
);
119 protected void postInitWrapped() {
124 protected void postInitNew() {
128 // apply new CND files after migration
129 if (cndFiles
!= null && cndFiles
.size() > 0)
138 * Import declared node type definitions and register namespaces. Tries to
139 * update the node definitions if they have changed. In case of failures an
140 * error will be logged but no exception will be thrown.
142 protected void prepareDataModel() {
143 // importing node def on remote si currently not supported
147 Session session
= null;
149 if (remoteSystemCredentials
== null)
152 session
= login(remoteSystemCredentials
);
153 // register namespaces
154 if (namespaces
.size() > 0) {
155 NamespaceHelper namespaceHelper
= new NamespaceHelper(session
);
156 namespaceHelper
.registerNamespaces(namespaces
);
158 // load CND files from classpath or as URL
159 for (String resUrl
: cndFiles
) {
162 if (resUrl
.startsWith("classpath:")) {
163 resUrl
= resUrl
.substring("classpath:".length());
165 } else if (resUrl
.indexOf(':') < 0) {
166 if (!resUrl
.startsWith("/")) {
167 resUrl
= "/" + resUrl
;
168 log
.warn("Classpath should start with '/'");
170 // resUrl = "classpath:" + resUrl;
177 Bundle dataModelBundle
= null;
179 if (bundleContext
!= null) {
180 Bundle currentBundle
= bundleContext
.getBundle();
181 url
= currentBundle
.getResource(resUrl
);
182 if (url
!= null) {// found
183 dataModelBundle
= findDataModelBundle(resUrl
);
186 url
= getClass().getClassLoader().getResource(resUrl
);
188 // url = Thread.currentThread()
189 // .getContextClassLoader()
190 // .getResource(resUrl);
193 url
= new URL(resUrl
);
196 // check existing data model nodes
197 new NamespaceHelper(session
).registerNamespace(
198 ArgeoNames
.ARGEO
, ArgeoNames
.ARGEO_NAMESPACE
);
200 .itemExists(ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
))
201 JcrUtils
.mkdirs(session
,
202 ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
);
203 Node dataModels
= session
204 .getNode(ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
);
205 NodeIterator it
= dataModels
.getNodes();
206 Node dataModel
= null;
207 while (it
.hasNext()) {
208 Node node
= it
.nextNode();
209 if (node
.getProperty(ArgeoNames
.ARGEO_URI
).getString()
216 // does nothing if data model already registered
217 if (dataModel
!= null && !forceCndImport
) {
218 if (dataModelBundle
!= null) {
219 String version
= dataModel
.getProperty(
220 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
)
222 String dataModelBundleVersion
= dataModelBundle
223 .getVersion().toString();
224 if (!version
.equals(dataModelBundleVersion
)) {
225 log
.warn("Data model with version "
226 + dataModelBundleVersion
227 + " available, current version is "
231 // do not implicitly update
235 InputStream in
= null;
236 Reader reader
= null;
239 in
= url
.openStream();
240 } else if (resourceLoader
!= null) {
241 Resource res
= resourceLoader
.getResource(resUrl
);
242 in
= res
.getInputStream();
245 throw new ArgeoException("No " + resUrl
246 + " in the classpath,"
247 + " make sure the containing"
248 + " package is visible.");
251 reader
= new InputStreamReader(in
);
252 // actually imports the CND
253 CndImporter
.registerNodeTypes(reader
, session
, true);
255 // FIXME: what if argeo.cnd would not be the first called on
256 // a new repo? argeo:dataModel would not be found
257 String fileName
= FilenameUtils
.getName(url
.getPath());
258 if (dataModel
== null) {
259 dataModel
= dataModels
.addNode(fileName
);
260 dataModel
.addMixin(ArgeoTypes
.ARGEO_DATA_MODEL
);
261 dataModel
.setProperty(ArgeoNames
.ARGEO_URI
, resUrl
);
263 session
.getWorkspace().getVersionManager()
264 .checkout(dataModel
.getPath());
266 if (dataModelBundle
!= null)
267 dataModel
.setProperty(
268 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
,
269 dataModelBundle
.getVersion().toString());
271 dataModel
.setProperty(
272 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
, "0.0.0");
273 JcrUtils
.updateLastModified(dataModel
);
275 session
.getWorkspace().getVersionManager()
276 .checkin(dataModel
.getPath());
278 IOUtils
.closeQuietly(in
);
279 IOUtils
.closeQuietly(reader
);
282 if (log
.isDebugEnabled())
283 log
.debug("Data model "
285 + (dataModelBundle
!= null ?
", version "
286 + dataModelBundle
.getVersion()
288 + dataModelBundle
.getSymbolicName() : ""));
290 } catch (Exception e
) {
291 JcrUtils
.discardQuietly(session
);
292 throw new ArgeoException("Cannot import node type definitions "
295 JcrUtils
.logoutQuietly(session
);
300 /** Executes migrations, if needed. */
301 protected void migrate() {
302 // Remote migration not supported
306 // No migration to perform
307 if (dataModelMigrations
.size() == 0)
310 Boolean restartAndClearCaches
= false;
313 Session session
= null;
316 for (JackrabbitDataModelMigration dataModelMigration
: new TreeSet
<JackrabbitDataModelMigration
>(
317 dataModelMigrations
)) {
318 if (dataModelMigration
.migrate(session
)) {
319 restartAndClearCaches
= true;
322 } catch (ArgeoException e
) {
324 } catch (Exception e
) {
325 throw new ArgeoException("Cannot migrate", e
);
327 JcrUtils
.logoutQuietly(session
);
330 // restart repository
331 if (restartAndClearCaches
) {
332 Repository repository
= getRepository();
333 if (repository
instanceof RepositoryImpl
) {
334 JackrabbitDataModelMigration
335 .clearRepositoryCaches(((RepositoryImpl
) repository
)
338 ((JackrabbitRepository
) repository
).shutdown();
339 createJackrabbitRepository();
342 // set data model version
345 } catch (RepositoryException e
) {
346 throw new ArgeoException("Cannot login to migrated repository", e
);
349 for (JackrabbitDataModelMigration dataModelMigration
: new TreeSet
<JackrabbitDataModelMigration
>(
350 dataModelMigrations
)) {
352 if (session
.itemExists(dataModelMigration
353 .getDataModelNodePath())) {
354 Node dataModelNode
= session
.getNode(dataModelMigration
355 .getDataModelNodePath());
356 dataModelNode
.setProperty(
357 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
,
358 dataModelMigration
.getTargetVersion());
361 } catch (Exception e
) {
362 log
.error("Cannot set model version", e
);
365 JcrUtils
.logoutQuietly(session
);
370 * REPOSITORY INTERCEPTOR
372 /** Central login method */
373 public Session
login(Credentials credentials
, String workspaceName
)
374 throws LoginException
, NoSuchWorkspaceException
,
375 RepositoryException
{
377 // retrieve credentials for remote
378 if (credentials
== null && isRemote()) {
379 Authentication authentication
= SecurityContextHolder
.getContext()
380 .getAuthentication();
381 if (authentication
!= null) {
382 if (authentication
instanceof UsernamePasswordAuthenticationToken
) {
383 UsernamePasswordAuthenticationToken upat
= (UsernamePasswordAuthenticationToken
) authentication
;
384 credentials
= new SimpleCredentials(upat
.getName(), upat
385 .getCredentials().toString().toCharArray());
386 } else if ((authentication
instanceof SystemAuthentication
)
387 && remoteSystemCredentials
!= null) {
388 credentials
= remoteSystemCredentials
;
393 return super.login(credentials
, workspaceName
);
401 protected InputStream
readConfiguration() {
403 return configuration
!= null ? configuration
.getInputStream()
405 } catch (IOException e
) {
406 throw new ArgeoException("Cannot read Jackrabbit configuration "
412 protected InputStream
readVariables() {
414 return variables
!= null ? variables
.getInputStream() : null;
415 } catch (IOException e
) {
416 throw new ArgeoException("Cannot read Jackrabbit variables "
422 protected String
resolvePlaceholders(String string
,
423 Map
<String
, String
> variables
) {
424 return SystemPropertyUtils
.resolvePlaceholders(string
);
427 /** Find which OSGi bundle provided the data model resource */
428 protected Bundle
findDataModelBundle(String resUrl
) {
429 if (resUrl
.startsWith("/"))
430 resUrl
= resUrl
.substring(1);
431 String pkg
= resUrl
.substring(0, resUrl
.lastIndexOf('/')).replace('/',
433 ServiceReference paSr
= bundleContext
434 .getServiceReference(PackageAdmin
.class.getName());
435 PackageAdmin packageAdmin
= (PackageAdmin
) bundleContext
438 // find exported package
439 ExportedPackage exportedPackage
= null;
440 ExportedPackage
[] exportedPackages
= packageAdmin
441 .getExportedPackages(pkg
);
442 if (exportedPackages
== null)
443 throw new ArgeoException("No exported package found for " + pkg
);
444 for (ExportedPackage ep
: exportedPackages
) {
445 for (Bundle b
: ep
.getImportingBundles()) {
446 if (b
.getBundleId() == bundleContext
.getBundle().getBundleId()) {
447 exportedPackage
= ep
;
453 Bundle exportingBundle
= null;
454 if (exportedPackage
!= null) {
455 exportingBundle
= exportedPackage
.getExportingBundle();
457 throw new ArgeoException("No OSGi exporting package found for "
460 return exportingBundle
;
466 public void setConfiguration(Resource configuration
) {
467 this.configuration
= configuration
;
470 public void setNamespaces(Map
<String
, String
> namespaces
) {
471 this.namespaces
= namespaces
;
474 public void setCndFiles(List
<String
> cndFiles
) {
475 this.cndFiles
= cndFiles
;
478 public void setVariables(Resource variables
) {
479 this.variables
= variables
;
482 public void setRemoteSystemCredentials(Credentials remoteSystemCredentials
) {
483 this.remoteSystemCredentials
= remoteSystemCredentials
;
486 public void setDataModelMigrations(
487 Set
<JackrabbitDataModelMigration
> dataModelMigrations
) {
488 this.dataModelMigrations
= dataModelMigrations
;
491 public void setBundleContext(BundleContext bundleContext
) {
492 this.bundleContext
= bundleContext
;
495 public void setForceCndImport(Boolean forceCndUpdate
) {
496 this.forceCndImport
= forceCndUpdate
;
499 public void setResourceLoader(ResourceLoader resourceLoader
) {
500 this.resourceLoader
= resourceLoader
;