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=cf4ba02b8417c3e1d45027c0fb1ad55853fb1ec2;hb=e70fb767d8ff5972d88ab436762e71ea622a0f72;hp=7de3e40c11c4bb2b02c108c050371d31ec6f7d94;hpb=58e0e18d64a2080680a9f8397b0dfa2894519910;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 7de3e40c1..cf4ba02b8 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,82 +15,377 @@ */ package org.argeo.slc.repo; +import java.util.ArrayList; +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.Credentials; +import javax.jcr.ImportUUIDBehavior; +import javax.jcr.NoSuchWorkspaceException; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; 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.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 */ public class RepoSync implements Runnable { private final static Log log = LogFactory.getLog(RepoSync.class); - private String sourceRepo; - private String targetRepo; + // Centralizes definition of workspaces that must be ignored by the sync. + private final static List IGNORED_WSKP_LIST = Arrays.asList( + "security", "localrepo"); + + 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 List sourceWkspList; + + 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() { 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); - Session sourceSession = sourceRepository.login(sourceCredentials, - sourceWksp); + sourceDefaultSession = sourceRepository.login(sourceCredentials); - Credentials targetCredentials = null; - Session targetSession = targetRepository.login(targetCredentials, - sourceWksp); + if (targetRepository == null) + targetRepository = ArgeoJcrUtils.getRepositoryByUri( + repositoryFactory, targetRepoUri); + if (targetCredentials == null && targetUsername != null) + targetCredentials = new SimpleCredentials(targetUsername, + targetPassword); + targetDefaultSession = targetRepository.login(targetCredentials); - Long count = JcrUtils.copyFiles(sourceSession.getRootNode(), - targetSession.getRootNode(), true, null); + // FIXME implement a cleaner way to compute job size. + // Compute job size + if (monitor != null) { + monitor.beginTask("Computing fetch size...", -1); + Long totalAmount = 0l; + if (sourceWkspList != null) { + for (String wkspName : sourceWkspList) { + totalAmount += getNodesNumber(wkspName); + } + } else + for (String sourceWorkspaceName : sourceDefaultSession + .getWorkspace().getAccessibleWorkspaceNames()) { + totalAmount += getNodesNumber(sourceWorkspaceName); + } + monitor.beginTask("Fetch", totalAmount.intValue()); - long duration = (System.currentTimeMillis() - begin) / 1000;// in - // s - if (log.isDebugEnabled()) - log.debug("Copied " + count + " files in " + (duration / 60) - + "min " + (duration % 60) + "s"); + if (log.isDebugEnabled()) + log.debug("Nb of nodes to sync: " + totalAmount.intValue()); + } + + Map errors = new HashMap(); + for (String sourceWorkspaceName : sourceDefaultSession + .getWorkspace().getAccessibleWorkspaceNames()) { + + if (sourceWkspList != null + && !sourceWkspList.contains(sourceWorkspaceName)) + continue; + if (IGNORED_WSKP_LIST.contains(sourceWorkspaceName)) + continue; + + Session sourceSession = null; + Session targetSession = null; + try { + try { + targetSession = targetRepository.login( + targetCredentials, sourceWorkspaceName); + } catch (NoSuchWorkspaceException e) { + targetDefaultSession.getWorkspace().createWorkspace( + sourceWorkspaceName); + targetSession = targetRepository.login( + targetCredentials, sourceWorkspaceName); + } + sourceSession = sourceRepository.login(sourceCredentials, + sourceWorkspaceName); + syncWorkspace(sourceSession, targetSession); + } catch (Exception e) { + errors.put("Could not sync workspace " + + sourceWorkspaceName, e); + if (log.isDebugEnabled()) + e.printStackTrace(); + } finally { + JcrUtils.logoutQuietly(sourceSession); + JcrUtils.logoutQuietly(targetSession); + } + } + + if (monitor != null && monitor.isCanceled()) + log.info("Sync has been canceled by user"); + + long duration = (System.currentTimeMillis() - begin) / 1000;// s + log.info("Sync " + sourceRepoUri + " to " + targetRepoUri + " in " + + (duration / 60) + + + "min " + (duration % 60) + "s"); + + if (errors.size() > 0) { + throw new SlcException("Sync failed " + errors); + } + } catch (RepositoryException e) { + throw new SlcException("Cannot sync " + sourceRepoUri + " to " + + targetRepoUri, e); + } finally { + JcrUtils.logoutQuietly(sourceDefaultSession); + JcrUtils.logoutQuietly(targetDefaultSession); + } + } + + private long getNodesNumber(String wkspName) { + if (IGNORED_WSKP_LIST.contains(wkspName)) + return 0l; + Session sourceSession = null; + try { + sourceSession = sourceRepository.login(sourceCredentials, wkspName); + Query countQuery = sourceDefaultSession + .getWorkspace() + .getQueryManager() + .createQuery("select file from [nt:base] as file", + Query.JCR_SQL2); + QueryResult result = countQuery.execute(); + Long expectedCount = result.getNodes().getSize(); + return expectedCount; } catch (RepositoryException e) { - throw new SlcException("Cannot sync " + sourceRepo + " to " - + targetRepo, e); + throw new SlcException("Unexpected error while computing " + + "the size of the fetch for workspace " + wkspName, e); + } finally { + JcrUtils.logoutQuietly(sourceSession); } } - public void setSourceRepo(String sourceRepo) { - this.sourceRepo = sourceRepo; + protected void syncWorkspace(Session sourceSession, Session targetSession) { + try { + String msg = "Synchronizing workspace: " + + sourceSession.getWorkspace().getName(); + if (monitor != null) + monitor.setTaskName(msg); + if (log.isDebugEnabled()) + log.debug(msg); + for (NodeIterator it = sourceSession.getRootNode().getNodes(); it + .hasNext();) { + Node node = it.nextNode(); + if (node.getName().equals("jcr:system")) + continue; + syncNode(node, targetSession.getRootNode()); + } + if (log.isDebugEnabled()) + log.debug("Synced " + sourceSession.getWorkspace().getName()); + } catch (Exception e) { + throw new SlcException("Cannot sync " + + sourceSession.getWorkspace().getName() + " to " + + targetSession.getWorkspace().getName(), e); + } } - public void setTargetRepo(String targetRepo) { - this.targetRepo = targetRepo; + /** factorizes monitor management */ + private void updateMonitor(String msg) { + if (monitor != null) { + monitor.worked(1); + monitor.subTask(msg); + } } + protected void syncNode(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 = noRecurse(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(it.nextNode(), targetNode); + } + + if (sourceLastModified != null) { + targetNode.setProperty(Property.JCR_LAST_MODIFIED, + sourceLastModified); + targetNode.getSession().save(); + } + } + } + + protected Boolean noRecurse(Node sourceNode) throws RepositoryException { + if (sourceNode.isNodeType(NodeType.NT_FILE)) + return false; + return true; + } + + /** synchronise only one workspace retrieved by name */ public void setSourceWksp(String sourceWksp) { - this.sourceWksp = sourceWksp; + if (sourceWksp != null && !sourceWksp.trim().equals("")) { + List list = new ArrayList(); + list.add(sourceWksp); + setSourceWkspList(list); + } + } + + /** synchronise a list workspace that will be retrieved by name */ + public void setSourceWkspList(List sourceWkspList) { + // clean the list to ease later use + this.sourceWkspList = null; + if (sourceWkspList != null) { + for (String wkspName : sourceWkspList) { + if (!wkspName.trim().equals("")) { + // only instantiate if needed + if (this.sourceWkspList == null) + this.sourceWkspList = new ArrayList(); + this.sourceWkspList.add(wkspName); + } + } + } + } + + 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; } @@ -99,4 +394,31 @@ public class RepoSync implements Runnable { this.sourcePassword = sourcePassword; } -} + public void setTargetRepoUri(String targetRepoUri) { + this.targetRepoUri = targetRepoUri; + } + + public void setTargetUsername(String targetUsername) { + this.targetUsername = targetUsername; + } + + public void setTargetPassword(char[] targetPassword) { + 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; + } +} \ No newline at end of file