1 package org
.argeo
.jackrabbit
;
3 import java
.io
.ByteArrayInputStream
;
4 import java
.io
.InputStream
;
5 import java
.io
.InputStreamReader
;
8 import java
.util
.ArrayList
;
9 import java
.util
.Arrays
;
10 import java
.util
.HashMap
;
11 import java
.util
.List
;
14 import javax
.jcr
.Node
;
15 import javax
.jcr
.NodeIterator
;
16 import javax
.jcr
.Repository
;
17 import javax
.jcr
.Session
;
18 import javax
.jcr
.nodetype
.NodeType
;
20 import org
.apache
.commons
.io
.FilenameUtils
;
21 import org
.apache
.commons
.io
.IOUtils
;
22 import org
.apache
.commons
.logging
.Log
;
23 import org
.apache
.commons
.logging
.LogFactory
;
24 import org
.apache
.jackrabbit
.commons
.NamespaceHelper
;
25 import org
.apache
.jackrabbit
.commons
.cnd
.CndImporter
;
26 import org
.argeo
.ArgeoException
;
27 import org
.argeo
.jcr
.ArgeoJcrConstants
;
28 import org
.argeo
.jcr
.ArgeoNames
;
29 import org
.argeo
.jcr
.ArgeoTypes
;
30 import org
.argeo
.jcr
.JcrUtils
;
31 import org
.argeo
.util
.security
.DigestUtils
;
32 import org
.osgi
.framework
.Bundle
;
33 import org
.osgi
.framework
.BundleContext
;
34 import org
.osgi
.framework
.ServiceReference
;
35 import org
.osgi
.service
.packageadmin
.ExportedPackage
;
36 import org
.osgi
.service
.packageadmin
.PackageAdmin
;
38 public class JackrabbitDataModel
{
39 private final static Log log
= LogFactory
.getLog(JackrabbitDataModel
.class);
40 private final static String DIGEST_ALGORITHM
= "MD5";
41 final static String
[] DEFAULT_CNDS
= { "/org/argeo/jcr/argeo.cnd", "/org/argeo/cms/cms.cnd" };
44 /** Node type definitions in CND format */
45 private List
<String
> cndFiles
= new ArrayList
<String
>();
47 * Always import CNDs. Useful during development of new data models. In
48 * production, explicit migration processes should be used.
50 private Boolean forceCndImport
= true;
52 /** Namespaces to register: key is prefix, value namespace */
53 private Map
<String
, String
> namespaces
= new HashMap
<String
, String
>();
55 private final BundleContext bc
;
57 public JackrabbitDataModel(BundleContext bc
) {
62 * Import declared node type definitions and register namespaces. Tries to
63 * update the node definitions if they have changed. In case of failures an
64 * error will be logged but no exception will be thrown.
66 public void prepareDataModel(Repository repository
) {
67 cndFiles
= Arrays
.asList(DEFAULT_CNDS
);
68 if ((cndFiles
== null || cndFiles
.size() == 0) && (namespaces
== null || namespaces
.size() == 0))
71 Session session
= null;
73 session
= repository
.login();
74 // register namespaces
75 if (namespaces
.size() > 0) {
76 NamespaceHelper namespaceHelper
= new NamespaceHelper(session
);
77 namespaceHelper
.registerNamespaces(namespaces
);
80 // load CND files from classpath or as URL
81 for (String resUrl
: cndFiles
) {
82 processCndFile(session
, resUrl
);
84 } catch (Exception e
) {
85 JcrUtils
.discardQuietly(session
);
86 throw new ArgeoException("Cannot import node type definitions " + cndFiles
, e
);
88 JcrUtils
.logoutQuietly(session
);
93 protected void processCndFile(Session session
, String resUrl
) {
96 // check existing data model nodes
97 new NamespaceHelper(session
).registerNamespace(ArgeoNames
.ARGEO
, ArgeoNames
.ARGEO_NAMESPACE
);
98 if (!session
.itemExists(ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
))
99 JcrUtils
.mkdirs(session
, ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
);
100 Node dataModels
= session
.getNode(ArgeoJcrConstants
.DATA_MODELS_BASE_PATH
);
101 NodeIterator it
= dataModels
.getNodes();
102 Node dataModel
= null;
103 while (it
.hasNext()) {
104 Node node
= it
.nextNode();
105 if (node
.getProperty(ArgeoNames
.ARGEO_URI
).getString().equals(resUrl
)) {
111 Bundle bundle
= findDataModelBundle(resUrl
);
113 byte[] cndContent
= readCndContent(resUrl
);
114 String newDigest
= DigestUtils
.digest(DIGEST_ALGORITHM
, cndContent
);
116 String currentVersion
= null;
117 if (dataModel
!= null) {
118 currentVersion
= dataModel
.getProperty(ArgeoNames
.ARGEO_DATA_MODEL_VERSION
).getString();
119 if (dataModel
.hasNode(Node
.JCR_CONTENT
)) {
120 String oldDigest
= JcrUtils
.checksumFile(dataModel
, DIGEST_ALGORITHM
);
121 if (oldDigest
.equals(newDigest
)) {
122 if (log
.isTraceEnabled())
123 log
.trace("Data model " + resUrl
+ " hasn't changed, keeping version " + currentVersion
);
129 if (dataModel
!= null && !forceCndImport
) {
131 "Data model " + resUrl
+ " has changed since version " + currentVersion
133 ?
": version " + bundle
.getVersion() + ", bundle " + bundle
.getSymbolicName()
138 reader
= new InputStreamReader(new ByteArrayInputStream(cndContent
));
139 // actually imports the CND
141 CndImporter
.registerNodeTypes(reader
, session
, true);
142 } catch (Exception e
) {
143 log
.error("Cannot import data model " + resUrl
, e
);
147 if (dataModel
!= null && !dataModel
.isNodeType(NodeType
.NT_FILE
)) {
152 // FIXME: what if argeo.cnd would not be the first called on
153 // a new repo? argeo:dataModel would not be found
154 String fileName
= FilenameUtils
.getName(resUrl
);
155 if (dataModel
== null) {
156 dataModel
= dataModels
.addNode(fileName
, NodeType
.NT_FILE
);
157 dataModel
.addNode(Node
.JCR_CONTENT
, NodeType
.NT_RESOURCE
);
158 dataModel
.addMixin(ArgeoTypes
.ARGEO_DATA_MODEL
);
159 dataModel
.setProperty(ArgeoNames
.ARGEO_URI
, resUrl
);
161 session
.getWorkspace().getVersionManager().checkout(dataModel
.getPath());
164 dataModel
.setProperty(ArgeoNames
.ARGEO_DATA_MODEL_VERSION
, bundle
.getVersion().toString());
166 dataModel
.setProperty(ArgeoNames
.ARGEO_DATA_MODEL_VERSION
, "0.0.0");
167 JcrUtils
.copyBytesAsFile(dataModel
.getParent(), fileName
, cndContent
);
168 JcrUtils
.updateLastModified(dataModel
);
170 session
.getWorkspace().getVersionManager().checkin(dataModel
.getPath());
172 if (currentVersion
== null)
174 "Data model " + resUrl
176 ?
", version " + bundle
.getVersion() + ", bundle " + bundle
.getSymbolicName()
180 "Data model " + resUrl
+ " updated from version " + currentVersion
182 ?
", version " + bundle
.getVersion() + ", bundle " + bundle
.getSymbolicName()
184 } catch (Exception e
) {
185 throw new ArgeoException("Cannot process data model " + resUrl
, e
);
187 IOUtils
.closeQuietly(reader
);
191 protected byte[] readCndContent(String resUrl
) {
192 BundleContext bundleContext
= bc
;
193 InputStream in
= null;
197 if (bundleContext
!= null && resUrl
.startsWith("classpath:")) {
198 resUrl
= resUrl
.substring("classpath:".length());
200 } else if (resUrl
.indexOf(':') < 0) {
201 if (!resUrl
.startsWith("/")) {
202 resUrl
= "/" + resUrl
;
203 log
.warn("Classpath should start with '/'");
212 // if (bundleContext != null) {
213 Bundle currentBundle
= bundleContext
.getBundle();
214 url
= currentBundle
.getResource(resUrl
);
216 // resUrl = "classpath:" + resUrl;
219 } else if (!resUrl
.startsWith("classpath:")) {
220 url
= new URL(resUrl
);
224 in
= url
.openStream();
225 // } else if (resourceLoader != null) {
226 // Resource res = resourceLoader.getResource(resUrl);
227 // in = res.getInputStream();
228 // url = res.getURL();
230 throw new ArgeoException(
231 "No " + resUrl
+ " in the classpath," + " make sure the containing" + " package is visible.");
234 return IOUtils
.toByteArray(in
);
235 } catch (Exception e
) {
236 throw new ArgeoException("Cannot read CND from " + resUrl
, e
);
238 IOUtils
.closeQuietly(in
);
245 /** Find which OSGi bundle provided the data model resource */
246 protected Bundle
findDataModelBundle(String resUrl
) {
247 BundleContext bundleContext
= bc
;
248 if (bundleContext
== null)
251 if (resUrl
.startsWith("/"))
252 resUrl
= resUrl
.substring(1);
253 String pkg
= resUrl
.substring(0, resUrl
.lastIndexOf('/')).replace('/', '.');
254 ServiceReference
<PackageAdmin
> paSr
= bundleContext
.getServiceReference(PackageAdmin
.class);
255 PackageAdmin packageAdmin
= (PackageAdmin
) bundleContext
.getService(paSr
);
257 // find exported package
258 ExportedPackage exportedPackage
= null;
259 ExportedPackage
[] exportedPackages
= packageAdmin
.getExportedPackages(pkg
);
260 if (exportedPackages
== null)
261 throw new ArgeoException("No exported package found for " + pkg
);
262 for (ExportedPackage ep
: exportedPackages
) {
263 for (Bundle b
: ep
.getImportingBundles()) {
264 if (b
.getBundleId() == bundleContext
.getBundle().getBundleId()) {
265 exportedPackage
= ep
;
271 Bundle exportingBundle
= null;
272 if (exportedPackage
!= null) {
273 exportingBundle
= exportedPackage
.getExportingBundle();
275 // assume this is in the same bundle
276 exportingBundle
= bundleContext
.getBundle();
277 // throw new ArgeoException("No OSGi exporting package found for "
280 return exportingBundle
;