]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.server.jcr/src/org/argeo/jackrabbit/JackrabbitDataModel.java
- Introduce WebCmsSession
[lgpl/argeo-commons.git] / org.argeo.server.jcr / src / org / argeo / jackrabbit / JackrabbitDataModel.java
1 package org.argeo.jackrabbit;
2
3 import java.io.ByteArrayInputStream;
4 import java.io.InputStream;
5 import java.io.InputStreamReader;
6 import java.io.Reader;
7 import java.net.URL;
8 import java.util.ArrayList;
9 import java.util.Arrays;
10 import java.util.HashMap;
11 import java.util.List;
12 import java.util.Map;
13
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;
19
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;
37
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", "/org/argeo/jcr/docbook/docbook.cnd" };
42
43 // data model
44 /** Node type definitions in CND format */
45 private List<String> cndFiles = new ArrayList<String>();
46 /**
47 * Always import CNDs. Useful during development of new data models. In
48 * production, explicit migration processes should be used.
49 */
50 private Boolean forceCndImport = true;
51
52 /** Namespaces to register: key is prefix, value namespace */
53 private Map<String, String> namespaces = new HashMap<String, String>();
54
55 private final BundleContext bc;
56
57 public JackrabbitDataModel(BundleContext bc) {
58 this.bc = bc;
59 }
60
61 /**
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.
65 */
66 public void prepareDataModel(Repository repository) {
67 cndFiles = Arrays.asList(DEFAULT_CNDS);
68 if ((cndFiles == null || cndFiles.size() == 0) && (namespaces == null || namespaces.size() == 0))
69 return;
70
71 Session session = null;
72 try {
73 session = repository.login();
74 // register namespaces
75 if (namespaces.size() > 0) {
76 NamespaceHelper namespaceHelper = new NamespaceHelper(session);
77 namespaceHelper.registerNamespaces(namespaces);
78 }
79
80 // load CND files from classpath or as URL
81 for (String resUrl : cndFiles) {
82 processCndFile(session, resUrl);
83 }
84 } catch (Exception e) {
85 JcrUtils.discardQuietly(session);
86 throw new ArgeoException("Cannot import node type definitions " + cndFiles, e);
87 } finally {
88 JcrUtils.logoutQuietly(session);
89 }
90
91 }
92
93 protected void processCndFile(Session session, String resUrl) {
94 Reader reader = null;
95 try {
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)) {
106 dataModel = node;
107 break;
108 }
109 }
110
111 Bundle bundle = findDataModelBundle(resUrl);
112
113 byte[] cndContent = readCndContent(resUrl);
114 String newDigest = DigestUtils.digest(DIGEST_ALGORITHM, cndContent);
115
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);
124 return;
125 }
126 }
127 }
128
129 if (dataModel != null && !forceCndImport) {
130 log.info(
131 "Data model " + resUrl + " has changed since version " + currentVersion
132 + (bundle != null
133 ? ": version " + bundle.getVersion() + ", bundle " + bundle.getSymbolicName()
134 : ""));
135 return;
136 }
137
138 reader = new InputStreamReader(new ByteArrayInputStream(cndContent));
139 // actually imports the CND
140 try {
141 CndImporter.registerNodeTypes(reader, session, true);
142 } catch (Exception e) {
143 log.error("Cannot import data model " + resUrl, e);
144 return;
145 }
146
147 if (dataModel != null && !dataModel.isNodeType(NodeType.NT_FILE)) {
148 dataModel.remove();
149 dataModel = null;
150 }
151
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);
160 } else {
161 session.getWorkspace().getVersionManager().checkout(dataModel.getPath());
162 }
163 if (bundle != null)
164 dataModel.setProperty(ArgeoNames.ARGEO_DATA_MODEL_VERSION, bundle.getVersion().toString());
165 else
166 dataModel.setProperty(ArgeoNames.ARGEO_DATA_MODEL_VERSION, "0.0.0");
167 JcrUtils.copyBytesAsFile(dataModel.getParent(), fileName, cndContent);
168 JcrUtils.updateLastModified(dataModel);
169 session.save();
170 session.getWorkspace().getVersionManager().checkin(dataModel.getPath());
171
172 if (currentVersion == null)
173 log.info(
174 "Data model " + resUrl
175 + (bundle != null
176 ? ", version " + bundle.getVersion() + ", bundle " + bundle.getSymbolicName()
177 : ""));
178 else
179 log.info(
180 "Data model " + resUrl + " updated from version " + currentVersion
181 + (bundle != null
182 ? ", version " + bundle.getVersion() + ", bundle " + bundle.getSymbolicName()
183 : ""));
184 } catch (Exception e) {
185 throw new ArgeoException("Cannot process data model " + resUrl, e);
186 } finally {
187 IOUtils.closeQuietly(reader);
188 }
189 }
190
191 protected byte[] readCndContent(String resUrl) {
192 BundleContext bundleContext = bc;
193 InputStream in = null;
194 try {
195 boolean classpath;
196 // normalize URL
197 if (bundleContext != null && resUrl.startsWith("classpath:")) {
198 resUrl = resUrl.substring("classpath:".length());
199 classpath = true;
200 } else if (resUrl.indexOf(':') < 0) {
201 if (!resUrl.startsWith("/")) {
202 resUrl = "/" + resUrl;
203 log.warn("Classpath should start with '/'");
204 }
205 classpath = true;
206 } else {
207 classpath = false;
208 }
209
210 URL url = null;
211 if (classpath) {
212 // if (bundleContext != null) {
213 Bundle currentBundle = bundleContext.getBundle();
214 url = currentBundle.getResource(resUrl);
215 // } else {
216 // resUrl = "classpath:" + resUrl;
217 // url = null;
218 // }
219 } else if (!resUrl.startsWith("classpath:")) {
220 url = new URL(resUrl);
221 }
222
223 if (url != null) {
224 in = url.openStream();
225 // } else if (resourceLoader != null) {
226 // Resource res = resourceLoader.getResource(resUrl);
227 // in = res.getInputStream();
228 // url = res.getURL();
229 } else {
230 throw new ArgeoException(
231 "No " + resUrl + " in the classpath," + " make sure the containing" + " package is visible.");
232 }
233
234 return IOUtils.toByteArray(in);
235 } catch (Exception e) {
236 throw new ArgeoException("Cannot read CND from " + resUrl, e);
237 } finally {
238 IOUtils.closeQuietly(in);
239 }
240 }
241
242 /*
243 * UTILITIES
244 */
245 /** Find which OSGi bundle provided the data model resource */
246 protected Bundle findDataModelBundle(String resUrl) {
247 BundleContext bundleContext = bc;
248 if (bundleContext == null)
249 return null;
250
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);
256
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;
266 break;
267 }
268 }
269 }
270
271 Bundle exportingBundle = null;
272 if (exportedPackage != null) {
273 exportingBundle = exportedPackage.getExportingBundle();
274 } else {
275 // assume this is in the same bundle
276 exportingBundle = bundleContext.getBundle();
277 // throw new ArgeoException("No OSGi exporting package found for "
278 // + resUrl);
279 }
280 return exportingBundle;
281 }
282
283 }