]> git.argeo.org Git - lgpl/argeo-commons.git/blob - server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitWrapper.java
Introduce file repository creation via repository factory
[lgpl/argeo-commons.git] / server / runtime / org.argeo.server.jackrabbit / src / main / java / org / argeo / jackrabbit / JackrabbitWrapper.java
1 /*
2 * Copyright (C) 2007-2012 Argeo GmbH
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.Credentials;
28 import javax.jcr.Node;
29 import javax.jcr.NodeIterator;
30 import javax.jcr.Repository;
31 import javax.jcr.Session;
32
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;
53
54 /**
55 * Wrapper around a Jackrabbit repository which allows to simplify configuration
56 * and intercept some actions. It exposes itself as a {@link Repository}.
57 */
58 public class JackrabbitWrapper extends JcrRepositoryWrapper implements
59 ResourceLoaderAware {
60 private Log log = LogFactory.getLog(JackrabbitWrapper.class);
61
62 // local
63 private ResourceLoader resourceLoader;
64
65 // data model
66 /** Node type definitions in CND format */
67 private List<String> cndFiles = new ArrayList<String>();
68 /**
69 * Always import CNDs. Useful during development of new data models. In
70 * production, explicit migration processes should be used.
71 */
72 private Boolean forceCndImport = false;
73
74 /** Namespaces to register: key is prefix, value namespace */
75 private Map<String, String> namespaces = new HashMap<String, String>();
76
77 private BundleContext bundleContext;
78
79 /**
80 * Explicitly set admin credentials used in initialization. Useful for
81 * testing, in real applications authentication is rather dealt with
82 * externally
83 */
84 private Credentials adminCredentials = null;
85
86 /**
87 * Empty constructor, {@link #init()} should be called after properties have
88 * been set
89 */
90 public JackrabbitWrapper() {
91 }
92
93 @Override
94 public void init() {
95 prepareDataModel();
96 }
97
98 /*
99 * DATA MODEL
100 */
101
102 /**
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.
106 */
107 protected void prepareDataModel() {
108 if ((cndFiles == null || cndFiles.size() == 0)
109 && (namespaces == null || namespaces.size() == 0))
110 return;
111
112 Session session = null;
113 try {
114 session = login(adminCredentials);
115 // register namespaces
116 if (namespaces.size() > 0) {
117 NamespaceHelper namespaceHelper = new NamespaceHelper(session);
118 namespaceHelper.registerNamespaces(namespaces);
119 }
120
121 // load CND files from classpath or as URL
122 for (String resUrl : cndFiles) {
123 URL url;
124 Bundle dataModelBundle = null;
125
126 {// TODO put this in a separate method
127 boolean classpath;
128 // normalize URL
129 if (bundleContext != null
130 && resUrl.startsWith("classpath:")) {
131 resUrl = resUrl.substring("classpath:".length());
132 classpath = true;
133 } else if (resUrl.indexOf(':') < 0) {
134 if (!resUrl.startsWith("/")) {
135 resUrl = "/" + resUrl;
136 log.warn("Classpath should start with '/'");
137 }
138 // resUrl = "classpath:" + resUrl;
139 classpath = true;
140 } else {
141 classpath = false;
142 }
143
144 if (classpath) {
145 if (bundleContext != null) {
146 Bundle currentBundle = bundleContext.getBundle();
147 url = currentBundle.getResource(resUrl);
148 if (url != null) {// found
149 dataModelBundle = findDataModelBundle(resUrl);
150 }
151 } else {
152 resUrl = "classpath:" + resUrl;
153 url = null;
154 // url =
155 // getClass().getClassLoader().getResource(resUrl);
156 // if (url == null)
157 // url = Thread.currentThread()
158 // .getContextClassLoader()
159 // .getResource(resUrl);
160 }
161 } else if (!resUrl.startsWith("classpath:")) {
162 url = new URL(resUrl);
163 } else {
164 url = null;
165 }
166 }
167 // check existing data model nodes
168 new NamespaceHelper(session).registerNamespace(
169 ArgeoNames.ARGEO, ArgeoNames.ARGEO_NAMESPACE);
170 if (!session
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()
181 .equals(resUrl)) {
182 dataModel = node;
183 break;
184 }
185 }
186
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)
192 .getString();
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 "
199 + version);
200 }
201 }
202 // do not implicitly update
203 return;
204 }
205
206 InputStream in = null;
207 Reader reader = null;
208 try {
209 if (url != null) {
210 in = url.openStream();
211 } else if (resourceLoader != null) {
212 Resource res = resourceLoader.getResource(resUrl);
213 in = res.getInputStream();
214 url = res.getURL();
215 } else {
216 throw new ArgeoException("No " + resUrl
217 + " in the classpath,"
218 + " make sure the containing"
219 + " package is visible.");
220 }
221
222 reader = new InputStreamReader(in);
223 // actually imports the CND
224 CndImporter.registerNodeTypes(reader, session, true);
225
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);
233 } else {
234 session.getWorkspace().getVersionManager()
235 .checkout(dataModel.getPath());
236 }
237 if (dataModelBundle != null)
238 dataModel.setProperty(
239 ArgeoNames.ARGEO_DATA_MODEL_VERSION,
240 dataModelBundle.getVersion().toString());
241 else
242 dataModel.setProperty(
243 ArgeoNames.ARGEO_DATA_MODEL_VERSION, "0.0.0");
244 JcrUtils.updateLastModified(dataModel);
245 session.save();
246 session.getWorkspace().getVersionManager()
247 .checkin(dataModel.getPath());
248 } finally {
249 IOUtils.closeQuietly(in);
250 IOUtils.closeQuietly(reader);
251 }
252
253 if (log.isDebugEnabled())
254 log.debug("Data model "
255 + resUrl
256 + (dataModelBundle != null ? ", version "
257 + dataModelBundle.getVersion()
258 + ", bundle "
259 + dataModelBundle.getSymbolicName() : ""));
260 }
261 } catch (Exception e) {
262 JcrUtils.discardQuietly(session);
263 throw new ArgeoException("Cannot import node type definitions "
264 + cndFiles, e);
265 } finally {
266 JcrUtils.logoutQuietly(session);
267 }
268
269 }
270
271 /*
272 * REPOSITORY INTERCEPTOR
273 */
274
275 /*
276 * UTILITIES
277 */
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('/',
283 '.');
284 ServiceReference paSr = bundleContext
285 .getServiceReference(PackageAdmin.class.getName());
286 PackageAdmin packageAdmin = (PackageAdmin) bundleContext
287 .getService(paSr);
288
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;
299 break;
300 }
301 }
302 }
303
304 Bundle exportingBundle = null;
305 if (exportedPackage != null) {
306 exportingBundle = exportedPackage.getExportingBundle();
307 } else {
308 throw new ArgeoException("No OSGi exporting package found for "
309 + resUrl);
310 }
311 return exportingBundle;
312 }
313
314 /*
315 * FIELDS ACCESS
316 */
317 public void setNamespaces(Map<String, String> namespaces) {
318 this.namespaces = namespaces;
319 }
320
321 public void setCndFiles(List<String> cndFiles) {
322 this.cndFiles = cndFiles;
323 }
324
325 public void setBundleContext(BundleContext bundleContext) {
326 this.bundleContext = bundleContext;
327 }
328
329 public void setForceCndImport(Boolean forceCndUpdate) {
330 this.forceCndImport = forceCndUpdate;
331 }
332
333 public void setResourceLoader(ResourceLoader resourceLoader) {
334 this.resourceLoader = resourceLoader;
335 }
336
337 public void setAdminCredentials(Credentials adminCredentials) {
338 this.adminCredentials = adminCredentials;
339 }
340
341 }