You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by tr...@apache.org on 2013/08/10 07:53:54 UTC

svn commit: r1512568 [12/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...

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/AggregatorProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/AggregatorProvider.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/AggregatorProvider.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/AggregatorProvider.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,86 @@
+/*
+ * 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.fs.impl;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.vault.fs.api.Aggregator;
+import org.apache.jackrabbit.vault.fs.api.DumpContext;
+
+/**
+ * List of configured aggregators that selects one given a repository node.
+ */
+public class AggregatorProvider {
+
+    /**
+     * list of aggregators
+     */
+    private final List<Aggregator> aggregators;
+
+    /**
+     * Constructs a new aggregator provider with a given aggregator list.
+     * @param aggregators the list of aggregators.
+     */
+    public AggregatorProvider(List<Aggregator> aggregators) {
+        this.aggregators = Collections.unmodifiableList(aggregators);
+    }
+
+    /**
+     * Returns the list of aggregators
+     * @return the list of aggregators
+     */
+    public List<Aggregator> getAggregators() {
+        return aggregators;
+    }
+
+    /**
+     * Selects an aggregator that can handle the given node. If no aggregator can
+     * be found, <code>null</code> is returned. Although this is a very rare case
+     * because there should always be a default, catch-all aggregator.
+     *
+     * @param node the node to match
+     * @return an aggregator that handles the node or <code>null</code> if not found.
+     * @throws RepositoryException if a repository error occurs
+     */
+    public Aggregator getAggregator(Node node, String path) throws RepositoryException {
+        for (Aggregator a: aggregators) {
+            if (a.matches(node, path)) {
+                return a;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void dump(DumpContext ctx, boolean isLast) {
+        ctx.println(isLast, "aggregators");
+        ctx.indent(isLast);
+        for (Iterator<Aggregator> iter = aggregators.iterator(); iter.hasNext();) {
+            Aggregator a = iter.next();
+            a.dump(ctx, !iter.hasNext());
+        }
+        ctx.outdent();
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/ArtifactSetImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/ArtifactSetImpl.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/ArtifactSetImpl.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/ArtifactSetImpl.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,328 @@
+/*
+ * 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.fs.impl;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.vault.fs.PropertyValueArtifact;
+import org.apache.jackrabbit.vault.fs.SerializerArtifact;
+import org.apache.jackrabbit.vault.fs.api.Artifact;
+import org.apache.jackrabbit.vault.fs.api.ArtifactIterator;
+import org.apache.jackrabbit.vault.fs.api.ArtifactSet;
+import org.apache.jackrabbit.vault.fs.api.ArtifactType;
+import org.apache.jackrabbit.vault.fs.api.DumpContext;
+import org.apache.jackrabbit.vault.fs.api.Dumpable;
+import org.apache.jackrabbit.vault.fs.api.ItemFilter;
+import org.apache.jackrabbit.vault.fs.api.ItemFilterSet;
+import org.apache.jackrabbit.vault.fs.io.Serializer;
+
+/**
+ * Implements a set of artifacts. The artifacts can be retrieved via an
+ * {@link ArtifactIterator}. Special artifacts like {@link ArtifactType#PRIMARY}
+ * and {@link ArtifactType#DIRECTORY} can be retrieved individually. This set
+ * only allows one of those artifacts per type.
+ *
+ * All modifications to this set have no influence on the persisted data.
+ *
+ */
+public class ArtifactSetImpl implements Dumpable, ArtifactSet {
+
+    /**
+     * the list of artifacts
+     */
+    private final List<Artifact> artifacts = new LinkedList<Artifact>();
+
+    /**
+     * the list of removed
+     */
+    private List<Artifact> removed;
+    
+    /**
+     * Set of artifacts that are only allowed once
+     */
+    private final Map<ArtifactType, Artifact> single = new EnumMap<ArtifactType, Artifact>(ArtifactType.class);
+
+    /**
+     * list of hint artifacts
+     */
+    private Set<Artifact> hints;
+
+    /**
+     * The item filter set that defines the coverage of this artifact set
+     */
+    private ItemFilterSet coverageFilter = (ItemFilterSet) new ItemFilterSet().addInclude(ItemFilter.ALL);
+
+    public ItemFilterSet getCoverage() {
+        return coverageFilter;
+    }
+
+    /**
+     * Sets the item filter set that defines the coverage of the items in
+     * this artifact set.
+     * @param filter the item filter set.
+     */
+    public void setCoverage(ItemFilterSet filter) {
+        this.coverageFilter = filter;
+    }
+
+    public void addAll(Collection<? extends Artifact> artifacts) {
+         for (Artifact artifact: artifacts) {
+             add(artifact);
+         }
+     }
+
+     public void addAll(ArtifactSet artifacts) {
+        addAll(((ArtifactSetImpl) artifacts).artifacts);
+    }
+
+    public void add(Artifact artifact) {
+        ArtifactType type = artifact.getType();
+        if (type == ArtifactType.PRIMARY || type == ArtifactType.DIRECTORY) {
+            if (single.containsKey(type)) {
+                throw new IllegalArgumentException("Only 1 " + type + " artifact allowed.");
+            }
+            single.put(type, artifact);
+            artifacts.add(artifact);
+        } else if (type == ArtifactType.HINT) {
+            if (hints == null) {
+                hints = new HashSet<Artifact>();
+            }
+            hints.add(artifact);
+        } else {
+            artifacts.add(artifact);
+        }
+    }
+
+    /**
+     * Adds an {@link SerializerArtifact} with the given name, type and
+     * serializer.
+     *
+     * @param parent the parent artifact
+     * @param name the name of the artifact
+     * @param ext the extension of the artifact
+     * @param type the type of the artifact
+     * @param ser the serializer of the artifact
+     * @param lastModified the last modified date
+     * @return the added artifact
+     */
+    public SerializerArtifact add(Artifact parent, String name, String ext,
+                                  ArtifactType type, Serializer ser,
+                                  long lastModified) {
+        SerializerArtifact a = new SerializerArtifact(parent, name, ext, type, ser, lastModified);
+        add(a);
+        return a;
+    }
+
+    /**
+     * Adds an {@link PropertyValueArtifact} with the given name, type and
+     * serializer
+     *
+     * @param parent parent artifact
+     * @param relPath the name of the artifact
+     * @param ext the extension
+     * @param type the type of the artifact
+     * @param prop the property of the artifact
+     * @param lastModified the last modified date. can be 0.
+     *
+     * @return a collection of artifacts
+     * @throws RepositoryException if an error occurs.
+     */
+    public Collection<PropertyValueArtifact> add(Artifact parent, String relPath,
+                 String ext, ArtifactType type, Property prop, long lastModified)
+            throws RepositoryException {
+        Collection<PropertyValueArtifact> a =
+                PropertyValueArtifact.create(parent, relPath, ext, type, prop, lastModified);
+        addAll(a);
+        return a;
+    }
+
+
+    public Artifact getPrimaryData() {
+        return single.get(ArtifactType.PRIMARY);
+    }
+
+    public Artifact getDirectory() {
+        return single.get(ArtifactType.DIRECTORY);
+    }
+
+    public boolean isEmpty() {
+        return artifacts.isEmpty();
+    }
+
+    public int size() {
+        return artifacts.size();
+    }
+
+    /**
+     * Removes the artifact from this set.
+     *
+     * @param a the artifact to remove
+     * @return <code>true</code> if the artifact was removed.
+     */
+    public boolean remove(Artifact a) {
+        if (artifacts.remove(a)) {
+            single.remove(a.getType());
+            if (removed == null) {
+                removed = new LinkedList<Artifact>();
+                removed.add(a);
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Puts the given artifact to this set. In comparison to the add operations,
+     * an already existing artifact is replaced. If the type of the newly
+     * artifact is {@link ArtifactType#BINARY} it equivalent with the
+     * same name is replaced.
+     *
+     * @param a the artifact to put
+     * @return the previous artifact or <code>null</code>
+     */
+    public Artifact put(Artifact a) {
+        Artifact prev = null;
+        int idx = artifacts.indexOf(a);
+        if (idx >=0) {
+            prev = artifacts.remove(idx);
+        }
+        single.put(a.getType(), a);
+        artifacts.add(a);
+        return prev;
+    }
+
+    /**
+     * Returns the number of artifacts in this set that have the given type.
+     * @param type the artifact type
+     * @return the number of artifacts in this set that have the given type.
+     */
+    public int size(ArtifactType type) {
+        int num = 0;
+        if (type == ArtifactType.HINT) {
+            num = hints == null ? 0 : hints.size();
+        } else {
+            for (Artifact a: artifacts) {
+                if (a.getType() == type) {
+                    num++;
+                }
+            }
+        }
+        return num;
+    }
+
+    /**
+     * Returns a collection of all artifacts that have the given type.
+     * @param type the type of the artifacts to return
+     * @return the artifacts
+     */
+    public Collection<Artifact> values(ArtifactType type) {
+        List<Artifact> ret = new LinkedList<Artifact>();
+        if (type == ArtifactType.HINT) {
+            if (hints != null) {
+                ret.addAll(hints);
+            }
+        } else {
+            for (Artifact a: artifacts) {
+                if (type == null || a.getType() == type) {
+                    ret.add(a);
+                }
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Returns a collection of all artifacts
+     * @return the artifacts
+     */
+    public Collection<Artifact> values() {
+        return Collections.unmodifiableList(artifacts);
+    }
+
+    /**
+     * Returns the collection of removed artifacts
+     * @return the removed artifacts
+     */
+    public Collection<Artifact> removed() {
+        if (removed == null) {
+            return Collections.emptyList();
+        } else {
+            return Collections.unmodifiableList(removed);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString() {
+        StringBuilder buf = new StringBuilder("ArtifactSet={");
+        Iterator<Artifact> iter = artifacts.iterator();
+        while (iter.hasNext()) {
+            buf.append(iter.next());
+            if (iter.hasNext()) {
+                buf.append(", ");
+            }
+        }
+        buf.append('}');
+        return buf.toString();
+    }
+
+    /**
+     * Returns the artifact with the given path or <code>null</code> if it does
+     * not exist.
+     * @param path the name of the artifact.
+     * @return the desired artifact or <code>null</code>
+     */
+    public Artifact getArtifact(String path) {
+        for (Artifact a: artifacts) {
+            if (a.getRelativePath().equals(path)) {
+                return a;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void dump(DumpContext ctx, boolean isLast) {
+        ctx.println(isLast, "Artifacts");
+        ctx.indent(isLast);
+        for (Artifact a: artifacts) {
+            a.dump(ctx, false);
+        }
+        ctx.println(true, "Coverage");
+        ctx.indent(true);
+        coverageFilter.dump(ctx, true);
+        ctx.outdent();
+        ctx.outdent();
+    }
+
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/TransactionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/TransactionImpl.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/TransactionImpl.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/TransactionImpl.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,770 @@
+/*
+ * 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.fs.impl;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.vault.fs.DirectoryArtifact;
+import org.apache.jackrabbit.vault.fs.api.Aggregate;
+import org.apache.jackrabbit.vault.fs.api.Artifact;
+import org.apache.jackrabbit.vault.fs.api.ArtifactType;
+import org.apache.jackrabbit.vault.fs.api.ImportArtifact;
+import org.apache.jackrabbit.vault.fs.api.ImportInfo;
+import org.apache.jackrabbit.vault.fs.api.SerializationType;
+import org.apache.jackrabbit.vault.fs.api.VaultFile;
+import org.apache.jackrabbit.vault.fs.api.VaultFileOutput;
+import org.apache.jackrabbit.vault.fs.api.VaultFileSystem;
+import org.apache.jackrabbit.vault.fs.api.VaultFsTransaction;
+import org.apache.jackrabbit.vault.fs.api.VaultInputSource;
+import org.apache.jackrabbit.vault.fs.impl.io.DocViewAnalyzer;
+import org.apache.jackrabbit.vault.fs.impl.io.ImportInfoImpl;
+import org.apache.jackrabbit.vault.fs.impl.io.InputSourceArtifact;
+import org.apache.jackrabbit.vault.fs.impl.io.XmlAnalyzer;
+import org.apache.jackrabbit.vault.fs.io.AutoSave;
+import org.apache.jackrabbit.vault.fs.io.DocViewAnalyzerListener;
+import org.apache.jackrabbit.vault.util.Constants;
+import org.apache.jackrabbit.vault.util.PathComparator;
+import org.apache.jackrabbit.vault.util.PathUtil;
+import org.apache.jackrabbit.vault.util.PlatformNameFormat;
+import org.apache.jackrabbit.vault.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides transactional brackets around a write back transaction to
+ * the Vault filesystem. a transaction is always needed due to the fact that
+ * several jcr files could belong to the same artifact.
+ *
+ * TODO: check all vault operations!
+ *
+ */
+public class TransactionImpl implements VaultFsTransaction {
+
+    /**
+     * default logger
+     */
+    private static final Logger log = LoggerFactory.getLogger(TransactionImpl.class);
+
+    private final VaultFileSystem fs;
+
+    private final List<Change> changes = new LinkedList<Change>();
+
+    private final Map<String, DotXmlInfo> dotXmlNodes = new HashMap<String, DotXmlInfo>();
+
+    private boolean verbose;
+
+    private final AutoSave autoSave = new AutoSave();
+
+    public TransactionImpl(VaultFileSystem fs) {
+        this.fs = fs;
+    }
+
+    public AutoSave getAutoSave() {
+        return autoSave;
+    }
+
+    public boolean isVerbose() {
+        return verbose;
+    }
+
+    public void setVerbose(boolean verbose) {
+        this.verbose = verbose;
+    }
+
+    public void delete(VaultFile file) throws IOException {
+        if (file != null) {
+            changes.add(new Change(Type.DELETED, file, null));
+        }
+    }
+
+    public void modify(VaultFile file, VaultInputSource input) throws IOException {
+        final Change change = new Change(Type.MODIFIED, file, input);
+        changes.add(change);
+
+        // special handling for .content.xml
+        if (file.getName().equals(Constants.DOT_CONTENT_XML)) {
+            String repoParentPath = file.getAggregate().getPath();
+            // analyze the xml to scan for added nodes
+            DocViewAnalyzer.analyze(
+                new DocViewAnalyzerListener(){
+                    public void onNode(String path, boolean intermediate, String nodeType) {
+                        if (!intermediate) {
+                            dotXmlNodes.put(path, new DotXmlInfo(change, nodeType == null));
+                        }
+                    }
+                }, fs.getAggregateManager().getSession(), repoParentPath, input);
+        }
+    }
+
+    public VaultFileOutput add(String path, VaultInputSource input)
+            throws IOException, RepositoryException {
+
+        String repoPath = PlatformNameFormat.getRepositoryPath(path, true);
+        String repoName = Text.getName(repoPath);
+        String parentPath = Text.getRelativeParent(path, 1);
+        String repoParentPath = Text.getRelativeParent(repoPath, 1);
+
+        if (repoName.equals(Constants.DOT_CONTENT_XML)) {
+            // special handling for .content.xml
+            final Change change = new Change(Type.ADDED_X, repoParentPath, path, input);
+            changes.add(change);
+
+            // analyze the xml to scan for added nodes
+            DocViewAnalyzer.analyze(
+                new DocViewAnalyzerListener(){
+                    public void onNode(String path, boolean intermediate, String nodeType) {
+                        if (!intermediate) {
+                            dotXmlNodes.put(path, new DotXmlInfo(change, nodeType == null));
+                        }
+                    }
+                }, fs.getAggregateManager().getSession(), repoParentPath, input);
+            // create artifact
+            String parentExt = parentPath.endsWith(".dir") ? ".dir" : "";
+            Artifact parent = new DirectoryArtifact(Text.getName(repoParentPath), parentExt);
+            InputSourceArtifact isa = new InputSourceArtifact(
+                    parent,
+                    Constants.DOT_CONTENT_XML,
+                    "",
+                    ArtifactType.PRIMARY, input,
+                    SerializationType.XML_DOCVIEW);
+            isa.setContentType("text/xml");
+            // attach to change
+            change.isa = isa;
+            return new VaultFileOutputImpl(change);
+
+        } else {
+            // normal file, detect type
+            SerializationType serType = SerializationType.GENERIC;
+            ArtifactType aType = ArtifactType.FILE;
+            String extension = "";
+            int idx = repoName.lastIndexOf('.');
+            if (idx > 0) {
+                String base = repoName.substring(0, idx);
+                String ext = repoName.substring(idx);
+                if (ext.equals(".xml")) {
+                    // this can either be an generic exported docview or
+                    // a 'user-xml' that is imported as file
+                    // btw: this only works for input sources that can
+                    //      refetch their input stream
+                    serType = XmlAnalyzer.analyze(input);
+                    if (serType == SerializationType.XML_DOCVIEW) {
+                        // in this case, the extension was added by the exporter.
+                        aType = ArtifactType.PRIMARY;
+                        repoName = base;
+                        extension = ext;
+                    } else {
+                        // force generic
+                        serType = SerializationType.GENERIC;
+                    }
+                } else if (ext.equals(".xcnd")) {
+                    aType = ArtifactType.PRIMARY;
+                    serType = SerializationType.CND;
+                    repoName = base;
+                    extension = ext;
+                } else if (ext.equals(".binary")) {
+                    aType = ArtifactType.BINARY;
+                    repoName = base;
+                    extension = ext;
+                }
+            }
+            InputSourceArtifact isa = new InputSourceArtifact(null, repoName,
+                    extension, aType, input, serType);
+
+            Change change = new Change(Type.ADDED, repoParentPath + "/" + repoName, path, input);
+            change.isa = isa;
+            changes.add(change);
+            return new VaultFileOutputImpl(change);
+        }
+    }
+
+    public void mkdir(String path) throws IOException, RepositoryException {
+        changes.add(new Change(Type.MKDIR, PlatformNameFormat.getRepositoryPath(path), path, null));
+    }
+
+    public Collection<Info> commit() throws RepositoryException, IOException {
+        Map<String, Info> infos = new HashMap<String, Info>();
+
+        // remember all nodes to checkin again
+        ImportInfoImpl allInfos = new ImportInfoImpl();
+
+        // first scan all changes for additions that need to be attached to a
+        // .content.xml change
+        if (!dotXmlNodes.isEmpty()) {
+            Iterator<Change> iter = changes.iterator();
+            while (iter.hasNext()) {
+                Change c = iter.next();
+                if (c.type == Type.ADDED) {
+                    if (c.isa.getType() == ArtifactType.BINARY) {
+                        DotXmlInfo dxi = dotXmlNodes.get(c.repoPath);
+                        if (dxi != null) {
+                            dxi.change.add(c);
+                            iter.remove();
+                        }
+                    }
+                } else if (c.type == Type.MKDIR) {
+                    DotXmlInfo dxi = dotXmlNodes.get(c.repoPath);
+                    if (dxi != null) {
+                        iter.remove();
+                    }
+                }
+            }
+        }
+
+        // process the changes and group them by artifact path
+        Map<String, TxInfo> modified = new TreeMap<String, TxInfo>(new PathComparator());
+        boolean ignoreMP = true;
+        while (!changes.isEmpty()) {
+            int size = changes.size();
+            // process as many changes that have a parent file
+            Iterator<Change> iter = changes.iterator();
+            while (iter.hasNext()) {
+                Change change = iter.next();
+                if (processChange(change, modified, ignoreMP)) {
+                    changes.remove(change);
+                    iter = changes.iterator();
+                }
+            }
+            if (changes.size() == size) {
+                if (ignoreMP) {
+                    ignoreMP = false;
+                } else {
+                    for (Change c: changes) {
+                        infos.put(c.filePath, new Info(Type.ERROR, c.filePath));
+                    }
+                    // abort iteration
+                    changes.clear();
+                }
+            } else {
+                // write back the current collected modifications and generate a
+                // new modified info list
+                for (TxInfo info : modified.values()) {
+
+                    // check if primary artifact is still present
+                    if (info.out == null && info.aggregate == null) {
+                        // this was an intermediate directory delete
+                        for (String path: info.original.keySet()) {
+                            infos.put(path, new Info(Type.DELETED, path));
+                            if (verbose) {
+                                log.info("...comitting  DEL {}", path);
+                            }
+                        }
+                    } else if (info.out.getArtifacts().isEmpty() && info.aggregate != null) {
+                        // delete entire node if aggregate is still attached
+                        if (info.aggregate.isAttached()) {
+                            info.aggregate.remove(false);
+                        }
+                        // generate infos for the deleted ones
+                        for (String path: info.original.keySet()) {
+                            infos.put(path, new Info(Type.DELETED, path));
+                            if (verbose) {
+                                log.info("...comitting  DEL {}", path);
+                            }
+                        }
+                        // mark the primary artifact of the parent as modified
+                        // TODO fix
+                        String cXmlPath = info.parentFile.getPath();
+                        if (cXmlPath.endsWith("/")) {
+                            cXmlPath+= Constants.DOT_CONTENT_XML;
+                        } else {
+                            cXmlPath+= "/" + Constants.DOT_CONTENT_XML;
+                        }
+                        Info i = infos.get(cXmlPath);
+                        if (i == null) {
+                            infos.put(cXmlPath, new Info(Type.MODIFIED, cXmlPath));
+                        }
+                    } else if (info.aggregate == null) {
+                        // this was and addition
+                        // for now, just guess from the artifacts the new files
+                        String parentPath = info.parentFile.getPath();
+                        if (!parentPath.endsWith("/")) {
+                            parentPath += "/";
+                        }
+                        for (Artifact a: info.out.getArtifacts().values()) {
+                            if (a instanceof ImportArtifact) {
+                                String path = parentPath + a.getPlatformPath();
+                                infos.put(path, new Info(Type.ADDED, path));
+                            }
+                        }
+
+                        ImportInfo ret = info.out.close();
+                        if (ret != null) {
+                            allInfos.merge(ret);
+                            if (verbose) {
+                                for (Map.Entry e: ret.getModifications().entrySet()) {
+                                    log.info("...comitting  {} {}", e.getValue(), e.getKey());
+                                }
+                            }
+                        }
+                        // modify parent
+                        infos.put(info.parentFile.getPath(), new Info(Type.MODIFIED, info.parentFile.getPath()));
+
+                    } else {
+                        // this was a modification
+                        ImportInfo ret = info.out.close();
+                        if (ret != null) {
+                            allInfos.merge(ret);
+                        }
+                        for (VaultFile file: info.original.values()) {
+                            infos.put(file.getPath(), new Info(Type.MODIFIED, file.getPath()));
+                            if (verbose) {
+                                log.info("...comitting  UPD {}", file.getPath());
+                            }
+                        }
+                        if (verbose && ret != null) {
+                            for (Map.Entry e: ret.getModifications().entrySet()) {
+                                log.info("...comitting  {} {}", e.getValue(), e.getKey());
+                            }
+                        }
+                    }
+                }
+            }
+            modified.clear();
+            fs.invalidate();
+        }
+        if (verbose) {
+            log.info("Persisting changes...");
+        }
+        if (allInfos.numErrors() > 0) {
+            try {
+                fs.getAggregateManager().getSession().refresh(false);
+            } catch (RepositoryException e) {
+                // ignore
+            }
+            throw new RepositoryException("There were errors during commit. Aborting transaction.");
+        }
+        fs.getAggregateManager().getSession().save();
+        allInfos.checkinNodes(fs.getAggregateManager().getSession());
+        fs.invalidate();
+        return infos.values();
+    }
+
+
+    private boolean processChange(Change change, Map<String, TxInfo> modified, boolean ignoreMP)
+            throws RepositoryException, IOException {
+        switch (change.type) {
+            case ADDED_X: {
+                // special handling for .content.xml
+                // filePath: /vltTest/foo/.content.xml
+                // repoPath: /vltTest/foo
+
+                // parentPath: /vltTest/foo
+                String parentPath = Text.getRelativeParent(change.filePath, 1);
+                VaultFile parent = fs.getFile(parentPath);
+                TxInfo txInfo;
+                String repoPath;
+                if (parent == null) {
+                    // parentPath: /vltTest
+                    parentPath = Text.getRelativeParent(parentPath, 1);
+                    parent = fs.getFile(parentPath);
+                    repoPath = change.repoPath;
+                    String repoName = Text.getName(repoPath);
+                    if (parent == null) {
+                        // special case if parent is an intermediate directory
+                        // that is not created yet. for example _jcr_content
+                        // /header.png/_jcr_content/renditions
+
+                        // check if parent node exists
+                        if (!fs.getAggregateManager().getSession().nodeExists(Text.getRelativeParent(repoPath, 1))) {
+                            return false;
+                        }
+                        while ((parent == null || parent.getAggregate() == null)
+                                && parentPath.length() > 0) {
+                            String parentName = Text.getName(parentPath);
+                            if (parentName.endsWith(".dir")) {
+                                parentName = parentName.substring(0, parentName.length() - 4);
+                            }
+                            repoName = PlatformNameFormat.getRepositoryName(parentName) + "/" + repoName;
+                            parentPath = Text.getRelativeParent(parentPath, 1);
+                            parent = fs.getFile(parentPath);
+                        }
+                        if (parent == null || parent.getAggregate() == null) {
+                            return false;
+                        }
+                        String repoRelPath = Text.getName(parentPath) + "/" + repoName;
+                        txInfo = modified.get(parent.getAggregate().getPath());
+                        if (txInfo == null) {
+                            txInfo = new TxInfo(parent.getAggregate());
+                            modified.put(parent.getAggregate().getPath(), txInfo);
+                        }
+                        txInfo.out.getArtifacts().add(new InputSourceArtifact(null,
+                                repoRelPath, change.isa.getExtension(),
+                                ArtifactType.FILE,
+                                change.isa.getInputSource(), change.isa.getSerializationType()
+                        ));
+                    } else {
+                        // repoPath: /vltTest.foo
+                        assertInFilter(repoPath);
+                        txInfo = modified.get(repoPath);
+                        if (txInfo == null) {
+                            txInfo = new TxInfo(repoPath,
+                                    ((AggregateImpl) parent.getAggregate()).create(repoName));
+                            txInfo.parentFile = parent;
+                            modified.put(repoPath, txInfo);
+                        }
+                        txInfo.out.getArtifacts().add(change.isa);
+                    }
+                } else {
+                    // repoPath: /vltTest/foo
+                    repoPath = parent.getAggregate().getPath();
+                    assertInFilter(repoPath);
+                    txInfo = modified.get(repoPath);
+                    if (txInfo == null) {
+                        txInfo = new TxInfo(parent.getAggregate());
+                        txInfo.parentFile = parent;
+                        modified.put(repoPath, txInfo);
+                    }
+                    txInfo.out.getArtifacts().add(change.isa);
+                }
+                if (txInfo == null) {
+                    return false;
+                }
+                // add sub changes
+                if (change.subChanges != null) {
+                    for (Change sc: change.subChanges) {
+                        // need to adjust relative path of input
+                        // repoPath    = /vltTest/foo
+                        // sc.repoPath = /vltTest/foo/text/file
+                        // relPath     = foo/text/file
+                        String relPath = PathUtil.getRelativePath(repoPath, sc.repoPath);
+                        relPath  = Text.getName(repoPath) + "/" + relPath;
+                        if (!relPath.equals(sc.isa.getRelativePath())) {
+                            // todo: check if correct platform path
+                            sc.isa = new InputSourceArtifact(
+                                    null,
+                                    relPath,
+                                    "",
+                                    sc.isa.getType(),
+                                    sc.isa.getInputSource(),
+                                    sc.isa.getSerializationType()
+                            );
+                        }
+                        txInfo.out.getArtifacts().add(sc.isa);
+                    }
+                }
+                if (verbose) {
+                    log.info("...scheduling ADD {}/{}", parent.getPath(), Constants.DOT_CONTENT_XML);
+                }
+            } break;
+            case ADDED: {
+                // get parent file
+                String parentPath = Text.getRelativeParent(change.filePath, 1);
+
+                // stop processing if parent file does not exist.
+                VaultFile parent = fs.getFile(parentPath);
+                String repoName = change.isa.getRelativePath();
+                if (parent == null || parent.getAggregate() == null) {
+                    if (ignoreMP) {
+                        return false;
+                    }
+                    // Hack: if parent is intermediate directory, search
+                    // next valid parent and modify its artifact set.
+                    // since we cannot easily determine if the parent is an
+                    // intermediate directory, we just process all failing ones
+                    // at the end.
+                    while ((parent == null || parent.getAggregate() == null)
+                            && parentPath.length() > 0) {
+                        String parentName = Text.getName(parentPath);
+                        if (parentName.endsWith(".dir")) {
+                            parentName = parentName.substring(0, parentName.length() - 4);
+                        }
+                        repoName = PlatformNameFormat.getRepositoryName(parentName) + "/" + repoName;
+                        parentPath = Text.getRelativeParent(parentPath, 1);
+                        parent = fs.getFile(parentPath);
+                    }
+                    if (parent == null) {
+                        // no parent found ?
+                        return false;
+                    }
+                    String repoPath = parent.getAggregate().getPath();
+                    String repoRelPath = Text.getName(repoPath) + "/" + repoName;
+                    if (!repoPath.endsWith("/")) {
+                        repoPath += "/";
+                    }
+                    repoPath += repoName;
+                    assertInFilter(repoPath);
+                    if (false && change.isa.getSerializationType() == SerializationType.XML_DOCVIEW) {
+                        // special case that full coverage is below a intermediate
+                        // ignore and wait for next cycle
+                    } else {
+                        TxInfo txInfo = modified.get(parent.getAggregate().getPath());
+                        if (txInfo == null) {
+                            txInfo = new TxInfo(parent.getAggregate());
+                            modified.put(parent.getAggregate().getPath(), txInfo);
+                        }
+                        txInfo.out.getArtifacts().add(new InputSourceArtifact(null,
+                                repoRelPath, change.isa.getExtension(),
+                                ArtifactType.FILE,
+                                change.isa.getInputSource(), change.isa.getSerializationType()
+                        ));
+                    }
+                } else {
+                    String repoPath = parent.getAggregate().getPath();
+
+                    if (!repoPath.endsWith("/")) {
+                        repoPath += "/";
+                    }
+                    repoPath += repoName;
+                    assertInFilter(repoPath);
+                    TxInfo txInfo = modified.get(repoPath);
+                    if (txInfo == null) {
+                        txInfo = new TxInfo(repoPath, ((AggregateImpl) parent.getAggregate()).create(repoName));
+                        txInfo.setParentFile(parent);
+                        modified.put(repoPath, txInfo);
+                    }
+                    txInfo.out.getArtifacts().add(change.isa);
+
+                }
+                if (verbose) {
+                    log.info("...scheduling ADD {}/{}", parent.getPath(), repoName);
+                }
+            } break;
+            case MKDIR:{
+                // get parent file
+                String parentPath = Text.getRelativeParent(change.filePath, 1);
+                String name = Text.getName(change.filePath);
+                VaultFile parent = fs.getFile(parentPath);
+                if (parent == null || parent.isTransient()) {
+                    return false;
+                }
+                String repoName = PlatformNameFormat.getRepositoryName(name);
+                int idx = repoName.lastIndexOf('.');
+                if (idx > 0) {
+                    String base = repoName.substring(0, idx);
+                    String ext = repoName.substring(idx);
+                    if (ext.equals(".dir")) {
+                        // assume no directories with .dir extension
+                        repoName = base;
+                    }
+                }
+                String repoPath = parent.getAggregate().getPath();
+                if (!repoPath.endsWith("/")) {
+                    repoPath += "/";
+                }
+                repoPath += repoName;
+                assertInFilter(repoPath);
+                TxInfo txInfo = modified.get(repoPath);
+                if (txInfo == null) {
+                    txInfo = new TxInfo(repoPath, ((AggregateImpl) parent.getAggregate()).create(repoName));
+                    txInfo.setParentFile(parent);
+                    modified.put(repoPath, txInfo);
+                }
+                txInfo.out.addArtifact(new DirectoryArtifact(repoName));
+                if (verbose) {
+                    log.info("...scheduling MKD {}/{}", parent.getPath(), repoName);
+                }
+            } break;
+            case DELETED: {
+                Aggregate an = change.file.getAggregate();
+                if (an == null) {
+                    // intermediate directory
+                    // can't handle here
+                    assertInFilter(change.repoPath);
+                    TxInfo txInfo = new TxInfo(change.repoPath, null);
+                    txInfo.original.put(change.file.getPath(), change.file);
+                    modified.put(txInfo.artifactsPath, txInfo);
+                } else {
+                    assertInFilter(an.getPath());
+                    TxInfo txInfo = modified.get(an.getPath());
+                    if (txInfo == null) {
+                        txInfo = new TxInfo(an);
+                        VaultFile dir = null;
+                        for (VaultFile rel : change.file.getRelated()) {
+                            txInfo.original.put(rel.getPath(), rel);
+                            if (rel.isDirectory()) {
+                                dir = rel;
+                            }
+                        }
+                        modified.put(txInfo.artifactsPath, txInfo);
+                        // set parent file
+                        if (dir == null) {
+                            txInfo.parentFile = change.file.getParent();
+                        } else {
+                            txInfo.parentFile = dir.getParent();
+                        }
+                    }
+                    txInfo.out.getArtifacts().remove(change.file.getArtifact());
+                    if (verbose) {
+                        log.info("...scheduling DEL {}", an.getPath());
+                    }
+                }
+            } break;
+            case MODIFIED: {
+                Aggregate an = change.file.getAggregate();
+                TxInfo txInfo = modified.get(an.getPath());
+                if (txInfo == null) {
+                    txInfo = new TxInfo(an);
+                    VaultFile dir = null;
+                    for (VaultFile rel: change.file.getRelated()) {
+                        txInfo.original.put(rel.getPath(), rel);
+                        if (rel.isDirectory()) {
+                            dir = rel;
+                        }
+                    }
+                    modified.put(txInfo.artifactsPath, txInfo);
+                    // set parent file
+                    if (dir == null) {
+                        txInfo.parentFile = change.file.getParent();
+                    } else {
+                        txInfo.parentFile = dir.getParent();
+                    }
+                }
+                InputSourceArtifact isa = new InputSourceArtifact(change.file.getArtifact(), change.input);
+                txInfo.out.getArtifacts().put(isa);
+                // add sub changes
+                if (change.subChanges != null) {
+                    for (Change sc: change.subChanges) {
+                        // need to adjust relative path of input
+                        // repoPath    = /vltTest/foo
+                        // sc.repoPath = /vltTest/foo/text/file
+                        // relPath     = foo/text/file
+                        String relPath = PathUtil.getRelativePath(change.repoPath, sc.repoPath);
+                        relPath  = Text.getName(change.repoPath) + "/" + relPath;
+                        if (!relPath.equals(sc.isa.getRelativePath())) {
+                            // todo: check if correct platform path
+                            sc.isa = new InputSourceArtifact(
+                                    null,
+                                    relPath,
+                                    sc.isa.getExtension(),
+                                    sc.isa.getType(),
+                                    sc.isa.getInputSource(),
+                                    sc.isa.getSerializationType()
+                            );
+                        }
+                        txInfo.out.getArtifacts().add(sc.isa);
+                    }
+                }
+
+                if (verbose) {
+                    log.info("...scheduling UPD {}/{}", isa.getRelativePath());
+                }
+            } break;
+            case MOVED:
+            case ERROR:
+                break;
+        }
+        return true;
+    }
+
+    private void assertInFilter(String repoPath) {
+        if (!fs.getWorkspaceFilter().contains(repoPath)) {
+            log.warn("{} is excluded by the workspace filter. continuing with unknown results.", repoPath);
+        }
+    }
+
+    protected static class Change {
+
+        private final Type type;
+
+        private VaultFile file;
+
+        private VaultInputSource input;
+
+        private String filePath;
+
+        private String repoPath;
+
+        private InputSourceArtifact isa;
+
+        private LinkedList<Change> subChanges;
+
+        public Change(Type type, VaultFile file, VaultInputSource input) {
+            this.type = type;
+            this.file = file;
+            this.repoPath = file.getAggregatePath();
+            String relPath = file.getRepoRelPath();
+            if (relPath != null && relPath.length() > 0) {
+                this.repoPath += "/" + relPath;
+            }
+            this.input = input;
+        }
+
+        public Change(Type type, String repoPath, String filePath, VaultInputSource input) {
+            this.type = type;
+            this.repoPath = repoPath;
+            this.input = input;
+            this.filePath = filePath;
+        }
+
+        public void setInputSource(VaultInputSource input) {
+            this.input = input;
+        }
+
+        public void setContentType(String contentType) {
+            if (isa != null) {
+                isa.setContentType(contentType);
+            }
+        }
+
+        public void add(Change c) {
+            if (subChanges == null) {
+                subChanges = new LinkedList<Change>();
+            }
+            subChanges.add(c);
+        }
+    }
+
+    private static class DotXmlInfo {
+
+        private final Change change;
+
+        private final boolean intermediate;
+
+        private DotXmlInfo(Change change, boolean intermediate) {
+            this.change = change;
+            this.intermediate = intermediate;
+        }
+    }
+    private static class TxInfo {
+
+        private final AggregateImpl aggregate;
+
+        private final String artifactsPath;
+
+        private final AggregateBuilder out;
+
+        private final Map<String, VaultFile> original = new HashMap<String, VaultFile>();
+
+        private VaultFile parentFile;
+
+        public TxInfo(Aggregate a) throws RepositoryException {
+            aggregate = (AggregateImpl) a;
+            artifactsPath = a.getPath();
+            out = aggregate.getBuilder();
+        }
+
+        public TxInfo(String artifactsPath, AggregateBuilder out) {
+            aggregate = null;
+            this.artifactsPath = artifactsPath;
+            this.out = out;
+        }
+
+        public void setParentFile(VaultFile parentFile) {
+            this.parentFile = parentFile;
+        }
+    }
+
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/VaultFileImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/VaultFileImpl.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/VaultFileImpl.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/VaultFileImpl.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,460 @@
+/*
+ * 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.fs.impl;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.vault.fs.api.AccessType;
+import org.apache.jackrabbit.vault.fs.api.Aggregate;
+import org.apache.jackrabbit.vault.fs.api.Artifact;
+import org.apache.jackrabbit.vault.fs.api.ArtifactType;
+import org.apache.jackrabbit.vault.fs.api.DumpContext;
+import org.apache.jackrabbit.vault.fs.api.VaultFile;
+import org.apache.jackrabbit.vault.fs.api.VaultFileSystem;
+import org.apache.jackrabbit.vault.util.PlatformNameFormat;
+import org.apache.jackrabbit.vault.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implements the bridge between the repository based artifacts nodes and their
+ * file system representation as a collection of artifacts.
+ *
+ */
+public class VaultFileImpl implements VaultFile {
+
+    /**
+     * The default logger
+     */
+    protected static Logger log = LoggerFactory.getLogger(VaultFileImpl.class);
+
+    /**
+     * Vault filesystem of this file
+     */
+    private final VaultFileSystem fs;
+
+    /**
+     * the name of this entry
+     */
+    private final String name;
+
+    /**
+     * The artifact this represents or <code>null</code> if not attached
+     */
+    private Artifact artifact;
+
+    /**
+     * the jcr file node that this file belongs to
+     */
+    private VaultFileNode node;
+
+    /**
+     * the parent file
+     */
+    private VaultFileImpl parent;
+
+    /**
+     * a map of child nodes that weren't loaded yet
+     */
+    private LinkedHashMap<String, VaultFileNode> pendingChildNodes;
+
+    /**
+     * The map of child entries.
+     */
+    private LinkedHashMap<String, VaultFileImpl> children;
+
+    /**
+     * Internal constructor for the root file
+     *
+     * @param fs the file system
+     * @param rootPath path of the root node
+     * @param node the node
+     * @throws RepositoryException if an error occurs
+     */
+    protected VaultFileImpl(VaultFileSystem fs, String rootPath, VaultFileNode node)
+            throws RepositoryException {
+        this.fs = fs;
+        this.node = node;
+        if (rootPath.equals("")) {
+            this.name = rootPath;
+            // bit of a hack since we know how the root artifacts look like
+            for (Artifact a: node.getAggregate().getArtifacts().values()) {
+                if (a.getType() == ArtifactType.DIRECTORY) {
+                    this.artifact = a;
+                    node.getFiles().add(this);
+                } else {
+                    VaultFileImpl child = new VaultFileImpl(fs, a.getPlatformPath(), node, a);
+                    node.getFiles().add(child);
+                    this.addChild(child);
+                }
+            }
+        } else {
+            this.name = rootPath;
+            // special case when mounted deeply
+            for (Artifact a: node.getAggregate().getArtifacts().values()) {
+                if (a.getType() == ArtifactType.DIRECTORY) {
+                    this.artifact = a;
+                    node.getFiles().add(this);
+                } else {
+                    String p[] = Text.explode(a.getPlatformPath(), '/');
+                    VaultFileImpl entry = null;
+                    for (String cName: p) {
+                        if (entry == null) {
+                            entry = this;
+                        } else {
+                            entry = entry.getOrAddChild(cName);
+                        }
+                    }
+                    if (entry != null && entry != this) {
+                        entry.init(node, a);
+                    }
+                    // add to set of related files
+                    node.getFiles().add(entry);
+                }
+            }
+
+        }
+        for (VaultFileNode child: node.getChildren()) {
+            addPendingNode(child);
+        }
+    }
+
+
+    /**
+     * Internal constructor
+     *
+     * @param fs the file system
+     * @param name the file entry name
+     * @param node the node
+     * @param artifact the underlying artifact. can be <code>null</code>
+     * @throws RepositoryException if an error occurs
+     */
+    protected VaultFileImpl(VaultFileSystem fs, String name, VaultFileNode node,
+                      Artifact artifact)
+            throws RepositoryException {
+        this.fs = fs;
+        this.name = name;
+        init(node, artifact);
+    }
+
+    /**
+     * (re)initializes this file
+     * @param node the artifacts node
+     * @param a the artifact
+     * @throws RepositoryException if an error occurs
+     */
+    protected void init(VaultFileNode node, Artifact a) throws RepositoryException {
+        children = null;
+        pendingChildNodes = null;
+        this.node = node;
+        this.artifact = a;
+        if (node != null && a != null && a.getType() == ArtifactType.DIRECTORY) {
+            for (VaultFileNode child: node.getChildren()) {
+                addPendingNode(child);
+            }
+        }
+
+    }
+
+    protected void attach(VaultFileNode node, Artifact a) {
+        this.node = node;
+        this.artifact = a;
+    }
+
+    public String getPath() {
+        if (parent == null) {
+            return name.length() == 0 ? "/" : name;
+        } else {
+            return internalGetPath().toString();
+        }
+    }
+
+    public String getRepoRelPath() {
+        if (artifact == null) {
+            return null;
+        } else {
+            String relPath = artifact.getRelativePath();
+            int idx = relPath.indexOf('/');
+            if (idx > 0 && idx < relPath.length() - 1) {
+                return relPath.substring(idx + 1);
+            }
+            return "";
+        }
+    }
+
+    public String getAggregatePath() {
+        return node == null 
+                ? parent.getAggregatePath()
+                : node.getPath();
+    }
+
+    /**
+     * Internal builder for the path
+     * @return the string buffer.
+     */
+    private StringBuffer internalGetPath() {
+        if (parent == null) {
+            return new StringBuffer(name);
+        } else {
+            return parent.internalGetPath().append('/').append(name);
+        }
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Artifact getArtifact() {
+        return artifact;
+    }
+
+    public boolean isDirectory() {
+        return artifact == null || artifact.getType() == ArtifactType.DIRECTORY;
+    }
+
+    public boolean isTransient() {
+        return node == null;
+    }
+    
+    public VaultFileImpl getParent() throws IOException, RepositoryException {
+        return parent;
+    }
+
+    public Aggregate getAggregate() {
+        return node == null ? null : node.getAggregate();
+    }
+
+    public Aggregate getControllingAggregate() {
+        if (node == null && parent != null) {
+            return parent.getControllingAggregate();
+        } else {
+            return node == null ? null : node.getAggregate();
+        }
+    }
+
+    public VaultFileImpl getChild(String name) throws RepositoryException {
+        VaultFileImpl child = children == null ? null : children.get(name);
+        if (child == null) {
+            // try to load child
+            String repoName = PlatformNameFormat.getRepositoryName(name);
+            loadPendingNode(repoName);
+            child = children == null ? null : children.get(name);
+            // if still not present, load all
+            if (child == null) {
+                loadChildren();
+            }
+            child = children == null ? null : children.get(name);
+        }
+        return child;
+    }
+
+    public Collection<? extends VaultFile> getChildren() throws RepositoryException {
+        loadChildren();
+        return children.values();
+    }
+
+    /**
+     * Internally loads all children out of the map of pending node.
+     * @throws RepositoryException if an error occurs
+     */
+    private void loadChildren() throws RepositoryException {
+        if (children == null) {
+            children = new LinkedHashMap<String, VaultFileImpl>();
+        }
+        if (pendingChildNodes != null) {
+            Iterator<String> iter = pendingChildNodes.keySet().iterator();
+            while (iter.hasNext()) {
+                loadPendingNode(iter.next());
+                iter = pendingChildNodes.keySet().iterator();
+            }
+        }
+    }
+
+    /**
+     * Adds a node to the map of pending ones
+     * @param n the node
+     * @throws RepositoryException if an error occurs
+     */
+    protected void addPendingNode(VaultFileNode n) throws RepositoryException {
+        // we would need to assert that (n.getParent() == this.node) but this
+        // is not possible since this.node might not be loaded yet
+        if (pendingChildNodes == null) {
+            pendingChildNodes = new LinkedHashMap<String, VaultFileNode>();
+        }
+        String name = n.getName();
+        pendingChildNodes.put(name, n);
+        // if pending node is deep, we need to load it. otherwise it might not
+        // be found afterwards
+        if (name.indexOf('/') > 0) {
+            loadPendingNode(name);
+        }
+    }
+
+    /**
+     * Loads a pending node
+     * @param repoName the repository name of the node
+     * @throws RepositoryException if an error occurs
+     */
+    private void loadPendingNode(String repoName) throws RepositoryException {
+        // get node from pending ones
+        VaultFileNode n = pendingChildNodes == null ? null : pendingChildNodes.remove(repoName);
+        if (n == null) {
+            return;
+        }
+        VaultFileImpl parent = this;
+        // if pending node has a deep aggregate, we create intermediate
+        // "directories"
+        String aggName = n.getAggregate().getRelPath();
+        if (aggName.indexOf('/') > 0) {
+            String[] p = Text.explode(aggName, '/');
+            for (int i=0; i<p.length-1; i++) {
+                parent = parent.getOrAddChild(PlatformNameFormat.getPlatformName(p[i]));
+            }
+        }
+
+        for (Artifact a: n.getAggregate().getArtifacts().values()) {
+            String p[] = Text.explode(a.getPlatformPath(), '/');
+            VaultFileImpl entry = parent;
+            for (String cName: p) {
+                entry = entry.getOrAddChild(cName);
+            }
+            entry.init(n, a);
+            // add to set of related files
+            n.getFiles().add(entry);
+        }
+    }
+
+    /**
+     * Attaches a child file
+     * @param child the child
+     */
+    private void addChild(VaultFileImpl child) {
+        child.parent = this;
+        if (children == null) {
+            children = new LinkedHashMap<String, VaultFileImpl>();
+        }
+        children.put(child.name, child);
+    }
+
+    /**
+     * Returns the child of the given name or creates and adds a new one.
+     * @param name the name of the file
+     * @return the child
+     * @throws RepositoryException if an error occurs
+     */
+    protected VaultFileImpl getOrAddChild(String name) throws RepositoryException {
+        VaultFileImpl c = children == null ? null : children.get(name);
+        if (c == null) {
+            c = new VaultFileImpl(fs, name, null, null);
+            addChild(c);
+        }
+        return c;
+    }
+
+    public Collection<? extends VaultFile> getRelated() throws RepositoryException {
+        if (node == null) {
+            return null;
+        }
+        return node.getFiles();
+    }
+
+    public boolean canRead() {
+        return artifact != null
+                && artifact.getPreferredAccess() != AccessType.NONE;
+    }
+
+    public long lastModified() {
+        return artifact == null ? 0 : artifact.getLastModified();
+    }
+
+    public long length() {
+        return artifact == null ? -1 : artifact.getContentLength();
+    }
+
+    public String getContentType() {
+        return artifact == null ? null : artifact.getContentType();
+    }
+
+    public VaultFileSystem getFileSystem() {
+        return fs;
+    }
+
+    public void invalidate() throws RepositoryException {
+        log.info("invalidating file {}", getPath());
+
+        // special handling for root node
+        if (parent == null) {
+            // bit of a hack since we know how the root artifacts look like
+            node.invalidate();
+            for (Artifact a: node.getAggregate().getArtifacts().values()) {
+                if (a.getType() == ArtifactType.DIRECTORY) {
+                    this.artifact = a;
+                    node.getFiles().add(this);
+                } else {
+                    VaultFileImpl child = new VaultFileImpl(fs, a.getPlatformPath(), node, a);
+                    node.getFiles().add(child);
+                    this.addChild(child);
+                }
+            }
+            for (VaultFileNode child: node.getChildren()) {
+                addPendingNode(child);
+            }
+        } else {
+            // get the directory artifact of this file
+            for (VaultFileImpl f: node.getFiles()) {
+                if (f.parent != null && f.parent.artifact.getType() == ArtifactType.DIRECTORY) {
+                    f.parent.node.invalidate();
+                    f.parent.init(f.parent.node, f.parent.artifact);
+                    break;
+                }
+            }
+            //node.invalidate();
+            //init(node, artifact);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void dump(DumpContext ctx, boolean isLast) {
+        ctx.println(isLast, "Vault file");
+        ctx.indent(isLast);
+        ctx.printf(false, "name: %s", name);
+        ctx.printf(false, "path: %s", getPath());
+        ctx.printf(false, "# pending: %d", pendingChildNodes == null ? -1 : pendingChildNodes.size());
+        ctx.printf(false, "# children: %d", children == null ? -1 : children.size());
+        if (artifact != null) {
+            artifact.dump(ctx, false);
+        } else {
+            ctx.println(false, "Artifact: (null)");
+        }
+        if (node != null) {
+            node.dump(ctx, true);
+        } else {
+            ctx.println(true, "ArtifactsNode: (null)");
+        }
+        ctx.outdent();
+    }
+
+
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/VaultFileNode.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/VaultFileNode.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/VaultFileNode.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/VaultFileNode.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,113 @@
+/*
+ * 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.fs.impl;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.vault.fs.api.Aggregate;
+import org.apache.jackrabbit.vault.fs.api.DumpContext;
+import org.apache.jackrabbit.vault.fs.api.Dumpable;
+
+/**
+ * The jcr file node combines the aggregates with the Vault file. each file
+ * node produces a list of jcr files, composed out of the artifacts on the
+ * artifacts node. the Vault files are the inserted into the overall hierarchy
+ * of the Vault filesystem.
+ *
+ */
+public class VaultFileNode implements Dumpable {
+
+    /**
+     * the artifacts node
+     */
+    private final AggregateImpl aggregate;
+
+    /**
+     * the files of this node
+     */
+    private List<VaultFileImpl> files = new LinkedList<VaultFileImpl>();
+
+    private Collection<VaultFileNode> children;
+
+    private final VaultFileNode parent;
+
+    public VaultFileNode(VaultFileNode parent, AggregateImpl node) throws RepositoryException {
+        this.parent = parent;
+        this.aggregate = node;
+    }
+
+    public String getName() {
+        return aggregate.getRelPath();
+    }
+
+    public String getPath() {
+        return aggregate.getPath();
+    }
+
+    public Collection<VaultFileNode> getChildren() throws RepositoryException {
+        if (children == null) {
+            children = new LinkedList<VaultFileNode>();
+            List<? extends Aggregate> leaves = aggregate.getLeaves();
+            if (leaves != null && !leaves.isEmpty()) {
+                for (Aggregate child: leaves) {
+                    children.add(new VaultFileNode(this, (AggregateImpl) child));
+                }
+            }
+        }
+        return children;
+    }
+
+    public Aggregate getAggregate() {
+        return aggregate;
+    }
+
+    public void invalidate() {
+        files.clear();
+        aggregate.invalidate();
+        children = null;
+    }
+
+    public VaultFileNode getParent() {
+        return parent;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void dump(DumpContext ctx, boolean isLast) {
+        ctx.println(isLast, "VaultFileNode");
+        ctx.indent(isLast);
+        //ctx.printf(false, "# pending: %d", pendingChildNodes == null ? -1 : pendingChildNodes.size());
+        //ctx.printf(false, "# children: %d", children == null ? -1 : children.size());
+        if (aggregate != null) {
+            aggregate.dump(ctx, true);
+        } else {
+            ctx.println(true, "ArtifactsNode: (null)");
+        }
+        ctx.outdent();
+    }
+
+
+    protected List<VaultFileImpl> getFiles() {
+        return files;
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/VaultFileOutputImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/VaultFileOutputImpl.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/VaultFileOutputImpl.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/VaultFileOutputImpl.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,84 @@
+/*
+ * 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.fs.impl;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.vault.fs.api.VaultFileOutput;
+import org.apache.jackrabbit.vault.fs.api.VaultInputSource;
+import org.apache.jackrabbit.vault.util.FileInputSource;
+
+/**
+ * Provides methods for writing jcr files. This can either be done by providing
+ * an input source or by fetching an output stream. this output stream can be
+ * acquired via a {@link TransactionImpl}.
+ *
+ */
+public class VaultFileOutputImpl implements VaultFileOutput {
+
+    private final TransactionImpl.Change tx;
+
+    private OutputStream out;
+
+    private File tmpFile;
+
+    private VaultInputSource is;
+
+    protected VaultFileOutputImpl(TransactionImpl.Change tx) {
+        this.tx = tx;
+    }
+
+    protected VaultFileOutputImpl(TransactionImpl.Change tx, VaultInputSource input) {
+        this.tx = tx;
+        this.is = input;
+    }
+
+    public OutputStream getOutputStream() throws IOException {
+        if (out != null) {
+            throw new IOException("Output stream already obtained.");
+        }
+        tmpFile = File.createTempFile("vltfs", ".tmp");
+        tmpFile.deleteOnExit();
+        out = new FileOutputStream(tmpFile);
+        return out;
+    }
+
+    /*
+    public void setArtfiactType(ArtifactType artfiactType) {
+        this.artfiactType = artfiactType;
+    }
+    */
+
+    public void setContentType(String contentType) {
+        tx.setContentType(contentType);
+    }
+
+    public void close() throws IOException, RepositoryException {
+        if (out != null) {
+            out.close();
+            is = new FileInputSource(tmpFile);
+            tx.setInputSource(is);
+        }
+    }
+
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/VaultFileSystemImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/VaultFileSystemImpl.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/VaultFileSystemImpl.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/VaultFileSystemImpl.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,186 @@
+/*
+ * 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.fs.impl;
+
+import java.io.IOException;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.vault.fs.api.Aggregate;
+import org.apache.jackrabbit.vault.fs.api.AggregateManager;
+import org.apache.jackrabbit.vault.fs.api.VaultFile;
+import org.apache.jackrabbit.vault.fs.api.VaultFileSystem;
+import org.apache.jackrabbit.vault.fs.api.VaultFsConfig;
+import org.apache.jackrabbit.vault.fs.api.VaultFsTransaction;
+import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
+import org.apache.jackrabbit.vault.util.PathUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The Vault filesystem provides an additional abstraction layer on top of the
+ * artifacts manager tree. It is used to map the artifacts node artifacts to
+ * individual java.io like files.
+ *
+ */
+public class VaultFileSystemImpl implements VaultFileSystem {
+
+    /**
+     * default log
+     */
+    private static Logger log = LoggerFactory.getLogger(VaultFileSystemImpl.class);
+
+    /**
+     * The underlying artifacts manager
+     */
+    private AggregateManager mgr;
+
+    /**
+     * Indicates if this is our own manager and we need to release it
+     */
+    private boolean isOwnManager;
+
+    /**
+     * The os file root
+     */
+    private VaultFileImpl root;
+
+    /**
+     * the root path if mounted not a /
+     */
+    private final String rootPath;
+
+    /**
+     * Pattern that matches the root path
+     */
+    private final String rootPattern;
+
+
+    public void unmount() throws RepositoryException {
+        assertMounted();
+        if (isOwnManager) {
+            mgr.unmount();
+        }
+        mgr = null;
+        root = null;
+    }
+
+    /**
+     * Checks if this tree is still mounted and if the attached session
+     * is still live.
+     *
+     * @throws RepositoryException if not mounted or not live.
+     */
+    private void assertMounted() throws RepositoryException {
+        if (!isMounted()) {
+            throw new RepositoryException("JcrFS is not mounted anymore.");
+        }
+    }
+
+    public boolean isMounted() {
+        return mgr != null && mgr.isMounted();
+    }
+
+    /**
+     * Creates a new os file system that uses the given manager.
+     *
+     * @param rootAggregate the root artifacts node
+     * @param rootPath path of root file. used for remapping
+     * @param ownMgr <code>true</code> if it's own manager
+     * @throws IOException if an I/O error occurs
+     * @throws RepositoryException if a repository error occurs.
+     */
+    public VaultFileSystemImpl(Aggregate rootAggregate, String rootPath, boolean ownMgr)
+            throws IOException, RepositoryException {
+        if (!rootAggregate.allowsChildren()) {
+            throw new IOException("Root node must allow children.");
+        }
+        this.mgr = rootAggregate.getManager();
+        this.isOwnManager = ownMgr;
+
+        // create root files
+        VaultFileNode rootFileNode = new VaultFileNode(null, (AggregateImpl) rootAggregate);
+        this.rootPath = rootPath == null || rootPath.equals("/") ? "" : rootPath;
+        this.rootPattern = this.rootPath + "/";
+        this.root = new VaultFileImpl(this, this.rootPath, rootFileNode);
+    }
+
+    public VaultFile getRoot() {
+        return root;
+    }
+
+    public AggregateManager getAggregateManager() {
+        return mgr;
+    }
+
+    public VaultFile getFile(String path)
+            throws IOException, RepositoryException {
+        if (path.charAt(0) != '/') {
+            throw new IOException("Only absolute paths allowed");
+        }
+        if (rootPath.length() > 0) {
+            if (!path.equals(rootPath) && !path.startsWith(rootPattern)) {
+                throw new IOException("Path not under mountpoint.");
+            }
+            path = path.substring(rootPath.length());
+        }
+        return getFile(root, path);
+    }
+
+    public VaultFile getFile(VaultFile parent, String path)
+            throws IOException, RepositoryException {
+        if (path == null || path.equals("") || path.equals(".")) {
+            return parent;
+        } else if (path.equals("/")) {
+            return getRoot();
+        }
+        String[] pathElems = PathUtil.makePath((String[])null, path);
+        for (int i=0; i<pathElems.length && parent != null; i++) {
+            String elem = pathElems[i];
+            if (elem.equals("/")) {
+                parent = getRoot();
+            } else if (elem.equals("..")) {
+                parent = parent.getParent();
+            } else {
+                parent = parent.getChild(elem);
+            }
+        }
+        return parent;
+    }
+
+    public VaultFsTransaction startTransaction() {
+        return new TransactionImpl(this);
+    }
+
+    public void invalidate() throws RepositoryException {
+        // create root files
+        AggregateImpl rootAggregate = (AggregateImpl) root.getAggregate();
+        rootAggregate.invalidate();
+        VaultFileNode rootFileNode = new VaultFileNode(null, rootAggregate);
+        root = new VaultFileImpl(this, rootPath, rootFileNode);
+        log.info("Filesystem invalidated.");
+    }
+
+    public VaultFsConfig getConfig() {
+        return mgr.getConfig();
+    }
+
+    public WorkspaceFilter getWorkspaceFilter() {
+        return mgr.getWorkspaceFilter();
+    }
+}
\ No newline at end of file