]> git.argeo.org Git - gpl/argeo-slc.git/blob - runtime/org.argeo.slc.repo/src/main/java/org/argeo/slc/repo/RepoSync.java
d5337470740c2d4557472b5a0f071a06146e2319
[gpl/argeo-slc.git] / runtime / org.argeo.slc.repo / src / main / java / org / argeo / slc / repo / RepoSync.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.slc.repo;
17
18 import java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.Calendar;
21 import java.util.GregorianCalendar;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.TimeZone;
26
27 import javax.jcr.Credentials;
28 import javax.jcr.ImportUUIDBehavior;
29 import javax.jcr.NoSuchWorkspaceException;
30 import javax.jcr.Node;
31 import javax.jcr.NodeIterator;
32 import javax.jcr.Property;
33 import javax.jcr.Repository;
34 import javax.jcr.RepositoryException;
35 import javax.jcr.RepositoryFactory;
36 import javax.jcr.Session;
37 import javax.jcr.SimpleCredentials;
38 import javax.jcr.nodetype.NodeType;
39 import javax.jcr.query.Query;
40 import javax.jcr.query.QueryResult;
41
42 import org.apache.commons.logging.Log;
43 import org.apache.commons.logging.LogFactory;
44 import org.argeo.ArgeoMonitor;
45 import org.argeo.jcr.ArgeoJcrUtils;
46 import org.argeo.jcr.JcrUtils;
47 import org.argeo.slc.SlcException;
48 import org.xml.sax.ContentHandler;
49 import org.xml.sax.SAXException;
50
51 /** Sync to from software repositories */
52 public class RepoSync implements Runnable {
53 private final static Log log = LogFactory.getLog(RepoSync.class);
54
55 // Centralizes definition of workspaces that must be ignored by the sync.
56 private final static List<String> IGNORED_WSKP_LIST = Arrays.asList(
57 "security", "localrepo");
58
59 private final Calendar zero;
60 private Session sourceDefaultSession = null;
61 private Session targetDefaultSession = null;
62
63 private Repository sourceRepository;
64 private Credentials sourceCredentials;
65 private Repository targetRepository;
66 private Credentials targetCredentials;
67
68 // if Repository and Credentials objects are not explicitly set
69 private String sourceRepoUri;
70 private String sourceUsername;
71 private char[] sourcePassword;
72 private String targetRepoUri;
73 private String targetUsername;
74 private char[] targetPassword;
75
76 private RepositoryFactory repositoryFactory;
77
78 private ArgeoMonitor monitor;
79 private List<String> sourceWkspList;
80
81 public RepoSync() {
82 zero = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
83 zero.setTimeInMillis(0);
84 }
85
86 /**
87 *
88 * Shortcut to instantiate a RepoSync with already known repositories and
89 * credentials.
90 *
91 * @param sourceRepository
92 * @param sourceCredentials
93 * @param targetRepository
94 * @param targetCredentials
95 */
96 public RepoSync(Repository sourceRepository, Credentials sourceCredentials,
97 Repository targetRepository, Credentials targetCredentials) {
98 this();
99 this.sourceRepository = sourceRepository;
100 this.sourceCredentials = sourceCredentials;
101 this.targetRepository = targetRepository;
102 this.targetCredentials = targetCredentials;
103 }
104
105 public void run() {
106 try {
107 long begin = System.currentTimeMillis();
108
109 // Setup
110 if (sourceRepository == null)
111 sourceRepository = ArgeoJcrUtils.getRepositoryByUri(
112 repositoryFactory, sourceRepoUri);
113 if (sourceCredentials == null && sourceUsername != null)
114 sourceCredentials = new SimpleCredentials(sourceUsername,
115 sourcePassword);
116 sourceDefaultSession = sourceRepository.login(sourceCredentials);
117
118 if (targetRepository == null)
119 targetRepository = ArgeoJcrUtils.getRepositoryByUri(
120 repositoryFactory, targetRepoUri);
121 if (targetCredentials == null && targetUsername != null)
122 targetCredentials = new SimpleCredentials(targetUsername,
123 targetPassword);
124 targetDefaultSession = targetRepository.login(targetCredentials);
125
126 // FIXME implement a cleaner way to compute job size.
127 // Compute job size
128 if (monitor != null) {
129 monitor.beginTask("Computing fetch size...", -1);
130 Long totalAmount = 0l;
131 if (sourceWkspList != null) {
132 for (String wkspName : sourceWkspList) {
133 totalAmount += getNodesNumber(wkspName);
134 }
135 } else
136 for (String sourceWorkspaceName : sourceDefaultSession
137 .getWorkspace().getAccessibleWorkspaceNames()) {
138 totalAmount += getNodesNumber(sourceWorkspaceName);
139 }
140 monitor.beginTask("Fetch", totalAmount.intValue());
141
142 if (log.isDebugEnabled())
143 log.debug("Nb of nodes to sync: " + totalAmount.intValue());
144 }
145
146 Map<String, Exception> errors = new HashMap<String, Exception>();
147 for (String sourceWorkspaceName : sourceDefaultSession
148 .getWorkspace().getAccessibleWorkspaceNames()) {
149
150 if (sourceWkspList != null
151 && !sourceWkspList.contains(sourceWorkspaceName))
152 continue;
153 if (IGNORED_WSKP_LIST.contains(sourceWorkspaceName))
154 continue;
155
156 Session sourceSession = null;
157 Session targetSession = null;
158 try {
159 try {
160 targetSession = targetRepository.login(
161 targetCredentials, sourceWorkspaceName);
162 } catch (NoSuchWorkspaceException e) {
163 targetDefaultSession.getWorkspace().createWorkspace(
164 sourceWorkspaceName);
165 targetSession = targetRepository.login(
166 targetCredentials, sourceWorkspaceName);
167 }
168 sourceSession = sourceRepository.login(sourceCredentials,
169 sourceWorkspaceName);
170 syncWorkspace(sourceSession, targetSession);
171 } catch (Exception e) {
172 errors.put("Could not sync workspace "
173 + sourceWorkspaceName, e);
174 if (log.isDebugEnabled())
175 e.printStackTrace();
176 } finally {
177 JcrUtils.logoutQuietly(sourceSession);
178 JcrUtils.logoutQuietly(targetSession);
179 }
180 }
181
182 if (monitor != null && monitor.isCanceled())
183 log.info("Sync has been canceled by user");
184
185 long duration = (System.currentTimeMillis() - begin) / 1000;// s
186 log.info("Sync " + sourceRepoUri + " to " + targetRepoUri + " in "
187 + (duration / 60)
188
189 + "min " + (duration % 60) + "s");
190
191 if (errors.size() > 0) {
192 throw new SlcException("Sync failed " + errors);
193 }
194 } catch (RepositoryException e) {
195 throw new SlcException("Cannot sync " + sourceRepoUri + " to "
196 + targetRepoUri, e);
197 } finally {
198 JcrUtils.logoutQuietly(sourceDefaultSession);
199 JcrUtils.logoutQuietly(targetDefaultSession);
200 }
201 }
202
203 private long getNodesNumber(String wkspName) {
204 if (IGNORED_WSKP_LIST.contains(wkspName))
205 return 0l;
206 Session sourceSession = null;
207 try {
208 sourceSession = sourceRepository.login(sourceCredentials, wkspName);
209 Query countQuery = sourceDefaultSession
210 .getWorkspace()
211 .getQueryManager()
212 .createQuery("select file from [nt:base] as file",
213 Query.JCR_SQL2);
214 QueryResult result = countQuery.execute();
215 Long expectedCount = result.getNodes().getSize();
216 return expectedCount;
217 } catch (RepositoryException e) {
218 throw new SlcException("Unexpected error while computing "
219 + "the size of the fetch for workspace " + wkspName, e);
220 } finally {
221 JcrUtils.logoutQuietly(sourceSession);
222 }
223 }
224
225 protected void syncWorkspace(Session sourceSession, Session targetSession) {
226 try {
227 String msg = "Synchronizing workspace: "
228 + sourceSession.getWorkspace().getName();
229 if (monitor != null)
230 monitor.setTaskName(msg);
231 if (log.isDebugEnabled())
232 log.debug(msg);
233 for (NodeIterator it = sourceSession.getRootNode().getNodes(); it
234 .hasNext();) {
235 Node node = it.nextNode();
236 if (node.getName().equals("jcr:system"))
237 continue;
238 syncNode(node, targetSession.getRootNode());
239 }
240 if (log.isDebugEnabled())
241 log.debug("Synced " + sourceSession.getWorkspace().getName());
242 } catch (Exception e) {
243 throw new SlcException("Cannot sync "
244 + sourceSession.getWorkspace().getName() + " to "
245 + targetSession.getWorkspace().getName(), e);
246 }
247 }
248
249 /** factorizes monitor management */
250 private void updateMonitor(String msg) {
251 if (monitor != null) {
252 monitor.worked(1);
253 monitor.subTask(msg);
254 }
255 }
256
257 protected void syncNode(Node sourceNode, Node targetParentNode)
258 throws RepositoryException, SAXException {
259
260 // enable cancelation of the current fetch process
261 // FIXME insure the repository stays in a stable state
262 if (monitor != null && monitor.isCanceled()) {
263 updateMonitor("Fetched has been canceled, "
264 + "process is terminating");
265 return;
266 }
267
268 Boolean noRecurse = noRecurse(sourceNode);
269 Calendar sourceLastModified = null;
270 if (sourceNode.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
271 sourceLastModified = sourceNode.getProperty(
272 Property.JCR_LAST_MODIFIED).getDate();
273 }
274
275 if (sourceNode.getDefinition().isProtected())
276 return;
277
278 if (!targetParentNode.hasNode(sourceNode.getName())) {
279 String msg = "Adding " + sourceNode.getPath();
280 updateMonitor(msg);
281 if (log.isDebugEnabled())
282 log.debug(msg);
283 ContentHandler contentHandler = targetParentNode
284 .getSession()
285 .getWorkspace()
286 .getImportContentHandler(targetParentNode.getPath(),
287 ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
288 sourceNode.getSession().exportSystemView(sourceNode.getPath(),
289 contentHandler, false, noRecurse);
290 } else {
291 Node targetNode = targetParentNode.getNode(sourceNode.getName());
292 if (sourceLastModified != null) {
293 Calendar targetLastModified = null;
294 if (targetNode.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
295 targetLastModified = targetNode.getProperty(
296 Property.JCR_LAST_MODIFIED).getDate();
297 }
298
299 if (targetLastModified == null
300 || targetLastModified.before(sourceLastModified)) {
301 String msg = "Updating " + targetNode.getPath();
302 updateMonitor(msg);
303 if (log.isDebugEnabled())
304 log.debug(msg);
305 ContentHandler contentHandler = targetParentNode
306 .getSession()
307 .getWorkspace()
308 .getImportContentHandler(
309 targetParentNode.getPath(),
310 ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING);
311 sourceNode.getSession().exportSystemView(
312 sourceNode.getPath(), contentHandler, false,
313 noRecurse);
314 } else {
315 String msg = "Skipped up to date " + targetNode.getPath();
316 updateMonitor(msg);
317 if (log.isDebugEnabled())
318 log.debug(msg);
319 return;
320 }
321 }
322 }
323
324 if (noRecurse) {
325 // recurse
326 Node targetNode = targetParentNode.getNode(sourceNode.getName());
327 if (sourceLastModified != null) {
328 Calendar zero = new GregorianCalendar();
329 zero.setTimeInMillis(0);
330 targetNode.setProperty(Property.JCR_LAST_MODIFIED, zero);
331 targetNode.getSession().save();
332 }
333
334 for (NodeIterator it = sourceNode.getNodes(); it.hasNext();) {
335 syncNode(it.nextNode(), targetNode);
336 }
337
338 if (sourceLastModified != null) {
339 targetNode.setProperty(Property.JCR_LAST_MODIFIED,
340 sourceLastModified);
341 targetNode.getSession().save();
342 }
343 }
344 }
345
346 protected Boolean noRecurse(Node sourceNode) throws RepositoryException {
347 if (sourceNode.isNodeType(NodeType.NT_FILE))
348 return false;
349 return true;
350 }
351
352 /** synchronise only one workspace retrieved by name */
353 public void setSourceWksp(String sourceWksp) {
354 if (sourceWksp != null && !sourceWksp.trim().equals("")) {
355 List<String> list = new ArrayList<String>();
356 list.add(sourceWksp);
357 setSourceWkspList(list);
358 }
359 }
360
361 /** synchronise a list workspace that will be retrieved by name */
362 public void setSourceWkspList(List<String> sourceWkspList) {
363 // clean the list to ease later use
364 this.sourceWkspList = null;
365 if (sourceWkspList != null) {
366 for (String wkspName : sourceWkspList) {
367 if (!wkspName.trim().equals("")) {
368 // only instantiate if needed
369 if (this.sourceWkspList == null)
370 this.sourceWkspList = new ArrayList<String>();
371 this.sourceWkspList.add(wkspName);
372 }
373 }
374 }
375 }
376
377 public void setMonitor(ArgeoMonitor monitor) {
378 this.monitor = monitor;
379 }
380
381 public void setRepositoryFactory(RepositoryFactory repositoryFactory) {
382 this.repositoryFactory = repositoryFactory;
383 }
384
385 public void setSourceRepoUri(String sourceRepoUri) {
386 this.sourceRepoUri = sourceRepoUri;
387 }
388
389 public void setSourceUsername(String sourceUsername) {
390 this.sourceUsername = sourceUsername;
391 }
392
393 public void setSourcePassword(char[] sourcePassword) {
394 this.sourcePassword = sourcePassword;
395 }
396
397 public void setTargetRepoUri(String targetRepoUri) {
398 this.targetRepoUri = targetRepoUri;
399 }
400
401 public void setTargetUsername(String targetUsername) {
402 this.targetUsername = targetUsername;
403 }
404
405 public void setTargetPassword(char[] targetPassword) {
406 this.targetPassword = targetPassword;
407 }
408
409 public void setSourceRepository(Repository sourceRepository) {
410 this.sourceRepository = sourceRepository;
411 }
412
413 public void setSourceCredentials(Credentials sourceCredentials) {
414 this.sourceCredentials = sourceCredentials;
415 }
416
417 public void setTargetRepository(Repository targetRepository) {
418 this.targetRepository = targetRepository;
419 }
420
421 public void setTargetCredentials(Credentials targetCredentials) {
422 this.targetCredentials = targetCredentials;
423 }
424 }