You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by kw...@apache.org on 2022/02/20 10:59:27 UTC

[jackrabbit-filevault-package-maven-plugin] branch feature/JCVLT-558-generate-cnd created (now 5bd4b0c)

This is an automated email from the ASF dual-hosted git repository.

kwin pushed a change to branch feature/JCVLT-558-generate-cnd
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault-package-maven-plugin.git.


      at 5bd4b0c  JCRVLT-559 add mojo for generating CND file containing used node types and namespace of a package

This branch includes the following new commits:

     new 348bec2  JCRVLT-558 mojo for generating a cnd
     new 5bd4b0c  JCRVLT-559 add mojo for generating CND file containing used node types and namespace of a package

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


[jackrabbit-filevault-package-maven-plugin] 01/02: JCRVLT-558 mojo for generating a cnd

Posted by kw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch feature/JCVLT-558-generate-cnd
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault-package-maven-plugin.git

commit 348bec208edf0bf64391926eb62a60f0458a1cff
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Fri Feb 18 20:44:05 2022 +0100

    JCRVLT-558 mojo for generating a cnd
---
 .../filevault/maven/packaging/GenerateCndMojo.java | 183 +++++++++++++++++++++
 1 file changed, 183 insertions(+)

diff --git a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/GenerateCndMojo.java b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/GenerateCndMojo.java
new file mode 100644
index 0000000..3c2804c
--- /dev/null
+++ b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/GenerateCndMojo.java
@@ -0,0 +1,183 @@
+package org.apache.jackrabbit.filevault.maven.packaging;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.UncheckedIOException;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.jcr.NamespaceException;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.commons.cnd.ParseException;
+import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeDefinitionProvider;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.QNodeTypeDefinition;
+import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException;
+import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
+import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
+import org.apache.jackrabbit.spi.commons.nodetype.compact.CompactNodeTypeDefWriter;
+import org.apache.jackrabbit.vault.fs.io.DocViewParser;
+import org.apache.jackrabbit.vault.fs.io.DocViewParser.XmlParseException;
+import org.apache.jackrabbit.vault.fs.io.DocViewParserHandler;
+import org.apache.jackrabbit.vault.util.Constants;
+import org.apache.jackrabbit.vault.util.DocViewNode2;
+import org.apache.jackrabbit.vault.validation.spi.impl.nodetype.NodeTypeManagerProvider;
+import org.apache.jackrabbit.vault.validation.spi.impl.nodetype.NodeTypeValidatorFactory;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.jetbrains.annotations.NotNull;
+import org.xml.sax.InputSource;
+
+
+/**
+ * Generates a <a href="https://jackrabbit.apache.org/jcr/node-type-notation.html">CND file</a> containing all
+ * used node types and namespaces.
+ * It will end up in the <a href="https://jackrabbit.apache.org/filevault/nodetypes.html">package metadata and automatically be registered during installation</a>.
+ * @since 1.3.0
+ */
+@Mojo(
+        name = "generate-cnd",
+        defaultPhase = LifecyclePhase.GENERATE_SOURCES, 
+        requiresDependencyResolution = ResolutionScope.COMPILE,
+        threadSafe = true
+)
+public class GenerateCndMojo extends AbstractSourceAndMetadataPackageMojo {
+    /**
+     * The source cnd urls.
+     * Must be urls
+     */
+    @Parameter()
+    List<String> inputCndUrls = new LinkedList<>();
+
+    public GenerateCndMojo() {
+        
+    }
+
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException {
+        getLog().info("Retrieving used node types and namespaces...");
+        try {
+            NodeTypeManagerProvider ntManagerProvider = new NodeTypeManagerProvider();
+            NodeTypeValidatorFactory.registerNodeTypes(inputCndUrls, ntManagerProvider);
+            DocViewParser docViewParser = new DocViewParser(ntManagerProvider.getNamespaceResolver());
+            File cndOutputFile = new File(getGeneratedVaultDir(true), Constants.NODETYPES_CND);
+            // traverse relevant package files
+            Set<String> nodeTypes = collectNodeTypes(getJcrSourceDirectory().toPath(), docViewParser);
+            Collection<? extends QNodeTypeDefinition> ntDefinitons = resolveNodeTypes(nodeTypes, ntManagerProvider.getNameResolver(),
+                    ntManagerProvider.getNodeTypeDefinitionProvider());
+
+            // writes the cnd into the given file
+            try (Writer writer = Files.newBufferedWriter(cndOutputFile.toPath(), StandardCharsets.US_ASCII)) {
+                writeCnd(ntDefinitons, ntManagerProvider.getNamespaceResolver(), writer);
+            }
+        } catch (IOException | RepositoryException | ParseException e) {
+            throw new MojoExecutionException("Error while writing CND: " + e.getMessage(), e);
+        }
+    }
+
+    private Set<String> collectNodeTypes(Path jcrRootPath, DocViewParser docViewParser) throws IOException {
+        Set<String> nodeTypes = new HashSet<>();
+        // add default ones for simple file aggregates: https://jackrabbit.apache.org/filevault/vaultfs.html#simple-file-aggregates 
+        // (just for being complete, they are part of the JCR standard)
+        nodeTypes.add(JcrConstants.NT_FILE);
+        nodeTypes.add(JcrConstants.NT_FOLDER);
+
+        NodeTypeCollectorHandler nodeTypeCollectorHandler = new NodeTypeCollectorHandler(nodeTypes);
+        // extract types from docview files
+        try (Stream<Path> filePaths = Files.find(jcrRootPath, 50, (path, attributes) -> path.getFileName().toString().endsWith(".xml"))) {
+            filePaths.forEach(p -> {
+                try {
+                    getNodeTypes(p, docViewParser, nodeTypeCollectorHandler);
+                } catch (IOException e) {
+                    throw new UncheckedIOException(e);
+                }
+            });
+        }
+        return nodeTypes;
+    }
+
+    private void getNodeTypes(Path docViewFile, DocViewParser docViewParser, NodeTypeCollectorHandler nodeTypeCollectorHandler) throws IOException {
+        try (Reader reader = Files.newBufferedReader(docViewFile, StandardCharsets.UTF_8)) {
+            if (!DocViewParser.isDocView(reader)) {
+                return;
+            }
+            InputSource inputSource = new InputSource();
+            inputSource.setCharacterStream(reader);
+            try {
+                docViewParser.parse("/", inputSource, nodeTypeCollectorHandler);
+            } catch (XmlParseException e) {
+                getLog().warn("Could not parse " + docViewFile + ". Ignore for node type definition generation!", e);
+            }
+        }
+    }
+    
+    static final class NodeTypeCollectorHandler implements DocViewParserHandler {
+
+        private final Set<String> nodeTypes;
+        public NodeTypeCollectorHandler(Set<String> nodeTypes) {
+            this.nodeTypes = nodeTypes;
+        }
+
+        @Override
+        public void startDocViewNode(@NotNull String nodePath, @NotNull DocViewNode2 docViewNode, @NotNull Optional<DocViewNode2> parentDocViewNode, int line, int column)
+                throws IOException, RepositoryException {
+            docViewNode.getPrimaryType().ifPresent(type -> nodeTypes.add(type));
+            nodeTypes.addAll(docViewNode.getMixinTypes());
+        }
+
+        @Override
+        public void endDocViewNode(@NotNull String nodePath, @NotNull DocViewNode2 docViewNode, @NotNull Optional<DocViewNode2> parentDocViewNode, int line, int column)
+                throws IOException, RepositoryException {
+            // do nothing
+        }
+    }
+
+    // resolve from cnd file
+    private Collection<? extends QNodeTypeDefinition> resolveNodeTypes(Set<String> nodeTypeNames, NameResolver nameResolver, NodeTypeDefinitionProvider ntDefinitionProvider) throws IOException, RepositoryException, ParseException {
+        return resolveNodeTypesFromNames(nodeTypeNames.stream()
+                .map(name -> {
+                    try {
+                        return nameResolver.getQName(name);
+                    } catch (IllegalNameException|NamespaceException e) {
+                        throw new IllegalStateException(e);
+                    }
+                })
+                .collect(Collectors.toSet()), 
+                ntDefinitionProvider);
+    }
+
+    static Collection<? extends QNodeTypeDefinition> resolveNodeTypesFromNames(Set<Name> nodeTypeNames, NodeTypeDefinitionProvider ntDefinitionProvider) throws IOException, RepositoryException, ParseException {
+        return nodeTypeNames.stream().map(name -> {
+            try {
+                return ntDefinitionProvider.getNodeTypeDefinition(name);
+            } catch (RepositoryException e) {
+                throw new IllegalStateException("RepositoryException ", e);
+            }
+        }).collect(Collectors.toList());
+    }
+
+    private void writeCnd(Collection<? extends QNodeTypeDefinition> nodeTypeDefinitions, NamespaceResolver nsResolver, Writer writer) throws IOException {
+        CompactNodeTypeDefWriter cndWriter = new CompactNodeTypeDefWriter(writer, nsResolver, true);
+        for (QNodeTypeDefinition nodeTypeDefinition : nodeTypeDefinitions) {
+            cndWriter.write(nodeTypeDefinition);
+        }
+        cndWriter.close();
+    }
+}

