1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.argeo.slc.repo;
17
18 import java.io.InputStream;
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.Binary;
28 import javax.jcr.Credentials;
29 import javax.jcr.NoSuchWorkspaceException;
30 import javax.jcr.Node;
31 import javax.jcr.NodeIterator;
32 import javax.jcr.Property;
33 import javax.jcr.PropertyIterator;
34 import javax.jcr.PropertyType;
35 import javax.jcr.Repository;
36 import javax.jcr.RepositoryException;
37 import javax.jcr.RepositoryFactory;
38 import javax.jcr.Session;
39 import javax.jcr.SimpleCredentials;
40 import javax.jcr.nodetype.NodeType;
41 import javax.jcr.query.Query;
42 import javax.jcr.query.QueryResult;
43
44 import org.apache.commons.io.IOUtils;
45 import org.apache.commons.logging.Log;
46 import org.apache.commons.logging.LogFactory;
47 import org.argeo.jcr.JcrMonitor;
48 import org.argeo.jcr.JcrUtils;
49 import org.argeo.node.NodeUtils;
50 import org.argeo.slc.SlcException;
51 import org.xml.sax.SAXException;
52
53
54
55
56
57
58
59
60
61 public class RepoSync implements Runnable {
62 private final static Log log = LogFactory.getLog(RepoSync.class);
63
64
65 private final static List<String> IGNORED_WKSP_LIST = Arrays.asList("security", "localrepo");
66
67 private final Calendar zero;
68 private Session sourceDefaultSession = null;
69 private Session targetDefaultSession = null;
70
71 private Repository sourceRepository;
72 private Credentials sourceCredentials;
73 private Repository targetRepository;
74 private Credentials targetCredentials;
75
76
77 private String sourceRepoUri;
78 private String sourceUsername;
79 private char[] sourcePassword;
80 private String targetRepoUri;
81 private String targetUsername;
82 private char[] targetPassword;
83
84 private RepositoryFactory repositoryFactory;
85
86 private JcrMonitor monitor;
87 private Map<String, String> workspaceMap;
88
89
90 private Boolean filesOnly = false;
91
92 public RepoSync() {
93 zero = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
94 zero.setTimeInMillis(0);
95 }
96
97
98
99
100
101
102
103
104
105
106
107 public RepoSync(Repository sourceRepository, Credentials sourceCredentials, Repository targetRepository,
108 Credentials targetCredentials) {
109 this();
110 this.sourceRepository = sourceRepository;
111 this.sourceCredentials = sourceCredentials;
112 this.targetRepository = targetRepository;
113 this.targetCredentials = targetCredentials;
114 }
115
116 public void run() {
117 try {
118 long begin = System.currentTimeMillis();
119
120
121 if (sourceRepository == null)
122 sourceRepository = NodeUtils.getRepositoryByUri(repositoryFactory, sourceRepoUri);
123 if (sourceCredentials == null && sourceUsername != null)
124 sourceCredentials = new SimpleCredentials(sourceUsername, sourcePassword);
125
126 sourceDefaultSession = sourceRepository.login(sourceCredentials, RepoConstants.DEFAULT_DEFAULT_WORKSPACE);
127
128 if (targetRepository == null)
129 targetRepository = NodeUtils.getRepositoryByUri(repositoryFactory, targetRepoUri);
130 if (targetCredentials == null && targetUsername != null)
131 targetCredentials = new SimpleCredentials(targetUsername, targetPassword);
132 targetDefaultSession = targetRepository.login(targetCredentials);
133
134 Map<String, Exception> errors = new HashMap<String, Exception>();
135 for (String sourceWorkspaceName : sourceDefaultSession.getWorkspace().getAccessibleWorkspaceNames()) {
136 if (monitor != null && monitor.isCanceled())
137 break;
138
139 if (workspaceMap != null && !workspaceMap.containsKey(sourceWorkspaceName))
140 continue;
141 if (IGNORED_WKSP_LIST.contains(sourceWorkspaceName))
142 continue;
143
144 Session sourceSession = null;
145 Session targetSession = null;
146 String targetWorkspaceName = workspaceMap.get(sourceWorkspaceName);
147 try {
148 try {
149 targetSession = targetRepository.login(targetCredentials, targetWorkspaceName);
150 } catch (NoSuchWorkspaceException e) {
151 targetDefaultSession.getWorkspace().createWorkspace(targetWorkspaceName);
152 targetSession = targetRepository.login(targetCredentials, targetWorkspaceName);
153 }
154 sourceSession = sourceRepository.login(sourceCredentials, sourceWorkspaceName);
155 syncWorkspace(sourceSession, targetSession);
156 } catch (Exception e) {
157 errors.put("Could not sync workspace " + sourceWorkspaceName, e);
158 if (log.isErrorEnabled())
159 e.printStackTrace();
160
161 } finally {
162 JcrUtils.logoutQuietly(sourceSession);
163 JcrUtils.logoutQuietly(targetSession);
164 }
165 }
166
167 if (monitor != null && monitor.isCanceled())
168 log.info("Sync has been canceled by user");
169
170 long duration = (System.currentTimeMillis() - begin) / 1000;
171 log.info("Sync " + sourceRepoUri + " to " + targetRepoUri + " in " + (duration / 60)
172
173 + "min " + (duration % 60) + "s");
174
175 if (errors.size() > 0) {
176 throw new SlcException("Sync failed " + errors);
177 }
178 } catch (RepositoryException e) {
179 throw new SlcException("Cannot sync " + sourceRepoUri + " to " + targetRepoUri, e);
180 } finally {
181 JcrUtils.logoutQuietly(sourceDefaultSession);
182 JcrUtils.logoutQuietly(targetDefaultSession);
183 }
184 }
185
186 private long getNodesNumber(Session session) {
187 if (IGNORED_WKSP_LIST.contains(session.getWorkspace().getName()))
188 return 0l;
189 try {
190 Query countQuery = session.getWorkspace().getQueryManager().createQuery(
191 "select file from [" + (true ? NodeType.NT_FILE : NodeType.NT_BASE) + "] as file", Query.JCR_SQL2);
192
193 QueryResult result = countQuery.execute();
194 Long expectedCount = result.getNodes().getSize();
195 return expectedCount;
196 } catch (RepositoryException e) {
197 throw new SlcException("Unexpected error while computing " + "the size of the fetch for workspace "
198 + session.getWorkspace().getName(), e);
199 }
200 }
201
202 protected void syncWorkspace(Session sourceSession, Session targetSession) {
203 if (monitor != null) {
204 monitor.beginTask("Computing fetch size...", -1);
205 Long totalAmount = getNodesNumber(sourceSession);
206 monitor.beginTask("Fetch", totalAmount.intValue());
207 }
208
209 try {
210 String msg = "Synchronizing workspace: " + sourceSession.getWorkspace().getName();
211 if (monitor != null)
212 monitor.setTaskName(msg);
213 if (log.isDebugEnabled())
214 log.debug(msg);
215
216 for (NodeIterator it = sourceSession.getRootNode().getNodes(); it.hasNext();) {
217 Node node = it.nextNode();
218 if (node.getName().contains(":"))
219 continue;
220 if (node.getName().equals("download"))
221 continue;
222 if (!node.isNodeType(NodeType.NT_HIERARCHY_NODE))
223 continue;
224 syncNode(node, targetSession);
225 }
226
227
228
229
230
231
232
233
234
235
236
237
238 if (log.isDebugEnabled())
239 log.debug("Synced " + sourceSession.getWorkspace().getName());
240 } catch (Exception e) {
241 e.printStackTrace();
242 throw new SlcException("Cannot sync " + sourceSession.getWorkspace().getName() + " to "
243 + targetSession.getWorkspace().getName(), e);
244 }
245 }
246
247
248 private void updateMonitor(String msg) {
249 updateMonitor(msg, false);
250 }
251
252 protected void syncNode(Node sourceNode, Session targetSession) throws RepositoryException, SAXException {
253 if (filesOnly) {
254 Node targetNode;
255 if (targetSession.itemExists(sourceNode.getPath()))
256 targetNode = targetSession.getNode(sourceNode.getPath());
257 else
258 targetNode = JcrUtils.mkdirs(targetSession, sourceNode.getPath(), NodeType.NT_FOLDER);
259 JcrUtils.copyFiles(sourceNode, targetNode, true, monitor, true);
260 return;
261 }
262
263 try {
264 if (monitor != null && monitor.isCanceled()) {
265 updateMonitor("Fetched has been canceled, " + "process is terminating");
266 return;
267 }
268
269 Node targetParentNode = targetSession.getNode(sourceNode.getParent().getPath());
270 Node targetNode;
271 if (monitor != null && sourceNode.isNodeType(NodeType.NT_HIERARCHY_NODE))
272 monitor.subTask("Process " + sourceNode.getPath());
273
274 final Boolean isNew;
275 if (!targetSession.itemExists(sourceNode.getPath())) {
276 isNew = true;
277 targetNode = targetParentNode.addNode(sourceNode.getName(), sourceNode.getPrimaryNodeType().getName());
278 } else {
279 isNew = false;
280 targetNode = targetSession.getNode(sourceNode.getPath());
281 if (!targetNode.getPrimaryNodeType().getName().equals(sourceNode.getPrimaryNodeType().getName()))
282 targetNode.setPrimaryType(sourceNode.getPrimaryNodeType().getName());
283 }
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302 for (NodeType nt : sourceNode.getMixinNodeTypes()) {
303 if (!targetNode.isNodeType(nt.getName()) && targetNode.canAddMixin(nt.getName()))
304 targetNode.addMixin(nt.getName());
305 }
306 copyProperties(sourceNode, targetNode);
307
308
309 NodeIterator ni = sourceNode.getNodes();
310 while (ni != null && ni.hasNext()) {
311 Node sourceChild = ni.nextNode();
312 syncNode(sourceChild, targetSession);
313 }
314
315 copyTimestamps(sourceNode, targetNode);
316
317 if (sourceNode.isNodeType(NodeType.NT_HIERARCHY_NODE)) {
318 if (targetSession.hasPendingChanges()) {
319 if (sourceNode.isNodeType(NodeType.NT_FILE))
320 updateMonitor((isNew ? "Added " : "Updated ") + targetNode.getPath(), true);
321
322 targetSession.save();
323 } else {
324 if (sourceNode.isNodeType(NodeType.NT_FILE))
325 updateMonitor("Checked " + targetNode.getPath(), false);
326 }
327 }
328 } catch (RepositoryException e) {
329 throw new SlcException("Cannot sync source node " + sourceNode, e);
330 }
331 }
332
333 private void copyTimestamps(Node sourceNode, Node targetNode) throws RepositoryException {
334 if (sourceNode.getDefinition().isProtected())
335 return;
336 if (targetNode.getDefinition().isProtected())
337 return;
338 copyTimestamp(sourceNode, targetNode, Property.JCR_CREATED);
339 copyTimestamp(sourceNode, targetNode, Property.JCR_CREATED_BY);
340 copyTimestamp(sourceNode, targetNode, Property.JCR_LAST_MODIFIED);
341 copyTimestamp(sourceNode, targetNode, Property.JCR_LAST_MODIFIED_BY);
342 }
343
344 private void copyTimestamp(Node sourceNode, Node targetNode, String property) throws RepositoryException {
345 if (sourceNode.hasProperty(property)) {
346 Property p = sourceNode.getProperty(property);
347 if (p.getDefinition().isProtected())
348 return;
349 if (targetNode.hasProperty(property)
350 && targetNode.getProperty(property).getValue().equals(sourceNode.getProperty(property).getValue()))
351 return;
352 targetNode.setProperty(property, sourceNode.getProperty(property).getValue());
353 }
354 }
355
356 private void copyProperties(Node sourceNode, Node targetNode) throws RepositoryException {
357 properties: for (PropertyIterator pi = sourceNode.getProperties(); pi.hasNext();) {
358 Property p = pi.nextProperty();
359 if (p.getDefinition().isProtected())
360 continue properties;
361 if (p.getName().equals(Property.JCR_CREATED) || p.getName().equals(Property.JCR_CREATED_BY)
362 || p.getName().equals(Property.JCR_LAST_MODIFIED)
363 || p.getName().equals(Property.JCR_LAST_MODIFIED_BY))
364 continue properties;
365
366 if (p.getType() == PropertyType.BINARY) {
367 copyBinary(p, targetNode);
368 } else {
369
370 if (p.isMultiple()) {
371 if (!targetNode.hasProperty(p.getName())
372 || !Arrays.equals(targetNode.getProperty(p.getName()).getValues(), p.getValues()))
373 targetNode.setProperty(p.getName(), p.getValues());
374 } else {
375 if (!targetNode.hasProperty(p.getName())
376 || !targetNode.getProperty(p.getName()).getValue().equals(p.getValue()))
377 targetNode.setProperty(p.getName(), p.getValue());
378 }
379 }
380 }
381 }
382
383 private static void copyBinary(Property p, Node targetNode) throws RepositoryException {
384 InputStream in = null;
385 Binary sourceBinary = null;
386 Binary targetBinary = null;
387 try {
388 sourceBinary = p.getBinary();
389 if (targetNode.hasProperty(p.getName()))
390 targetBinary = targetNode.getProperty(p.getName()).getBinary();
391
392
393 if (targetBinary != null)
394 if (sourceBinary.getSize() == targetBinary.getSize()) {
395 if (log.isTraceEnabled())
396 log.trace("Skipped " + p.getPath());
397 return;
398 }
399
400 in = sourceBinary.getStream();
401 targetBinary = targetNode.getSession().getValueFactory().createBinary(in);
402 targetNode.setProperty(p.getName(), targetBinary);
403 } catch (Exception e) {
404 throw new SlcException("Could not transfer " + p, e);
405 } finally {
406 IOUtils.closeQuietly(in);
407 JcrUtils.closeQuietly(sourceBinary);
408 JcrUtils.closeQuietly(targetBinary);
409 }
410 }
411
412
413 private void updateMonitor(String msg, Boolean doLog) {
414 if (doLog && log.isDebugEnabled())
415 log.debug(msg);
416 if (monitor != null) {
417 monitor.worked(1);
418 monitor.subTask(msg);
419 }
420 }
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511 protected Boolean singleLevel(Node sourceNode) throws RepositoryException {
512 if (sourceNode.isNodeType(NodeType.NT_FILE))
513 return false;
514 return true;
515 }
516
517
518
519
520 public void setSourceWksp(String sourceWksp) {
521 if (sourceWksp != null && !sourceWksp.trim().equals("")) {
522 Map<String, String> map = new HashMap<String, String>();
523 map.put(sourceWksp, sourceWksp);
524 setWkspMap(map);
525 }
526 }
527
528
529
530
531
532
533 public void setWkspMap(Map<String, String> workspaceMap) {
534
535 this.workspaceMap = new HashMap<String, String>();
536 if (workspaceMap != null) {
537 workspaceNames: for (String srcName : workspaceMap.keySet()) {
538 String targetName = workspaceMap.get(srcName);
539
540
541 if (srcName.trim().equals(""))
542 continue workspaceNames;
543 if (targetName == null || "".equals(targetName.trim()))
544 targetName = srcName;
545 this.workspaceMap.put(srcName, targetName);
546 }
547 }
548
549 if (this.workspaceMap.size() == 0)
550 this.workspaceMap = null;
551 }
552
553 public void setMonitor(JcrMonitor monitor) {
554 this.monitor = monitor;
555 }
556
557 public void setRepositoryFactory(RepositoryFactory repositoryFactory) {
558 this.repositoryFactory = repositoryFactory;
559 }
560
561 public void setSourceRepoUri(String sourceRepoUri) {
562 this.sourceRepoUri = sourceRepoUri;
563 }
564
565 public void setSourceUsername(String sourceUsername) {
566 this.sourceUsername = sourceUsername;
567 }
568
569 public void setSourcePassword(char[] sourcePassword) {
570 this.sourcePassword = sourcePassword;
571 }
572
573 public void setTargetRepoUri(String targetRepoUri) {
574 this.targetRepoUri = targetRepoUri;
575 }
576
577 public void setTargetUsername(String targetUsername) {
578 this.targetUsername = targetUsername;
579 }
580
581 public void setTargetPassword(char[] targetPassword) {
582 this.targetPassword = targetPassword;
583 }
584
585 public void setSourceRepository(Repository sourceRepository) {
586 this.sourceRepository = sourceRepository;
587 }
588
589 public void setSourceCredentials(Credentials sourceCredentials) {
590 this.sourceCredentials = sourceCredentials;
591 }
592
593 public void setTargetRepository(Repository targetRepository) {
594 this.targetRepository = targetRepository;
595 }
596
597 public void setTargetCredentials(Credentials targetCredentials) {
598 this.targetCredentials = targetCredentials;
599 }
600
601 public void setFilesOnly(Boolean filesOnly) {
602 this.filesOnly = filesOnly;
603 }
604
605 }