jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From tri...@apache.org
Subject svn commit: r1512568 [35/39] - in /jackrabbit/commons/filevault/trunk: ./ parent/ vault-cli/ vault-cli/src/ vault-cli/src/main/ vault-cli/src/main/appassembler/ vault-cli/src/main/assembly/ vault-cli/src/main/java/ vault-cli/src/main/java/org/ vault-cl...
Date Sat, 10 Aug 2013 05:53:54 GMT
Added: jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/SyncHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/SyncHandler.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/SyncHandler.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/SyncHandler.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,430 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.sync.impl;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Pattern;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.jci.monitor.FilesystemAlterationListener;
+import org.apache.commons.jci.monitor.FilesystemAlterationObserver;
+import org.apache.commons.jci.monitor.FilesystemAlterationObserverImpl;
+import org.apache.jackrabbit.util.Text;
+import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
+import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
+import org.apache.jackrabbit.vault.fs.config.ExportRoot;
+import org.apache.jackrabbit.vault.util.Constants;
+import org.apache.jackrabbit.vault.util.PathComparator;
+import org.apache.jackrabbit.vault.util.PlatformNameFormat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+* <code>SyncHandler</code>...
+*/
+public class SyncHandler implements FilesystemAlterationListener {
+
+    /**
+     * default logger
+     */
+    private static final Logger log = LoggerFactory.getLogger(SyncHandler.class);
+
+    private static final String DEFAULT_FILTER = "default-filter.xml";
+
+    private final File fileRoot;
+
+    private final FilesystemAlterationObserverImpl observer;
+
+    private final Set<String> pendingJcrChanges = new HashSet<String>();
+
+    private final Map<String, File> pendingFsChanges = new TreeMap<String, File>(new PathComparator(Constants.FS_NATIVE.charAt(0)));
+
+    private final SyncLog syncLog;
+
+    private String[] preparedJcrChanges;
+
+    private final SyncConfig cfg;
+
+    private ExportRoot vltExportRoot;
+
+    private WorkspaceFilter filter;
+
+    private FStat filterStat;
+
+    private FStat configStat;
+
+
+    // default to exclude all hidden files and directories
+    private Pattern[] excluded = new Pattern[]{
+            Pattern.compile("\\..*")
+    };
+
+    private FileFilter fileFilter = new FileFilter() {
+        public boolean accept(File file) {
+            String name = file.getName();
+            if (file.isHidden()) {
+                return false;
+            }
+            for (Pattern p:excluded) {
+                if (p.matcher(name).matches()) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    };
+
+
+    public SyncHandler(File fileRoot) {
+        this.fileRoot = fileRoot;
+        vltExportRoot = ExportRoot.findRoot(fileRoot);
+        syncLog = new SyncLog(new File(fileRoot, SyncConstants.SYNCLOG_FILE_NAME));
+        syncLog.log("Vault sync service started and observing this directory.");
+        cfg = new SyncConfig(new File(fileRoot, SyncConstants.CONFIG_FILE_NAME));
+        try {
+            cfg.init();
+            configStat = new FStat(cfg.getFile());
+        } catch (IOException e) {
+            log.error("Error while initializing configuration file: {}", e.toString());
+        }
+        filterStat = new FStat();
+        updateFilter();
+        syncLog.log("Syncing in %s is %s by " + SyncConstants.CONFIG_FILE_NAME, fileRoot.getAbsolutePath(), cfg.isDisabled() ? "disabled" : "enabled");
+
+        observer = new FilesystemAlterationObserverImpl(fileRoot);
+        // "initialize" internal structure of observer (need dummy listener, otherwise events are not processed)
+        observer.addListener(new DummyListener());
+        observer.checkAndNotify();
+        observer.addListener(this);
+    }
+
+    private void updateFilter() {
+        try {
+            File filterFile;
+            // check for vlt context
+            if (vltExportRoot != null && vltExportRoot.isValid()) {
+                filterFile = new File(vltExportRoot.getMetaDir(), Constants.FILTER_XML);
+            } else {
+                filterFile = new File(fileRoot, SyncConstants.FILTER_FILE_NAME).getCanonicalFile();
+                if (!filterFile.exists()) {
+                    // init default filter
+                    InputStream in = SyncConfig.class.getResourceAsStream(DEFAULT_FILTER);
+                    if (in == null) {
+                        log.error("Unable to load default filter.");
+                    } else {
+                        OutputStream out = null;
+                        try {
+                            out = FileUtils.openOutputStream(filterFile);
+                            IOUtils.copy(in, out);
+                        } finally {
+                            IOUtils.closeQuietly(in);
+                            IOUtils.closeQuietly(out);
+                        }
+                    }
+                }
+            }
+            if (filterStat.modified(filterFile)) {
+                filter = loadFilter(filterFile);
+            }
+        } catch (IOException e) {
+            log.warn("Unable to read filter file: {}", e.toString());
+        }
+    }
+
+    private WorkspaceFilter loadFilter(File filterFile) {
+        if (filterFile.isFile()) {
+            DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+            try {
+                filter.load(filterFile);
+                syncLog.log("Sync root configured with filter %s", filterFile.getAbsolutePath());
+                return filter;
+            } catch (Exception e) {
+                log.error("Error while loading sync filter: " + e.toString());
+            }
+        } else {
+            log.info("Filter file missing: {}", filterFile.getAbsolutePath());
+        }
+        return null;
+    }
+
+    public boolean covers(String path) {
+        return filter != null && filter.covers(path);
+    }
+
+    public boolean contains(String path) {
+        return filter != null && filter.contains(path);
+    }
+
+    public void prepareForSync() {
+        // assert locked
+        preparedJcrChanges = pendingJcrChanges.toArray(new String[pendingJcrChanges.size()]);
+        pendingJcrChanges.clear();
+        pendingFsChanges.clear();
+    }
+
+    public void sync(Session session) throws RepositoryException, IOException {
+        updateConfig();
+        updateFilter();
+        if (filter == null) {
+            log.info("No filter present or configured in {}. Not syncing.", fileRoot.getAbsolutePath());
+            observer.checkAndNotify();
+            return;
+        }
+
+        log.debug("Starting sync cycle for {}.", this);
+        if (cfg.isDisabled()) {
+            log.debug("Syncing is disabled in {}.", fileRoot.getAbsolutePath());
+            // flush changes
+            observer.checkAndNotify();
+            pendingFsChanges.clear();
+            return;
+        }
+        // check if full sync is requested
+        SyncMode syncOnce = cfg.getSyncOnce();
+        if (syncOnce != null) {
+            cfg.setSyncOnce(null);
+            try {
+                cfg.save();
+            } catch (IOException e) {
+                log.error("Error while saving config", e);
+            }
+            log.info("Sync Once requested: {}", syncOnce);
+            syncTree(session, syncOnce);
+        } else {
+            SyncResult res = syncToDisk(session);
+            log.debug("Scanning filesystem for changes {}", this);
+            observer.checkAndNotify();
+            syncToJcr(session, res);
+            // todo: maybe save after each import?
+            session.save();
+            res.dump();
+        }
+        log.debug("Sync cycle completed for {}", this);
+    }
+
+    private void updateConfig() {
+        try {
+            if (configStat.modified(cfg.getFile())) {
+                // detect enabled/disabled changes
+                boolean wasDisabled = cfg.isDisabled();
+                cfg.load();
+                if (wasDisabled != cfg.isDisabled()) {
+                    syncLog.log("Syncing in %s is %s by " + SyncConstants.CONFIG_FILE_NAME, fileRoot.getAbsolutePath(), cfg.isDisabled() ? "disabled" : "enabled");
+                }
+            }
+        } catch (IOException e) {
+            // ignore
+            log.warn("Error while loading config: " + e.toString());
+        }
+    }
+
+    private TreeSync createTreeSync(SyncMode mode) {
+        TreeSync sync = new TreeSync(syncLog, fileFilter, filter);
+        sync.setSyncMode(mode);
+        return sync;
+    }
+
+    private void syncTree(Session session, SyncMode direction) throws RepositoryException, IOException {
+        TreeSync tree = createTreeSync(direction);
+        tree.sync(session.getRootNode(), fileRoot);
+        // flush fs changes
+        observer.checkAndNotify();
+        pendingFsChanges.clear();
+    }
+
+    private SyncResult syncToDisk(Session session) throws RepositoryException, IOException {
+        SyncResult res = new SyncResult();
+        for (String path: preparedJcrChanges) {
+            boolean recursive = path.endsWith("/");
+            if (recursive) {
+                path = path.substring(0, path.length() - 1);
+            }
+            if (!contains(path)) {
+                log.debug("**** rejected. filter does not include {}", path);
+                continue;
+            }
+            File file = getFileForJcrPath(path);
+            log.debug("**** about sync jcr:/{} -> file://{}", path, file.getAbsolutePath());
+            Node node;
+            Node parentNode;
+            if (session.nodeExists(path)) {
+                node = session.getNode(path);
+                parentNode = node.getParent();
+            } else {
+                node = null;
+                String parentPath = Text.getRelativeParent(path, 1);
+                parentNode = session.nodeExists(parentPath)
+                        ? session.getNode(parentPath)
+                        : null;
+            }
+            TreeSync tree = createTreeSync(SyncMode.JCR2FS);
+            res.merge(tree.syncSingle(parentNode, node, file, recursive));
+        }
+        return res;
+    }
+
+    private void syncToJcr(Session session, SyncResult res) throws RepositoryException, IOException {
+        for (String filePath: pendingFsChanges.keySet()) {
+            if (res.getByFsPath(filePath) != null) {
+                log.debug("ignoring change triggered by previous JCR->FS update. {}", filePath);
+                return;
+            }
+            File file = pendingFsChanges.get(filePath);
+            String path = getJcrPathForFile(file);
+            log.debug("**** about sync file:/{} -> jcr://{}", file.getAbsolutePath(), path);
+            if (!contains(path)) {
+                log.debug("**** rejected. filter does not include {}", path);
+                continue;
+            }
+            Node node;
+            Node parentNode;
+            if (session.nodeExists(path)) {
+                node = session.getNode(path);
+                parentNode = node.getParent();
+            } else {
+                node = null;
+                String parentPath = Text.getRelativeParent(path, 1);
+                parentNode = session.nodeExists(parentPath)
+                        ? session.getNode(parentPath)
+                        : null;
+            }
+            TreeSync tree = createTreeSync(SyncMode.FS2JCR);
+            tree.setSyncMode(SyncMode.FS2JCR);
+            res.merge(tree.syncSingle(parentNode, node, file, false));
+        }
+    }
+
+    public File getFileForJcrPath(String path) {
+        String[] segs = Text.explode(path, '/');
+        File file = fileRoot;
+        for (String seg : segs) {
+            file = new File(file, PlatformNameFormat.getPlatformName(seg));
+        }
+        return file;
+    }
+
+    public String getJcrPathForFile(File file) {
+        StringBuilder s = new StringBuilder();
+        while (!file.equals(fileRoot)) {
+            s.insert(0, PlatformNameFormat.getRepositoryName(file.getName())).insert(0, '/');
+            file = file.getParentFile();
+        }
+        return s.toString();
+    }
+
+    private void onChange(File file, String type) {
+        boolean accept = fileFilter.accept(file);
+        log.debug("{}({}), accepted={}", new Object[]{type, file.getAbsolutePath(), accept});
+        if (!accept) {
+            return;
+        }
+        pendingFsChanges.put(file.getAbsolutePath(), file);
+    }
+
+    public void onFileCreate(File file) {
+        onChange(file, "onFileCreate");
+    }
+
+    public void onFileChange(File file) {
+        onChange(file, "onFileChange");
+    }
+
+    public void onFileDelete(File file) {
+        onChange(file, "onFileDelete");
+    }
+
+    public void onDirectoryCreate(File file) {
+        onChange(file, "onDirectoryCreate");
+    }
+
+
+    public void onDirectoryDelete(File file) {
+        onChange(file, "onDirectoryDelete");
+    }
+
+    public void onStart(FilesystemAlterationObserver filesystemAlterationObserver) { }
+
+    public void onStop(FilesystemAlterationObserver filesystemAlterationObserver) { }
+
+    public void onDirectoryChange(File file) { }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("SyncSpec");
+        sb.append("{fileRoot=").append(fileRoot);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    public void registerPendingJcrChange(String path) {
+        pendingJcrChanges.add(path);
+    }
+
+    private static final class FStat {
+
+        private String path;
+
+        private long lastModified;
+
+        private FStat() {
+            path = "";
+        }
+
+        private FStat(File file) throws IOException {
+            this.path = file.getCanonicalPath();
+            this.lastModified = file.lastModified();
+        }
+
+        public boolean modified(File file) throws IOException {
+            String newPath = file.getCanonicalPath();
+            long newLastModified = file.lastModified();
+            if (!newPath.equals(path) || lastModified != newLastModified) {
+                path = newPath;
+                lastModified = newLastModified;
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    private static final class DummyListener implements FilesystemAlterationListener {
+        public void onStart(FilesystemAlterationObserver filesystemAlterationObserver) { }
+        public void onFileCreate(File file) { }
+        public void onFileChange(File file) { }
+        public void onFileDelete(File file) { }
+        public void onDirectoryCreate(File file) { }
+        public void onDirectoryChange(File file) { }
+        public void onDirectoryDelete(File file) { }
+        public void onStop(FilesystemAlterationObserver filesystemAlterationObserver) { }
+    }
+
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/SyncLog.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/SyncLog.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/SyncLog.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/SyncLog.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.sync.impl;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>SyncLog</code>...
+ */
+public class SyncLog {
+
+    /**
+     * default logger
+     */
+    private static final Logger log = LoggerFactory.getLogger(SyncLog.class);
+
+    private static SimpleDateFormat dateFmt = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss ");
+
+    private final File logFile;
+
+    public SyncLog(File logFile) {
+        this.logFile = logFile;
+    }
+
+    public void log(String fmt, Object ... args) {
+        String msg = String.format(fmt, args);
+        log.info("{}", msg);
+
+        StringBuilder line = new StringBuilder();
+        line.append(dateFmt.format(new Date()));
+        line.append(msg);
+        line.append("\n");
+        try {
+            FileWriter writer = new FileWriter(logFile, true);
+            writer.write(line.toString());
+            writer.close();
+        } catch (IOException e) {
+            log.error("Unable to update log file: " + e.toString());
+        }
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/SyncMode.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/SyncMode.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/SyncMode.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/SyncMode.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.sync.impl;
+
+/**
+* <code>SyncMode</code>...
+*/
+enum SyncMode {
+    FS2JCR,
+    JCR2FS,
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/SyncResult.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/SyncResult.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/SyncResult.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/SyncResult.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.sync.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+* <code>SyncResult</code>...
+*/
+public class SyncResult {
+
+    /**
+     * default logger
+     */
+    private static final Logger log = LoggerFactory.getLogger(SyncResult.class);
+
+    private final Map<String, Entry> byFsPath = new HashMap<String, Entry>();
+
+    private final Map<String, Entry> byJcrPath = new HashMap<String, Entry>();
+
+    public void addEntry(Entry e) {
+        byFsPath.put(e.fsPath, e);
+        byJcrPath.put(e.jcrPath, e);
+    }
+
+    public void addEntry(String jcrPath, String fsPath, Operation ops) {
+        addEntry(new Entry(jcrPath, fsPath, ops));
+    }
+
+    public Entry getByJcrPath(String path) {
+        return byJcrPath.get(path);
+    }
+
+    public Entry getByFsPath(String path) {
+        return byFsPath.get(path);
+    }
+
+    public Set<String> getFsPaths() {
+        return byFsPath.keySet();
+    }
+
+    public Set<String> getJcrPaths() {
+        return byJcrPath.keySet();
+    }
+
+    public void dump() {
+        for (Entry e: byFsPath.values()) {
+            log.info("SyncResult: fs={} jcr={} ops={}", new Object[]{e.fsPath, e.jcrPath, e.ops});
+        }
+    }
+
+    public void merge(SyncResult syncResult) {
+        byFsPath.putAll(syncResult.byFsPath);
+        byJcrPath.putAll(syncResult.byJcrPath);
+    }
+
+    public static class Entry {
+
+        private final String jcrPath;
+
+        private final String fsPath;
+
+        private final Operation ops;
+
+        public Entry(String jcrPath, String fsPath, Operation ops) {
+            this.jcrPath = jcrPath;
+            this.fsPath = fsPath;
+            this.ops = ops;
+        }
+
+    }
+
+    public enum Operation {
+        UPDATE_FS,
+        UPDATE_JCR,
+        DELETE_FS,
+        DELETE_JCR
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/TreeSync.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/TreeSync.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/TreeSync.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/TreeSync.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,493 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.sync.impl;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.vault.fs.api.SerializationType;
+import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
+import org.apache.jackrabbit.vault.util.FileInputSource;
+import org.apache.jackrabbit.vault.util.MimeTypes;
+import org.apache.jackrabbit.vault.util.PlatformNameFormat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>TreeSync</code>...
+ */
+public class TreeSync {
+
+    /**
+     * default logger
+     */
+    private static final Logger log = LoggerFactory.getLogger(TreeSync.class);
+
+    enum Type {
+        FILE,
+        DIRECTORY,
+        MISSING,
+        UNSUPPORTED,
+        FULL_COVERAGE
+    }
+
+    enum FilterStatus {
+        CONTAINED,
+        COVERED,
+        ANCESTOR,
+        OUTSIDE
+    }
+
+    private SyncMode syncMode = SyncMode.JCR2FS;
+
+    private boolean preserveFileDate = true;
+
+    private final SyncLog syncLog;
+
+    private final FileFilter fileFilter;
+
+    private final WorkspaceFilter wspFilter;
+
+    // currently hard coded
+    private final String[] FULL_COVERAGE_NTS = {
+            "rep:AccessControl",
+            "rep:Policy",
+            "cq:Widget",
+            "cq:EditConfig",
+            "cq:WorkflowModel",
+            "vlt:FullCoverage",
+            "mix:language",
+            "sling:OsgiConfig"
+    };
+
+    public TreeSync(SyncLog syncLog, FileFilter fileFilter, WorkspaceFilter wspFilter) {
+        this.syncLog = syncLog;
+        this.fileFilter = fileFilter;
+        this.wspFilter = wspFilter;
+    }
+
+    public void setSyncMode(SyncMode syncMode) {
+        this.syncMode = syncMode;
+    }
+
+    public void setPreserveFileDate(boolean preserveFileDate) {
+        this.preserveFileDate = preserveFileDate;
+    }
+
+    public SyncResult sync(Node node, File dir) throws RepositoryException, IOException {
+        SyncResult result = new SyncResult();
+        sync(result, node, dir);
+        result.dump();
+        return result;
+    }
+
+    public SyncResult syncSingle(Node parentNode, Node node, File file, boolean recursive) throws RepositoryException, IOException {
+        Entry e;
+        if (node == null) {
+            e = new Entry(parentNode, file);
+            e.jcrType = Type.MISSING;
+        } else {
+            e = new Entry(parentNode, file.getParentFile(), node);
+            e.jcrType = getJcrType(node);
+        }
+        e.fsType = getFsType(file);
+        e.fStat = getFilterStatus(e.getJcrPath());
+        SyncResult res = new SyncResult();
+        sync(res, e, recursive);
+        return res;
+    }
+
+    private FilterStatus getFilterStatus(String path) {
+        if (path == null) {
+            return FilterStatus.CONTAINED;
+        } else if (wspFilter.contains(path)) {
+            return FilterStatus.CONTAINED;
+        } else if (wspFilter.covers(path)) {
+            return FilterStatus.COVERED;
+        } else if (wspFilter.isAncestor(path)) {
+            return FilterStatus.ANCESTOR;
+        } else {
+            return FilterStatus.OUTSIDE;
+        }
+    }
+
+    private Type getJcrType(Node node) throws RepositoryException {
+        if (node == null) {
+            return Type.MISSING;
+        } else if (node.isNodeType(NodeType.NT_FILE)) {
+            if (node.getMixinNodeTypes().length == 0) {
+                // only ok, if pure nt:file
+                Node content = node.getNode(Node.JCR_CONTENT);
+                if (content.isNodeType(NodeType.NT_RESOURCE) && content.getMixinNodeTypes().length == 0) {
+                    return Type.FILE;
+                }
+            }
+            return Type.UNSUPPORTED;
+        }
+        for (String nt: FULL_COVERAGE_NTS) {
+            try {
+                if (node.isNodeType(nt)) {
+                    return Type.FULL_COVERAGE;
+                }
+            } catch (RepositoryException e) {
+                // ignore
+            }
+        }
+        if (node.isNodeType(NodeType.NT_HIERARCHY_NODE)) {
+            return Type.DIRECTORY;
+        } else {
+            return Type.UNSUPPORTED;
+        }
+    }
+
+    private Type getFsType(File file) {
+        if (!file.exists()) {
+            return Type.MISSING;
+        } else if (file.isDirectory()) {
+            // ignore directories ending with .dir as they are special directories for vlt
+            if (file.getName().endsWith(".dir")) {
+                return Type.UNSUPPORTED;
+            } else {
+                return Type.DIRECTORY;
+            }
+        } else if (file.isFile()) {
+            // check for vlt serialized XMLs and mark them as unsupported
+            try {
+                SerializationType type = XmlAnalyzer.analyze(new FileInputSource(file));
+                if (type == SerializationType.XML_DOCVIEW) {
+                    return Type.UNSUPPORTED;
+                }
+            } catch (IOException e) {
+                log.warn("Unable to analyze {}: {}", file.getAbsolutePath(), e.toString());
+                return Type.UNSUPPORTED;
+            }
+            return Type.FILE;
+        } else {
+            return Type.UNSUPPORTED;
+        }
+    }
+
+    private void sync(SyncResult res, Node node, File dir) throws RepositoryException, IOException {
+        Map<String, Entry> jcrEntries = new HashMap<String, Entry>();
+        Map<String, Entry> fsEntries = new HashMap<String, Entry>();
+        NodeIterator iter = node.getNodes();
+        while (iter.hasNext()) {
+            Node child = iter.nextNode();
+            Entry e = new Entry(node, dir, child);
+            e.jcrType = getJcrType(child);
+            e.fStat = getFilterStatus(e.getJcrPath());
+            jcrEntries.put(e.jcrName, e);
+            fsEntries.put(e.file.getName(), e);
+        }
+        if (dir.isDirectory()) {
+            for (File file: dir.listFiles(fileFilter)) {
+                Entry e = fsEntries.get(file.getName());
+                if (e == null) {
+                    e = new Entry(node, file);
+                }
+                e.fsType = getFsType(file);
+                e.fStat = getFilterStatus(e.getJcrPath());
+                jcrEntries.put(e.jcrName, e);
+                fsEntries.put(e.file.getName(), e);
+            }
+        }
+        // process
+        for (Entry e: jcrEntries.values()) {
+            sync(res, e, true);
+        }
+    }
+
+    private void sync(SyncResult res, Entry e, boolean recursive) throws RepositoryException, IOException {
+        if (e.fStat == FilterStatus.OUTSIDE) {
+            if (syncMode == SyncMode.JCR2FS) {
+                if (e.fsType == Type.FILE) {
+                    deleteFile(res, e);
+                } else if (e.fsType == Type.DIRECTORY) {
+                    deleteDirectory(res, e);
+                }
+            }
+        } else if (e.jcrType == Type.DIRECTORY) {
+            if (e.fsType == Type.DIRECTORY) {
+                if (recursive) {
+                    sync(res, e.node, e.file);
+                }
+            } else if (e.fsType == Type.MISSING) {
+                if (syncMode == SyncMode.FS2JCR) {
+                    if (e.fStat == FilterStatus.CONTAINED) {
+                        deleteFolder(res, e);
+                    }
+                } else {
+                    createDirectory(res, e);
+                    if (recursive) {
+                        sync(res, e.node, e.file);
+                    }
+                }
+            } else {
+                logConflict(e);
+            }
+        } else if (e.jcrType == Type.FILE) {
+            if (e.fsType == Type.FILE) {
+                if (e.fStat == FilterStatus.CONTAINED) {
+                    syncFiles(res, e);
+                }
+            } else if (e.fsType == Type.MISSING) {
+                if (e.fStat == FilterStatus.CONTAINED) {
+                    if (syncMode == SyncMode.FS2JCR) {
+                        deleteNtFile(res, e);
+                    } else {
+                        writeFile(res, e);
+                    }
+                }
+            } else {
+                logConflict(e);
+            }
+        } else if (e.jcrType == Type.FULL_COVERAGE) {
+            log.debug("refusing to traverse full coverage aggregates {}", e.node.getPath());
+        } else if (e.jcrType == Type.UNSUPPORTED) {
+            log.debug("refusing to traverse unsupported {}", e.node.getPath());
+        } else if (e.jcrType == Type.MISSING) {
+            if (e.fsType == Type.FILE) {
+                if (e.fStat == FilterStatus.CONTAINED) {
+                    if (syncMode == SyncMode.FS2JCR) {
+                        writeNtFile(res, e);
+                    } else {
+                        deleteFile(res, e);
+                    }
+                }
+            } else if (e.fsType == Type.DIRECTORY) {
+                if (e.fStat == FilterStatus.CONTAINED) {
+                    if (syncMode == SyncMode.FS2JCR) {
+                        writeFolder(res, e);
+                        if (e.node != null && recursive) {
+                            sync(res, e.node, e.file);
+                        }
+                    } else {
+                        deleteDirectory(res, e);
+                    }
+                } else {
+                    if (syncMode == SyncMode.FS2JCR) {
+                        // create intermediate node???
+                        log.warn("Creation of unknown intermediate nodes not supported yet. fsPath={} jcrPath={}", e.getFsPath(), e.getJcrPath());
+                    }
+                }
+
+            } else {
+                logConflict(e);
+            }
+        }
+    }
+
+    private void deleteFolder(SyncResult res, Entry e) throws RepositoryException {
+        String path = e.node.getPath();
+        e.node.remove();
+        syncLog.log("D jcr:/%s/", path);
+        res.addEntry(path, e.getFsPath(), SyncResult.Operation.DELETE_JCR);
+    }
+
+    private void deleteNtFile(SyncResult res, Entry e) throws RepositoryException {
+        String path = e.node.getPath();
+        e.node.remove();
+        syncLog.log("D jcr:/%s", path);
+        res.addEntry(path, e.getFsPath(), SyncResult.Operation.DELETE_JCR);
+    }
+
+    private void deleteFile(SyncResult res, Entry e) throws IOException, RepositoryException {
+        res.addEntry(e.getJcrPath(), e.getFsPath(), SyncResult.Operation.DELETE_FS);
+        String path = e.file.getAbsolutePath();
+        FileUtils.forceDelete(e.file);
+        syncLog.log("D file://%s", path);
+    }
+
+    private void deleteDirectory(SyncResult res, Entry e) throws IOException, RepositoryException {
+        deleteRecursive(res, e.file, e.getJcrPath());
+    }
+
+    private void deleteRecursive(SyncResult res, File directory, String jcrPath) throws IOException {
+        if (!directory.exists()) {
+            String message = directory + " does not exist";
+            throw new IllegalArgumentException(message);
+        }
+
+        if (!directory.isDirectory()) {
+            String message = directory + " is not a directory";
+            throw new IllegalArgumentException(message);
+        }
+
+        File[] files = directory.listFiles();
+        if (files == null) {
+            throw new IOException("Failed to list contents of " + directory);
+        }
+
+        for (File file : files) {
+            String subPath = jcrPath + "/" + PlatformNameFormat.getPlatformName(file.getName());
+            if (file.isDirectory()) {
+                deleteRecursive(res, file, subPath);
+            } else {
+                FileUtils.forceDelete(file);
+                syncLog.log("D file://%s", file.getAbsolutePath());
+                res.addEntry(subPath, file.getAbsolutePath(), SyncResult.Operation.DELETE_FS);
+            }
+        }
+        directory.delete();
+        syncLog.log("D file://%s/", directory.getAbsolutePath());
+        res.addEntry(jcrPath, directory.getAbsolutePath(), SyncResult.Operation.DELETE_FS);
+    }
+
+    private void createDirectory(SyncResult res, Entry e) throws RepositoryException {
+        e.file.mkdir();
+        syncLog.log("A file://%s/", e.getFsPath());
+        res.addEntry(e.getJcrPath(), e.getFsPath(), SyncResult.Operation.UPDATE_FS);
+    }
+
+    private void syncFiles(SyncResult res, Entry e) throws RepositoryException, IOException {
+        if (syncMode == SyncMode.FS2JCR) {
+            writeNtFile(res, e);
+        } else {
+            writeFile(res, e);
+        }
+    }
+
+    private void writeFile(SyncResult res, Entry e) throws IOException, RepositoryException {
+        String action = e.file.exists() ? "U" : "A";
+        Binary bin = null;
+        InputStream in = null;
+        OutputStream out = null;
+        try {
+            bin = e.node.getProperty("jcr:content/jcr:data").getBinary();
+            in = bin.getStream();
+            out = FileUtils.openOutputStream(e.file);
+            IOUtils.copy(in, out);
+            if (preserveFileDate) {
+                Calendar lastModified = e.node.getProperty("jcr:content/jcr:lastModified").getDate();
+                e.file.setLastModified(lastModified.getTimeInMillis());
+            }
+            syncLog.log("%s file://%s", action, e.file.getAbsolutePath());
+            res.addEntry(e.getJcrPath(), e.getFsPath(), SyncResult.Operation.UPDATE_FS);
+        } finally {
+            IOUtils.closeQuietly(in);
+            IOUtils.closeQuietly(out);
+            if (bin != null) {
+                bin.dispose();
+            }
+        }
+    }
+
+    private void writeNtFile(SyncResult res, Entry e) throws RepositoryException, IOException {
+        Node ntFile = e.node;
+        Node content;
+        String action = "A";
+        if (ntFile == null) {
+            e.node = ntFile = e.parentNode.addNode(e.jcrName, NodeType.NT_FILE);
+            content = ntFile.addNode(Node.JCR_CONTENT, NodeType.NT_RESOURCE);
+        } else {
+            content = ntFile.getNode(Node.JCR_CONTENT);
+            action = "U";
+        }
+        Calendar cal = Calendar.getInstance();
+        if (preserveFileDate) {
+            cal.setTimeInMillis(e.file.lastModified());
+        }
+        InputStream in = FileUtils.openInputStream(e.file);
+        Binary bin = content.getSession().getValueFactory().createBinary(in);
+        content.setProperty(Property.JCR_DATA, bin);
+        content.setProperty(Property.JCR_LAST_MODIFIED, cal);
+        content.setProperty(Property.JCR_MIMETYPE, MimeTypes.getMimeType(e.file.getName(), MimeTypes.APPLICATION_OCTET_STREAM));
+        syncLog.log("%s jcr://%s", action, ntFile.getPath());
+        res.addEntry(e.getJcrPath(), e.getFsPath(), SyncResult.Operation.UPDATE_JCR);
+    }
+
+    private void writeFolder(SyncResult res, Entry e) throws RepositoryException {
+        e.node = e.parentNode.addNode(e.jcrName, NodeType.NT_FOLDER);
+        syncLog.log("A jcr://%s/", e.node.getPath());
+        res.addEntry(e.getJcrPath(), e.getFsPath(), SyncResult.Operation.UPDATE_JCR);
+    }
+
+    private void logConflict(Entry e) {
+        log.error("Sync conflict. JCR type is {}, but FS type is {}", e.jcrType, e.fsType);
+    }
+
+    private static final class Entry {
+
+        private final File file;
+
+        private Type fsType = Type.MISSING;
+
+        private final Node parentNode;
+
+        private Node node;
+
+        private final String jcrName;
+
+        private Type jcrType = Type.MISSING;
+
+        private FilterStatus fStat = FilterStatus.OUTSIDE;
+
+        private Entry(Node parentNode, File file) {
+            this.parentNode = parentNode;
+            this.file = file;
+            this.jcrName = PlatformNameFormat.getRepositoryName(file.getName());
+        }
+
+        private Entry(Node parentNode, File parentDir, Node node) throws RepositoryException {
+            this.parentNode = parentNode;
+            this.node = node;
+            this.jcrName = node.getName();
+            this.file = new File(parentDir, PlatformNameFormat.getPlatformName(jcrName));
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("Entry");
+            sb.append("{fsName='").append(file.getName()).append('\'');
+            sb.append(", fsType=").append(fsType);
+            sb.append(", jcrName='").append(jcrName).append('\'');
+            sb.append(", jcrType=").append(jcrType);
+            sb.append('}');
+            return sb.toString();
+        }
+
+        public String getFsPath() {
+            return file.getAbsolutePath();
+        }
+
+        public String getJcrPath() throws RepositoryException {
+            if (parentNode == null && node == null) {
+                return null;
+            }
+            return node == null
+                    ? parentNode.getPath() + "/" + jcrName
+                    : node.getPath();
+
+        }
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/VaultSyncServiceImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/VaultSyncServiceImpl.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/VaultSyncServiceImpl.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/VaultSyncServiceImpl.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,233 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.sync.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.EventListener;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.jackrabbit.util.Text;
+import org.apache.sling.commons.osgi.OsgiUtil;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>VaultSyncServiceImpl</code>...
+ */
+@Component(label = "Vault Sync Service", metatype = true, immediate = true)
+public class VaultSyncServiceImpl implements EventListener, Runnable {
+
+    /**
+     * default logger
+     */
+    private static final Logger log = LoggerFactory.getLogger(VaultSyncServiceImpl.class);
+
+    private static final String[] DEFAULT_SYNC_SPECS = {};
+
+    @Property(label = "Sync filesystem directories", cardinality = 100)
+    public static final String SYNC_SPECS = "vault.sync.syncroots";
+
+    @Property(label = "FS check interval (seconds)", intValue = 5)
+    public static final String SYNC_FS_CHECK_INTERVAL = "vault.sync.fscheckinterval";
+
+    @Property(label = "Enabled", boolValue = false)
+    public static final String SYNC_ENABLED = "vault.sync.enabled";
+
+    @Reference
+    private SlingRepository repository;
+
+    private Session session;
+
+    private SyncHandler[] syncHandlers = new SyncHandler[0];
+
+    private boolean enabled;
+
+    private long checkDelay;
+
+    private Thread fsCheckThread;
+
+    private final Lock waitLock = new ReentrantLock();
+
+    private final Condition waitCondition = waitLock.newCondition();
+
+    @Activate
+    protected void activate(Map<String, Object> props) throws RepositoryException {
+        List<SyncHandler> newSyncSpecs = new LinkedList<SyncHandler>();
+        String[] syncRoots = OsgiUtil.toStringArray(props.get(SYNC_SPECS), DEFAULT_SYNC_SPECS);
+        for (String def : syncRoots) {
+            SyncHandler spec = new SyncHandler(new File(def));
+            newSyncSpecs.add(spec);
+            log.info("Added sync specification: {}", spec);
+        }
+        syncHandlers = newSyncSpecs.toArray(new SyncHandler[newSyncSpecs.size()]);
+        enabled = OsgiUtil.toBoolean(props.get(SYNC_ENABLED), false);
+        checkDelay = OsgiUtil.toLong(props.get(SYNC_FS_CHECK_INTERVAL), 5) * 1000;
+
+        log.info("Vault Sync service is {}", enabled ? "enabled" : "disabled");
+        if (enabled) {
+            // setup session
+            session = repository.loginAdministrative(null);
+
+            // set up observation listener
+            session.getWorkspace().getObservationManager().addEventListener(
+                    this,
+                    Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_CHANGED | Event.PROPERTY_ADDED | Event.PROPERTY_REMOVED,
+                    "/",
+                    true /* isDeep */,
+                    null /* uuid */,
+                    null /* nodeTypeName */,
+                    true /* noLocal */
+            );
+            fsCheckThread = new Thread(this, "Vault Sync Thread");
+            fsCheckThread.setDaemon(true);
+            fsCheckThread.start();
+        }
+
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        waitLock.lock();
+        try {
+            if (session != null) {
+                session.logout();
+                session = null;
+            }
+            enabled = false;
+            waitCondition.signalAll();
+        } finally {
+            waitLock.unlock();
+        }
+        if (fsCheckThread != null) {
+            try {
+                fsCheckThread.join();
+            } catch (InterruptedException e) {
+                log.warn("error while waiting for thread to terminate", e);
+            }
+            fsCheckThread = null;
+        }
+    }
+
+    public void run() {
+        waitLock.lock();
+        try {
+            while (enabled) {
+                SyncHandler[] specs = syncHandlers;
+                try {
+                    for (SyncHandler spec : specs) {
+                        spec.prepareForSync();
+                    }
+                    waitLock.unlock();
+                    for (SyncHandler spec : specs) {
+                        try {
+                            spec.sync(session);
+                        } catch (RepositoryException e) {
+                            log.warn("Error during sync", e);
+                        } catch (IOException e) {
+                            log.warn("Error during sync", e);
+                        }
+                    }
+                } finally {
+                    waitLock.lock();
+                }
+                try {
+                    waitCondition.await(checkDelay, TimeUnit.MILLISECONDS);
+                } catch (InterruptedException e) {
+                    log.warn("interrupted while waiting.");
+                }
+            }
+        } finally {
+            waitLock.unlock();
+        }
+    }
+
+    public void onEvent(EventIterator events) {
+        try {
+            Set<String> modified = new HashSet<String>();
+            Set<String> deleted = new HashSet<String>();
+            while (events.hasNext()) {
+                Event evt = events.nextEvent();
+                String path = evt.getPath();
+                if (evt.getType() == Event.PROPERTY_ADDED
+                        || evt.getType() == Event.PROPERTY_CHANGED
+                        || evt.getType() == Event.PROPERTY_REMOVED) {
+                    path = Text.getRelativeParent(path, 1);
+                }
+                // currently we only support nt:files, so we can ignore everything below jcr:content
+                int idx = path.indexOf("/jcr:content");
+                if (idx >= 0) {
+                    path = path.substring(0, idx);
+                }
+                if (evt.getType() == Event.NODE_REMOVED) {
+                    deleted.add(evt.getIdentifier());
+                    modified.add(path);
+                } else if (evt.getType() == Event.NODE_ADDED) {
+                    if (deleted.contains(evt.getIdentifier())) {
+                        modified.add(path + "/");
+                    } else {
+                        modified.add(path);
+                    }
+                } else {
+                    modified.add(path);
+                }
+            }
+            waitLock.lock();
+            try {
+                for (String path: modified) {
+                    SyncHandler spec = getSyncHandler(path);
+                    if (spec != null) {
+                        spec.registerPendingJcrChange(path);
+                    }
+                }
+                waitCondition.signalAll();
+            } finally {
+                waitLock.unlock();
+            }
+        } catch (RepositoryException e) {
+            log.warn("Error while processing events", e);
+        }
+    }
+
+    private SyncHandler getSyncHandler(String path) {
+        for (SyncHandler spec : syncHandlers) {
+            if (spec.covers(path)) {
+                return spec;
+            }
+        }
+        return null;
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/XmlAnalyzer.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/XmlAnalyzer.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/XmlAnalyzer.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-sync/src/main/java/org/apache/jackrabbit/vault/sync/impl/XmlAnalyzer.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.vault.sync.impl;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.vault.fs.api.SerializationType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.InputSource;
+
+/**
+ * Analyzes a xml source and guesses the type. the following types are
+ * recognized:
+ * <ul>
+ * <li> {@link org.apache.jackrabbit.vault.fs.api.SerializationType#GENERIC} if the source is not a valid XML
+ * <li> {@link org.apache.jackrabbit.vault.fs.api.SerializationType#XML_GENERIC} if the XML type is not known. eg. a user-xml
+ * <li> {@link org.apache.jackrabbit.vault.fs.api.SerializationType#XML_DOCVIEW} if the XML is a docview serialization
+ * </ul>
+ * Please note, that the docview serialization is recognized if the first
+ * element contains a jcr:primaryType attribute.
+ *
+ * Note: this is a copy of the package internal org.apache.jackrabbit.vault.impl.io.XmlAnalyzer
+ */
+public class XmlAnalyzer {
+
+    /**
+     * the default logger
+     */
+    private static final Logger log = LoggerFactory.getLogger(XmlAnalyzer.class);
+
+    private XmlAnalyzer() {
+    }
+
+    /**
+     * Analyzes the given source.
+     *
+     * @param source the source to analyze
+     * @return the serialization type
+     * @throws java.io.IOException if an I/O error occurs
+     */
+    public static SerializationType analyze(InputSource source) throws IOException {
+        Reader r = source.getCharacterStream();
+        SerializationType type = SerializationType.UNKOWN;
+        if (r == null) {
+            if (source.getEncoding() == null) {
+                r = new InputStreamReader(source.getByteStream());
+            } else {
+                r = new InputStreamReader(source.getByteStream(), source.getEncoding());
+            }
+        }
+        try {
+            // read a couple of chars...1024 should be enough
+            char[] buffer = new char[1024];
+            int pos = 0;
+            while (pos<buffer.length) {
+                int read = r.read(buffer, pos, buffer.length - pos);
+                if (read < 0) {
+                    break;
+                }
+                pos+=read;
+            }
+            String str = new String(buffer, 0, pos);
+            // check for docview
+            if (str.contains("<jcr:root ") && str.contains("\"http://www.jcp.org/jcr/1.0\"")) {
+                type = SerializationType.XML_DOCVIEW;
+            } else if (str.contains("<?xml ")) {
+                type = SerializationType.XML_GENERIC;
+            } else {
+                type = SerializationType.GENERIC;
+            }
+        } finally {
+            IOUtils.closeQuietly(r);
+        }
+        log.debug("Analyzed {}. Type = {}", source.getSystemId(), type);
+        return type;
+    }
+
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-sync/src/main/resources/org/apache/jackrabbit/vault/sync/impl/default-config.properties
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-sync/src/main/resources/org/apache/jackrabbit/vault/sync/impl/default-config.properties?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-sync/src/main/resources/org/apache/jackrabbit/vault/sync/impl/default-config.properties (added)
+++ jackrabbit/commons/filevault/trunk/vault-sync/src/main/resources/org/apache/jackrabbit/vault/sync/impl/default-config.properties Sat Aug 10 05:53:42 2013
@@ -0,0 +1,49 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Vault Sync Config File
+#
+# Note: Changes to this file are detected automatically if the Vault Sync Service is
+# running and this directory is configured as sync root.
+#
+# Supported Properties:
+# ---------------------
+#
+# disabled = ( "true" | "false" )
+#     Defines if syncing of this directory is generally disabled. It can be useful
+#     to disabled syncing temporarily if structural changes need to be done that required
+#     several modifications.
+#
+#     defaults to: false
+disabled=false
+
+# sync-once= ( "" | "JCR2FS" | "FS2JCR" )
+#     If non empty the next scan will trigger a full sync in the direction indicated.
+#
+#     JCR2FS: 'export' all content in the JCR repository and write to the local disk.
+#     FS2JCR: 'import' all content from the disk into the JCR repository.
+#
+#     defaults to: ""
+sync-once=
+
+# sync-log = <filename>
+#     Defines the filename of the sync log.
+#
+#     defaults to: .vlt-sync.log
+#
+#sync-log=.vlt-sync.log
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-sync/src/main/resources/org/apache/jackrabbit/vault/sync/impl/default-filter.xml
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-sync/src/main/resources/org/apache/jackrabbit/vault/sync/impl/default-filter.xml?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-sync/src/main/resources/org/apache/jackrabbit/vault/sync/impl/default-filter.xml (added)
+++ jackrabbit/commons/filevault/trunk/vault-sync/src/main/resources/org/apache/jackrabbit/vault/sync/impl/default-filter.xml Sat Aug 10 05:53:42 2013
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<workspaceFilter version="1.0">
+    <!--
+    <filter root="/apps/geometrixx"/>
+    <filter root="/etc/designs/geometrixx"/>
+    -->
+</workspaceFilter>
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-vlt/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-vlt/pom.xml?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-vlt/pom.xml (added)
+++ jackrabbit/commons/filevault/trunk/vault-vlt/pom.xml Sat Aug 10 05:53:42 2013
@@ -0,0 +1,98 @@
+<?xml version="1.0"?><!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd ">
+    <modelVersion>4.0.0</modelVersion>
+    <!-- ====================================================================== -->
+    <!-- P A R E N T  P R O J E C T  D E S C R I P T I O N                      -->
+    <!-- ====================================================================== -->
+    <parent>
+        <groupId>org.apache.jackrabbit.vault</groupId>
+        <artifactId>parent</artifactId>
+        <relativePath>../parent/pom.xml</relativePath>
+        <version>3.0.0-SNAPSHOT</version>
+    </parent>
+
+    <!-- ====================================================================== -->
+    <!-- P R O J E C T  D E S C R I P T I O N                                   -->
+    <!-- ====================================================================== -->
+    <artifactId>vault-vlt</artifactId>
+    <version>3.0.0-SNAPSHOT</version>
+    <packaging>jar</packaging>
+
+    <name>Apache Jackrabbit FileVault Platform Interaction</name>
+
+    <!-- ====================================================================== -->
+    <!-- S C M  D E F I N I T I O N                                             -->
+    <!-- ====================================================================== -->
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/jackrabbit/commons/filevault/trunk/vault-vlt</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/jackrabbit/commons/filevault/trunk/vault-vlt</developerConnection>
+        <url>http://svn.apache.org/viewvc/asf/jackrabbit/commons/filevault/trunk/vault-vlt</url>
+    </scm>
+
+
+    <!-- ====================================================================== -->
+    <!-- D E P E N D E N C I E S                                                -->
+    <!-- ====================================================================== -->
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.jackrabbit.vault</groupId>
+            <artifactId>org.apache.jackrabbit.vault</artifactId>
+            <version>3.0.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit.vault</groupId>
+            <artifactId>vault-sync</artifactId>
+            <version>3.0.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-jcr-commons</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit.vault</groupId>
+            <artifactId>vault-diff</artifactId>
+            <version>3.0.0-SNAPSHOT</version>
+        </dependency>
+
+        <!-- JCR Stuff -->
+        <dependency>
+            <groupId>javax.jcr</groupId>
+            <artifactId>jcr</artifactId>
+        </dependency>
+
+        <!-- SLF4j / Log4j -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>

Added: jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/ConfigCredentialsProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/ConfigCredentialsProvider.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/ConfigCredentialsProvider.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/ConfigCredentialsProvider.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.vlt;
+
+import java.io.IOException;
+
+import javax.jcr.Credentials;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.jackrabbit.vault.fs.api.RepositoryAddress;
+import org.apache.jackrabbit.vault.fs.config.ConfigurationException;
+import org.apache.jackrabbit.vault.fs.config.SimpleCredentialsConfig;
+import org.apache.jackrabbit.vault.fs.config.VaultAuthConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>CredentialsProvider</code>...
+ *
+ */
+public class ConfigCredentialsProvider extends DefaultCredentialsProvider {
+
+    protected static Logger log = LoggerFactory.getLogger(ConfigCredentialsProvider.class);
+
+    private VaultAuthConfig config;
+
+    public ConfigCredentialsProvider(CredentialsProvider base) {
+        super(base);
+        config = new VaultAuthConfig();
+        try {
+            config.load();
+        } catch (IOException e) {
+            log.error("Error while loading auth configuration: {} ", e.toString());
+        } catch (ConfigurationException e) {
+            log.error("Error while loading auth configuration: {} ", e.toString());
+        }
+    }
+
+    public Credentials getCredentials(RepositoryAddress mountpoint) {
+        // check if temporary creds are set
+        if (super.getCredentials(mountpoint) != null) {
+            return super.getCredentials(mountpoint);
+        }
+        Credentials creds = fetchCredentials(mountpoint);
+        return creds == null
+                ? super.getCredentials(mountpoint)
+                : creds;
+    }
+
+    private Credentials fetchCredentials(RepositoryAddress mountpoint) {
+        VaultAuthConfig.RepositoryConfig cfg = config.getRepoConfig(
+                getLookupId(mountpoint)
+        );
+        if (cfg == null) {
+            return null;
+        }
+        return cfg.getCredsConfig().getCredentials();
+    }
+
+    private String getLookupId(RepositoryAddress mountpoint) {
+        return mountpoint.getSpecificURI() + "/" + mountpoint.getWorkspace();
+    }
+    
+    public void storeCredentials(RepositoryAddress mountpoint, Credentials creds) {
+        if (!(creds instanceof SimpleCredentials)) {
+            if (creds != null) {
+                log.error("Unable to store non-simple credentials of type " + creds.getClass().getName());
+            }
+            return;
+        }
+        VaultAuthConfig.RepositoryConfig cfg  = new VaultAuthConfig.RepositoryConfig(
+                getLookupId(mountpoint)
+        );
+        cfg.addCredsConfig(new SimpleCredentialsConfig(((SimpleCredentials) creds)));
+        config.addRepositoryConfig(cfg);
+        try {
+            config.save();
+        } catch (IOException e) {
+            log.error("Error while saving auth configuration: {} ", e.toString());
+        }
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/CredentialsProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/CredentialsProvider.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/CredentialsProvider.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/CredentialsProvider.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.vlt;
+
+import javax.jcr.Credentials;
+
+import org.apache.jackrabbit.vault.fs.api.RepositoryAddress;
+
+/**
+ * <code>CredentialsProvider</code>...
+ *
+ */
+public interface CredentialsProvider {
+
+    Credentials getCredentials(RepositoryAddress mountpoint);
+
+    void storeCredentials(RepositoryAddress mountpoint, Credentials creds);
+
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/DefaultCredentialsProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/DefaultCredentialsProvider.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/DefaultCredentialsProvider.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/DefaultCredentialsProvider.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.vlt;
+
+import javax.jcr.Credentials;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.jackrabbit.vault.fs.api.RepositoryAddress;
+
+/**
+ * <code>CredentialsProvider</code>...
+ *
+ */
+public class DefaultCredentialsProvider implements CredentialsProvider {
+
+    private Credentials creds;
+
+    private CredentialsProvider base;
+
+    public DefaultCredentialsProvider() {
+    }
+
+    public DefaultCredentialsProvider(CredentialsProvider base) {
+        this.base = base;
+    }
+
+    public void setDefaultCredentials(String userPass) {
+        if (userPass != null) {
+            int idx = userPass.indexOf(':');
+            if (idx > 0) {
+                creds = new SimpleCredentials(userPass.substring(0, idx), userPass.substring(idx + 1).toCharArray());
+            } else {
+                creds = new SimpleCredentials(userPass, new char[0]);
+            }
+        } else {
+            creds = null;
+        }
+    }
+
+    public Credentials getCredentials(RepositoryAddress mountpoint) {
+        return creds == null && base != null
+                ? base.getCredentials(mountpoint)
+                : creds;
+    }
+
+    public void storeCredentials(RepositoryAddress mountpoint, Credentials creds) {
+        this.creds = creds;
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/FileAction.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/FileAction.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/FileAction.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/FileAction.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.vlt;
+
+/**
+ * The file action represents an action that was performed on a file.
+ *
+ */
+public enum FileAction {
+
+    ADDED ("A"),
+    UPDATED ("U"),
+    MERGED ("G"),
+    CONFLICTED ("C"),
+    DELETED ("D"),
+    IGNORED ("I"),
+    VOID (" "),
+    REVERTED ("R");
+
+    /**
+     * A one letter name of the action
+     */
+    public final String letter;
+
+    private FileAction(String shortName) {
+        this.letter = shortName;
+    }
+
+    public String toString() {
+        return name().toLowerCase() + " (" + letter + ")";
+    }
+
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/FileList.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/FileList.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/FileList.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/FileList.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.vlt;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Pattern;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.vault.vlt.meta.Ignored;
+import org.apache.jackrabbit.vault.vlt.meta.VltEntries;
+import org.apache.jackrabbit.vault.vlt.meta.VltEntry;
+
+/**
+ * <code>FileList</code>...
+ *
+ */
+public class FileList {
+
+    private final Map<String, VltFile> files = new TreeMap<String, VltFile>();
+
+    private final VltDirectory dir;
+
+    private final VltEntries entries;
+
+    private final Set<String> globalIgnores;
+
+    private List<Pattern> ignored = new LinkedList<Pattern>();
+
+    public FileList(VltDirectory dir, VltEntries entries) throws VltException {
+        this.dir = dir;
+        this.entries = entries;
+        globalIgnores = new HashSet<String>(
+                dir.getContext().getMetaInf().getSettings().getIgnoredNames());
+        globalIgnores.add(Ignored.FILE_NAME);
+        loadIgnored(dir.getDirectory());
+        scanDirectory(dir.getDirectory());
+        scanEntries();
+    }
+
+    private void loadIgnored(File directory) throws VltException {
+        try {
+            File file = new File(directory, Ignored.FILE_NAME);
+            if (file.isFile() && file.canRead()) {
+                for (Object o: FileUtils.readLines(file, "utf-8")) {
+                    ignored.add(createPatternFromGlob(o.toString()));
+                }
+            }
+        } catch (IOException e) {
+            throw new VltException("Unable to load .vltignore file", e);
+        }
+    }
+
+    private static Pattern createPatternFromGlob(String glob) {
+        // only support * and ?
+        glob = glob.replace(".", "\\.");
+        glob = glob.replace("*", ".*");
+        glob = glob.replace("?", ".+");
+        return Pattern.compile(glob);
+    }
+
+    public boolean isVltIgnored(String name) {
+        for (Pattern p: ignored) {
+            if (p.matcher(name).matches()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void scanEntries() throws VltException {
+        for (VltEntry entry: entries.entries()) {
+            if (!files.containsKey(entry.getName())) {
+                VltFile file = new VltFile(dir, entry.getName(), entry);
+                files.put(file.getName(), file);
+            }
+        }
+    }
+
+    private void scanDirectory(File localDir) throws VltException {
+        File[] localFiles = localDir.listFiles();
+        if (localFiles != null) {
+            for (File localFile : localFiles) {
+                if (!isIgnored(localFile)) {
+                    String name = localFile.getName();
+                    VltEntry entry = entries.getEntry(name);
+                    VltFile file = new VltFile(dir, name, entry);
+                    files.put(name, file);
+                }
+            }
+        }
+    }
+
+    public boolean isIgnored(File file) {
+        // currently only check for extension for conflict files
+        String name = file.getName();
+        return name.equals(VltDirectory.META_DIR_NAME)
+                || globalIgnores.contains(name) || isVltIgnored(name);
+    }
+
+    public VltFile getFile(String name) {
+        return files.get(name);
+    }
+
+    public boolean hasFile(String name) {
+        return files.containsKey(name);
+    }
+
+    public Collection<VltFile> getFiles() {
+        return files.values();
+    }
+
+    public Set<String> getFileNames() {
+        return new HashSet<String>(files.keySet());
+    }
+
+    public void addFile(VltFile file) {
+        files.put(file.getName(), file);
+    }
+}
\ No newline at end of file



Mime
View raw message