2 * Copyright (C) 2007-2012 Argeo GmbH
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org
.argeo
.slc
.repo
;
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
;
25 import java
.util
.TimeZone
;
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
;
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
;
51 /** Sync to from software repositories */
52 public class RepoSync
implements Runnable
{
53 private final static Log log
= LogFactory
.getLog(RepoSync
.class);
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");
59 private final Calendar zero
;
60 private Session sourceDefaultSession
= null;
61 private Session targetDefaultSession
= null;
63 private Repository sourceRepository
;
64 private Credentials sourceCredentials
;
65 private Repository targetRepository
;
66 private Credentials targetCredentials
;
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
;
76 private RepositoryFactory repositoryFactory
;
78 private ArgeoMonitor monitor
;
79 private List
<String
> sourceWkspList
;
82 zero
= new GregorianCalendar(TimeZone
.getTimeZone("UTC"));
83 zero
.setTimeInMillis(0);
88 * Shortcut to instantiate a RepoSync with already known repositories and
91 * @param sourceRepository
92 * @param sourceCredentials
93 * @param targetRepository
94 * @param targetCredentials
96 public RepoSync(Repository sourceRepository
, Credentials sourceCredentials
,
97 Repository targetRepository
, Credentials targetCredentials
) {
99 this.sourceRepository
= sourceRepository
;
100 this.sourceCredentials
= sourceCredentials
;
101 this.targetRepository
= targetRepository
;
102 this.targetCredentials
= targetCredentials
;
107 long begin
= System
.currentTimeMillis();
110 if (sourceRepository
== null)
111 sourceRepository
= ArgeoJcrUtils
.getRepositoryByUri(
112 repositoryFactory
, sourceRepoUri
);
113 if (sourceCredentials
== null && sourceUsername
!= null)
114 sourceCredentials
= new SimpleCredentials(sourceUsername
,
116 sourceDefaultSession
= sourceRepository
.login(sourceCredentials
);
118 if (targetRepository
== null)
119 targetRepository
= ArgeoJcrUtils
.getRepositoryByUri(
120 repositoryFactory
, targetRepoUri
);
121 if (targetCredentials
== null && targetUsername
!= null)
122 targetCredentials
= new SimpleCredentials(targetUsername
,
124 targetDefaultSession
= targetRepository
.login(targetCredentials
);
126 // FIXME implement a cleaner way to 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
);
136 for (String sourceWorkspaceName
: sourceDefaultSession
137 .getWorkspace().getAccessibleWorkspaceNames()) {
138 totalAmount
+= getNodesNumber(sourceWorkspaceName
);
140 monitor
.beginTask("Fetch", totalAmount
.intValue());
142 if (log
.isDebugEnabled())
143 log
.debug("Nb of nodes to sync: " + totalAmount
.intValue());
146 Map
<String
, Exception
> errors
= new HashMap
<String
, Exception
>();
147 for (String sourceWorkspaceName
: sourceDefaultSession
148 .getWorkspace().getAccessibleWorkspaceNames()) {
150 if (sourceWkspList
!= null
151 && !sourceWkspList
.contains(sourceWorkspaceName
))
153 if (IGNORED_WSKP_LIST
.contains(sourceWorkspaceName
))
156 Session sourceSession
= null;
157 Session targetSession
= null;
160 targetSession
= targetRepository
.login(
161 targetCredentials
, sourceWorkspaceName
);
162 } catch (NoSuchWorkspaceException e
) {
163 targetDefaultSession
.getWorkspace().createWorkspace(
164 sourceWorkspaceName
);
165 targetSession
= targetRepository
.login(
166 targetCredentials
, sourceWorkspaceName
);
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())
177 JcrUtils
.logoutQuietly(sourceSession
);
178 JcrUtils
.logoutQuietly(targetSession
);
182 if (monitor
!= null && monitor
.isCanceled())
183 log
.info("Sync has been canceled by user");
185 long duration
= (System
.currentTimeMillis() - begin
) / 1000;// s
186 log
.info("Sync " + sourceRepoUri
+ " to " + targetRepoUri
+ " in "
189 + "min " + (duration
% 60) + "s");
191 if (errors
.size() > 0) {
192 throw new SlcException("Sync failed " + errors
);
194 } catch (RepositoryException e
) {
195 throw new SlcException("Cannot sync " + sourceRepoUri
+ " to "
198 JcrUtils
.logoutQuietly(sourceDefaultSession
);
199 JcrUtils
.logoutQuietly(targetDefaultSession
);
203 private long getNodesNumber(String wkspName
) {
204 if (IGNORED_WSKP_LIST
.contains(wkspName
))
206 Session sourceSession
= null;
208 sourceSession
= sourceRepository
.login(sourceCredentials
, wkspName
);
209 Query countQuery
= sourceDefaultSession
212 .createQuery("select file from [nt:base] as file",
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
);
221 JcrUtils
.logoutQuietly(sourceSession
);
225 protected void syncWorkspace(Session sourceSession
, Session targetSession
) {
227 String msg
= "Synchronizing workspace: "
228 + sourceSession
.getWorkspace().getName();
230 monitor
.setTaskName(msg
);
231 if (log
.isDebugEnabled())
233 for (NodeIterator it
= sourceSession
.getRootNode().getNodes(); it
235 Node node
= it
.nextNode();
236 if (node
.getName().equals("jcr:system"))
238 syncNode(node
, targetSession
.getRootNode());
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
);
249 /** factorizes monitor management */
250 private void updateMonitor(String msg
) {
251 if (monitor
!= null) {
253 monitor
.subTask(msg
);
257 protected void syncNode(Node sourceNode
, Node targetParentNode
)
258 throws RepositoryException
, SAXException
{
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");
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();
275 if (sourceNode
.getDefinition().isProtected())
276 log
.warn(sourceNode
+ " is protected.");
278 if (!targetParentNode
.hasNode(sourceNode
.getName())) {
279 String msg
= "Adding " + sourceNode
.getPath();
281 if (log
.isDebugEnabled())
283 ContentHandler contentHandler
= targetParentNode
286 .getImportContentHandler(targetParentNode
.getPath(),
287 ImportUUIDBehavior
.IMPORT_UUID_COLLISION_THROW
);
288 sourceNode
.getSession().exportSystemView(sourceNode
.getPath(),
289 contentHandler
, false, noRecurse
);
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();
299 if (targetLastModified
== null
300 || targetLastModified
.before(sourceLastModified
)) {
301 String msg
= "Updating " + targetNode
.getPath();
303 if (log
.isDebugEnabled())
305 ContentHandler contentHandler
= targetParentNode
308 .getImportContentHandler(
309 targetParentNode
.getPath(),
310 ImportUUIDBehavior
.IMPORT_UUID_COLLISION_REMOVE_EXISTING
);
311 sourceNode
.getSession().exportSystemView(
312 sourceNode
.getPath(), contentHandler
, false,
315 String msg
= "Skipped up to date " + targetNode
.getPath();
317 if (log
.isDebugEnabled())
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();
334 for (NodeIterator it
= sourceNode
.getNodes(); it
.hasNext();) {
335 syncNode(it
.nextNode(), targetNode
);
338 if (sourceLastModified
!= null) {
339 targetNode
.setProperty(Property
.JCR_LAST_MODIFIED
,
341 targetNode
.getSession().save();
346 protected Boolean
noRecurse(Node sourceNode
) throws RepositoryException
{
347 if (sourceNode
.isNodeType(NodeType
.NT_FILE
))
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
);
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
);
377 public void setMonitor(ArgeoMonitor monitor
) {
378 this.monitor
= monitor
;
381 public void setRepositoryFactory(RepositoryFactory repositoryFactory
) {
382 this.repositoryFactory
= repositoryFactory
;
385 public void setSourceRepoUri(String sourceRepoUri
) {
386 this.sourceRepoUri
= sourceRepoUri
;
389 public void setSourceUsername(String sourceUsername
) {
390 this.sourceUsername
= sourceUsername
;
393 public void setSourcePassword(char[] sourcePassword
) {
394 this.sourcePassword
= sourcePassword
;
397 public void setTargetRepoUri(String targetRepoUri
) {
398 this.targetRepoUri
= targetRepoUri
;
401 public void setTargetUsername(String targetUsername
) {
402 this.targetUsername
= targetUsername
;
405 public void setTargetPassword(char[] targetPassword
) {
406 this.targetPassword
= targetPassword
;
409 public void setSourceRepository(Repository sourceRepository
) {
410 this.sourceRepository
= sourceRepository
;
413 public void setSourceCredentials(Credentials sourceCredentials
) {
414 this.sourceCredentials
= sourceCredentials
;
417 public void setTargetRepository(Repository targetRepository
) {
418 this.targetRepository
= targetRepository
;
421 public void setTargetCredentials(Credentials targetCredentials
) {
422 this.targetCredentials
= targetCredentials
;