]> git.argeo.org Git - lgpl/argeo-commons.git/blob - server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java
Start refactoring Jackrabbit container
[lgpl/argeo-commons.git] / server / runtime / org.argeo.server.jackrabbit / src / main / java / org / argeo / jackrabbit / JackrabbitContainer.java
1 /*
2 * Copyright (C) 2010 Mathieu Baudier <mbaudier@argeo.org>
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
17 package org.argeo.jackrabbit;
18
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.InputStreamReader;
22 import java.io.Reader;
23 import java.net.URL;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.TreeSet;
31
32 import javax.jcr.Credentials;
33 import javax.jcr.LoginException;
34 import javax.jcr.NoSuchWorkspaceException;
35 import javax.jcr.Node;
36 import javax.jcr.Repository;
37 import javax.jcr.RepositoryException;
38 import javax.jcr.Session;
39 import javax.jcr.SimpleCredentials;
40
41 import org.apache.commons.io.IOUtils;
42 import org.apache.commons.logging.Log;
43 import org.apache.commons.logging.LogFactory;
44 import org.apache.jackrabbit.api.JackrabbitRepository;
45 import org.apache.jackrabbit.commons.NamespaceHelper;
46 import org.apache.jackrabbit.commons.cnd.CndImporter;
47 import org.apache.jackrabbit.core.RepositoryImpl;
48 import org.argeo.ArgeoException;
49 import org.argeo.jcr.ArgeoNames;
50 import org.argeo.jcr.JcrUtils;
51 import org.argeo.security.SystemAuthentication;
52 import org.osgi.framework.Bundle;
53 import org.osgi.framework.BundleContext;
54 import org.osgi.framework.ServiceReference;
55 import org.osgi.service.packageadmin.ExportedPackage;
56 import org.osgi.service.packageadmin.PackageAdmin;
57 import org.springframework.core.io.Resource;
58 import org.springframework.security.Authentication;
59 import org.springframework.security.context.SecurityContextHolder;
60 import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
61 import org.springframework.util.SystemPropertyUtils;
62
63 /**
64 * Wrapper around a Jackrabbit repository which allows to configure it in Spring
65 * and expose it as a {@link Repository}.
66 */
67 public class JackrabbitContainer extends JackrabbitWrapper {
68 private Log log = LogFactory.getLog(JackrabbitContainer.class);
69
70 // remote
71 private Credentials remoteSystemCredentials = null;
72
73 // local
74 private Resource configuration;
75 private Resource variables;
76
77 // data model
78 /** Node type definitions in CND format */
79 private List<String> cndFiles = new ArrayList<String>();
80
81 /** Migrations to execute (if not already done) */
82 private Set<JackrabbitDataModelMigration> dataModelMigrations = new HashSet<JackrabbitDataModelMigration>();
83
84 /** Namespaces to register: key is prefix, value namespace */
85 private Map<String, String> namespaces = new HashMap<String, String>();
86
87 private BundleContext bundleContext;
88
89 /**
90 * Empty constructor, {@link #init()} should be called after properties have
91 * been set
92 */
93 public JackrabbitContainer() {
94 }
95
96 /**
97 * Convenience constructor for remote, {@link #init()} is called in the
98 * constructor.
99 */
100 public JackrabbitContainer(String uri, Credentials remoteSystemCredentials) {
101 setUri(uri);
102 setRemoteSystemCredentials(remoteSystemCredentials);
103 init();
104 }
105
106 @Override
107 protected void postInitWrapped() {
108 prepareDataModel();
109 }
110
111 @Override
112 protected void postInitNew() {
113 // migrate if needed
114 migrate();
115
116 // apply new CND files after migration
117 if (cndFiles != null && cndFiles.size() > 0)
118 prepareDataModel();
119 }
120
121 /*
122 * DATA MODEL
123 */
124
125 /**
126 * Import declared node type definitions and register namespaces. Tries to
127 * update the node definitions if they have changed. In case of failures an
128 * error will be logged but no exception will be thrown.
129 */
130 protected void prepareDataModel() {
131 // importing node def on remote si currently not supported
132 if (isRemote())
133 return;
134
135 Session session = null;
136 try {
137 session = login();
138 // register namespaces
139 if (namespaces.size() > 0) {
140 NamespaceHelper namespaceHelper = new NamespaceHelper(session);
141 namespaceHelper.registerNamespaces(namespaces);
142 }
143 // load CND files from classpath or as URL
144 for (String resUrl : cndFiles) {
145 boolean classpath;
146 // if (resUrl.startsWith("classpath:")) {
147 // // resUrl = resUrl.substring("classpath:".length());
148 // classpath = true;
149 // } else if (resUrl.indexOf(':') < 0) {
150 // if (!resUrl.startsWith("/")) {
151 // resUrl = "/" + resUrl;
152 // log.warn("Classpath should start with '/'");
153 // }
154 // resUrl = "classpath:" + resUrl;
155 // classpath = true;
156 // } else {
157 // classpath = false;
158 // }
159
160 if (resUrl.startsWith("classpath:")) {
161 resUrl = resUrl.substring("classpath:".length());
162 classpath = true;
163 } else if (resUrl.indexOf(':') < 0) {
164 if (!resUrl.startsWith("/")) {
165 resUrl = "/" + resUrl;
166 log.warn("Classpath should start with '/'");
167 }
168 // resUrl = "classpath:" + resUrl;
169 classpath = true;
170 } else {
171 classpath = false;
172 }
173
174 // Resource resource =
175 // resourceLoader.getResource(resUrl);
176
177 // = classpath ? new ClassPathResource(resUrl) : new
178 // UrlResource(resUrl);
179
180 URL url;
181 Bundle dataModelBundle = null;
182 if (classpath) {
183 if (bundleContext != null) {
184 Bundle currentBundle = bundleContext.getBundle();
185 url = currentBundle.getResource(resUrl);
186 if (url != null) {// found
187 dataModelBundle = findDataModelBundle(resUrl);
188 }
189 } else {
190 url = getClass().getClassLoader().getResource(resUrl);
191 }
192 if (url == null)
193 throw new ArgeoException("No " + resUrl
194 + " in the classpath,"
195 + " make sure the containing"
196 + " package is visible.");
197
198 } else {
199 url = new URL(resUrl);
200 }
201
202 Reader reader = null;
203 try {
204 reader = new InputStreamReader(url.openStream());
205 CndImporter.registerNodeTypes(reader, session, true);
206 } finally {
207 IOUtils.closeQuietly(reader);
208 }
209
210 if (log.isDebugEnabled())
211 log.debug("Data model "
212 + resUrl
213 + (dataModelBundle != null ? ", version "
214 + dataModelBundle.getVersion()
215 + ", bundle "
216 + dataModelBundle.getSymbolicName() : ""));
217 }
218 } catch (Exception e) {
219 JcrUtils.discardQuietly(session);
220 throw new ArgeoException("Cannot import node type definitions "
221 + cndFiles, e);
222 } finally {
223 JcrUtils.logoutQuietly(session);
224 }
225
226 }
227
228 /** Executes migrations, if needed. */
229 protected void migrate() {
230 // Remote migration not supported
231 if (isRemote())
232 return;
233
234 // No migration to perform
235 if (dataModelMigrations.size() == 0)
236 return;
237
238 Boolean restartAndClearCaches = false;
239
240 // migrate data
241 Session session = null;
242 try {
243 session = login();
244 for (JackrabbitDataModelMigration dataModelMigration : new TreeSet<JackrabbitDataModelMigration>(
245 dataModelMigrations)) {
246 if (dataModelMigration.migrate(session)) {
247 restartAndClearCaches = true;
248 }
249 }
250 } catch (ArgeoException e) {
251 throw e;
252 } catch (Exception e) {
253 throw new ArgeoException("Cannot migrate", e);
254 } finally {
255 JcrUtils.logoutQuietly(session);
256 }
257
258 // restart repository
259 if (restartAndClearCaches) {
260 Repository repository = getRepository();
261 if (repository instanceof RepositoryImpl) {
262 JackrabbitDataModelMigration
263 .clearRepositoryCaches(((RepositoryImpl) repository)
264 .getConfig());
265 }
266 ((JackrabbitRepository) repository).shutdown();
267 createJackrabbitRepository();
268 }
269
270 // set data model version
271 try {
272 session = login();
273 } catch (RepositoryException e) {
274 throw new ArgeoException("Cannot login to migrated repository", e);
275 }
276
277 for (JackrabbitDataModelMigration dataModelMigration : new TreeSet<JackrabbitDataModelMigration>(
278 dataModelMigrations)) {
279 try {
280 if (session.itemExists(dataModelMigration
281 .getDataModelNodePath())) {
282 Node dataModelNode = session.getNode(dataModelMigration
283 .getDataModelNodePath());
284 dataModelNode.setProperty(
285 ArgeoNames.ARGEO_DATA_MODEL_VERSION,
286 dataModelMigration.getTargetVersion());
287 session.save();
288 }
289 } catch (Exception e) {
290 log.error("Cannot set model version", e);
291 }
292 }
293 JcrUtils.logoutQuietly(session);
294
295 }
296
297 /*
298 * REPOSITORY INTERCEPTOR
299 */
300 /** Central login method */
301 public Session login(Credentials credentials, String workspaceName)
302 throws LoginException, NoSuchWorkspaceException,
303 RepositoryException {
304
305 // retrieve credentials for remote
306 if (credentials == null && isRemote()) {
307 Authentication authentication = SecurityContextHolder.getContext()
308 .getAuthentication();
309 if (authentication != null) {
310 if (authentication instanceof UsernamePasswordAuthenticationToken) {
311 UsernamePasswordAuthenticationToken upat = (UsernamePasswordAuthenticationToken) authentication;
312 credentials = new SimpleCredentials(upat.getName(), upat
313 .getCredentials().toString().toCharArray());
314 } else if ((authentication instanceof SystemAuthentication)
315 && remoteSystemCredentials != null) {
316 credentials = remoteSystemCredentials;
317 }
318 }
319 }
320
321 return super.login(credentials, workspaceName);
322 }
323
324 /*
325 * UTILITIES
326 */
327
328 @Override
329 protected InputStream readConfiguration() {
330 try {
331 return configuration != null ? configuration.getInputStream()
332 : null;
333 } catch (IOException e) {
334 throw new ArgeoException("Cannot read Jackrabbit configuration "
335 + configuration, e);
336 }
337 }
338
339 @Override
340 protected InputStream readVariables() {
341 try {
342 return variables != null ? variables.getInputStream() : null;
343 } catch (IOException e) {
344 throw new ArgeoException("Cannot read Jackrabbit variables "
345 + variables, e);
346 }
347 }
348
349 @Override
350 protected String resolvePlaceholders(String string,
351 Map<String, String> variables) {
352 return SystemPropertyUtils.resolvePlaceholders(string);
353 }
354
355 /** Find which OSGi bundle provided the data model resource */
356 protected Bundle findDataModelBundle(String resUrl) {
357 if (resUrl.startsWith("/"))
358 resUrl = resUrl.substring(1);
359 String pkg = resUrl.substring(0, resUrl.lastIndexOf('/')).replace('/',
360 '.');
361 ServiceReference paSr = bundleContext
362 .getServiceReference(PackageAdmin.class.getName());
363 PackageAdmin packageAdmin = (PackageAdmin) bundleContext
364 .getService(paSr);
365
366 // find exported package
367 ExportedPackage exportedPackage = null;
368 ExportedPackage[] exportedPackages = packageAdmin
369 .getExportedPackages(pkg);
370 if (exportedPackages == null)
371 throw new ArgeoException("No exported package found for " + pkg);
372 for (ExportedPackage ep : exportedPackages) {
373 for (Bundle b : ep.getImportingBundles()) {
374 if (b.getBundleId() == bundleContext.getBundle().getBundleId()) {
375 exportedPackage = ep;
376 break;
377 }
378 }
379 }
380
381 Bundle exportingBundle = null;
382 if (exportedPackage != null) {
383 exportingBundle = exportedPackage.getExportingBundle();
384 } else {
385 throw new ArgeoException("No OSGi exporting package found for "
386 + resUrl);
387 }
388 return exportingBundle;
389 }
390
391 /*
392 * FIELDS ACCESS
393 */
394 public void setConfiguration(Resource configuration) {
395 this.configuration = configuration;
396 }
397
398 public void setNamespaces(Map<String, String> namespaces) {
399 this.namespaces = namespaces;
400 }
401
402 public void setCndFiles(List<String> cndFiles) {
403 this.cndFiles = cndFiles;
404 }
405
406 public void setVariables(Resource variables) {
407 this.variables = variables;
408 }
409
410 public void setRemoteSystemCredentials(Credentials remoteSystemCredentials) {
411 this.remoteSystemCredentials = remoteSystemCredentials;
412 }
413
414 public void setDataModelMigrations(
415 Set<JackrabbitDataModelMigration> dataModelMigrations) {
416 this.dataModelMigrations = dataModelMigrations;
417 }
418
419 public void setBundleContext(BundleContext bundleContext) {
420 this.bundleContext = bundleContext;
421 }
422 }