[jackrabbit-filevault-package-maven-plugin] 02/02: JCRVLT-559 add mojo for generating CND file containing used node types and namespace of a package

Posted by kw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch feature/JCVLT-558-generate-cnd
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault-package-maven-plugin.git

commit 5bd4b0c19f41c0c9d59f5d9a67aef5cf147b7916
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Sun Feb 20 11:59:18 2022 +0100

    JCRVLT-559 add mojo for generating CND file containing used node types
    and namespace of a package
---
 pom.xml                                            |   2 +-
 .../filevault/maven/packaging/GenerateCndMojo.java | 118 ++++++++++++++++-----
 2 files changed, 95 insertions(+), 25 deletions(-)

diff --git a/pom.xml b/pom.xml
index c38113c..dd93ec7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -32,7 +32,7 @@
     <!-- ====================================================================== -->
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>filevault-package-maven-plugin</artifactId>
-    <version>1.2.3-SNAPSHOT</version>
+    <version>1.3.0-SNAPSHOT</version>
     <packaging>maven-plugin</packaging>
 
     <name>Apache Jackrabbit FileVault - Package Maven Plugin</name>
diff --git a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/GenerateCndMojo.java b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/GenerateCndMojo.java
index 3c2804c..7a1fb60 100644
--- a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/GenerateCndMojo.java
+++ b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/GenerateCndMojo.java
@@ -1,3 +1,19 @@
+/*
+ * 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.filevault.maven.packaging;
 
 import java.io.File;
@@ -24,6 +40,7 @@ import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.commons.cnd.ParseException;
 import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeDefinitionProvider;
 import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.QNodeDefinition;
 import org.apache.jackrabbit.spi.QNodeTypeDefinition;
 import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException;
 import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
@@ -34,8 +51,9 @@ import org.apache.jackrabbit.vault.fs.io.DocViewParser.XmlParseException;
 import org.apache.jackrabbit.vault.fs.io.DocViewParserHandler;
 import org.apache.jackrabbit.vault.util.Constants;
 import org.apache.jackrabbit.vault.util.DocViewNode2;
-import org.apache.jackrabbit.vault.validation.spi.impl.nodetype.NodeTypeManagerProvider;
-import org.apache.jackrabbit.vault.validation.spi.impl.nodetype.NodeTypeValidatorFactory;
+import org.apache.jackrabbit.vault.util.StandaloneManagerProvider;
+import org.apache.jackrabbit.vault.validation.spi.util.classloaderurl.CndUtil;
+import org.apache.jackrabbit.vault.validation.spi.util.classloaderurl.URLFactory;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
 import org.apache.maven.plugins.annotations.LifecyclePhase;
@@ -63,7 +81,7 @@ public class GenerateCndMojo extends AbstractSourceAndMetadataPackageMojo {
      * The source cnd urls.
      * Must be urls
      */
