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