]> git.argeo.org Git - lgpl/argeo-commons.git/blob - server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitWrapper.java
Restructure JCR repository wrappers
[lgpl/argeo-commons.git] / server / runtime / org.argeo.server.jackrabbit / src / main / java / org / argeo / jackrabbit / JackrabbitWrapper.java
1 /*
2 * Copyright (C) 2007-2012 Mathieu Baudier
3 *
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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16 package org.argeo.jackrabbit;
17
18 import java.io.InputStream;
19 import java.io.InputStreamReader;
20 import java.io.Reader;
21 import java.net.URL;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26
27 import javax.jcr.Node;
28 import javax.jcr.NodeIterator;
29 import javax.jcr.Repository;
30 import javax.jcr.Session;
31
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;
52
53 /**
54 * Wrapper around a Jackrabbit repository which allows to simplify configuration
55 * and intercept some actions. It exposes itself as a {@link Repository}.
56 */
57 public class JackrabbitWrapper extends JcrRepositoryWrapper implements
58 ResourceLoaderAware {
59 private Log log = LogFactory.getLog(JackrabbitWrapper.class);
60
61 // local
62 private ResourceLoader resourceLoader;
63
64 // data model
65 /** Node type definitions in CND format */
66 private List<String> cndFiles = new ArrayList<String>();
67 /**
68 * Always import CNDs. Useful during development of new data models. In
69 * production, explicit migration processes should be used.
70 */
71 private Boolean forceCndImport = false;
72
73 /** Namespaces to register: key is prefix, value namespace */
74 private Map<String, String> namespaces = new HashMap<String, String>();
75
76 private BundleContext bundleContext;
77
78 /**
79 * Empty constructor, {@link #init()} should be called after properties have
80 * been set
81 */
82 public JackrabbitWrapper() {
83 }
84
85 @Override
86 public void init() {
87 prepareDataModel();
88 }
89
90 /*
91 * DATA MODEL
92 */
93
94 /**
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.
98 */
99 protected void prepareDataModel() {
100 if ((cndFiles == null || cndFiles.size() == 0)
101 && (namespaces == null || namespaces.size() == 0))
102 return;
103
104 Session session = null;
105 try {
106 session = login();
107 // register namespaces
108 if (namespaces.size() > 0) {
109 NamespaceHelper namespaceHelper = new NamespaceHelper(session);
110 namespaceHelper.registerNamespaces(namespaces);
111 }
112
113 // load CND files from classpath or as URL
114 for (String resUrl : cndFiles) {
115 boolean classpath;
116 // normalize URL
117 if (resUrl.startsWith("classpath:")) {
118 resUrl = resUrl.substring("classpath:".length());
119 classpath = true;
120 } else if (resUrl.indexOf(':') < 0) {
121 if (!resUrl.startsWith("/")) {
122 resUrl = "/" + resUrl;
123 log.warn("Classpath should start with '/'");
124 }
125 // resUrl = "classpath:" + resUrl;
126 classpath = true;
127 } else {
128 classpath = false;
129 }
130
131 URL url;
132 Bundle dataModelBundle = null;
133 if (classpath) {
134 if (bundleContext != null) {
135 Bundle currentBundle = bundleContext.getBundle();
136 url = currentBundle.getResource(resUrl);
137 if (url != null) {// found
138 dataModelBundle = findDataModelBundle(resUrl);
139 }
140 } else {
141 url = getClass().getClassLoader().getResource(resUrl);
142 // if (url == null)
143 // url = Thread.currentThread()
144 // .getContextClassLoader()
145 // .getResource(resUrl);
146 }
147 } else {
148 url = new URL(resUrl);
149 }
150
151 // check existing data model nodes
152 new NamespaceHelper(session).registerNamespace(
153 ArgeoNames.ARGEO, ArgeoNames.ARGEO_NAMESPACE);
154 if (!session
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()
165 .equals(resUrl)) {
166 dataModel = node;
167 break;
168 }
169 }
170
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)
176 .getString();
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 "
183 + version);
184 }
185 }
186 // do not implicitly update
187 return;
188 }
189
190 InputStream in = null;
191 Reader reader = null;
192 try {
193 if (url != null) {
194 in = url.openStream();
195 } else if (resourceLoader != null) {
196 Resource res = resourceLoader.getResource(resUrl);
197 in = res.getInputStream();
198 url = res.getURL();
199 } else {
200 throw new ArgeoException("No " + resUrl
201 + " in the classpath,"
202 + " make sure the containing"
203 + " package is visible.");
204 }
205
206 reader = new InputStreamReader(in);
207 // actually imports the CND
208 CndImporter.registerNodeTypes(reader, session, true);
209
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);
217 } else {
218 session.getWorkspace().getVersionManager()
219 .checkout(dataModel.getPath());
220 }
221 if (dataModelBundle != null)
222 dataModel.setProperty(
223 ArgeoNames.ARGEO_DATA_MODEL_VERSION,
224 dataModelBundle.getVersion().toString());
225 else
226 dataModel.setProperty(
227 ArgeoNames.ARGEO_DATA_MODEL_VERSION, "0.0.0");
228 JcrUtils.updateLastModified(dataModel);
229 session.save();
230 session.getWorkspace().getVersionManager()
231 .checkin(dataModel.getPath());
232 } finally {
233 IOUtils.closeQuietly(in);
234 IOUtils.closeQuietly(reader);
235 }
236
237 if (log.isDebugEnabled())
238 log.debug("Data model "
239 + resUrl
240 + (dataModelBundle != null ? ", version "
241 + dataModelBundle.getVersion()
242 + ", bundle "
243 + dataModelBundle.getSymbolicName() : ""));
244 }
245 } catch (Exception e) {
246 JcrUtils.discardQuietly(session);
247 throw new ArgeoException("Cannot import node type definitions "
248 + cndFiles, e);
249 } finally {
250 JcrUtils.logoutQuietly(session);
251 }
252
253 }
254
255 /*
256 * REPOSITORY INTERCEPTOR
257 */
258
259 /*
260 * UTILITIES
261 */
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('/',
267 '.');
268 ServiceReference paSr = bundleContext
269 .getServiceReference(PackageAdmin.class.getName());
270 PackageAdmin packageAdmin = (PackageAdmin) bundleContext
271 .getService(paSr);
272
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;
283 break;
284 }
285 }
286 }
287
288 Bundle exportingBundle = null;
289 if (exportedPackage != null) {
290 exportingBundle = exportedPackage.getExportingBundle();
291 } else {
292 throw new ArgeoException("No OSGi exporting package found for "
293 + resUrl);
294 }
295 return exportingBundle;
296 }
297
298 /*
299 * FIELDS ACCESS
300 */
301 public void setNamespaces(Map<String, String> namespaces) {
302 this.namespaces = namespaces;
303 }
304
305 public void setCndFiles(List<String> cndFiles) {
306 this.cndFiles = cndFiles;
307 }
308
309 public void setBundleContext(BundleContext bundleContext) {
310 this.bundleContext = bundleContext;
311 }
312
313 public void setForceCndImport(Boolean forceCndUpdate) {
314 this.forceCndImport = forceCndUpdate;
315 }
316
317 public void setResourceLoader(ResourceLoader resourceLoader) {
318 this.resourceLoader = resourceLoader;
319 }
320
321 }