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
.Repository
;
37 import javax
.jcr
.RepositoryException
;
38 import javax
.jcr
.Session
;
39 import javax
.jcr
.SimpleCredentials
;
41 import org
.apache
.commons
.io
.IOUtils
;
42 import org
.apache
.commons
.logging
.Log
;
43 import org
.apache
.commons
.logging
.LogFactory
;
44 import org
.apache
.jackrabbit
.api
.JackrabbitRepository
;
45 import org
.apache
.jackrabbit
.commons
.NamespaceHelper
;
46 import org
.apache
.jackrabbit
.commons
.cnd
.CndImporter
;
47 import org
.apache
.jackrabbit
.core
.RepositoryImpl
;
48 import org
.argeo
.ArgeoException
;
49 import org
.argeo
.jcr
.ArgeoNames
;
50 import org
.argeo
.jcr
.JcrUtils
;
51 import org
.argeo
.security
.SystemAuthentication
;
52 import org
.osgi
.framework
.Bundle
;
53 import org
.osgi
.framework
.BundleContext
;
54 import org
.osgi
.framework
.ServiceReference
;
55 import org
.osgi
.service
.packageadmin
.ExportedPackage
;
56 import org
.osgi
.service
.packageadmin
.PackageAdmin
;
57 import org
.springframework
.core
.io
.Resource
;
58 import org
.springframework
.security
.Authentication
;
59 import org
.springframework
.security
.context
.SecurityContextHolder
;
60 import org
.springframework
.security
.providers
.UsernamePasswordAuthenticationToken
;
61 import org
.springframework
.util
.SystemPropertyUtils
;
64 * Wrapper around a Jackrabbit repository which allows to configure it in Spring
65 * and expose it as a {@link Repository}.
67 public class JackrabbitContainer
extends JackrabbitWrapper
{
68 private Log log
= LogFactory
.getLog(JackrabbitContainer
.class);
71 private Credentials remoteSystemCredentials
= null;
74 private Resource configuration
;
75 private Resource variables
;
78 /** Node type definitions in CND format */
79 private List
<String
> cndFiles
= new ArrayList
<String
>();
81 /** Migrations to execute (if not already done) */
82 private Set
<JackrabbitDataModelMigration
> dataModelMigrations
= new HashSet
<JackrabbitDataModelMigration
>();
84 /** Namespaces to register: key is prefix, value namespace */
85 private Map
<String
, String
> namespaces
= new HashMap
<String
, String
>();
87 private BundleContext bundleContext
;
90 * Empty constructor, {@link #init()} should be called after properties have
93 public JackrabbitContainer() {
97 * Convenience constructor for remote, {@link #init()} is called in the
100 public JackrabbitContainer(String uri
, Credentials remoteSystemCredentials
) {
102 setRemoteSystemCredentials(remoteSystemCredentials
);
107 protected void postInitWrapped() {
112 protected void postInitNew() {
116 // apply new CND files after migration
117 if (cndFiles
!= null && cndFiles
.size() > 0)
126 * Import declared node type definitions and register namespaces. Tries to
127 * update the node definitions if they have changed. In case of failures an
128 * error will be logged but no exception will be thrown.
130 protected void prepareDataModel() {
131 // importing node def on remote si currently not supported
135 Session session
= null;
138 // register namespaces
139 if (namespaces
.size() > 0) {
140 NamespaceHelper namespaceHelper
= new NamespaceHelper(session
);
141 namespaceHelper
.registerNamespaces(namespaces
);
143 // load CND files from classpath or as URL
144 for (String resUrl
: cndFiles
) {
146 // if (resUrl.startsWith("classpath:")) {
147 // // resUrl = resUrl.substring("classpath:".length());
149 // } else if (resUrl.indexOf(':') < 0) {
150 // if (!resUrl.startsWith("/")) {
151 // resUrl = "/" + resUrl;
152 // log.warn("Classpath should start with '/'");
154 // resUrl = "classpath:" + resUrl;
157 // classpath = false;
160 if (resUrl
.startsWith("classpath:")) {
161 resUrl
= resUrl
.substring("classpath:".length());
163 } else if (resUrl
.indexOf(':') < 0) {
164 if (!resUrl
.startsWith("/")) {
165 resUrl
= "/" + resUrl
;
166 log
.warn("Classpath should start with '/'");
168 // resUrl = "classpath:" + resUrl;
174 // Resource resource =
175 // resourceLoader.getResource(resUrl);
177 // = classpath ? new ClassPathResource(resUrl) : new
178 // UrlResource(resUrl);
181 Bundle dataModelBundle
= null;
183 if (bundleContext
!= null) {
184 Bundle currentBundle
= bundleContext
.getBundle();
185 url
= currentBundle
.getResource(resUrl
);
186 if (url
!= null) {// found
187 dataModelBundle
= findDataModelBundle(resUrl
);
190 url
= getClass().getClassLoader().getResource(resUrl
);
193 throw new ArgeoException("No " + resUrl
194 + " in the classpath,"
195 + " make sure the containing"
196 + " package is visible.");
199 url
= new URL(resUrl
);
202 Reader reader
= null;
204 reader
= new InputStreamReader(url
.openStream());
205 CndImporter
.registerNodeTypes(reader
, session
, true);
207 IOUtils
.closeQuietly(reader
);
210 if (log
.isDebugEnabled())
211 log
.debug("Data model "
213 + (dataModelBundle
!= null ?
", version "
214 + dataModelBundle
.getVersion()
216 + dataModelBundle
.getSymbolicName() : ""));
218 } catch (Exception e
) {
219 JcrUtils
.discardQuietly(session
);
220 throw new ArgeoException("Cannot import node type definitions "
223 JcrUtils
.logoutQuietly(session
);
228 /** Executes migrations, if needed. */
229 protected void migrate() {
230 // Remote migration not supported
234 // No migration to perform
235 if (dataModelMigrations
.size() == 0)
238 Boolean restartAndClearCaches
= false;
241 Session session
= null;
244 for (JackrabbitDataModelMigration dataModelMigration
: new TreeSet
<JackrabbitDataModelMigration
>(
245 dataModelMigrations
)) {
246 if (dataModelMigration
.migrate(session
)) {
247 restartAndClearCaches
= true;
250 } catch (ArgeoException e
) {
252 } catch (Exception e
) {
253 throw new ArgeoException("Cannot migrate", e
);
255 JcrUtils
.logoutQuietly(session
);
258 // restart repository
259 if (restartAndClearCaches
) {
260 Repository repository
= getRepository();
261 if (repository
instanceof RepositoryImpl
) {
262 JackrabbitDataModelMigration
263 .clearRepositoryCaches(((RepositoryImpl
) repository
)
266 ((JackrabbitRepository
) repository
).shutdown();
267 createJackrabbitRepository();
270 // set data model version
273 } catch (RepositoryException e
) {
274 throw new ArgeoException("Cannot login to migrated repository", e
);
277 for (JackrabbitDataModelMigration dataModelMigration
: new TreeSet
<JackrabbitDataModelMigration
>(
278 dataModelMigrations
)) {
280 if (session
.itemExists(dataModelMigration
281 .getDataModelNodePath())) {
282 Node dataModelNode
= session
.getNode(dataModelMigration
283 .getDataModelNodePath());
284 dataModelNode
.setProperty(
285 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
,
286 dataModelMigration
.getTargetVersion());
289 } catch (Exception e
) {
290 log
.error("Cannot set model version", e
);
293 JcrUtils
.logoutQuietly(session
);
298 * REPOSITORY INTERCEPTOR
300 /** Central login method */
301 public Session
login(Credentials credentials
, String workspaceName
)
302 throws LoginException
, NoSuchWorkspaceException
,
303 RepositoryException
{
305 // retrieve credentials for remote
306 if (credentials
== null && isRemote()) {
307 Authentication authentication
= SecurityContextHolder
.getContext()
308 .getAuthentication();
309 if (authentication
!= null) {
310 if (authentication
instanceof UsernamePasswordAuthenticationToken
) {
311 UsernamePasswordAuthenticationToken upat
= (UsernamePasswordAuthenticationToken
) authentication
;
312 credentials
= new SimpleCredentials(upat
.getName(), upat
313 .getCredentials().toString().toCharArray());
314 } else if ((authentication
instanceof SystemAuthentication
)
315 && remoteSystemCredentials
!= null) {
316 credentials
= remoteSystemCredentials
;
321 return super.login(credentials
, workspaceName
);
329 protected InputStream
readConfiguration() {
331 return configuration
!= null ? configuration
.getInputStream()
333 } catch (IOException e
) {
334 throw new ArgeoException("Cannot read Jackrabbit configuration "
340 protected InputStream
readVariables() {
342 return variables
!= null ? variables
.getInputStream() : null;
343 } catch (IOException e
) {
344 throw new ArgeoException("Cannot read Jackrabbit variables "
350 protected String
resolvePlaceholders(String string
,
351 Map
<String
, String
> variables
) {
352 return SystemPropertyUtils
.resolvePlaceholders(string
);
355 /** Find which OSGi bundle provided the data model resource */
356 protected Bundle
findDataModelBundle(String resUrl
) {
357 if (resUrl
.startsWith("/"))
358 resUrl
= resUrl
.substring(1);
359 String pkg
= resUrl
.substring(0, resUrl
.lastIndexOf('/')).replace('/',
361 ServiceReference paSr
= bundleContext
362 .getServiceReference(PackageAdmin
.class.getName());
363 PackageAdmin packageAdmin
= (PackageAdmin
) bundleContext
366 // find exported package
367 ExportedPackage exportedPackage
= null;
368 ExportedPackage
[] exportedPackages
= packageAdmin
369 .getExportedPackages(pkg
);
370 if (exportedPackages
== null)
371 throw new ArgeoException("No exported package found for " + pkg
);
372 for (ExportedPackage ep
: exportedPackages
) {
373 for (Bundle b
: ep
.getImportingBundles()) {
374 if (b
.getBundleId() == bundleContext
.getBundle().getBundleId()) {
375 exportedPackage
= ep
;
381 Bundle exportingBundle
= null;
382 if (exportedPackage
!= null) {
383 exportingBundle
= exportedPackage
.getExportingBundle();
385 throw new ArgeoException("No OSGi exporting package found for "
388 return exportingBundle
;
394 public void setConfiguration(Resource configuration
) {
395 this.configuration
= configuration
;
398 public void setNamespaces(Map
<String
, String
> namespaces
) {
399 this.namespaces
= namespaces
;
402 public void setCndFiles(List
<String
> cndFiles
) {
403 this.cndFiles
= cndFiles
;
406 public void setVariables(Resource variables
) {
407 this.variables
= variables
;
410 public void setRemoteSystemCredentials(Credentials remoteSystemCredentials
) {
411 this.remoteSystemCredentials
= remoteSystemCredentials
;
414 public void setDataModelMigrations(
415 Set
<JackrabbitDataModelMigration
> dataModelMigrations
) {
416 this.dataModelMigrations
= dataModelMigrations
;
419 public void setBundleContext(BundleContext bundleContext
) {
420 this.bundleContext
= bundleContext
;