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
.InputStream
;
19 import java
.io
.InputStreamReader
;
20 import java
.io
.Reader
;
22 import java
.util
.ArrayList
;
23 import java
.util
.HashMap
;
24 import java
.util
.List
;
27 import javax
.jcr
.Node
;
28 import javax
.jcr
.NodeIterator
;
29 import javax
.jcr
.Repository
;
30 import javax
.jcr
.Session
;
32 import org
.apache
.commons
.io
.FilenameUtils
;
33 import org
.apache
.commons
.io
.IOUtils
;
34 import org
.apache
.commons
.logging
.Log
;
35 import org
.apache
.commons
.logging
.LogFactory
;
36 import org
.apache
.jackrabbit
.commons
.NamespaceHelper
;
37 import org
.apache
.jackrabbit
.commons
.cnd
.CndImporter
;
38 import org
.argeo
.ArgeoException
;
39 import org
.argeo
.jcr
.ArgeoJcrConstants
;
40 import org
.argeo
.jcr
.ArgeoNames
;
41 import org
.argeo
.jcr
.ArgeoTypes
;
42 import org
.argeo
.jcr
.JcrRepositoryWrapper
;
43 import org
.argeo
.jcr
.JcrUtils
;
44 import org
.osgi
.framework
.Bundle
;
45 import org
.osgi
.framework
.BundleContext
;
46 import org
.osgi
.framework
.ServiceReference
;
47 import org
.osgi
.service
.packageadmin
.ExportedPackage
;
48 import org
.osgi
.service
.packageadmin
.PackageAdmin
;
49 import org
.springframework
.context
.ResourceLoaderAware
;
50 import org
.springframework
.core
.io
.Resource
;
51 import org
.springframework
.core
.io
.ResourceLoader
;
54 * Wrapper around a Jackrabbit repository which allows to simplify configuration
55 * and intercept some actions. It exposes itself as a {@link Repository}.
57 public class JackrabbitWrapper
extends JcrRepositoryWrapper
implements
59 private Log log
= LogFactory
.getLog(JackrabbitWrapper
.class);
62 private ResourceLoader resourceLoader
;
65 /** Node type definitions in CND format */
66 private List
<String
> cndFiles
= new ArrayList
<String
>();
68 * Always import CNDs. Useful during development of new data models. In
69 * production, explicit migration processes should be used.
71 private Boolean forceCndImport
= false;
73 /** Namespaces to register: key is prefix, value namespace */
74 private Map
<String
, String
> namespaces
= new HashMap
<String
, String
>();
76 private BundleContext bundleContext
;
79 * Empty constructor, {@link #init()} should be called after properties have
82 public JackrabbitWrapper() {
95 * Import declared node type definitions and register namespaces. Tries to
96 * update the node definitions if they have changed. In case of failures an
97 * error will be logged but no exception will be thrown.
99 protected void prepareDataModel() {
100 if ((cndFiles
== null || cndFiles
.size() == 0)
101 && (namespaces
== null || namespaces
.size() == 0))
104 Session session
= null;
107 // register namespaces
108 if (namespaces
.size() > 0) {
109 NamespaceHelper namespaceHelper
= new NamespaceHelper(session
);
110 namespaceHelper
.registerNamespaces(namespaces
);
113 // load CND files from classpath or as URL
114 for (String resUrl
: cndFiles
) {
117 if (resUrl
.startsWith("classpath:")) {
118 resUrl
= resUrl
.substring("classpath:".length());
120 } else if (resUrl
.indexOf(':') < 0) {
121 if (!resUrl
.startsWith("/")) {
122 resUrl
= "/" + resUrl
;
123 log
.warn("Classpath should start with '/'");
125 // resUrl = "classpath:" + resUrl;
132 Bundle dataModelBundle
= null;
134 if (bundleContext
!= null) {
135 Bundle currentBundle
= bundleContext
.getBundle();
136 url
= currentBundle
.getResource(resUrl
);
137 if (url
!= null) {// found
138 dataModelBundle
= findDataModelBundle(resUrl
);
141 url
= getClass().getClassLoader().getResource(resUrl
);
143 // url = Thread.currentThread()
144 // .getContextClassLoader()
145 // .getResource(resUrl);
148 url
= new URL(resUrl
);
151 // check existing data model nodes
152 new NamespaceHelper(session
).registerNamespace(
153 ArgeoNames
.ARGEO
, ArgeoNames
.ARGEO_NAMESPACE
);
155 .itemExists(ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
))
156 JcrUtils
.mkdirs(session
,
157 ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
);
158 Node dataModels
= session
159 .getNode(ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
);
160 NodeIterator it
= dataModels
.getNodes();
161 Node dataModel
= null;
162 while (it
.hasNext()) {
163 Node node
= it
.nextNode();
164 if (node
.getProperty(ArgeoNames
.ARGEO_URI
).getString()
171 // does nothing if data model already registered
172 if (dataModel
!= null && !forceCndImport
) {
173 if (dataModelBundle
!= null) {
174 String version
= dataModel
.getProperty(
175 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
)
177 String dataModelBundleVersion
= dataModelBundle
178 .getVersion().toString();
179 if (!version
.equals(dataModelBundleVersion
)) {
180 log
.warn("Data model with version "
181 + dataModelBundleVersion
182 + " available, current version is "
186 // do not implicitly update
190 InputStream in
= null;
191 Reader reader
= null;
194 in
= url
.openStream();
195 } else if (resourceLoader
!= null) {
196 Resource res
= resourceLoader
.getResource(resUrl
);
197 in
= res
.getInputStream();
200 throw new ArgeoException("No " + resUrl
201 + " in the classpath,"
202 + " make sure the containing"
203 + " package is visible.");
206 reader
= new InputStreamReader(in
);
207 // actually imports the CND
208 CndImporter
.registerNodeTypes(reader
, session
, true);
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(url
.getPath());
213 if (dataModel
== null) {
214 dataModel
= dataModels
.addNode(fileName
);
215 dataModel
.addMixin(ArgeoTypes
.ARGEO_DATA_MODEL
);
216 dataModel
.setProperty(ArgeoNames
.ARGEO_URI
, resUrl
);
218 session
.getWorkspace().getVersionManager()
219 .checkout(dataModel
.getPath());
221 if (dataModelBundle
!= null)
222 dataModel
.setProperty(
223 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
,
224 dataModelBundle
.getVersion().toString());
226 dataModel
.setProperty(
227 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
, "0.0.0");
228 JcrUtils
.updateLastModified(dataModel
);
230 session
.getWorkspace().getVersionManager()
231 .checkin(dataModel
.getPath());
233 IOUtils
.closeQuietly(in
);
234 IOUtils
.closeQuietly(reader
);
237 if (log
.isDebugEnabled())
238 log
.debug("Data model "
240 + (dataModelBundle
!= null ?
", version "
241 + dataModelBundle
.getVersion()
243 + dataModelBundle
.getSymbolicName() : ""));
245 } catch (Exception e
) {
246 JcrUtils
.discardQuietly(session
);
247 throw new ArgeoException("Cannot import node type definitions "
250 JcrUtils
.logoutQuietly(session
);
256 * REPOSITORY INTERCEPTOR
262 /** Find which OSGi bundle provided the data model resource */
263 protected Bundle
findDataModelBundle(String resUrl
) {
264 if (resUrl
.startsWith("/"))
265 resUrl
= resUrl
.substring(1);
266 String pkg
= resUrl
.substring(0, resUrl
.lastIndexOf('/')).replace('/',
268 ServiceReference paSr
= bundleContext
269 .getServiceReference(PackageAdmin
.class.getName());
270 PackageAdmin packageAdmin
= (PackageAdmin
) bundleContext
273 // find exported package
274 ExportedPackage exportedPackage
= null;
275 ExportedPackage
[] exportedPackages
= packageAdmin
276 .getExportedPackages(pkg
);
277 if (exportedPackages
== null)
278 throw new ArgeoException("No exported package found for " + pkg
);
279 for (ExportedPackage ep
: exportedPackages
) {
280 for (Bundle b
: ep
.getImportingBundles()) {
281 if (b
.getBundleId() == bundleContext
.getBundle().getBundleId()) {
282 exportedPackage
= ep
;
288 Bundle exportingBundle
= null;
289 if (exportedPackage
!= null) {
290 exportingBundle
= exportedPackage
.getExportingBundle();
292 throw new ArgeoException("No OSGi exporting package found for "
295 return exportingBundle
;
301 public void setNamespaces(Map
<String
, String
> namespaces
) {
302 this.namespaces
= namespaces
;
305 public void setCndFiles(List
<String
> cndFiles
) {
306 this.cndFiles
= cndFiles
;
309 public void setBundleContext(BundleContext bundleContext
) {
310 this.bundleContext
= bundleContext
;
313 public void setForceCndImport(Boolean forceCndUpdate
) {
314 this.forceCndImport
= forceCndUpdate
;
317 public void setResourceLoader(ResourceLoader resourceLoader
) {
318 this.resourceLoader
= resourceLoader
;