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
.Node
;
30 import javax
.jcr
.NodeIterator
;
31 import javax
.jcr
.Repository
;
32 import javax
.jcr
.Session
;
33 import javax
.jcr
.nodetype
.NodeType
;
35 import org
.apache
.commons
.io
.FilenameUtils
;
36 import org
.apache
.commons
.io
.IOUtils
;
37 import org
.apache
.commons
.logging
.Log
;
38 import org
.apache
.commons
.logging
.LogFactory
;
39 import org
.apache
.jackrabbit
.commons
.NamespaceHelper
;
40 import org
.apache
.jackrabbit
.commons
.cnd
.CndImporter
;
41 import org
.argeo
.ArgeoException
;
42 import org
.argeo
.jcr
.ArgeoJcrConstants
;
43 import org
.argeo
.jcr
.ArgeoNames
;
44 import org
.argeo
.jcr
.ArgeoTypes
;
45 import org
.argeo
.jcr
.JcrRepositoryWrapper
;
46 import org
.argeo
.jcr
.JcrUtils
;
47 import org
.argeo
.util
.security
.DigestUtils
;
48 import org
.osgi
.framework
.Bundle
;
49 import org
.osgi
.framework
.BundleContext
;
50 import org
.osgi
.framework
.ServiceReference
;
51 import org
.osgi
.service
.packageadmin
.ExportedPackage
;
52 import org
.osgi
.service
.packageadmin
.PackageAdmin
;
53 import org
.springframework
.context
.ResourceLoaderAware
;
54 import org
.springframework
.core
.io
.Resource
;
55 import org
.springframework
.core
.io
.ResourceLoader
;
58 * Wrapper around a Jackrabbit repository which allows to simplify configuration
59 * and intercept some actions. It exposes itself as a {@link Repository}.
61 public class JackrabbitWrapper
extends JcrRepositoryWrapper
implements
63 private final static Log log
= LogFactory
.getLog(JackrabbitWrapper
.class);
64 private final static String DIGEST_ALGORITHM
= "MD5";
67 private ResourceLoader resourceLoader
;
70 /** Node type definitions in CND format */
71 private List
<String
> cndFiles
= new ArrayList
<String
>();
73 * Always import CNDs. Useful during development of new data models. In
74 * production, explicit migration processes should be used.
76 private Boolean forceCndImport
= true;
78 /** Namespaces to register: key is prefix, value namespace */
79 private Map
<String
, String
> namespaces
= new HashMap
<String
, String
>();
81 private BundleContext bundleContext
;
84 * Explicitly set admin credentials used in initialization. Useful for
85 * testing, in real applications authentication is rather dealt with
88 private Credentials adminCredentials
= null;
91 * Empty constructor, {@link #init()} should be called after properties have
94 public JackrabbitWrapper() {
107 * Import declared node type definitions and register namespaces. Tries to
108 * update the node definitions if they have changed. In case of failures an
109 * error will be logged but no exception will be thrown.
111 protected void prepareDataModel() {
112 if ((cndFiles
== null || cndFiles
.size() == 0)
113 && (namespaces
== null || namespaces
.size() == 0))
116 Session session
= null;
118 session
= login(adminCredentials
);
119 // register namespaces
120 if (namespaces
.size() > 0) {
121 NamespaceHelper namespaceHelper
= new NamespaceHelper(session
);
122 namespaceHelper
.registerNamespaces(namespaces
);
125 // load CND files from classpath or as URL
126 for (String resUrl
: cndFiles
) {
127 processCndFile(session
, resUrl
);
129 } catch (Exception e
) {
130 JcrUtils
.discardQuietly(session
);
131 throw new ArgeoException("Cannot import node type definitions "
134 JcrUtils
.logoutQuietly(session
);
139 protected void processCndFile(Session session
, String resUrl
) {
140 Reader reader
= null;
142 // check existing data model nodes
143 new NamespaceHelper(session
).registerNamespace(ArgeoNames
.ARGEO
,
144 ArgeoNames
.ARGEO_NAMESPACE
);
145 if (!session
.itemExists(ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
))
146 JcrUtils
.mkdirs(session
,
147 ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
);
148 Node dataModels
= session
149 .getNode(ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
);
150 NodeIterator it
= dataModels
.getNodes();
151 Node dataModel
= null;
152 while (it
.hasNext()) {
153 Node node
= it
.nextNode();
154 if (node
.getProperty(ArgeoNames
.ARGEO_URI
).getString()
161 byte[] cndContent
= readCndContent(resUrl
);
162 String newDigest
= DigestUtils
.digest(DIGEST_ALGORITHM
, cndContent
);
163 Bundle bundle
= findDataModelBundle(resUrl
);
165 String currentVersion
= null;
166 if (dataModel
!= null) {
167 currentVersion
= dataModel
.getProperty(
168 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
).getString();
169 if (dataModel
.hasNode(Node
.JCR_CONTENT
)) {
170 String oldDigest
= JcrUtils
.checksumFile(dataModel
,
172 if (oldDigest
.equals(newDigest
)) {
173 if (log
.isDebugEnabled())
174 log
.debug("Data model " + resUrl
175 + " hasn't changed, keeping version "
182 if (dataModel
!= null && !forceCndImport
) {
183 log
.info("Data model "
185 + " has changed since version "
187 + (bundle
!= null ?
": version " + bundle
.getVersion()
188 + ", bundle " + bundle
.getSymbolicName() : ""));
192 reader
= new InputStreamReader(new ByteArrayInputStream(cndContent
));
193 // actually imports the CND
194 CndImporter
.registerNodeTypes(reader
, session
, true);
196 if (dataModel
!= null && !dataModel
.isNodeType(NodeType
.NT_FILE
)) {
201 // FIXME: what if argeo.cnd would not be the first called on
202 // a new repo? argeo:dataModel would not be found
203 String fileName
= FilenameUtils
.getName(resUrl
);
204 if (dataModel
== null) {
205 dataModel
= dataModels
.addNode(fileName
, NodeType
.NT_FILE
);
206 dataModel
.addNode(Node
.JCR_CONTENT
, NodeType
.NT_RESOURCE
);
207 dataModel
.addMixin(ArgeoTypes
.ARGEO_DATA_MODEL
);
208 dataModel
.setProperty(ArgeoNames
.ARGEO_URI
, resUrl
);
210 session
.getWorkspace().getVersionManager()
211 .checkout(dataModel
.getPath());
214 dataModel
.setProperty(ArgeoNames
.ARGEO_DATA_MODEL_VERSION
,
215 bundle
.getVersion().toString());
217 dataModel
.setProperty(ArgeoNames
.ARGEO_DATA_MODEL_VERSION
,
219 JcrUtils
.copyBytesAsFile(dataModel
.getParent(), fileName
,
221 JcrUtils
.updateLastModified(dataModel
);
223 session
.getWorkspace().getVersionManager()
224 .checkin(dataModel
.getPath());
226 if (currentVersion
== null)
227 log
.info("Data model "
229 + (bundle
!= null ?
", version " + bundle
.getVersion()
230 + ", bundle " + bundle
.getSymbolicName() : ""));
232 log
.info("Data model "
234 + " updated from version "
236 + (bundle
!= null ?
", version " + bundle
.getVersion()
237 + ", bundle " + bundle
.getSymbolicName() : ""));
238 } catch (Exception e
) {
239 throw new ArgeoException("Cannot process data model " + resUrl
, e
);
241 IOUtils
.closeQuietly(reader
);
245 protected byte[] readCndContent(String resUrl
) {
246 InputStream in
= null;
250 if (bundleContext
!= null && resUrl
.startsWith("classpath:")) {
251 resUrl
= resUrl
.substring("classpath:".length());
253 } else if (resUrl
.indexOf(':') < 0) {
254 if (!resUrl
.startsWith("/")) {
255 resUrl
= "/" + resUrl
;
256 log
.warn("Classpath should start with '/'");
265 if (bundleContext
!= null) {
266 Bundle currentBundle
= bundleContext
.getBundle();
267 url
= currentBundle
.getResource(resUrl
);
269 resUrl
= "classpath:" + resUrl
;
272 } else if (!resUrl
.startsWith("classpath:")) {
273 url
= new URL(resUrl
);
277 in
= url
.openStream();
278 } else if (resourceLoader
!= null) {
279 Resource res
= resourceLoader
.getResource(resUrl
);
280 in
= res
.getInputStream();
283 throw new ArgeoException("No " + resUrl
+ " in the classpath,"
284 + " make sure the containing" + " package is visible.");
287 return IOUtils
.toByteArray(in
);
288 } catch (Exception e
) {
289 throw new ArgeoException("Cannot read CND from " + resUrl
, e
);
291 IOUtils
.closeQuietly(in
);
296 * REPOSITORY INTERCEPTOR
302 /** Find which OSGi bundle provided the data model resource */
303 protected Bundle
findDataModelBundle(String resUrl
) {
304 if (bundleContext
== null)
307 if (resUrl
.startsWith("/"))
308 resUrl
= resUrl
.substring(1);
309 String pkg
= resUrl
.substring(0, resUrl
.lastIndexOf('/')).replace('/',
311 ServiceReference paSr
= bundleContext
312 .getServiceReference(PackageAdmin
.class.getName());
313 PackageAdmin packageAdmin
= (PackageAdmin
) bundleContext
316 // find exported package
317 ExportedPackage exportedPackage
= null;
318 ExportedPackage
[] exportedPackages
= packageAdmin
319 .getExportedPackages(pkg
);
320 if (exportedPackages
== null)
321 throw new ArgeoException("No exported package found for " + pkg
);
322 for (ExportedPackage ep
: exportedPackages
) {
323 for (Bundle b
: ep
.getImportingBundles()) {
324 if (b
.getBundleId() == bundleContext
.getBundle().getBundleId()) {
325 exportedPackage
= ep
;
331 Bundle exportingBundle
= null;
332 if (exportedPackage
!= null) {
333 exportingBundle
= exportedPackage
.getExportingBundle();
335 throw new ArgeoException("No OSGi exporting package found for "
338 return exportingBundle
;
344 public void setNamespaces(Map
<String
, String
> namespaces
) {
345 this.namespaces
= namespaces
;
348 public void setCndFiles(List
<String
> cndFiles
) {
349 this.cndFiles
= cndFiles
;
352 public void setBundleContext(BundleContext bundleContext
) {
353 this.bundleContext
= bundleContext
;
356 public void setForceCndImport(Boolean forceCndUpdate
) {
357 this.forceCndImport
= forceCndUpdate
;
360 public void setResourceLoader(ResourceLoader resourceLoader
) {
361 this.resourceLoader
= resourceLoader
;
364 public void setAdminCredentials(Credentials adminCredentials
) {
365 this.adminCredentials
= adminCredentials
;