]> git.argeo.org Git - lgpl/argeo-commons.git/blob - server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java
Improve migration
[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.ByteArrayInputStream;
20 import java.io.File;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.InputStreamReader;
24 import java.io.Reader;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Properties;
31 import java.util.Set;
32 import java.util.TreeSet;
33 import java.util.concurrent.Executor;
34
35 import javax.jcr.Credentials;
36 import javax.jcr.LoginException;
37 import javax.jcr.NoSuchWorkspaceException;
38 import javax.jcr.Node;
39 import javax.jcr.Repository;
40 import javax.jcr.RepositoryException;
41 import javax.jcr.Session;
42 import javax.jcr.Value;
43
44 import org.apache.commons.io.FileUtils;
45 import org.apache.commons.io.IOUtils;
46 import org.apache.commons.logging.Log;
47 import org.apache.commons.logging.LogFactory;
48 import org.apache.jackrabbit.api.JackrabbitRepository;
49 import org.apache.jackrabbit.commons.NamespaceHelper;
50 import org.apache.jackrabbit.commons.cnd.CndImporter;
51 import org.apache.jackrabbit.core.RepositoryImpl;
52 import org.apache.jackrabbit.core.TransientRepository;
53 import org.apache.jackrabbit.core.config.RepositoryConfig;
54 import org.apache.jackrabbit.core.config.RepositoryConfigurationParser;
55 import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory;
56 import org.argeo.ArgeoException;
57 import org.argeo.jcr.ArgeoNames;
58 import org.argeo.jcr.JcrUtils;
59 import org.springframework.context.ResourceLoaderAware;
60 import org.springframework.core.io.Resource;
61 import org.springframework.core.io.ResourceLoader;
62 import org.springframework.util.SystemPropertyUtils;
63 import org.xml.sax.InputSource;
64
65 /**
66 * Wrapper around a Jackrabbit repository which allows to configure it in Spring
67 * and expose it as a {@link Repository}.
68 */
69 public class JackrabbitContainer implements Repository, ResourceLoaderAware {
70 private Log log = LogFactory.getLog(JackrabbitContainer.class);
71
72 private Resource configuration;
73 private File homeDirectory;
74 private Resource variables;
75
76 private Boolean inMemory = false;
77 private String uri = null;
78
79 // wrapped repository
80 private Repository repository;
81 private RepositoryConfig repositoryConfig;
82
83 // CND
84 private ResourceLoader resourceLoader;
85
86 /** Node type definitions in CND format */
87 private List<String> cndFiles = new ArrayList<String>();
88
89 /** Migrations to execute (if not already done) */
90 private Set<JackrabbitDataModelMigration> dataModelMigrations = new HashSet<JackrabbitDataModelMigration>();
91
92 /** Namespaces to register: key is prefix, value namespace */
93 private Map<String, String> namespaces = new HashMap<String, String>();
94
95 private Boolean autocreateWorkspaces = false;
96
97 private Executor systemExecutor;
98
99 public void init() throws Exception {
100 if (repository != null) {
101 // we are just wrapping another repository
102 importNodeTypeDefinitions(repository);
103 return;
104 }
105
106 createJackrabbitRepository();
107
108 // migrate if needed
109 migrate();
110
111 // apply new CND files after migration
112 if (cndFiles != null && cndFiles.size() > 0)
113 importNodeTypeDefinitions(repository);
114 }
115
116 /** Actually creates a new repository. */
117 protected void createJackrabbitRepository() {
118 try {
119 // remote repository
120 if (uri != null && !uri.trim().equals("")) {
121 Map<String, String> params = new HashMap<String, String>();
122 params.put(
123 org.apache.jackrabbit.commons.JcrUtils.REPOSITORY_URI,
124 uri);
125 repository = (JackrabbitRepository) new Jcr2davRepositoryFactory()
126 .getRepository(params);
127 if (repository == null)
128 throw new ArgeoException("Remote Davex repository " + uri
129 + " not found");
130 log.info("Initialized Jackrabbit repository " + repository
131 + " from URI " + uri);
132 // do not perform further initialization since we assume that
133 // the
134 // remote repository has been properly configured
135 return;
136 }
137
138 // local repository
139 if (inMemory && getHomeDirectory().exists()) {
140 FileUtils.deleteDirectory(getHomeDirectory());
141 log.warn("Deleted Jackrabbit home directory "
142 + getHomeDirectory());
143 }
144
145 Properties vars = getConfigurationProperties();
146 InputStream in = configuration.getInputStream();
147 try {
148 vars.put(
149 RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE,
150 getHomeDirectory().getCanonicalPath());
151 repositoryConfig = RepositoryConfig.create(new InputSource(in),
152 vars);
153 } catch (Exception e) {
154 throw new RuntimeException("Cannot read configuration", e);
155 } finally {
156 IOUtils.closeQuietly(in);
157 }
158
159 if (inMemory)
160 repository = new TransientRepository(repositoryConfig);
161 else
162 repository = RepositoryImpl.create(repositoryConfig);
163
164 log.info("Initialized Jackrabbit repository " + repository + " in "
165 + getHomeDirectory() + " with config " + configuration);
166 } catch (Exception e) {
167 throw new ArgeoException("Cannot create Jackrabbit repository "
168 + getHomeDirectory(), e);
169 }
170 }
171
172 /** Executes migrations, if needed. */
173 protected void migrate() {
174 Boolean restartAndClearCaches = false;
175
176 // migrate data
177 Session session = null;
178 try {
179 session = login();
180 for (JackrabbitDataModelMigration dataModelMigration : new TreeSet<JackrabbitDataModelMigration>(
181 dataModelMigrations)) {
182 if (dataModelMigration.migrate(session)) {
183 restartAndClearCaches = true;
184 }
185 }
186 } catch (ArgeoException e) {
187 throw e;
188 } catch (Exception e) {
189 throw new ArgeoException("Cannot migrate", e);
190 } finally {
191 JcrUtils.logoutQuietly(session);
192 }
193
194 // restart repository
195 if (restartAndClearCaches) {
196 JackrabbitDataModelMigration
197 .clearRepositoryCaches(repositoryConfig);
198 ((JackrabbitRepository) repository).shutdown();
199 createJackrabbitRepository();
200 }
201
202 // set data model version
203 try {
204 session = login();
205 } catch (RepositoryException e) {
206 throw new ArgeoException("Cannot login to migrated repository", e);
207 }
208
209 for (JackrabbitDataModelMigration dataModelMigration : new TreeSet<JackrabbitDataModelMigration>(
210 dataModelMigrations)) {
211 try {
212 if (session.itemExists(dataModelMigration
213 .getDataModelNodePath())) {
214 Node dataModelNode = session.getNode(dataModelMigration
215 .getDataModelNodePath());
216 dataModelNode.setProperty(
217 ArgeoNames.ARGEO_DATA_MODEL_VERSION,
218 dataModelMigration.getTargetVersion());
219 session.save();
220 }
221 } catch (Exception e) {
222 log.error("Cannot set model version", e);
223 }
224 }
225 JcrUtils.logoutQuietly(session);
226
227 }
228
229 /** Lazy init. */
230 protected File getHomeDirectory() {
231 return homeDirectory;
232 // if (home != null)
233 // return home;
234 //
235 // try {
236 // String osgiData = System.getProperty("osgi.instance.area");
237 // if (osgiData != null)
238 // osgiData = osgiData.substring("file:".length());
239 // String path;
240 // if (homeDirectory == null)
241 // path = "./jackrabbit";
242 // else
243 // path = homeDirectory;
244 // if (path.startsWith(".") && osgiData != null) {
245 // home = new File(osgiData + '/' + path).getCanonicalFile();
246 // } else
247 // home = new File(path).getCanonicalFile();
248 // return home;
249 // } catch (Exception e) {
250 // throw new ArgeoException("Cannot define Jackrabbit home based on "
251 // + homeDirectory, e);
252 // }
253 }
254
255 public void dispose() throws Exception {
256 if (repository != null) {
257 if (repository instanceof JackrabbitRepository)
258 ((JackrabbitRepository) repository).shutdown();
259 else if (repository instanceof RepositoryImpl)
260 ((RepositoryImpl) repository).shutdown();
261 else if (repository instanceof TransientRepository)
262 ((TransientRepository) repository).shutdown();
263 }
264
265 if (inMemory)
266 if (getHomeDirectory().exists()) {
267 FileUtils.deleteDirectory(getHomeDirectory());
268 if (log.isDebugEnabled())
269 log.debug("Deleted Jackrabbit home directory "
270 + getHomeDirectory());
271 }
272
273 if (uri != null && !uri.trim().equals(""))
274 log.info("Destroyed Jackrabbit repository with uri " + uri);
275 else
276 log.info("Destroyed Jackrabbit repository " + repository + " in "
277 + getHomeDirectory() + " with config " + configuration);
278 }
279
280 /**
281 * @deprecated explicitly declare {@link #dispose()} as destroy-method
282 * instead.
283 */
284 public void destroy() throws Exception {
285 log.error("## Declare destroy-method=\"dispose\". in the Jackrabbit container bean");
286 }
287
288 /** @deprecated explicitly declare {@link #init()} as init-method instead. */
289 public void afterPropertiesSet() throws Exception {
290 log.error("## Declare init-method=\"init\". in the Jackrabbit container bean");
291 }
292
293 protected Properties getConfigurationProperties() {
294 InputStream propsIn = null;
295 Properties vars;
296 try {
297 vars = new Properties();
298 if (variables != null) {
299 propsIn = variables.getInputStream();
300 vars.load(propsIn);
301 }
302 // resolve system properties
303 for (Object key : vars.keySet()) {
304 // TODO: implement a smarter mechanism to resolve nested ${}
305 String newValue = SystemPropertyUtils.resolvePlaceholders(vars
306 .getProperty(key.toString()));
307 vars.put(key, newValue);
308 }
309 // override with system properties
310 vars.putAll(System.getProperties());
311
312 if (log.isTraceEnabled()) {
313 log.trace("Jackrabbit config variables:");
314 for (Object key : new TreeSet<Object>(vars.keySet()))
315 log.trace(key + "=" + vars.getProperty(key.toString()));
316 }
317
318 } catch (IOException e) {
319 throw new ArgeoException("Cannot read configuration properties", e);
320 } finally {
321 IOUtils.closeQuietly(propsIn);
322 }
323 return vars;
324 }
325
326 /**
327 * Import declared node type definitions, trying to update them if they have
328 * changed. In case of failures an error will be logged but no exception
329 * will be thrown.
330 */
331 protected void importNodeTypeDefinitions(final Repository repository) {
332 Runnable action = new Runnable() {
333 public void run() {
334 Reader reader = null;
335 Session session = null;
336 try {
337 session = repository.login();
338 processNewSession(session);
339 // Load cnds as resources
340 for (String resUrl : cndFiles) {
341 Resource res = resourceLoader.getResource(resUrl);
342 byte[] arr = IOUtils.toByteArray(res.getInputStream());
343 reader = new InputStreamReader(
344 new ByteArrayInputStream(arr));
345 CndImporter.registerNodeTypes(reader, session, true);
346 }
347 session.save();
348 } catch (Exception e) {
349 log.error(
350 "Cannot import node type definitions " + cndFiles,
351 e);
352 JcrUtils.discardQuietly(session);
353 } finally {
354 IOUtils.closeQuietly(reader);
355 JcrUtils.logoutQuietly(session);
356 }
357 }
358 };
359
360 if (systemExecutor != null)
361 systemExecutor.execute(action);
362 else
363 action.run();
364 }
365
366 // JCR REPOSITORY (delegated)
367 public String getDescriptor(String key) {
368 return repository.getDescriptor(key);
369 }
370
371 public String[] getDescriptorKeys() {
372 return repository.getDescriptorKeys();
373 }
374
375 public Session login() throws LoginException, RepositoryException {
376 Session session = repository.login();
377 processNewSession(session);
378 return session;
379 }
380
381 public Session login(Credentials credentials, String workspaceName)
382 throws LoginException, NoSuchWorkspaceException,
383 RepositoryException {
384 Session session;
385 try {
386 session = repository.login(credentials, workspaceName);
387 } catch (NoSuchWorkspaceException e) {
388 if (autocreateWorkspaces)
389 session = createWorkspaceAndLogsIn(credentials, workspaceName);
390 else
391 throw e;
392 }
393 processNewSession(session);
394 return session;
395 }
396
397 public Session login(Credentials credentials) throws LoginException,
398 RepositoryException {
399 Session session = repository.login(credentials);
400 processNewSession(session);
401 return session;
402 }
403
404 public Session login(String workspaceName) throws LoginException,
405 NoSuchWorkspaceException, RepositoryException {
406 Session session;
407 try {
408 session = repository.login(workspaceName);
409 } catch (NoSuchWorkspaceException e) {
410 if (autocreateWorkspaces)
411 session = createWorkspaceAndLogsIn(null, workspaceName);
412 else
413 throw e;
414 }
415 processNewSession(session);
416 return session;
417 }
418
419 protected synchronized void processNewSession(Session session) {
420 try {
421 NamespaceHelper namespaceHelper = new NamespaceHelper(session);
422 namespaceHelper.registerNamespaces(namespaces);
423 } catch (Exception e) {
424 throw new ArgeoException("Cannot process new session", e);
425 }
426 }
427
428 /**
429 * Logs in to the default workspace, creates the required workspace, logs
430 * out, logs in to the required workspace.
431 */
432 protected Session createWorkspaceAndLogsIn(Credentials credentials,
433 String workspaceName) throws RepositoryException {
434 if (workspaceName == null)
435 throw new ArgeoException("No workspace specified.");
436 Session session = repository.login(credentials);
437 session.getWorkspace().createWorkspace(workspaceName);
438 session.logout();
439 return repository.login(credentials, workspaceName);
440 }
441
442 public void setResourceLoader(ResourceLoader resourceLoader) {
443 this.resourceLoader = resourceLoader;
444 }
445
446 public boolean isStandardDescriptor(String key) {
447 return repository.isStandardDescriptor(key);
448 }
449
450 public boolean isSingleValueDescriptor(String key) {
451 return repository.isSingleValueDescriptor(key);
452 }
453
454 public Value getDescriptorValue(String key) {
455 return repository.getDescriptorValue(key);
456 }
457
458 public Value[] getDescriptorValues(String key) {
459 return repository.getDescriptorValues(key);
460 }
461
462 // BEANS METHODS
463 public void setHomeDirectory(File homeDirectory) {
464 this.homeDirectory = homeDirectory;
465 }
466
467 public void setConfiguration(Resource configuration) {
468 this.configuration = configuration;
469 }
470
471 public void setInMemory(Boolean inMemory) {
472 this.inMemory = inMemory;
473 }
474
475 public void setNamespaces(Map<String, String> namespaces) {
476 this.namespaces = namespaces;
477 }
478
479 public void setCndFiles(List<String> cndFiles) {
480 this.cndFiles = cndFiles;
481 }
482
483 public void setVariables(Resource variables) {
484 this.variables = variables;
485 }
486
487 public void setUri(String uri) {
488 this.uri = uri;
489 }
490
491 public void setSystemExecutor(Executor systemExecutor) {
492 this.systemExecutor = systemExecutor;
493 }
494
495 public void setRepository(Repository repository) {
496 this.repository = repository;
497 }
498
499 public void setDataModelMigrations(
500 Set<JackrabbitDataModelMigration> dataModelMigrations) {
501 this.dataModelMigrations = dataModelMigrations;
502 }
503
504 }