]> git.argeo.org Git - lgpl/argeo-commons.git/blob - server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java
Improve RCP
[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 long begin = System.currentTimeMillis();
119 try {
120 // remote repository
121 if (uri != null && !uri.trim().equals("")) {
122 Map<String, String> params = new HashMap<String, String>();
123 params.put(
124 org.apache.jackrabbit.commons.JcrUtils.REPOSITORY_URI,
125 uri);
126 repository = new Jcr2davRepositoryFactory()
127 .getRepository(params);
128 if (repository == null)
129 throw new ArgeoException("Remote Davex repository " + uri
130 + " not found");
131 log.info("Initialized Jackrabbit repository " + repository
132 + " from URI " + uri);
133 // do not perform further initialization since we assume that
134 // the
135 // remote repository has been properly configured
136 return;
137 }
138
139 // local repository
140 if (inMemory && getHomeDirectory().exists()) {
141 FileUtils.deleteDirectory(getHomeDirectory());
142 log.warn("Deleted Jackrabbit home directory "
143 + getHomeDirectory());
144 }
145
146 Properties vars = getConfigurationProperties();
147 InputStream in = configuration.getInputStream();
148 try {
149 vars.put(
150 RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE,
151 getHomeDirectory().getCanonicalPath());
152 repositoryConfig = RepositoryConfig.create(new InputSource(in),
153 vars);
154 } catch (Exception e) {
155 throw new RuntimeException("Cannot read configuration", e);
156 } finally {
157 IOUtils.closeQuietly(in);
158 }
159
160 if (inMemory)
161 repository = new TransientRepository(repositoryConfig);
162 else
163 repository = RepositoryImpl.create(repositoryConfig);
164
165 double duration = ((double) (System.currentTimeMillis() - begin)) / 1000;
166 log.info("Initialized Jackrabbit repository in " + duration
167 + " s, home: " + getHomeDirectory() + ", config: "
168 + configuration);
169 } catch (Exception e) {
170 throw new ArgeoException("Cannot create Jackrabbit repository "
171 + getHomeDirectory(), e);
172 }
173 }
174
175 /** Executes migrations, if needed. */
176 protected void migrate() {
177 // No migration to perform
178 if (dataModelMigrations.size() == 0)
179 return;
180
181 Boolean restartAndClearCaches = false;
182
183 // migrate data
184 Session session = null;
185 try {
186 session = login();
187 for (JackrabbitDataModelMigration dataModelMigration : new TreeSet<JackrabbitDataModelMigration>(
188 dataModelMigrations)) {
189 if (dataModelMigration.migrate(session)) {
190 restartAndClearCaches = true;
191 }
192 }
193 } catch (ArgeoException e) {
194 throw e;
195 } catch (Exception e) {
196 throw new ArgeoException("Cannot migrate", e);
197 } finally {
198 JcrUtils.logoutQuietly(session);
199 }
200
201 // restart repository
202 if (restartAndClearCaches) {
203 JackrabbitDataModelMigration
204 .clearRepositoryCaches(repositoryConfig);
205 ((JackrabbitRepository) repository).shutdown();
206 createJackrabbitRepository();
207 }
208
209 // set data model version
210 try {
211 session = login();
212 } catch (RepositoryException e) {
213 throw new ArgeoException("Cannot login to migrated repository", e);
214 }
215
216 for (JackrabbitDataModelMigration dataModelMigration : new TreeSet<JackrabbitDataModelMigration>(
217 dataModelMigrations)) {
218 try {
219 if (session.itemExists(dataModelMigration
220 .getDataModelNodePath())) {
221 Node dataModelNode = session.getNode(dataModelMigration
222 .getDataModelNodePath());
223 dataModelNode.setProperty(
224 ArgeoNames.ARGEO_DATA_MODEL_VERSION,
225 dataModelMigration.getTargetVersion());
226 session.save();
227 }
228 } catch (Exception e) {
229 log.error("Cannot set model version", e);
230 }
231 }
232 JcrUtils.logoutQuietly(session);
233
234 }
235
236 /** Lazy init. */
237 protected File getHomeDirectory() {
238 try {
239 return homeDirectory.getCanonicalFile();
240 } catch (IOException e) {
241 throw new ArgeoException("Cannot get canonical file for "
242 + homeDirectory, e);
243 }
244 }
245
246 public void dispose() throws Exception {
247 long begin = System.currentTimeMillis();
248 if (repository != null) {
249 if (repository instanceof JackrabbitRepository)
250 ((JackrabbitRepository) repository).shutdown();
251 else if (repository instanceof RepositoryImpl)
252 ((RepositoryImpl) repository).shutdown();
253 else if (repository instanceof TransientRepository)
254 ((TransientRepository) repository).shutdown();
255 }
256
257 if (inMemory)
258 if (getHomeDirectory().exists()) {
259 FileUtils.deleteDirectory(getHomeDirectory());
260 if (log.isDebugEnabled())
261 log.debug("Deleted Jackrabbit home directory "
262 + getHomeDirectory());
263 }
264
265 double duration = ((double) (System.currentTimeMillis() - begin)) / 1000;
266 if (uri != null && !uri.trim().equals(""))
267 log.info("Destroyed Jackrabbit repository with uri " + uri);
268 else
269 log.info("Destroyed Jackrabbit repository in " + duration
270 + " s, home: " + getHomeDirectory() + ", config "
271 + configuration);
272 }
273
274 /**
275 * @deprecated explicitly declare {@link #dispose()} as destroy-method
276 * instead.
277 */
278 public void destroy() throws Exception {
279 log.error("## Declare destroy-method=\"dispose\". in the Jackrabbit container bean");
280 }
281
282 /** @deprecated explicitly declare {@link #init()} as init-method instead. */
283 public void afterPropertiesSet() throws Exception {
284 log.error("## Declare init-method=\"init\". in the Jackrabbit container bean");
285 }
286
287 protected Properties getConfigurationProperties() {
288 InputStream propsIn = null;
289 Properties vars;
290 try {
291 vars = new Properties();
292 if (variables != null) {
293 propsIn = variables.getInputStream();
294 vars.load(propsIn);
295 }
296 // resolve system properties
297 for (Object key : vars.keySet()) {
298 // TODO: implement a smarter mechanism to resolve nested ${}
299 String newValue = SystemPropertyUtils.resolvePlaceholders(vars
300 .getProperty(key.toString()));
301 vars.put(key, newValue);
302 }
303 // override with system properties
304 vars.putAll(System.getProperties());
305
306 if (log.isTraceEnabled()) {
307 log.trace("Jackrabbit config variables:");
308 for (Object key : new TreeSet<Object>(vars.keySet()))
309 log.trace(key + "=" + vars.getProperty(key.toString()));
310 }
311
312 } catch (IOException e) {
313 throw new ArgeoException("Cannot read configuration properties", e);
314 } finally {
315 IOUtils.closeQuietly(propsIn);
316 }
317 return vars;
318 }
319
320 /**
321 * Import declared node type definitions, trying to update them if they have
322 * changed. In case of failures an error will be logged but no exception
323 * will be thrown.
324 */
325 protected void importNodeTypeDefinitions(final Repository repository) {
326 Runnable action = new Runnable() {
327 public void run() {
328 Reader reader = null;
329 Session session = null;
330 try {
331 session = repository.login();
332 processNewSession(session);
333 // Load cnds as resources
334 for (String resUrl : cndFiles) {
335 Resource res = resourceLoader.getResource(resUrl);
336 byte[] arr = IOUtils.toByteArray(res.getInputStream());
337 reader = new InputStreamReader(
338 new ByteArrayInputStream(arr));
339 CndImporter.registerNodeTypes(reader, session, true);
340 }
341 session.save();
342 } catch (Exception e) {
343 log.error(
344 "Cannot import node type definitions " + cndFiles,
345 e);
346 JcrUtils.discardQuietly(session);
347 } finally {
348 IOUtils.closeQuietly(reader);
349 JcrUtils.logoutQuietly(session);
350 }
351 }
352 };
353
354 if (systemExecutor != null)
355 systemExecutor.execute(action);
356 else
357 action.run();
358 }
359
360 // JCR REPOSITORY (delegated)
361 public String getDescriptor(String key) {
362 return getRepository().getDescriptor(key);
363 }
364
365 public String[] getDescriptorKeys() {
366 return getRepository().getDescriptorKeys();
367 }
368
369 public Session login() throws LoginException, RepositoryException {
370 Session session = getRepository().login();
371 processNewSession(session);
372 return session;
373 }
374
375 public Session login(Credentials credentials, String workspaceName)
376 throws LoginException, NoSuchWorkspaceException,
377 RepositoryException {
378 Session session;
379 try {
380 session = getRepository().login(credentials, workspaceName);
381 } catch (NoSuchWorkspaceException e) {
382 if (autocreateWorkspaces)
383 session = createWorkspaceAndLogsIn(credentials, workspaceName);
384 else
385 throw e;
386 }
387 processNewSession(session);
388 return session;
389 }
390
391 public Session login(Credentials credentials) throws LoginException,
392 RepositoryException {
393 Session session = getRepository().login(credentials);
394 processNewSession(session);
395 return session;
396 }
397
398 public Session login(String workspaceName) throws LoginException,
399 NoSuchWorkspaceException, RepositoryException {
400 Session session;
401 try {
402 session = getRepository().login(workspaceName);
403 } catch (NoSuchWorkspaceException e) {
404 if (autocreateWorkspaces)
405 session = createWorkspaceAndLogsIn(null, workspaceName);
406 else
407 throw e;
408 }
409 processNewSession(session);
410 return session;
411 }
412
413 /** Wraps access to the repository, making sure it is available. */
414 protected Repository getRepository() {
415 if (repository == null) {
416 throw new ArgeoException(
417 "No repository initialized."
418 + " Was the init() method called?"
419 + " The dispose() method should also be called on shutdown.");
420 }
421 return repository;
422 }
423
424 protected synchronized void processNewSession(Session session) {
425 try {
426 NamespaceHelper namespaceHelper = new NamespaceHelper(session);
427 namespaceHelper.registerNamespaces(namespaces);
428 } catch (Exception e) {
429 throw new ArgeoException("Cannot process new session", e);
430 }
431 }
432
433 /**
434 * Logs in to the default workspace, creates the required workspace, logs
435 * out, logs in to the required workspace.
436 */
437 protected Session createWorkspaceAndLogsIn(Credentials credentials,
438 String workspaceName) throws RepositoryException {
439 if (workspaceName == null)
440 throw new ArgeoException("No workspace specified.");
441 Session session = getRepository().login(credentials);
442 session.getWorkspace().createWorkspace(workspaceName);
443 session.logout();
444 return getRepository().login(credentials, workspaceName);
445 }
446
447 public void setResourceLoader(ResourceLoader resourceLoader) {
448 this.resourceLoader = resourceLoader;
449 }
450
451 public boolean isStandardDescriptor(String key) {
452 return getRepository().isStandardDescriptor(key);
453 }
454
455 public boolean isSingleValueDescriptor(String key) {
456 return getRepository().isSingleValueDescriptor(key);
457 }
458
459 public Value getDescriptorValue(String key) {
460 return getRepository().getDescriptorValue(key);
461 }
462
463 public Value[] getDescriptorValues(String key) {
464 return getRepository().getDescriptorValues(key);
465 }
466
467 // BEANS METHODS
468 public void setHomeDirectory(File homeDirectory) {
469 this.homeDirectory = homeDirectory;
470 }
471
472 public void setConfiguration(Resource configuration) {
473 this.configuration = configuration;
474 }
475
476 public void setInMemory(Boolean inMemory) {
477 this.inMemory = inMemory;
478 }
479
480 public void setNamespaces(Map<String, String> namespaces) {
481 this.namespaces = namespaces;
482 }
483
484 public void setCndFiles(List<String> cndFiles) {
485 this.cndFiles = cndFiles;
486 }
487
488 public void setVariables(Resource variables) {
489 this.variables = variables;
490 }
491
492 public void setUri(String uri) {
493 this.uri = uri;
494 }
495
496 public void setSystemExecutor(Executor systemExecutor) {
497 this.systemExecutor = systemExecutor;
498 }
499
500 public void setRepository(Repository repository) {
501 this.repository = repository;
502 }
503
504 public void setDataModelMigrations(
505 Set<JackrabbitDataModelMigration> dataModelMigrations) {
506 this.dataModelMigrations = dataModelMigrations;
507 }
508
509 }