-    @Parameter()
+    @Parameter(property = "vault.inputCndUrls")
     List<String> inputCndUrls = new LinkedList<>();
 
     public GenerateCndMojo() {
@@ -74,20 +92,37 @@ public class GenerateCndMojo extends AbstractSourceAndMetadataPackageMojo {
     public void execute() throws MojoExecutionException, MojoFailureException {
         getLog().info("Retrieving used node types and namespaces...");
         try {
-            NodeTypeManagerProvider ntManagerProvider = new NodeTypeManagerProvider();
-            NodeTypeValidatorFactory.registerNodeTypes(inputCndUrls, ntManagerProvider);
-            DocViewParser docViewParser = new DocViewParser(ntManagerProvider.getNamespaceResolver());
+            StandaloneManagerProvider managerProvider = new StandaloneManagerProvider();
+            URLFactory.processUrlStreams(CndUtil.resolveJarUrls(inputCndUrls), t -> {
+                try {
+                    managerProvider.registerNodeTypes(t);
+                } catch (IOException e) {
+                    throw new UncheckedIOException(e);
+                }
+                catch (ParseException | RepositoryException e) {
+                    throw new IllegalArgumentException(e);
+                }
+            });
+            DocViewParser docViewParser = new DocViewParser(managerProvider.getNamespaceResolver());
             File cndOutputFile = new File(getGeneratedVaultDir(true), Constants.NODETYPES_CND);
             // traverse relevant package files
-            Set<String> nodeTypes = collectNodeTypes(getJcrSourceDirectory().toPath(), docViewParser);
-            Collection<? extends QNodeTypeDefinition> ntDefinitons = resolveNodeTypes(nodeTypes, ntManagerProvider.getNameResolver(),
-                    ntManagerProvider.getNodeTypeDefinitionProvider());
+            final Set<String> nodeTypes;
+            try {
+                nodeTypes = collectNodeTypes(getJcrSourceDirectory().toPath(), docViewParser);
+            } catch (UncheckedIOException e) {
+                throw e.getCause();
+            }
+            getLog().info("Found " + nodeTypes.size() + " unique node types" );
+            Collection<? extends QNodeTypeDefinition> ntDefinitons = resolveNodeTypes(nodeTypes, managerProvider.getNameResolver(),
+                    managerProvider.getNodeTypeDefinitionProvider());
 
-            // writes the cnd into the given file
+            // writes the CND into the given file
             try (Writer writer = Files.newBufferedWriter(cndOutputFile.toPath(), StandardCharsets.US_ASCII)) {
-                writeCnd(ntDefinitons, ntManagerProvider.getNamespaceResolver(), writer);
+                writeCnd(ntDefinitons, managerProvider.getNodeTypeDefinitionProvider(), managerProvider.getNamespaceResolver(), writer);
             }
-        } catch (IOException | RepositoryException | ParseException e) {
+
+            getLog().info("Written CND file to " + getProjectRelativeFilePath(cndOutputFile));
+        } catch (IOException | RepositoryException | ParseException | IllegalStateException e) {
             throw new MojoExecutionException("Error while writing CND: " + e.getMessage(), e);
         }
     }
@@ -101,7 +136,7 @@ public class GenerateCndMojo extends AbstractSourceAndMetadataPackageMojo {
 
         NodeTypeCollectorHandler nodeTypeCollectorHandler = new NodeTypeCollectorHandler(nodeTypes);
         // extract types from docview files
-        try (Stream<Path> filePaths = Files.find(jcrRootPath, 50, (path, attributes) -> path.getFileName().toString().endsWith(".xml"))) {
+        try (Stream<Path> filePaths = Files.find(jcrRootPath, 50, (path, attributes) -> !attributes.isDirectory() && path.getFileName().toString().endsWith(".xml"))) {
             filePaths.forEach(p -> {
                 try {
                     getNodeTypes(p, docViewParser, nodeTypeCollectorHandler);
@@ -115,21 +150,22 @@ public class GenerateCndMojo extends AbstractSourceAndMetadataPackageMojo {
 
     private void getNodeTypes(Path docViewFile, DocViewParser docViewParser, NodeTypeCollectorHandler nodeTypeCollectorHandler) throws IOException {
         try (Reader reader = Files.newBufferedReader(docViewFile, StandardCharsets.UTF_8)) {
+            reader.mark(1024);
             if (!DocViewParser.isDocView(reader)) {
                 return;
             }
-            InputSource inputSource = new InputSource();
-            inputSource.setCharacterStream(reader);
+            reader.reset();
+            InputSource inputSource = new InputSource(reader);
             try {
+                // TODO: get root node path
                 docViewParser.parse("/", inputSource, nodeTypeCollectorHandler);
             } catch (XmlParseException e) {
                 getLog().warn("Could not parse " + docViewFile + ". Ignore for node type definition generation!", e);
             }
         }
     }
-    
-    static final class NodeTypeCollectorHandler implements DocViewParserHandler {
 
+    final class NodeTypeCollectorHandler implements DocViewParserHandler {
         private final Set<String> nodeTypes;
         public NodeTypeCollectorHandler(Set<String> nodeTypes) {
             this.nodeTypes = nodeTypes;
@@ -138,8 +174,15 @@ public class GenerateCndMojo extends AbstractSourceAndMetadataPackageMojo {
         @Override
         public void startDocViewNode(@NotNull String nodePath, @NotNull DocViewNode2 docViewNode, @NotNull Optional<DocViewNode2> parentDocViewNode, int line, int column)
                 throws IOException, RepositoryException {
-            docViewNode.getPrimaryType().ifPresent(type -> nodeTypes.add(type));
-            nodeTypes.addAll(docViewNode.getMixinTypes());
+            Optional<String> primaryType = docViewNode.getPrimaryType();
+            if (primaryType.isPresent()) {
+                if (nodeTypes.add(primaryType.get())) {
+                    getLog().debug("Found primary type " + primaryType.get() + " in " + nodePath);
+                }
+            }
+            if (nodeTypes.addAll(docViewNode.getMixinTypes())) {
+                getLog().debug("Found mixin types " + docViewNode.getMixinTypes() + " in " + nodePath);
+            }
         }
 
         @Override
@@ -150,20 +193,20 @@ public class GenerateCndMojo extends AbstractSourceAndMetadataPackageMojo {
     }
 
     // resolve from cnd file
-    private Collection<? extends QNodeTypeDefinition> resolveNodeTypes(Set<String> nodeTypeNames, NameResolver nameResolver, NodeTypeDefinitionProvider ntDefinitionProvider) throws IOException, RepositoryException, ParseException {
+    private Collection<QNodeTypeDefinition> resolveNodeTypes(Set<String> nodeTypeNames, NameResolver nameResolver, NodeTypeDefinitionProvider ntDefinitionProvider) throws IOException, RepositoryException, ParseException {
         return resolveNodeTypesFromNames(nodeTypeNames.stream()
                 .map(name -> {
                     try {
                         return nameResolver.getQName(name);
                     } catch (IllegalNameException|NamespaceException e) {
-                        throw new IllegalStateException(e);
+                        throw new IllegalStateException("Cannot get expanded name for type " + name, e);
                     }
                 })
                 .collect(Collectors.toSet()), 
                 ntDefinitionProvider);
     }
 
-    static Collection<? extends QNodeTypeDefinition> resolveNodeTypesFromNames(Set<Name> nodeTypeNames, NodeTypeDefinitionProvider ntDefinitionProvider) throws IOException, RepositoryException, ParseException {
+    static Collection<QNodeTypeDefinition> resolveNodeTypesFromNames(Set<Name> nodeTypeNames, NodeTypeDefinitionProvider ntDefinitionProvider) throws IOException, RepositoryException, ParseException {
         return nodeTypeNames.stream().map(name -> {
             try {
                 return ntDefinitionProvider.getNodeTypeDefinition(name);
@@ -173,11 +216,38 @@ public class GenerateCndMojo extends AbstractSourceAndMetadataPackageMojo {
         }).collect(Collectors.toList());
     }
 
-    private void writeCnd(Collection<? extends QNodeTypeDefinition> nodeTypeDefinitions, NamespaceResolver nsResolver, Writer writer) throws IOException {
+    private void writeCnd(Collection<? extends QNodeTypeDefinition> nodeTypeDefinitions, NodeTypeDefinitionProvider ntDefinitionProvider, NamespaceResolver nsResolver, Writer writer) throws IOException, RepositoryException {
         CompactNodeTypeDefWriter cndWriter = new CompactNodeTypeDefWriter(writer, nsResolver, true);
+        Set<Name> written = new HashSet<>();
         for (QNodeTypeDefinition nodeTypeDefinition : nodeTypeDefinitions) {
-            cndWriter.write(nodeTypeDefinition);
+            writeNodeType(nodeTypeDefinition, cndWriter, written, ntDefinitionProvider);
         }
         cndWriter.close();
     }
+
+    private void writeNodeType(Name nodeType, CompactNodeTypeDefWriter cndWriter, Set<Name> written, NodeTypeDefinitionProvider ntDefinitionProvider) throws IOException, RepositoryException {
+        if (nodeType == null || written.contains(nodeType)) {
+            return;
+        }
+        QNodeTypeDefinition ntDefinition = ntDefinitionProvider.getNodeTypeDefinition(nodeType);
+        writeNodeType(ntDefinition, cndWriter, written, ntDefinitionProvider);
+    }
+
+    private void writeNodeType(QNodeTypeDefinition ntDefinition, CompactNodeTypeDefWriter cndWriter, Set<Name> written, NodeTypeDefinitionProvider ntDefinitionProvider)
+            throws IOException, RepositoryException {
+        cndWriter.write(ntDefinition);
+        written.add(ntDefinition.getName());
+        // also write all referenced node types
+        for (Name superType: ntDefinition.getSupertypes()) {
+            writeNodeType(superType, cndWriter, written, ntDefinitionProvider);
+        }
+        for (QNodeDefinition cntDefinition: ntDefinition.getChildNodeDefs()) {
+            writeNodeType(cntDefinition.getDefaultPrimaryType(), cndWriter, written, ntDefinitionProvider);
+            if (cntDefinition.getRequiredPrimaryTypes() != null) {
+                for (Name name: cntDefinition.getRequiredPrimaryTypes()) {
+                    writeNodeType(name, cndWriter, written, ntDefinitionProvider);
+                }
+            }
+        }
+    }
 }