X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=runtime%2Forg.argeo.slc.repo%2Fsrc%2Fmain%2Fjava%2Forg%2Fargeo%2Fslc%2Frepo%2FRepoSync.java;h=7f1e1e32d2f70b7cd4e4f6424dbaf6f2dc755445;hb=1a0340b557d4cd38e763e4922a3ee4e5dcc8f6d3;hp=3bcea12283a703bfc2a2d53f31ecf30d30e2f2b0;hpb=bf416e2c694bc81a50e894bb3fb8c377d1cccb36;p=gpl%2Fargeo-slc.git diff --git a/runtime/org.argeo.slc.repo/src/main/java/org/argeo/slc/repo/RepoSync.java b/runtime/org.argeo.slc.repo/src/main/java/org/argeo/slc/repo/RepoSync.java index 3bcea1228..7f1e1e32d 100644 --- a/runtime/org.argeo.slc.repo/src/main/java/org/argeo/slc/repo/RepoSync.java +++ b/runtime/org.argeo.slc.repo/src/main/java/org/argeo/slc/repo/RepoSync.java @@ -15,98 +15,151 @@ */ package org.argeo.slc.repo; +import java.io.InputStream; +import java.util.Arrays; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.TimeZone; +import javax.jcr.Binary; import javax.jcr.Credentials; -import javax.jcr.ImportUUIDBehavior; import javax.jcr.NoSuchWorkspaceException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.RepositoryFactory; import javax.jcr.Session; import javax.jcr.SimpleCredentials; import javax.jcr.nodetype.NodeType; +import javax.jcr.query.Query; +import javax.jcr.query.QueryResult; +import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoMonitor; import org.argeo.jcr.ArgeoJcrUtils; import org.argeo.jcr.JcrUtils; import org.argeo.slc.SlcException; -import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; -/** Sync to from software repositories */ +/** + * Synchronise workspaces from a remote software repository to the local + * repository (Synchronisation in the other direction does not work). + * + * Workspaces are retrieved by name given a map that links the source with a + * target name. If a target workspace does not exist, it is created. Otherwise + * we copy the content of the source workspace into the target one. + */ public class RepoSync implements Runnable { private final static Log log = LogFactory.getLog(RepoSync.class); - private final Calendar zero; + // Centralizes definition of workspaces that must be ignored by the sync. + private final static List IGNORED_WKSP_LIST = Arrays.asList( + "security", "localrepo"); - private String sourceRepo; - private String targetRepo; + private final Calendar zero; + private Session sourceDefaultSession = null; + private Session targetDefaultSession = null; - private String sourceWksp; + private Repository sourceRepository; + private Credentials sourceCredentials; + private Repository targetRepository; + private Credentials targetCredentials; + // if Repository and Credentials objects are not explicitly set + private String sourceRepoUri; private String sourceUsername; private char[] sourcePassword; + private String targetRepoUri; private String targetUsername; private char[] targetPassword; private RepositoryFactory repositoryFactory; + private ArgeoMonitor monitor; + private Map workspaceMap; + + // TODO fix monitor + private Boolean filesOnly = false; + public RepoSync() { zero = new GregorianCalendar(TimeZone.getTimeZone("UTC")); zero.setTimeInMillis(0); } + /** + * + * Shortcut to instantiate a RepoSync with already known repositories and + * credentials. + * + * @param sourceRepository + * @param sourceCredentials + * @param targetRepository + * @param targetCredentials + */ + public RepoSync(Repository sourceRepository, Credentials sourceCredentials, + Repository targetRepository, Credentials targetCredentials) { + this(); + this.sourceRepository = sourceRepository; + this.sourceCredentials = sourceCredentials; + this.targetRepository = targetRepository; + this.targetCredentials = targetCredentials; + } + public void run() { - Session sourceDefaultSession = null; - Session targetDefaultSession = null; try { long begin = System.currentTimeMillis(); - Repository sourceRepository = ArgeoJcrUtils.getRepositoryByUri( - repositoryFactory, sourceRepo); - Repository targetRepository = ArgeoJcrUtils.getRepositoryByUri( - repositoryFactory, targetRepo); - Credentials sourceCredentials = null; - if (sourceUsername != null) + // Setup + if (sourceRepository == null) + sourceRepository = ArgeoJcrUtils.getRepositoryByUri( + repositoryFactory, sourceRepoUri); + if (sourceCredentials == null && sourceUsername != null) sourceCredentials = new SimpleCredentials(sourceUsername, sourcePassword); - Credentials targetCredentials = null; - if (targetUsername != null) + sourceDefaultSession = sourceRepository.login(sourceCredentials); + + if (targetRepository == null) + targetRepository = ArgeoJcrUtils.getRepositoryByUri( + repositoryFactory, targetRepoUri); + if (targetCredentials == null && targetUsername != null) targetCredentials = new SimpleCredentials(targetUsername, targetPassword); + targetDefaultSession = targetRepository.login(targetCredentials); Map errors = new HashMap(); - sourceDefaultSession = sourceRepository.login(sourceCredentials); - targetDefaultSession = targetRepository.login(targetCredentials); for (String sourceWorkspaceName : sourceDefaultSession .getWorkspace().getAccessibleWorkspaceNames()) { - if (sourceWksp != null && !sourceWksp.trim().equals("") - && !sourceWorkspaceName.equals(sourceWksp)) - continue; - if (sourceWorkspaceName.equals("security")) + if (monitor != null && monitor.isCanceled()) + break; + + if (workspaceMap != null + && !workspaceMap.containsKey(sourceWorkspaceName)) continue; - if (sourceWorkspaceName.equals("localrepo")) + if (IGNORED_WKSP_LIST.contains(sourceWorkspaceName)) continue; + Session sourceSession = null; Session targetSession = null; + String targetWorkspaceName = workspaceMap + .get(sourceWorkspaceName); try { try { targetSession = targetRepository.login( - targetCredentials, sourceWorkspaceName); + targetCredentials, targetWorkspaceName); } catch (NoSuchWorkspaceException e) { targetDefaultSession.getWorkspace().createWorkspace( - sourceWorkspaceName); + targetWorkspaceName); targetSession = targetRepository.login( - targetCredentials, sourceWorkspaceName); + targetCredentials, targetWorkspaceName); } sourceSession = sourceRepository.login(sourceCredentials, sourceWorkspaceName); @@ -114,23 +167,20 @@ public class RepoSync implements Runnable { } catch (Exception e) { errors.put("Could not sync workspace " + sourceWorkspaceName, e); + if (log.isErrorEnabled()) + e.printStackTrace(); + } finally { JcrUtils.logoutQuietly(sourceSession); JcrUtils.logoutQuietly(targetSession); } } - // Session sourceSession = sourceRepository.login(sourceCredentials, - // sourceWksp); - // - // Credentials targetCredentials = null; - // Session targetSession = targetRepository.login(targetCredentials, - // sourceWksp); - // - // Long count = JcrUtils.copyFiles(sourceSession.getRootNode(), - // targetSession.getRootNode(), true, null); + + if (monitor != null && monitor.isCanceled()) + log.info("Sync has been canceled by user"); long duration = (System.currentTimeMillis() - begin) / 1000;// s - log.info("Sync " + sourceRepo + " to " + targetRepo + " in " + log.info("Sync " + sourceRepoUri + " to " + targetRepoUri + " in " + (duration / 60) + "min " + (duration % 60) + "s"); @@ -139,139 +189,407 @@ public class RepoSync implements Runnable { throw new SlcException("Sync failed " + errors); } } catch (RepositoryException e) { - throw new SlcException("Cannot sync " + sourceRepo + " to " - + targetRepo, e); + throw new SlcException("Cannot sync " + sourceRepoUri + " to " + + targetRepoUri, e); } finally { JcrUtils.logoutQuietly(sourceDefaultSession); JcrUtils.logoutQuietly(targetDefaultSession); } } + private long getNodesNumber(Session session) { + if (IGNORED_WKSP_LIST.contains(session.getWorkspace().getName())) + return 0l; + try { + Query countQuery = session + .getWorkspace() + .getQueryManager() + .createQuery( + "select file from [" + + (true ? NodeType.NT_FILE + : NodeType.NT_BASE) + "] as file", + Query.JCR_SQL2); + + QueryResult result = countQuery.execute(); + Long expectedCount = result.getNodes().getSize(); + return expectedCount; + } catch (RepositoryException e) { + throw new SlcException("Unexpected error while computing " + + "the size of the fetch for workspace " + + session.getWorkspace().getName(), e); + } + } + protected void syncWorkspace(Session sourceSession, Session targetSession) { + if (monitor != null) { + monitor.beginTask("Computing fetch size...", -1); + Long totalAmount = getNodesNumber(sourceSession); + monitor.beginTask("Fetch", totalAmount.intValue()); + } + try { + String msg = "Synchronizing workspace: " + + sourceSession.getWorkspace().getName(); + if (monitor != null) + monitor.setTaskName(msg); if (log.isDebugEnabled()) - log.debug("Syncing " + sourceSession.getWorkspace().getName() - + "..."); - for (NodeIterator it = sourceSession.getRootNode().getNodes(); it - .hasNext();) { - Node node = it.nextNode(); - if (node.getName().equals("jcr:system")) - continue; - // ContentHandler targetHandler = targetSession - // .getWorkspace() - // .getImportContentHandler( - // "/", - // ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING); - // sourceSession.exportSystemView(node.getPath(), targetHandler, - // true, false); - // if (log.isDebugEnabled()) - // log.debug(" " + node.getPath()); - syncNode(node, targetSession.getRootNode()); + log.debug(msg); + + if (filesOnly) { + JcrUtils.copyFiles(sourceSession.getRootNode(), + targetSession.getRootNode(), true, monitor); + } else { + for (NodeIterator it = sourceSession.getRootNode().getNodes(); it + .hasNext();) { + Node node = it.nextNode(); + if (node.getName().equals("jcr:system")) + continue; + syncNode(node, targetSession); + } } if (log.isDebugEnabled()) log.debug("Synced " + sourceSession.getWorkspace().getName()); } catch (Exception e) { + e.printStackTrace(); throw new SlcException("Cannot sync " + sourceSession.getWorkspace().getName() + " to " + targetSession.getWorkspace().getName(), e); } } - protected void syncNode(Node sourceNode, Node targetParentNode) + /** factorizes monitor management */ + private void updateMonitor(String msg) { + updateMonitor(msg, false); + } + + protected void syncNode(Node sourceNode, Session targetSession) throws RepositoryException, SAXException { - Boolean noRecurse = noRecurse(sourceNode); - Calendar sourceLastModified = null; - if (sourceNode.isNodeType(NodeType.MIX_LAST_MODIFIED)) { - sourceLastModified = sourceNode.getProperty( - Property.JCR_LAST_MODIFIED).getDate(); - } + // Boolean singleLevel = singleLevel(sourceNode); + try { + if (monitor != null && monitor.isCanceled()) { + updateMonitor("Fetched has been canceled, " + + "process is terminating"); + return; + } - if (!targetParentNode.hasNode(sourceNode.getName())) { - ContentHandler contentHandler = targetParentNode - .getSession() - .getWorkspace() - .getImportContentHandler(targetParentNode.getPath(), - ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW); - sourceNode.getSession().exportSystemView(sourceNode.getPath(), - contentHandler, false, noRecurse); - if (log.isDebugEnabled()) - log.debug("Added " + sourceNode.getPath()); - } else { - Node targetNode = targetParentNode.getNode(sourceNode.getName()); - if (sourceLastModified != null) { - Calendar targetLastModified = null; - if (targetNode.isNodeType(NodeType.MIX_LAST_MODIFIED)) { - targetLastModified = targetNode.getProperty( - Property.JCR_LAST_MODIFIED).getDate(); - } + Node targetParentNode = targetSession.getNode(sourceNode + .getParent().getPath()); + Node targetNode; + if (monitor != null + && sourceNode.isNodeType(NodeType.NT_HIERARCHY_NODE)) + monitor.subTask("Process " + sourceNode.getPath()); + + final Boolean isNew; + if (!targetSession.itemExists(sourceNode.getPath())) { + isNew = true; + targetNode = targetParentNode.addNode(sourceNode.getName(), + sourceNode.getPrimaryNodeType().getName()); + } else { + isNew = false; + targetNode = targetSession.getNode(sourceNode.getPath()); + if (!targetNode.getPrimaryNodeType().getName() + .equals(sourceNode.getPrimaryNodeType().getName())) + targetNode.setPrimaryType(sourceNode.getPrimaryNodeType() + .getName()); + } + + // export + // sourceNode.getSession().exportSystemView(sourceNode.getPath(), + // contentHandler, false, singleLevel); + + // if (singleLevel) { + // if (targetSession.hasPendingChanges()) { + // // updateMonitor( + // // (isNew ? "Added " : "Updated ") + targetNode.getPath(), + // // true); + // if (doSave) + // targetSession.save(); + // } else { + // // updateMonitor("Checked " + targetNode.getPath(), false); + // } + // } + + // mixin and properties + for (NodeType nt : sourceNode.getMixinNodeTypes()) { + if (!targetNode.isNodeType(nt.getName()) + && targetNode.canAddMixin(nt.getName())) + targetNode.addMixin(nt.getName()); + } + copyProperties(sourceNode, targetNode); + + // next level + NodeIterator ni = sourceNode.getNodes(); + while (ni != null && ni.hasNext()) { + Node sourceChild = ni.nextNode(); + syncNode(sourceChild, targetSession); + } + + copyTimestamps(sourceNode, targetNode); - if (targetLastModified == null - || targetLastModified.before(sourceLastModified)) { - ContentHandler contentHandler = targetParentNode - .getSession() - .getWorkspace() - .getImportContentHandler( - targetParentNode.getPath(), - ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING); - sourceNode.getSession().exportSystemView( - sourceNode.getPath(), contentHandler, false, - noRecurse); - if (log.isDebugEnabled()) - log.debug("Updated " + targetNode.getPath()); + if (sourceNode.isNodeType(NodeType.NT_HIERARCHY_NODE)) { + if (targetSession.hasPendingChanges()) { + if (sourceNode.isNodeType(NodeType.NT_FILE)) + updateMonitor((isNew ? "Added " : "Updated ") + + targetNode.getPath(), true); + // if (doSave) + targetSession.save(); } else { - if (log.isDebugEnabled()) - log.debug("Skipped up to date " + targetNode.getPath()); - // if (!noRecurse) - return; + if (sourceNode.isNodeType(NodeType.NT_FILE)) + updateMonitor("Checked " + targetNode.getPath(), false); } } + } catch (RepositoryException e) { + throw new SlcException("Cannot sync source node " + sourceNode, e); } + } - if (noRecurse) { - // recurse - Node targetNode = targetParentNode.getNode(sourceNode.getName()); - if (sourceLastModified != null) { - Calendar zero = new GregorianCalendar(); - zero.setTimeInMillis(0); - targetNode.setProperty(Property.JCR_LAST_MODIFIED, zero); - targetNode.getSession().save(); - } + private void copyTimestamps(Node sourceNode, Node targetNode) + throws RepositoryException { + if (sourceNode.getDefinition().isProtected()) + return; + if (targetNode.getDefinition().isProtected()) + return; + copyTimestamp(sourceNode, targetNode, Property.JCR_CREATED); + copyTimestamp(sourceNode, targetNode, Property.JCR_CREATED_BY); + copyTimestamp(sourceNode, targetNode, Property.JCR_LAST_MODIFIED); + copyTimestamp(sourceNode, targetNode, Property.JCR_LAST_MODIFIED_BY); + } - for (NodeIterator it = sourceNode.getNodes(); it.hasNext();) { - syncNode(it.nextNode(), targetNode); - } + private void copyTimestamp(Node sourceNode, Node targetNode, String property) + throws RepositoryException { + if (sourceNode.hasProperty(property)) { + Property p = sourceNode.getProperty(property); + if (p.getDefinition().isProtected()) + return; + if (targetNode.hasProperty(property) + && targetNode + .getProperty(property) + .getValue() + .equals(sourceNode.getProperty(property).getValue())) + return; + targetNode.setProperty(property, sourceNode.getProperty(property) + .getValue()); + } + } - if (sourceLastModified != null) { - targetNode.setProperty(Property.JCR_LAST_MODIFIED, - sourceLastModified); - targetNode.getSession().save(); + private void copyProperties(Node sourceNode, Node targetNode) + throws RepositoryException { + properties: for (PropertyIterator pi = sourceNode.getProperties(); pi + .hasNext();) { + Property p = pi.nextProperty(); + if (p.getDefinition().isProtected()) + continue properties; + if (p.getName().equals(Property.JCR_CREATED) + || p.getName().equals(Property.JCR_CREATED_BY) + || p.getName().equals(Property.JCR_LAST_MODIFIED) + || p.getName().equals(Property.JCR_LAST_MODIFIED_BY)) + continue properties; + + if (p.getType() == PropertyType.BINARY) { + copyBinary(p, targetNode); + } else { + + if (p.isMultiple()) { + if (!targetNode.hasProperty(p.getName()) + || !Arrays.equals( + targetNode.getProperty(p.getName()) + .getValues(), p.getValues())) + targetNode.setProperty(p.getName(), p.getValues()); + } else { + if (!targetNode.hasProperty(p.getName()) + || !targetNode.getProperty(p.getName()).getValue() + .equals(p.getValue())) + targetNode.setProperty(p.getName(), p.getValue()); + } } } + } + + private static void copyBinary(Property p, Node targetNode) + throws RepositoryException { + InputStream in = null; + Binary sourceBinary = null; + Binary targetBinary = null; + try { + sourceBinary = p.getBinary(); + if (targetNode.hasProperty(p.getName())) + targetBinary = targetNode.getProperty(p.getName()).getBinary(); + + // optim FIXME make it more configurable + if (targetBinary != null) + if (sourceBinary.getSize() == targetBinary.getSize()) { + if (log.isTraceEnabled()) + log.trace("Skipped " + p.getPath()); + return; + } + + in = sourceBinary.getStream(); + targetBinary = targetNode.getSession().getValueFactory() + .createBinary(in); + targetNode.setProperty(p.getName(), targetBinary); + } catch (Exception e) { + throw new SlcException("Could not transfer " + p, e); + } finally { + IOUtils.closeQuietly(in); + JcrUtils.closeQuietly(sourceBinary); + JcrUtils.closeQuietly(targetBinary); + } + } + /** factorizes monitor management */ + private void updateMonitor(String msg, Boolean doLog) { + if (doLog && log.isDebugEnabled()) + log.debug(msg); + if (monitor != null) { + monitor.worked(1); + monitor.subTask(msg); + } } - protected Boolean noRecurse(Node sourceNode) throws RepositoryException { + // private void syncNode_old(Node sourceNode, Node targetParentNode) + // throws RepositoryException, SAXException { + // + // // enable cancelation of the current fetch process + // // FIXME insure the repository stays in a stable state + // if (monitor != null && monitor.isCanceled()) { + // updateMonitor("Fetched has been canceled, " + // + "process is terminating"); + // return; + // } + // + // Boolean noRecurse = singleLevel(sourceNode); + // Calendar sourceLastModified = null; + // if (sourceNode.isNodeType(NodeType.MIX_LAST_MODIFIED)) { + // sourceLastModified = sourceNode.getProperty( + // Property.JCR_LAST_MODIFIED).getDate(); + // } + // + // if (sourceNode.getDefinition().isProtected()) + // log.warn(sourceNode + " is protected."); + // + // if (!targetParentNode.hasNode(sourceNode.getName())) { + // String msg = "Adding " + sourceNode.getPath(); + // updateMonitor(msg); + // if (log.isDebugEnabled()) + // log.debug(msg); + // ContentHandler contentHandler = targetParentNode + // .getSession() + // .getWorkspace() + // .getImportContentHandler(targetParentNode.getPath(), + // ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW); + // sourceNode.getSession().exportSystemView(sourceNode.getPath(), + // contentHandler, false, noRecurse); + // } else { + // Node targetNode = targetParentNode.getNode(sourceNode.getName()); + // if (sourceLastModified != null) { + // Calendar targetLastModified = null; + // if (targetNode.isNodeType(NodeType.MIX_LAST_MODIFIED)) { + // targetLastModified = targetNode.getProperty( + // Property.JCR_LAST_MODIFIED).getDate(); + // } + // + // if (targetLastModified == null + // || targetLastModified.before(sourceLastModified)) { + // String msg = "Updating " + targetNode.getPath(); + // updateMonitor(msg); + // if (log.isDebugEnabled()) + // log.debug(msg); + // ContentHandler contentHandler = targetParentNode + // .getSession() + // .getWorkspace() + // .getImportContentHandler( + // targetParentNode.getPath(), + // ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING); + // sourceNode.getSession().exportSystemView( + // sourceNode.getPath(), contentHandler, false, + // noRecurse); + // } else { + // String msg = "Skipped up to date " + targetNode.getPath(); + // updateMonitor(msg); + // if (log.isDebugEnabled()) + // log.debug(msg); + // return; + // } + // } + // } + // + // if (noRecurse) { + // // recurse + // Node targetNode = targetParentNode.getNode(sourceNode.getName()); + // if (sourceLastModified != null) { + // Calendar zero = new GregorianCalendar(); + // zero.setTimeInMillis(0); + // targetNode.setProperty(Property.JCR_LAST_MODIFIED, zero); + // targetNode.getSession().save(); + // } + // + // for (NodeIterator it = sourceNode.getNodes(); it.hasNext();) { + // syncNode_old(it.nextNode(), targetNode); + // } + // + // if (sourceLastModified != null) { + // targetNode.setProperty(Property.JCR_LAST_MODIFIED, + // sourceLastModified); + // targetNode.getSession().save(); + // } + // } + // } + + protected Boolean singleLevel(Node sourceNode) throws RepositoryException { if (sourceNode.isNodeType(NodeType.NT_FILE)) return false; return true; } - public void setSourceRepo(String sourceRepo) { - this.sourceRepo = sourceRepo; + /** + * Synchronises only one workspace, retrieved by name without changing its + * name. + */ + public void setSourceWksp(String sourceWksp) { + if (sourceWksp != null && !sourceWksp.trim().equals("")) { + Map map = new HashMap(); + map.put(sourceWksp, sourceWksp); + setWkspMap(map); + } } - public void setTargetRepo(String targetRepo) { - this.targetRepo = targetRepo; + /** + * Synchronises a map of workspaces that will be retrieved by name. If the + * target name is not defined (eg null or an empty string) for a given + * source workspace, we use the source name as target name. + */ + public void setWkspMap(Map workspaceMap) { + // clean the list to ease later use + this.workspaceMap = new HashMap(); + if (workspaceMap != null) { + workspaceNames: for (String srcName : workspaceMap.keySet()) { + String targetName = workspaceMap.get(srcName); + + // Sanity check + if (srcName.trim().equals("")) + continue workspaceNames; + if (targetName == null || "".equals(targetName.trim())) + targetName = srcName; + this.workspaceMap.put(srcName, targetName); + } + } + // clean the map to ease later use + if (this.workspaceMap.size() == 0) + this.workspaceMap = null; } - public void setSourceWksp(String sourceWksp) { - this.sourceWksp = sourceWksp; + public void setMonitor(ArgeoMonitor monitor) { + this.monitor = monitor; } public void setRepositoryFactory(RepositoryFactory repositoryFactory) { this.repositoryFactory = repositoryFactory; } + public void setSourceRepoUri(String sourceRepoUri) { + this.sourceRepoUri = sourceRepoUri; + } + public void setSourceUsername(String sourceUsername) { this.sourceUsername = sourceUsername; } @@ -280,6 +598,10 @@ public class RepoSync implements Runnable { this.sourcePassword = sourcePassword; } + public void setTargetRepoUri(String targetRepoUri) { + this.targetRepoUri = targetRepoUri; + } + public void setTargetUsername(String targetUsername) { this.targetUsername = targetUsername; } @@ -288,4 +610,24 @@ public class RepoSync implements Runnable { this.targetPassword = targetPassword; } -} + public void setSourceRepository(Repository sourceRepository) { + this.sourceRepository = sourceRepository; + } + + public void setSourceCredentials(Credentials sourceCredentials) { + this.sourceCredentials = sourceCredentials; + } + + public void setTargetRepository(Repository targetRepository) { + this.targetRepository = targetRepository; + } + + public void setTargetCredentials(Credentials targetCredentials) { + this.targetCredentials = targetCredentials; + } + + public void setFilesOnly(Boolean filesOnly) { + this.filesOnly = filesOnly; + } + +} \ No newline at end of file