]> git.argeo.org Git - lgpl/argeo-commons.git/blob - server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java
Ability to load CND definitions from the same bundle.
[lgpl/argeo-commons.git] / server / runtime / org.argeo.server.jackrabbit / src / main / java / org / argeo / jackrabbit / JackrabbitContainer.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.File;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.util.HashSet;
22 import java.util.Map;
23 import java.util.Properties;
24 import java.util.Set;
25 import java.util.TreeSet;
26 import java.util.UUID;
27
28 import javax.jcr.Node;
29 import javax.jcr.Repository;
30 import javax.jcr.RepositoryException;
31 import javax.jcr.Session;
32
33 import org.apache.commons.io.FileUtils;
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.api.JackrabbitRepository;
38 import org.apache.jackrabbit.core.RepositoryImpl;
39 import org.apache.jackrabbit.core.config.RepositoryConfig;
40 import org.apache.jackrabbit.core.config.RepositoryConfigurationParser;
41 import org.argeo.ArgeoException;
42 import org.argeo.jcr.ArgeoNames;
43 import org.argeo.jcr.JcrUtils;
44 import org.argeo.jcr.MaintainedRepository;
45 import org.springframework.core.io.Resource;
46 import org.springframework.util.SystemPropertyUtils;
47 import org.xml.sax.InputSource;
48
49 /**
50 * Wrapper around a Jackrabbit repository which allows to configure it in Spring
51 * and expose it as a {@link Repository}.
52 */
53 public class JackrabbitContainer extends JackrabbitWrapper implements
54 MaintainedRepository {
55 private final static Log log = LogFactory.getLog(JackrabbitContainer.class);
56
57 // local
58 private Resource configuration;
59 private Resource variables;
60 private RepositoryConfig repositoryConfig;
61 private File homeDirectory;
62 private Boolean inMemory = false;
63
64 /** Migrations to execute (if not already done) */
65 private Set<JackrabbitDataModelMigration> dataModelMigrations = new HashSet<JackrabbitDataModelMigration>();
66
67 /**
68 * Empty constructor, {@link #init()} should be called after properties have
69 * been set
70 */
71 public JackrabbitContainer() {
72 }
73
74 public void init() {
75 long begin = System.currentTimeMillis();
76
77 if (getRepository() != null)
78 throw new ArgeoException(
79 "Cannot be used to wrap another repository");
80 Repository repository = createJackrabbitRepository();
81 super.setRepository(repository);
82
83 // migrate if needed
84 migrate();
85
86 // apply new CND files after migration
87 prepareDataModel();
88
89 double duration = ((double) (System.currentTimeMillis() - begin)) / 1000;
90 if (log.isDebugEnabled())
91 log.debug("Initialized JCR repository wrapper in " + duration
92 + " s");
93 }
94
95 /** Actually creates the new repository. */
96 protected Repository createJackrabbitRepository() {
97 long begin = System.currentTimeMillis();
98 InputStream configurationIn = null;
99 Repository repository;
100 try {
101 // temporary
102 if (inMemory && getHomeDirectory().exists()) {
103 FileUtils.deleteDirectory(getHomeDirectory());
104 log.warn("Deleted Jackrabbit home directory "
105 + getHomeDirectory());
106 }
107
108 // process configuration file
109 Properties vars = getConfigurationProperties();
110 configurationIn = readConfiguration();
111 vars.put(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE,
112 getHomeDirectory().getCanonicalPath());
113 repositoryConfig = RepositoryConfig.create(new InputSource(
114 configurationIn), vars);
115
116 //
117 // Actual repository creation
118 //
119 repository = RepositoryImpl.create(repositoryConfig);
120
121 double duration = ((double) (System.currentTimeMillis() - begin)) / 1000;
122 if (log.isTraceEnabled())
123 log.trace("Created Jackrabbit repository in " + duration
124 + " s, home: " + getHomeDirectory());
125
126 return repository;
127 } catch (Exception e) {
128 throw new ArgeoException("Cannot create Jackrabbit repository "
129 + getHomeDirectory(), e);
130 } finally {
131 IOUtils.closeQuietly(configurationIn);
132 }
133 }
134
135 /** Lazy init. */
136 protected File getHomeDirectory() {
137 try {
138 if (homeDirectory == null) {
139 if (inMemory) {
140 homeDirectory = new File(
141 System.getProperty("java.io.tmpdir")
142 + File.separator
143 + System.getProperty("user.name")
144 + File.separator + "jackrabbit-"
145 + UUID.randomUUID());
146 homeDirectory.mkdirs();
147 // will it work if directory is not empty??
148 homeDirectory.deleteOnExit();
149 }
150 }
151
152 return homeDirectory.getCanonicalFile();
153 } catch (IOException e) {
154 throw new ArgeoException("Cannot get canonical file for "
155 + homeDirectory, e);
156 }
157 }
158
159 /** Executes migrations, if needed. */
160 protected void migrate() {
161 // No migration to perform
162 if (dataModelMigrations.size() == 0)
163 return;
164
165 Boolean restartAndClearCaches = false;
166
167 // migrate data
168 Session session = null;
169 try {
170 session = login();
171 for (JackrabbitDataModelMigration dataModelMigration : new TreeSet<JackrabbitDataModelMigration>(
172 dataModelMigrations)) {
173 if (dataModelMigration.migrate(session)) {
174 restartAndClearCaches = true;
175 }
176 }
177 } catch (ArgeoException e) {
178 throw e;
179 } catch (Exception e) {
180 throw new ArgeoException("Cannot migrate", e);
181 } finally {
182 JcrUtils.logoutQuietly(session);
183 }
184
185 // restart repository
186 if (restartAndClearCaches) {
187 Repository repository = getRepository();
188 if (repository instanceof RepositoryImpl) {
189 JackrabbitDataModelMigration
190 .clearRepositoryCaches(((RepositoryImpl) repository)
191 .getConfig());
192 }
193 ((JackrabbitRepository) repository).shutdown();
194 createJackrabbitRepository();
195 }
196
197 // set data model version
198 try {
199 session = login();
200 } catch (RepositoryException e) {
201 throw new ArgeoException("Cannot login to migrated repository", e);
202 }
203
204 for (JackrabbitDataModelMigration dataModelMigration : new TreeSet<JackrabbitDataModelMigration>(
205 dataModelMigrations)) {
206 try {
207 if (session.itemExists(dataModelMigration
208 .getDataModelNodePath())) {
209 Node dataModelNode = session.getNode(dataModelMigration
210 .getDataModelNodePath());
211 dataModelNode.setProperty(
212 ArgeoNames.ARGEO_DATA_MODEL_VERSION,
213 dataModelMigration.getTargetVersion());
214 session.save();
215 }
216 } catch (Exception e) {
217 log.error("Cannot set model version", e);
218 }
219 }
220 JcrUtils.logoutQuietly(session);
221
222 }
223
224 /** Shutdown the repository */
225 public void destroy() throws Exception {
226 Repository repository = getRepository();
227 if (repository != null && repository instanceof RepositoryImpl) {
228 long begin = System.currentTimeMillis();
229 ((RepositoryImpl) repository).shutdown();
230 if (inMemory)
231 if (getHomeDirectory().exists()) {
232 FileUtils.deleteDirectory(getHomeDirectory());
233 if (log.isDebugEnabled())
234 log.debug("Deleted Jackrabbit home directory "
235 + getHomeDirectory());
236 }
237 double duration = ((double) (System.currentTimeMillis() - begin)) / 1000;
238 log.info("Destroyed Jackrabbit repository in " + duration
239 + " s, home: " + getHomeDirectory());
240 }
241 repository = null;
242 }
243
244 public void dispose() {
245 throw new IllegalArgumentException(
246 "Call destroy() method instead of dispose()");
247 }
248
249 /*
250 * UTILITIES
251 */
252 /**
253 * Reads the configuration which will initialize a {@link RepositoryConfig}.
254 */
255 protected InputStream readConfiguration() {
256 try {
257 return configuration != null ? configuration.getInputStream()
258 : null;
259 } catch (IOException e) {
260 throw new ArgeoException("Cannot read Jackrabbit configuration "
261 + configuration, e);
262 }
263 }
264
265 /**
266 * Reads the variables which will initialize a {@link Properties}. Returns
267 * null by default, to be overridden.
268 *
269 * @return a new stream or null if no variables available
270 */
271 protected InputStream readVariables() {
272 try {
273 return variables != null ? variables.getInputStream() : null;
274 } catch (IOException e) {
275 throw new ArgeoException("Cannot read Jackrabbit variables "
276 + variables, e);
277 }
278 }
279
280 /**
281 * Resolves ${} placeholders in the provided string. Based on system
282 * properties if no map is provided.
283 */
284 protected String resolvePlaceholders(String string,
285 Map<String, String> variables) {
286 return SystemPropertyUtils.resolvePlaceholders(string);
287 }
288
289 /** Generates the properties to use in the configuration. */
290 protected Properties getConfigurationProperties() {
291 InputStream propsIn = null;
292 Properties vars;
293 try {
294 vars = new Properties();
295 propsIn = readVariables();
296 if (propsIn != null) {
297 vars.load(propsIn);
298 }
299 // resolve system properties
300 for (Object key : vars.keySet()) {
301 // TODO: implement a smarter mechanism to resolve nested ${}
302 String newValue = resolvePlaceholders(
303 vars.getProperty(key.toString()), null);
304 vars.put(key, newValue);
305 }
306 // override with system properties
307 vars.putAll(System.getProperties());
308
309 if (log.isTraceEnabled()) {
310 log.trace("Jackrabbit config variables:");
311 for (Object key : new TreeSet<Object>(vars.keySet()))
312 log.trace(key + "=" + vars.getProperty(key.toString()));
313 }
314
315 } catch (IOException e) {
316 throw new ArgeoException("Cannot read configuration properties", e);
317 } finally {
318 IOUtils.closeQuietly(propsIn);
319 }
320 return vars;
321 }
322
323 /*
324 * FIELDS ACCESS
325 */
326
327 public void setHomeDirectory(File homeDirectory) {
328 this.homeDirectory = homeDirectory;
329 }
330
331 public void setInMemory(Boolean inMemory) {
332 this.inMemory = inMemory;
333 }
334
335 public void setRepository(Repository repository) {
336 throw new ArgeoException("Cannot be used to wrap another repository");
337 }
338
339 public void setDataModelMigrations(
340 Set<JackrabbitDataModelMigration> dataModelMigrations) {
341 this.dataModelMigrations = dataModelMigrations;
342 }
343
344 public void setVariables(Resource variables) {
345 this.variables = variables;
346 }
347
348 public void setConfiguration(Resource configuration) {
349 this.configuration = configuration;
350 }
351
352 }