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