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
.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
.Credentials
;
28 import javax
.jcr
.Node
;
29 import javax
.jcr
.NodeIterator
;
30 import javax
.jcr
.Repository
;
31 import javax
.jcr
.Session
;
33 import org
.apache
.commons
.io
.FilenameUtils
;
34 import org
.apache
.commons
.io
.IOUtils
;
35 import org
.apache
.commons
.logging
.Log
;
36 import org
.apache
.commons
.logging
.LogFactory
;
37 import org
.apache
.jackrabbit
.commons
.NamespaceHelper
;
38 import org
.apache
.jackrabbit
.commons
.cnd
.CndImporter
;
39 import org
.argeo
.ArgeoException
;
40 import org
.argeo
.jcr
.ArgeoJcrConstants
;
41 import org
.argeo
.jcr
.ArgeoNames
;
42 import org
.argeo
.jcr
.ArgeoTypes
;
43 import org
.argeo
.jcr
.JcrRepositoryWrapper
;
44 import org
.argeo
.jcr
.JcrUtils
;
45 import org
.osgi
.framework
.Bundle
;
46 import org
.osgi
.framework
.BundleContext
;
47 import org
.osgi
.framework
.ServiceReference
;
48 import org
.osgi
.service
.packageadmin
.ExportedPackage
;
49 import org
.osgi
.service
.packageadmin
.PackageAdmin
;
50 import org
.springframework
.context
.ResourceLoaderAware
;
51 import org
.springframework
.core
.io
.Resource
;
52 import org
.springframework
.core
.io
.ResourceLoader
;
55 * Wrapper around a Jackrabbit repository which allows to simplify configuration
56 * and intercept some actions. It exposes itself as a {@link Repository}.
58 public class JackrabbitWrapper
extends JcrRepositoryWrapper
implements
60 private Log log
= LogFactory
.getLog(JackrabbitWrapper
.class);
63 private ResourceLoader resourceLoader
;
66 /** Node type definitions in CND format */
67 private List
<String
> cndFiles
= new ArrayList
<String
>();
69 * Always import CNDs. Useful during development of new data models. In
70 * production, explicit migration processes should be used.
72 private Boolean forceCndImport
= false;
74 /** Namespaces to register: key is prefix, value namespace */
75 private Map
<String
, String
> namespaces
= new HashMap
<String
, String
>();
77 private BundleContext bundleContext
;
80 * Explicitly set admin credentials used in initialization. Useful for
81 * testing, in real applications authentication is rather dealt with
84 private Credentials adminCredentials
= null;
87 * Empty constructor, {@link #init()} should be called after properties have
90 public JackrabbitWrapper() {
103 * Import declared node type definitions and register namespaces. Tries to
104 * update the node definitions if they have changed. In case of failures an
105 * error will be logged but no exception will be thrown.
107 protected void prepareDataModel() {
108 if ((cndFiles
== null || cndFiles
.size() == 0)
109 && (namespaces
== null || namespaces
.size() == 0))
112 Session session
= null;
114 session
= login(adminCredentials
);
115 // register namespaces
116 if (namespaces
.size() > 0) {
117 NamespaceHelper namespaceHelper
= new NamespaceHelper(session
);
118 namespaceHelper
.registerNamespaces(namespaces
);
121 // load CND files from classpath or as URL
122 for (String resUrl
: cndFiles
) {
124 Bundle dataModelBundle
= null;
126 {// TODO put this in a separate method
129 if (bundleContext
!= null
130 && resUrl
.startsWith("classpath:")) {
131 resUrl
= resUrl
.substring("classpath:".length());
133 } else if (resUrl
.indexOf(':') < 0) {
134 if (!resUrl
.startsWith("/")) {
135 resUrl
= "/" + resUrl
;
136 log
.warn("Classpath should start with '/'");
138 // resUrl = "classpath:" + resUrl;
145 if (bundleContext
!= null) {
146 Bundle currentBundle
= bundleContext
.getBundle();
147 url
= currentBundle
.getResource(resUrl
);
148 if (url
!= null) {// found
149 dataModelBundle
= findDataModelBundle(resUrl
);
152 resUrl
= "classpath:" + resUrl
;
155 // getClass().getClassLoader().getResource(resUrl);
157 // url = Thread.currentThread()
158 // .getContextClassLoader()
159 // .getResource(resUrl);
161 } else if (!resUrl
.startsWith("classpath:")) {
162 url
= new URL(resUrl
);
167 // check existing data model nodes
168 new NamespaceHelper(session
).registerNamespace(
169 ArgeoNames
.ARGEO
, ArgeoNames
.ARGEO_NAMESPACE
);
171 .itemExists(ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
))
172 JcrUtils
.mkdirs(session
,
173 ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
);
174 Node dataModels
= session
175 .getNode(ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
);
176 NodeIterator it
= dataModels
.getNodes();
177 Node dataModel
= null;
178 while (it
.hasNext()) {
179 Node node
= it
.nextNode();
180 if (node
.getProperty(ArgeoNames
.ARGEO_URI
).getString()
187 // does nothing if data model already registered
188 if (dataModel
!= null && !forceCndImport
) {
189 if (dataModelBundle
!= null) {
190 String version
= dataModel
.getProperty(
191 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
)
193 String dataModelBundleVersion
= dataModelBundle
194 .getVersion().toString();
195 if (!version
.equals(dataModelBundleVersion
)) {
196 log
.warn("Data model with version "
197 + dataModelBundleVersion
198 + " available, current version is "
202 // do not implicitly update
206 InputStream in
= null;
207 Reader reader
= null;
210 in
= url
.openStream();
211 } else if (resourceLoader
!= null) {
212 Resource res
= resourceLoader
.getResource(resUrl
);
213 in
= res
.getInputStream();
216 throw new ArgeoException("No " + resUrl
217 + " in the classpath,"
218 + " make sure the containing"
219 + " package is visible.");
222 reader
= new InputStreamReader(in
);
223 // actually imports the CND
224 CndImporter
.registerNodeTypes(reader
, session
, true);
226 // FIXME: what if argeo.cnd would not be the first called on
227 // a new repo? argeo:dataModel would not be found
228 String fileName
= FilenameUtils
.getName(url
.getPath());
229 if (dataModel
== null) {
230 dataModel
= dataModels
.addNode(fileName
);
231 dataModel
.addMixin(ArgeoTypes
.ARGEO_DATA_MODEL
);
232 dataModel
.setProperty(ArgeoNames
.ARGEO_URI
, resUrl
);
234 session
.getWorkspace().getVersionManager()
235 .checkout(dataModel
.getPath());
237 if (dataModelBundle
!= null)
238 dataModel
.setProperty(
239 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
,
240 dataModelBundle
.getVersion().toString());
242 dataModel
.setProperty(
243 ArgeoNames
.ARGEO_DATA_MODEL_VERSION
, "0.0.0");
244 JcrUtils
.updateLastModified(dataModel
);
246 session
.getWorkspace().getVersionManager()
247 .checkin(dataModel
.getPath());
249 IOUtils
.closeQuietly(in
);
250 IOUtils
.closeQuietly(reader
);
253 if (log
.isDebugEnabled())
254 log
.debug("Data model "
256 + (dataModelBundle
!= null ?
", version "
257 + dataModelBundle
.getVersion()
259 + dataModelBundle
.getSymbolicName() : ""));
261 } catch (Exception e
) {
262 JcrUtils
.discardQuietly(session
);
263 throw new ArgeoException("Cannot import node type definitions "
266 JcrUtils
.logoutQuietly(session
);
272 * REPOSITORY INTERCEPTOR
278 /** Find which OSGi bundle provided the data model resource */
279 protected Bundle
findDataModelBundle(String resUrl
) {
280 if (resUrl
.startsWith("/"))
281 resUrl
= resUrl
.substring(1);
282 String pkg
= resUrl
.substring(0, resUrl
.lastIndexOf('/')).replace('/',
284 ServiceReference paSr
= bundleContext
285 .getServiceReference(PackageAdmin
.class.getName());
286 PackageAdmin packageAdmin
= (PackageAdmin
) bundleContext
289 // find exported package
290 ExportedPackage exportedPackage
= null;
291 ExportedPackage
[] exportedPackages
= packageAdmin
292 .getExportedPackages(pkg
);
293 if (exportedPackages
== null)
294 throw new ArgeoException("No exported package found for " + pkg
);
295 for (ExportedPackage ep
: exportedPackages
) {
296 for (Bundle b
: ep
.getImportingBundles()) {
297 if (b
.getBundleId() == bundleContext
.getBundle().getBundleId()) {
298 exportedPackage
= ep
;
304 Bundle exportingBundle
= null;
305 if (exportedPackage
!= null) {
306 exportingBundle
= exportedPackage
.getExportingBundle();
308 throw new ArgeoException("No OSGi exporting package found for "
311 return exportingBundle
;
317 public void setNamespaces(Map
<String
, String
> namespaces
) {
318 this.namespaces
= namespaces
;
321 public void setCndFiles(List
<String
> cndFiles
) {
322 this.cndFiles
= cndFiles
;
325 public void setBundleContext(BundleContext bundleContext
) {
326 this.bundleContext
= bundleContext
;
329 public void setForceCndImport(Boolean forceCndUpdate
) {
330 this.forceCndImport
= forceCndUpdate
;
333 public void setResourceLoader(ResourceLoader resourceLoader
) {
334 this.resourceLoader
= resourceLoader
;
337 public void setAdminCredentials(Credentials adminCredentials
) {
338 this.adminCredentials
= adminCredentials
;