2 * Copyright (C) 2007-2012 Argeo GmbH
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
.ByteArrayInputStream
;
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
.List
;
28 import javax
.jcr
.Credentials
;
29 import javax
.jcr
.LoginException
;
30 import javax
.jcr
.NoSuchWorkspaceException
;
31 import javax
.jcr
.Node
;
32 import javax
.jcr
.NodeIterator
;
33 import javax
.jcr
.RepositoryException
;
34 import javax
.jcr
.Session
;
35 import javax
.jcr
.nodetype
.NodeType
;
37 import org
.apache
.commons
.io
.FilenameUtils
;
38 import org
.apache
.commons
.io
.IOUtils
;
39 import org
.apache
.commons
.logging
.Log
;
40 import org
.apache
.commons
.logging
.LogFactory
;
41 import org
.apache
.jackrabbit
.api
.JackrabbitRepository
;
42 import org
.apache
.jackrabbit
.commons
.NamespaceHelper
;
43 import org
.apache
.jackrabbit
.commons
.cnd
.CndImporter
;
44 import org
.argeo
.ArgeoException
;
45 import org
.argeo
.jcr
.ArgeoJcrConstants
;
46 import org
.argeo
.jcr
.ArgeoNames
;
47 import org
.argeo
.jcr
.ArgeoTypes
;
48 import org
.argeo
.jcr
.JcrRepositoryWrapper
;
49 import org
.argeo
.jcr
.JcrUtils
;
50 import org
.argeo
.util
.security
.DigestUtils
;
51 import org
.osgi
.framework
.Bundle
;
52 import org
.osgi
.framework
.BundleContext
;
53 import org
.osgi
.framework
.ServiceReference
;
54 import org
.osgi
.service
.packageadmin
.ExportedPackage
;
55 import org
.osgi
.service
.packageadmin
.PackageAdmin
;
56 import org
.springframework
.context
.ResourceLoaderAware
;
57 import org
.springframework
.core
.io
.Resource
;
58 import org
.springframework
.core
.io
.ResourceLoader
;
61 * Wrapper around a Jackrabbit repository which allows to simplify configuration
62 * and intercept some actions. It exposes itself as a {@link Repository}.
64 @SuppressWarnings("deprecation")
65 public class JackrabbitWrapper
extends JcrRepositoryWrapper
implements
66 JackrabbitRepository
, ResourceLoaderAware
{
67 private final static Log log
= LogFactory
.getLog(JackrabbitWrapper
.class);
68 private final static String DIGEST_ALGORITHM
= "MD5";
71 private ResourceLoader resourceLoader
;
74 /** Node type definitions in CND format */
75 private List
<String
> cndFiles
= new ArrayList
<String
>();
77 * Always import CNDs. Useful during development of new data models. In
78 * production, explicit migration processes should be used.
80 private Boolean forceCndImport
= true;
82 /** Namespaces to register: key is prefix, value namespace */
83 private Map
<String
, String
> namespaces
= new HashMap
<String
, String
>();
85 private BundleContext bundleContext
;
88 * Explicitly set admin credentials used in initialization. Useful for
89 * testing, in real applications authentication is rather dealt with
92 private Credentials adminCredentials
= null;
95 * Empty constructor, {@link #init()} should be called after properties have
98 public JackrabbitWrapper() {
111 * Import declared node type definitions and register namespaces. Tries to
112 * update the node definitions if they have changed. In case of failures an
113 * error will be logged but no exception will be thrown.
115 protected void prepareDataModel() {
116 if ((cndFiles
== null || cndFiles
.size() == 0)
117 && (namespaces
== null || namespaces
.size() == 0))
120 Session session
= null;
122 session
= login(adminCredentials
);
123 // register namespaces
124 if (namespaces
.size() > 0) {
125 NamespaceHelper namespaceHelper
= new NamespaceHelper(session
);
126 namespaceHelper
.registerNamespaces(namespaces
);
129 // load CND files from classpath or as URL
130 for (String resUrl
: cndFiles
) {
131 processCndFile(session
, resUrl
);
133 } catch (Exception e
) {
134 JcrUtils
.discardQuietly(session
);
135 throw new ArgeoException("Cannot import node type definitions "
138 JcrUtils
.logoutQuietly(session
);
143 protected void processCndFile(Session session
, String resUrl
) {
144 Reader reader
= null;
146 // check existing data model nodes
147 new NamespaceHelper(session
).registerNamespace(ArgeoNames
.ARGEO
,
148 ArgeoNames
.ARGEO_NAMESPACE
);
149 if (!session
.itemExists(ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
))
150 JcrUtils
.mkdirs(session
,
151 ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
);
152 Node dataModels
= session
153 .getNode(ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
);
154 NodeIterator it
= dataModels
.getNodes();
155 Node dataModel
= null;
156 while (it
.hasNext()) {
157 Node node
= it
.nextNode();
158 if (node
.getProperty(ArgeoNames
.ARGEO_URI
).getString()
165 byte[] cndContent
= readCndContent(resUrl
);
166 String newDigest
= DigestUtils
.digest(DIGEST_ALGORITHM
, cndContent
);
167 Bundle bundle
= findDataModelBundle(resUrl
);
169 String currentVersion
= null;
170 if (dataModel
!= null) {
171 currentVersion
= dataModel
.getProperty(
172 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
).getString();
173 if (dataModel
.hasNode(Node
.JCR_CONTENT
)) {
174 String oldDigest
= JcrUtils
.checksumFile(dataModel
,
176 if (oldDigest
.equals(newDigest
)) {
177 if (log
.isTraceEnabled())
178 log
.trace("Data model " + resUrl
179 + " hasn't changed, keeping version "
186 if (dataModel
!= null && !forceCndImport
) {
187 log
.info("Data model "
189 + " has changed since version "
191 + (bundle
!= null ?
": version " + bundle
.getVersion()
192 + ", bundle " + bundle
.getSymbolicName() : ""));
196 reader
= new InputStreamReader(new ByteArrayInputStream(cndContent
));
197 // actually imports the CND
199 CndImporter
.registerNodeTypes(reader
, session
, true);
200 } catch (Exception e
) {
201 log
.error("Cannot import data model " + resUrl
, e
);
205 if (dataModel
!= null && !dataModel
.isNodeType(NodeType
.NT_FILE
)) {
210 // FIXME: what if argeo.cnd would not be the first called on
211 // a new repo? argeo:dataModel would not be found
212 String fileName
= FilenameUtils
.getName(resUrl
);
213 if (dataModel
== null) {
214 dataModel
= dataModels
.addNode(fileName
, NodeType
.NT_FILE
);
215 dataModel
.addNode(Node
.JCR_CONTENT
, NodeType
.NT_RESOURCE
);
216 dataModel
.addMixin(ArgeoTypes
.ARGEO_DATA_MODEL
);
217 dataModel
.setProperty(ArgeoNames
.ARGEO_URI
, resUrl
);
219 session
.getWorkspace().getVersionManager()
220 .checkout(dataModel
.getPath());
223 dataModel
.setProperty(ArgeoNames
.ARGEO_DATA_MODEL_VERSION
,
224 bundle
.getVersion().toString());
226 dataModel
.setProperty(ArgeoNames
.ARGEO_DATA_MODEL_VERSION
,
228 JcrUtils
.copyBytesAsFile(dataModel
.getParent(), fileName
,
230 JcrUtils
.updateLastModified(dataModel
);
232 session
.getWorkspace().getVersionManager()
233 .checkin(dataModel
.getPath());
235 if (currentVersion
== null)
236 log
.info("Data model "
238 + (bundle
!= null ?
", version " + bundle
.getVersion()
239 + ", bundle " + bundle
.getSymbolicName() : ""));
241 log
.info("Data model "
243 + " updated from version "
245 + (bundle
!= null ?
", version " + bundle
.getVersion()
246 + ", bundle " + bundle
.getSymbolicName() : ""));
247 } catch (Exception e
) {
248 throw new ArgeoException("Cannot process data model " + resUrl
, e
);
250 IOUtils
.closeQuietly(reader
);
254 protected byte[] readCndContent(String resUrl
) {
255 InputStream in
= null;
259 if (bundleContext
!= null && resUrl
.startsWith("classpath:")) {
260 resUrl
= resUrl
.substring("classpath:".length());
262 } else if (resUrl
.indexOf(':') < 0) {
263 if (!resUrl
.startsWith("/")) {
264 resUrl
= "/" + resUrl
;
265 log
.warn("Classpath should start with '/'");
274 if (bundleContext
!= null) {
275 Bundle currentBundle
= bundleContext
.getBundle();
276 url
= currentBundle
.getResource(resUrl
);
278 resUrl
= "classpath:" + resUrl
;
281 } else if (!resUrl
.startsWith("classpath:")) {
282 url
= new URL(resUrl
);
286 in
= url
.openStream();
287 } else if (resourceLoader
!= null) {
288 Resource res
= resourceLoader
.getResource(resUrl
);
289 in
= res
.getInputStream();
292 throw new ArgeoException("No " + resUrl
+ " in the classpath,"
293 + " make sure the containing" + " package is visible.");
296 return IOUtils
.toByteArray(in
);
297 } catch (Exception e
) {
298 throw new ArgeoException("Cannot read CND from " + resUrl
, e
);
300 IOUtils
.closeQuietly(in
);
305 * JACKRABBIT REPOSITORY IMPLEMENTATION
308 public Session
login(Credentials credentials
, String workspaceName
,
309 Map
<String
, Object
> attributes
) throws LoginException
,
310 NoSuchWorkspaceException
, RepositoryException
{
311 // TODO Auto-generated method stub
316 public void shutdown() {
317 // TODO Auto-generated method stub
324 /** Find which OSGi bundle provided the data model resource */
325 protected Bundle
findDataModelBundle(String resUrl
) {
326 if (bundleContext
== null)
329 if (resUrl
.startsWith("/"))
330 resUrl
= resUrl
.substring(1);
331 String pkg
= resUrl
.substring(0, resUrl
.lastIndexOf('/')).replace('/',
333 ServiceReference
<PackageAdmin
> paSr
= bundleContext
334 .getServiceReference(PackageAdmin
.class);
335 PackageAdmin packageAdmin
= (PackageAdmin
) bundleContext
338 // find exported package
339 ExportedPackage exportedPackage
= null;
340 ExportedPackage
[] exportedPackages
= packageAdmin
341 .getExportedPackages(pkg
);
342 if (exportedPackages
== null)
343 throw new ArgeoException("No exported package found for " + pkg
);
344 for (ExportedPackage ep
: exportedPackages
) {
345 for (Bundle b
: ep
.getImportingBundles()) {
346 if (b
.getBundleId() == bundleContext
.getBundle().getBundleId()) {
347 exportedPackage
= ep
;
353 Bundle exportingBundle
= null;
354 if (exportedPackage
!= null) {
355 exportingBundle
= exportedPackage
.getExportingBundle();
357 // assume this is in the same bundle
358 exportingBundle
= bundleContext
.getBundle();
359 // throw new ArgeoException("No OSGi exporting package found for "
362 return exportingBundle
;
368 public void setNamespaces(Map
<String
, String
> namespaces
) {
369 this.namespaces
= namespaces
;
372 public void setCndFiles(List
<String
> cndFiles
) {
373 this.cndFiles
= cndFiles
;
376 public void setBundleContext(BundleContext bundleContext
) {
377 this.bundleContext
= bundleContext
;
380 protected BundleContext
getBundleContext() {
381 return bundleContext
;
384 public void setForceCndImport(Boolean forceCndUpdate
) {
385 this.forceCndImport
= forceCndUpdate
;
388 public void setResourceLoader(ResourceLoader resourceLoader
) {
389 this.resourceLoader
= resourceLoader
;
392 public void setAdminCredentials(Credentials adminCredentials
) {
393 this.adminCredentials
= adminCredentials
;