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 2020/07/02 18:33:30 UTC
[jackrabbit-filevault] branch master updated: JCRVLT-426 add node
type validator (#75)
This is an automated email from the ASF dual-hosted git repository.
kwin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault.git
The following commit(s) were added to refs/heads/master by this push:
new 127fa8a JCRVLT-426 add node type validator (#75)
127fa8a is described below
commit 127fa8ab80d3dcaff7c615afedc25a0293559408
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Thu Jul 2 20:33:21 2020 +0200
JCRVLT-426 add node type validator (#75)
---
vault-doc/src/site/markdown/validation.md | 2 +-
vault-validation/pom.xml | 5 +
.../impl/util/DocumentViewXmlContentHandler.java | 12 +
.../validation/impl/util/ValidatorException.java | 2 +-
.../validation/spi/DocumentViewXmlValidator.java | 18 +
.../vault/validation/spi/JcrPathValidator.java | 1 +
.../vault/validation/spi/NodePathValidator.java | 2 +-
.../spi/impl/DocumentViewParserValidator.java | 8 +-
.../spi/impl/PrimaryNodeTypeValidator.java | 62 ---
.../spi/impl/PrimaryNodeTypeValidatorFactory.java | 51 ---
.../impl/nodetype/DocViewPropertyValueFactory.java | 56 +++
.../spi/impl/nodetype/NodeNameAndType.java | 100 +++++
.../spi/impl/nodetype/NodeTypeManagerProvider.java | 204 ++++++++++
.../spi/impl/nodetype/NodeTypeValidator.java | 424 ++++++++++++++++++++
.../impl/nodetype/NodeTypeValidatorFactory.java | 155 +++++++
.../vault/validation/spi/package-info.java | 2 +-
.../classloaderurl/ClassLoaderUrlConnection.java | 45 +++
.../ThreadContextClassLoaderURLStreamHandler.java} | 19 +-
.../classloaderurl/URLFactory.java} | 29 +-
.../{ => util/classloaderurl}/package-info.java | 8 +-
.../src/main/resources/default-nodetypes.cnd | 446 +++++++++++++++++++++
.../vault/validation/ValidationExecutorTest.java | 10 +-
.../spi/impl/DocumentViewParserValidatorTest.java | 41 ++
.../spi/impl/PrimaryNodeTypeValidatorTest.java | 72 ----
.../spi/impl/nodetype/NodeTypeValidatorTest.java | 209 ++++++++++
25 files changed, 1771 insertions(+), 212 deletions(-)
diff --git a/vault-doc/src/site/markdown/validation.md b/vault-doc/src/site/markdown/validation.md
index cd206ab..a009152 100644
--- a/vault-doc/src/site/markdown/validation.md
+++ b/vault-doc/src/site/markdown/validation.md
@@ -55,7 +55,7 @@ ID | Description | Options
`jackrabbit-mergelimitations` | Checks for the limitation of import mode=merge outlined at [JCRVLT-255][jcrvlt-255]. | none
`jackrabbit-oakindex` | Checks if the package (potentially) modifies/creates an OakIndexDefinition. This is done by evaluating both the filter.xml for potential matches as well as the actual content for nodes with jcr:primaryType `oak:indexDefinition`. | none
`jackrabbit-packagetype` | Checks if the package type is correctly set for this package, i.e. is compliant with all rules outlined at [JCRVLT-170][jcrvlt-170]. | *jcrInstallerNodePathRegex*: the regex of the node paths which all OSGi bundles and configurations within packages must match ([JCR Installer](https://sling.apache.org/documentation/bundles/jcr-installer-provider.html)) (default=`/([^/]*/){0,4}?(install|config)(\\.[^/]*)*/(\\d{1,3}/)?.+?\\.`).<br/>*additionalJcrInstallerFileNode [...]
-`jackrabbit-primarynodetype` | Checks if all non empty elements within [DocView files](docview.html) have the mandatory property `jcr:primaryType` set. | none
+`jackrabbit-nodetypes` | Checks if all non empty elements within [DocView files](docview.html) have the mandatory property `jcr:primaryType` set and follow the [node type definition of their given type](https://jackrabbit.apache.org/jcr/node-types.html). | *cnds*: A URI pointing to one or multiple [CNDs](https://jackrabbit.apache.org/jcr/node-type-notation.html) (separated by `,`) which define the additional namespaces and nodetypes used apart from the [default ones defined in JCR 2.0](h [...]
### Custom Validators
diff --git a/vault-validation/pom.xml b/vault-validation/pom.xml
index fe12cb7..73bfe92 100644
--- a/vault-validation/pom.xml
+++ b/vault-validation/pom.xml
@@ -101,6 +101,11 @@
</dependency>
<dependency>
+ <groupId>org.apache.jackrabbit</groupId>
+ <artifactId>jackrabbit-jcr2spi</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<scope>provided</scope>
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/impl/util/DocumentViewXmlContentHandler.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/impl/util/DocumentViewXmlContentHandler.java
index f65172c..beb25a7 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/impl/util/DocumentViewXmlContentHandler.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/impl/util/DocumentViewXmlContentHandler.java
@@ -256,6 +256,18 @@ public class DocumentViewXmlContentHandler extends DefaultHandler implements Nam
if (node == null || nodePath == null) {
throw new IllegalStateException("Seems that the XML is not well formed");
}
+ violations.add(new ValidationViolation(ValidationMessageSeverity.DEBUG, "Validate node '" + node + "' end"));
+ for (Map.Entry<String, DocumentViewXmlValidator> entry : validators.entrySet()) {
+ try {
+ Collection<ValidationMessage> messages = entry.getValue().validateEnd(node, new NodeContextImpl(nodePath, filePath, basePath), elementNameStack.size() < 1);
+ if (messages != null && !messages.isEmpty()) {
+ violations.addAll(ValidationViolation.wrapMessages(entry.getKey(), messages, filePath, null, nodePath.toString(),
+ locator.getLineNumber(), locator.getColumnNumber()));
+ }
+ } catch (RuntimeException e) {
+ throw new ValidatorException(entry.getKey(), e, filePath, locator.getLineNumber(), locator.getColumnNumber(), e);
+ }
+ }
}
@Override
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/impl/util/ValidatorException.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/impl/util/ValidatorException.java
index 8ccdc8e..c707176 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/impl/util/ValidatorException.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/impl/util/ValidatorException.java
@@ -24,7 +24,7 @@ import java.nio.file.Path;
public class ValidatorException extends RuntimeException {
private ValidatorException(String id, String messageSuffix, Throwable cause) {
- super("Exception in validator '" + id + "'" + messageSuffix, cause);
+ super("Exception in validator '" + id + "'" + messageSuffix + ": " + cause.getMessage(), cause);
}
public ValidatorException(String id, Throwable cause) {
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/DocumentViewXmlValidator.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/DocumentViewXmlValidator.java
index f14a1d9..2527372 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/DocumentViewXmlValidator.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/DocumentViewXmlValidator.java
@@ -72,4 +72,22 @@ public interface DocumentViewXmlValidator extends Validator {
default @Nullable Collection<ValidationMessage> validate(@NotNull DocViewNode node, @NotNull NodeContext nodeContext, boolean isRoot) {
return validate(node, nodeContext.getNodePath(), nodeContext.getFilePath(), isRoot);
}
+
+
+ /**
+ * Called for the end of each new JCR document view node.
+ * Deserialization of the node information was already done when this method is called as well as all child nodes within the same docview file have been processed.
+ * The node and attribute names have the string representation outlined in {@link Name} (i.e. including the namespace uri in the format <code>{namespaceURI}localPart</code>).
+ * This is also referred to as <a href="https://docs.adobe.com/docs/en/spec/jcr/2.0/3_Repository_Model.html#3.2.5.1%20Expanded%20Form">JCR name expanded form</a>.
+ * To construct such names either use {@link NameUtil} or use the constants from {@link NameConstants}.
+ *
+ * The node's label refers to the XML element name specifying the node. There shouldn't be any checks derived from it, but only from the expanded name.
+ * @param node the node which should be validated
+ * @param nodeContext the information about the node context (like path)
+ * @param isRoot {@code true} in case this is the root node of the docview file otherwise {@code false}
+ * @return validation messages or {@code null}
+ */
+ default @Nullable Collection<ValidationMessage> validateEnd(@NotNull DocViewNode node, @NotNull NodeContext nodeContext, boolean isRoot) {
+ return null;
+ }
}
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/JcrPathValidator.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/JcrPathValidator.java
index 06e730f..6a2cd9f 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/JcrPathValidator.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/JcrPathValidator.java
@@ -28,6 +28,7 @@ import org.osgi.annotation.versioning.ProviderType;
* Validator interface for validating file paths for files and folders
* below jcr_root.
* Called after {@link GenericJcrDataValidator}.
+ * In contrast to {@link NodePathValidator} only called once per file and folder (even if those are covering multiple node paths).
*/
@ProviderType
public interface JcrPathValidator extends Validator {
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/NodePathValidator.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/NodePathValidator.java
index 6beb987..94a7816 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/NodePathValidator.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/NodePathValidator.java
@@ -26,7 +26,7 @@ import org.osgi.annotation.versioning.ProviderType;
/**
* Validator interface for validating node paths.
* For validators interested in the actual properties use either {@link DocumentViewXmlValidator} or {@link GenericJcrDataValidator}.
- *
+ * In contrast to {@link JcrPathValidator} might be called multiple times per file in case it covers multiple nodes.
*/
@ProviderType
public interface NodePathValidator extends Validator {
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/DocumentViewParserValidator.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/DocumentViewParserValidator.java
index 1c00219..479db5e 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/DocumentViewParserValidator.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/DocumentViewParserValidator.java
@@ -48,6 +48,7 @@ import org.xml.sax.XMLReader;
public class DocumentViewParserValidator implements GenericJcrDataValidator {
+ public static final String EXTENDED_FILE_AGGREGATE_FOLDER_SUFFIX = ".dir";
private final Map<String, DocumentViewXmlValidator> docViewValidators;
private final SAXParser saxParser;
private final @NotNull ValidationMessageSeverity severity;
@@ -99,12 +100,11 @@ public class DocumentViewParserValidator implements GenericJcrDataValidator {
return messages;
}
-
/** @param input the given input stream must be reset later on
* @param path
* @return either the path of the root node of the given docview xml or {@code null} if no docview xml given
* @throws IOException */
- private static Path getDocumentViewXmlRootPath(BufferedInputStream input, Path path) throws IOException {
+ static Path getDocumentViewXmlRootPath(BufferedInputStream input, Path path) throws IOException {
Path name = path.getFileName();
Path rootPath = null;
@@ -112,6 +112,10 @@ public class DocumentViewParserValidator implements GenericJcrDataValidator {
if (name.equals(Paths.get(Constants.DOT_CONTENT_XML))) {
if (nameCount > 1) {
rootPath = path.subpath(0, nameCount - 1);
+ // fix root mapping for http://jackrabbit.apache.org/filevault/vaultfs.html#Extended_File_aggregates
+ if (rootPath.toString().endsWith(EXTENDED_FILE_AGGREGATE_FOLDER_SUFFIX)) {
+ rootPath = Paths.get(rootPath.toString().substring(0, rootPath.toString().length() - EXTENDED_FILE_AGGREGATE_FOLDER_SUFFIX.length()));
+ }
} else {
rootPath = Paths.get("");
}
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/PrimaryNodeTypeValidator.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/PrimaryNodeTypeValidator.java
deleted file mode 100644
index ac79971..0000000
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/PrimaryNodeTypeValidator.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.vault.validation.spi.impl;
-
-import java.nio.file.Path;
-import java.util.Collection;
-import java.util.Collections;
-
-import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
-import org.apache.jackrabbit.vault.util.DocViewNode;
-import org.apache.jackrabbit.vault.validation.spi.DocumentViewXmlValidator;
-import org.apache.jackrabbit.vault.validation.spi.ValidationMessage;
-import org.apache.jackrabbit.vault.validation.spi.ValidationMessageSeverity;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Makes sure that each node in a docview file containing at least one other property defines the primary type
- */
-public class PrimaryNodeTypeValidator implements DocumentViewXmlValidator {
-
- protected static final String MESSAGE_MISSING_PRIMARY_TYPE = "Mandatory jcr:primaryType missing on node '%s'";
- private final @NotNull ValidationMessageSeverity severity;
- private final @NotNull WorkspaceFilter filter;
-
- public PrimaryNodeTypeValidator(@NotNull ValidationMessageSeverity severity, @NotNull WorkspaceFilter filter) {
- super();
- this.severity = severity;
- this.filter = filter;
- }
-
- @Override
- public Collection<ValidationMessage> done() {
- return null;
- }
-
- @Override
- public Collection<ValidationMessage> validate(@NotNull DocViewNode node, @NotNull String nodePath, @NotNull Path filePath, boolean isRoot) {
- if (node.primary == null) {
- // only an issue if contained in the filter
- // if other properties are set this node is not only used for ordering purposes
- if (filter.contains(nodePath) && !node.props.isEmpty()) {
- return Collections.singleton(new ValidationMessage(severity, String.format(MESSAGE_MISSING_PRIMARY_TYPE, nodePath)));
- }
- }
- return null;
- }
-
-}
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/PrimaryNodeTypeValidatorFactory.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/PrimaryNodeTypeValidatorFactory.java
deleted file mode 100644
index 3e39770..0000000
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/PrimaryNodeTypeValidatorFactory.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.vault.validation.spi.impl;
-
-import org.apache.jackrabbit.vault.validation.spi.ValidationContext;
-import org.apache.jackrabbit.vault.validation.spi.Validator;
-import org.apache.jackrabbit.vault.validation.spi.ValidatorFactory;
-import org.apache.jackrabbit.vault.validation.spi.ValidatorSettings;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.kohsuke.MetaInfServices;
-
-@MetaInfServices
-public final class PrimaryNodeTypeValidatorFactory implements ValidatorFactory {
-
- @Override
- public @Nullable Validator createValidator(@NotNull ValidationContext context, @NotNull ValidatorSettings settings) {
- return new PrimaryNodeTypeValidator(settings.getDefaultSeverity(), context.getFilter());
- }
-
- @Override
- public boolean shouldValidateSubpackages() {
- return false;
- }
-
- @Override
- public @NotNull String getId() {
- return ValidatorFactory.ID_PREFIX_JACKRABBIT + "primarynodetype";
- }
-
- @Override
- public int getServiceRanking() {
- return 0;
- }
-
-
-}
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/DocViewPropertyValueFactory.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/DocViewPropertyValueFactory.java
new file mode 100644
index 0000000..a9704ba
--- /dev/null
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/DocViewPropertyValueFactory.java
@@ -0,0 +1,56 @@
+/*
+ * 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.validation.spi.impl.nodetype;
+
+import java.util.Collection;
+import java.util.LinkedList;
+
+import javax.jcr.PropertyType;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+import javax.jcr.ValueFormatException;
+
+import org.apache.jackrabbit.value.ValueFactoryImpl;
+import org.apache.jackrabbit.vault.util.DocViewProperty;
+
+public class DocViewPropertyValueFactory {
+
+ private final ValueFactory valueFactory;
+ public DocViewPropertyValueFactory() {
+ valueFactory = ValueFactoryImpl.getInstance();
+ }
+
+ private Value getValue(String value, int type) throws ValueFormatException {
+ if (type == PropertyType.UNDEFINED) {
+ type = PropertyType.STRING;
+ }
+ return valueFactory.createValue(value, type);
+ }
+
+ public Value getValue(DocViewProperty property) throws ValueFormatException {
+ return getValue(property.values[0], property.type);
+ }
+
+ public Value[] getValues(DocViewProperty property) throws ValueFormatException {
+ Collection<Value> values = new LinkedList<>();
+ for (String value : property.values) {
+ values.add(getValue(value, property.type));
+ }
+ return values.toArray(new Value[values.size()]);
+ }
+
+}
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeNameAndType.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeNameAndType.java
new file mode 100644
index 0000000..e71a523
--- /dev/null
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeNameAndType.java
@@ -0,0 +1,100 @@
+/*
+ * 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.validation.spi.impl.nodetype;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.jcr.NamespaceException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+
+import org.apache.jackrabbit.jcr2spi.nodetype.EffectiveNodeType;
+import org.apache.jackrabbit.jcr2spi.nodetype.EffectiveNodeTypeProvider;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.QNodeDefinition;
+import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException;
+import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.vault.util.DocViewNode;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Encapsulates the expanded name and its underlying effective node type.
+ * In addition stores references to the child node names and types.
+ */
+final class NodeNameAndType {
+ private final @NotNull Name name;
+ private final @NotNull EffectiveNodeType effectiveNodeType;
+ private final @NotNull List<NodeNameAndType> children;
+ private final @Nullable NodeNameAndType parent;
+
+ @SuppressWarnings("null")
+ public NodeNameAndType(@Nullable NodeNameAndType parent, @NotNull NameResolver nameResolver, @NotNull EffectiveNodeTypeProvider effectiveNodeTypeProvider, @NotNull DocViewNode node) throws IllegalNameException, NamespaceException, ConstraintViolationException, NoSuchNodeTypeException {
+ this.name = nameResolver.getQName(node.name);
+ Collection<Name> types = new LinkedList<>();
+ types.add(nameResolver.getQName(node.primary));
+ if (node.mixins != null) {
+ for (String mixin : node.mixins) {
+ types.add(nameResolver.getQName(mixin));
+ }
+ }
+ effectiveNodeType = effectiveNodeTypeProvider.getEffectiveNodeType(types.toArray(new Name[0]));
+ children = new LinkedList<>();
+ if (parent != null) {
+ parent.addChild(this);
+ }
+ this.parent = parent;
+ }
+
+ public boolean fulfillsNodeDefinition(QNodeDefinition nodeDefinition) {
+ // name must match
+ if (!nodeDefinition.getName().equals(NameConstants.ANY_NAME) && !nodeDefinition.getName().equals(name)) {
+ return false;
+ }
+
+ for (Name requiredType : nodeDefinition.getRequiredPrimaryTypes()) {
+ // type must match one of the given types
+ if (!effectiveNodeType.includesNodeType(requiredType)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void addChild(NodeNameAndType nodeNameAndTypes) {
+ children.add(nodeNameAndTypes);
+ }
+
+ public List<NodeNameAndType> getChildren() {
+ return children;
+ }
+
+ public Name getName() {
+ return name;
+ }
+
+ public EffectiveNodeType getEffectiveNodeType() {
+ return effectiveNodeType;
+ }
+
+ public NodeNameAndType getParent() {
+ return parent;
+ }
+}
\ No newline at end of file
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeManagerProvider.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeManagerProvider.java
new file mode 100644
index 0000000..1cc04af
--- /dev/null
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeManagerProvider.java
@@ -0,0 +1,204 @@
+/*
+ * 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.validation.spi.impl.nodetype;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.NamespaceException;
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.ValueFactory;
+import javax.jcr.nodetype.InvalidNodeTypeDefinitionException;
+import javax.jcr.nodetype.NodeTypeExistsException;
+import javax.jcr.nodetype.NodeTypeManager;
+
+import org.apache.jackrabbit.commons.cnd.CndImporter;
+import org.apache.jackrabbit.commons.cnd.ParseException;
+import org.apache.jackrabbit.jcr2spi.ManagerProvider;
+import org.apache.jackrabbit.jcr2spi.NamespaceRegistryImpl;
+import org.apache.jackrabbit.jcr2spi.NamespaceStorage;
+import org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyManager;
+import org.apache.jackrabbit.jcr2spi.lock.LockStateManager;
+import org.apache.jackrabbit.jcr2spi.nodetype.EffectiveNodeTypeProvider;
+import org.apache.jackrabbit.jcr2spi.nodetype.ItemDefinitionProvider;
+import org.apache.jackrabbit.jcr2spi.nodetype.ItemDefinitionProviderImpl;
+import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeDefinitionProvider;
+import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeManagerImpl;
+import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeRegistryImpl;
+import org.apache.jackrabbit.jcr2spi.security.AccessManager;
+import org.apache.jackrabbit.jcr2spi.security.authorization.AccessControlProvider;
+import org.apache.jackrabbit.jcr2spi.version.VersionManager;
+import org.apache.jackrabbit.spi.QValueFactory;
+import org.apache.jackrabbit.spi.commons.conversion.DefaultNamePathResolver;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
+import org.apache.jackrabbit.spi.commons.conversion.PathResolver;
+import org.apache.jackrabbit.spi.commons.namespace.NamespaceMapping;
+import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
+import org.apache.jackrabbit.spi.commons.namespace.RegistryNamespaceResolver;
+import org.apache.jackrabbit.spi.commons.nodetype.NodeTypeStorage;
+import org.apache.jackrabbit.spi.commons.nodetype.NodeTypeStorageImpl;
+import org.apache.jackrabbit.spi.commons.value.QValueFactoryImpl;
+import org.apache.jackrabbit.value.ValueFactoryImpl;
+
+public class NodeTypeManagerProvider implements ManagerProvider, NamespaceStorage {
+
+
+ // namespace related helpers
+ private final NamespaceMapping namespaceMapping;
+ private final NamespaceRegistry namespaceRegistry;
+ private final NamespaceResolver namespaceResolver;
+ private final NamePathResolver npResolver;
+
+ // nodetype related helpers
+ private final NodeTypeStorage nodeTypeStorage;
+ private final NodeTypeRegistryImpl nodeTypeRegistry;
+ private final NodeTypeManagerImpl nodeTypeManager;
+
+ private final ItemDefinitionProvider itemDefinitionProvider;
+
+ public NodeTypeManagerProvider() throws IOException, RepositoryException, ParseException {
+ namespaceMapping = new NamespaceMapping();
+ // add default mapping, the rest comes from the CDN provided via the reader
+ namespaceMapping.setMapping(NamespaceRegistry.PREFIX_EMPTY, NamespaceRegistry.NAMESPACE_EMPTY);
+ namespaceRegistry = new NamespaceRegistryImpl(this);
+ namespaceResolver = new RegistryNamespaceResolver(namespaceRegistry);
+ npResolver = new DefaultNamePathResolver(namespaceResolver);
+ nodeTypeStorage = new NodeTypeStorageImpl();
+ nodeTypeRegistry = NodeTypeRegistryImpl.create(nodeTypeStorage, namespaceRegistry);
+ nodeTypeManager = new NodeTypeManagerImpl(nodeTypeRegistry, this);
+ itemDefinitionProvider = new ItemDefinitionProviderImpl(nodeTypeRegistry, null, null);
+ // always provide default
+ try (Reader reader = new InputStreamReader(
+ this.getClass().getResourceAsStream("/default-nodetypes.cnd"),
+ StandardCharsets.US_ASCII)) {
+ registerNodeTypes(reader);
+ }
+ }
+
+ public void registerNodeTypes(Reader reader) throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, UnsupportedRepositoryOperationException, ParseException, RepositoryException, IOException {
+ CndImporter.registerNodeTypes(reader, null, nodeTypeManager, namespaceRegistry, getJcrValueFactory(), false);
+ }
+
+ @Override
+ public NamePathResolver getNamePathResolver() {
+ return npResolver;
+ }
+
+ @Override
+ public NameResolver getNameResolver() {
+ return npResolver;
+ }
+
+ @Override
+ public PathResolver getPathResolver() {
+ return npResolver;
+ }
+
+ @Override
+ public NamespaceResolver getNamespaceResolver() {
+ return namespaceResolver;
+ }
+
+ public NodeTypeManager getNodeTypeManager() {
+ return nodeTypeManager;
+ }
+
+ @Override
+ public HierarchyManager getHierarchyManager() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public AccessManager getAccessManager() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public LockStateManager getLockStateManager() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public VersionManager getVersionStateManager() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ItemDefinitionProvider getItemDefinitionProvider() {
+ return itemDefinitionProvider;
+ }
+
+ @Override
+ public NodeTypeDefinitionProvider getNodeTypeDefinitionProvider() {
+ return nodeTypeManager;
+ }
+
+ @Override
+ public EffectiveNodeTypeProvider getEffectiveNodeTypeProvider() {
+ return nodeTypeRegistry;
+ }
+
+ @Override
+ public ValueFactory getJcrValueFactory() throws RepositoryException {
+ return ValueFactoryImpl.getInstance();
+ }
+
+ @Override
+ public QValueFactory getQValueFactory() throws RepositoryException {
+ return QValueFactoryImpl.getInstance();
+ }
+
+ @Override
+ public AccessControlProvider getAccessControlProvider() throws RepositoryException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Map<String, String> getRegisteredNamespaces() throws RepositoryException {
+ return namespaceMapping.getPrefixToURIMapping();
+ }
+
+ @Override
+ public String getPrefix(String uri) throws NamespaceException, RepositoryException {
+ return namespaceMapping.getPrefix(uri);
+ }
+
+ @Override
+ public String getURI(String prefix) throws NamespaceException, RepositoryException {
+ return namespaceMapping.getURI(prefix);
+ }
+
+ @Override
+ public void registerNamespace(String prefix, String uri)
+ throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException {
+ namespaceMapping.setMapping(prefix, uri);
+ }
+
+ @Override
+ public void unregisterNamespace(String uri)
+ throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException {
+ namespaceMapping.removeMapping(uri);
+ }
+
+}
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeValidator.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeValidator.java
new file mode 100644
index 0000000..287f39a
--- /dev/null
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeValidator.java
@@ -0,0 +1,424 @@
+/*
+ * 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.validation.spi.impl.nodetype;
+
+import java.util.AbstractMap.SimpleEntry;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.jcr.NamespaceException;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+import javax.jcr.ValueFormatException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.jcr2spi.nodetype.EffectiveNodeType;
+import org.apache.jackrabbit.jcr2spi.nodetype.ItemDefinitionProvider;
+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.QPropertyDefinition;
+import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.spi.QValueFactory;
+import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
+import org.apache.jackrabbit.spi.commons.nodetype.constraint.ValueConstraint;
+import org.apache.jackrabbit.spi.commons.value.ValueFormat;
+import org.apache.jackrabbit.value.ValueHelper;
+import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
+import org.apache.jackrabbit.vault.fs.spi.ACLManagement;
+import org.apache.jackrabbit.vault.fs.spi.UserManagement;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.JackrabbitUserManagement;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.JcrACLManagement;
+import org.apache.jackrabbit.vault.util.DocViewNode;
+import org.apache.jackrabbit.vault.util.DocViewProperty;
+import org.apache.jackrabbit.vault.util.Text;
+import org.apache.jackrabbit.vault.validation.spi.DocumentViewXmlValidator;
+import org.apache.jackrabbit.vault.validation.spi.NodeContext;
+import org.apache.jackrabbit.vault.validation.spi.ValidationMessage;
+import org.apache.jackrabbit.vault.validation.spi.ValidationMessageSeverity;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class NodeTypeValidator implements DocumentViewXmlValidator {
+
+ static final String MESSAGE_MANDATORY_CHILD_NODE_MISSING = "Mandatory child node missing: %s";
+ static final String MESSAGE_PROPERTY_ERROR = "Error while retrieving property '%s': %s";
+ static final String MESSAGE_UNKNOWN_NODE_TYPE_OR_NAMESPACE = "Unknown node type or namespace: %s";
+ static final String MESSAGE_MISSING_PRIMARY_TYPE = "Mandatory jcr:primaryType missing on node '%s'";
+ static final String MESSAGE_PROPERTY_NOT_ALLOWED = "Property '%s' is not allowed in node with types '[%s]': %s";
+ static final String MESSAGE_MANDATORY_PROPERTY_MISSING = "Mandatory property '%s' missing in node with types [%s]";
+ static final String MESSAGE_CHILD_NODE_OF_NOT_CONTAINED_PARENT_POTENTIALLY_NOT_ALLOWED = "Node '%s' is not allowed as child of not contained node with potential default types '[%s]': %s";
+ static final String MESSAGE_CHILD_NODE_NOT_ALLOWED = "Node '%s' is not allowed as child of node with types '[%s]': %s";
+ private final WorkspaceFilter filter;
+ private final ValidationMessageSeverity defaultSeverity;
+ private final ValidationMessageSeverity severityForUnknownNodeTypes;
+ private final DocViewPropertyValueFactory docViewPropertyValueFactory;
+ private final NodeTypeManagerProvider ntManagerProvider;
+ private final Set<String> loggedUnknownNodeTypeMessages;
+
+ private final EffectiveNodeType defaultType;
+ private final UserManagement userManagement;
+ private final ACLManagement aclManagement;
+ private NodeContext protectedNodeContext;
+ private NodeNameAndType currentNodeNameAndType = null;
+
+ private static final Collection<Name> ALLOWED_PROTECTED_PROPERTIES = Arrays.asList(NameConstants.JCR_PRIMARYTYPE,
+ NameConstants.JCR_MIXINTYPES);
+
+ // properties being set by the {@link FileArtifactHandler} (they are part of another file) are ignored
+ private static final Map<Name, List<Name>> IGNORED_MANDATORY_PROPERTIES_PER_NODE_TYPE = Stream.of(
+ new SimpleEntry<>(NameConstants.NT_RESOURCE,
+ Arrays.asList(NameConstants.JCR_DATA)))
+ .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue));
+
+ public NodeTypeValidator(@NotNull WorkspaceFilter filter, @NotNull NodeTypeManagerProvider ntManagerProvider,
+ @NotNull EffectiveNodeType defaultEffectiveNodeType, @NotNull ValidationMessageSeverity defaultSeverity,
+ @NotNull ValidationMessageSeverity severityForUnknownNodeTypes) {
+ this.filter = filter;
+ this.ntManagerProvider = ntManagerProvider;
+ this.defaultType = defaultEffectiveNodeType;
+ this.defaultSeverity = defaultSeverity;
+ this.severityForUnknownNodeTypes = severityForUnknownNodeTypes;
+ this.docViewPropertyValueFactory = new DocViewPropertyValueFactory();
+ this.userManagement = new JackrabbitUserManagement();
+ this.aclManagement = new JcrACLManagement();
+ this.loggedUnknownNodeTypeMessages = new HashSet<>();
+ }
+
+ static String getDocViewNodeLabel(DocViewNode node) {
+ StringBuilder sb = new StringBuilder(node.name);
+ sb.append(" [").append(node.primary);
+ if (node.mixins != null && node.mixins.length > 0) {
+ sb.append(" (").append(StringUtils.join(node.mixins, ", ")).append(")");
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ @Override
+ public @Nullable Collection<ValidationMessage> validate(@NotNull DocViewNode node, @NotNull NodeContext nodeContext,
+ boolean isRoot) {
+
+ if (node.primary == null) {
+ // only an issue if contained in the filter
+ // if other properties are set this node is not only used for ordering purposes
+ if (filter.contains(nodeContext.getNodePath()) && !node.props.isEmpty()) {
+ return Collections.singleton(
+ new ValidationMessage(defaultSeverity, String.format(MESSAGE_MISSING_PRIMARY_TYPE, nodeContext.getNodePath())));
+ } else {
+ // order node only or outside filter
+ return null;
+ }
+ }
+
+ // special handling for users and acls
+ if (aclManagement.isACLNodeType(node.primary) || userManagement.isAuthorizableNodeType(node.primary)) {
+ protectedNodeContext = nodeContext;
+ }
+
+ boolean allowProtectedSubNodesAndProperties = protectedNodeContext != null;
+
+ Collection<ValidationMessage> messages = new LinkedList<>();
+
+ try {
+ // check node itself against parent node type
+ if (!aclManagement.isACLNodeType(node.primary)) {
+ final EffectiveNodeType parentNodeType;
+ final boolean useDefaultNodeType;
+ String parentNodePath = Text.getRelativeParent(nodeContext.getNodePath(), 1);
+
+ if (currentNodeNameAndType == null || !filter.contains(parentNodePath)) {
+ parentNodeType = defaultType;
+ useDefaultNodeType = true;
+ } else {
+ parentNodeType = currentNodeNameAndType.getEffectiveNodeType();
+ useDefaultNodeType = false;
+ }
+
+ String constraintViolation = getChildNodeConstraintViolation(node, parentNodeType,
+ ntManagerProvider.getNodeTypeDefinitionProvider(),
+ ntManagerProvider.getNameResolver(), ntManagerProvider.getItemDefinitionProvider(),
+ allowProtectedSubNodesAndProperties);
+ if (constraintViolation != null) {
+ messages.add(new ValidationMessage(defaultSeverity,
+ String.format(
+ useDefaultNodeType ? MESSAGE_CHILD_NODE_OF_NOT_CONTAINED_PARENT_POTENTIALLY_NOT_ALLOWED
+ : MESSAGE_CHILD_NODE_NOT_ALLOWED,
+ getDocViewNodeLabel(node),
+ effectiveNodeTypeToString(ntManagerProvider.getNameResolver(), parentNodeType),
+ constraintViolation)));
+
+ }
+ }
+
+ // get current node's node type and name and register in tree
+ NodeNameAndType newNodeNameAndType = new NodeNameAndType(currentNodeNameAndType, ntManagerProvider.getNameResolver(),
+ ntManagerProvider.getEffectiveNodeTypeProvider(), node);
+
+ // check all properties
+ Collection<Name> foundProperties = new ArrayList<>(node.props.size());
+ for (DocViewProperty property : node.props.values()) {
+ String constraintViolation = getPropertyConstraintViolation(property, newNodeNameAndType.getEffectiveNodeType(),
+ allowProtectedSubNodesAndProperties);
+ if (constraintViolation != null) {
+ messages.add(new ValidationMessage(defaultSeverity, String.format(MESSAGE_PROPERTY_NOT_ALLOWED, property,
+ effectiveNodeTypeToString(ntManagerProvider.getNameResolver(), newNodeNameAndType.getEffectiveNodeType()),
+ constraintViolation)));
+ }
+ foundProperties.add(NameFactoryImpl.getInstance().create(property.name));
+ }
+ // are all mandatory properties covered?
+ for (QPropertyDefinition mandatoryPropertyDefinition : newNodeNameAndType.getEffectiveNodeType()
+ .getMandatoryQPropertyDefinitions()) {
+ // ignore auto-created properties as they are created on-demand
+ if (!mandatoryPropertyDefinition.isAutoCreated() && !foundProperties.contains(mandatoryPropertyDefinition.getName())) {
+
+ // ignore propertes which may be provided by the {@link FileArtifactHandler} (they are part of another file)
+ List<Name> ignoredProperties = IGNORED_MANDATORY_PROPERTIES_PER_NODE_TYPE.get(mandatoryPropertyDefinition.getDeclaringNodeType());
+ if (ignoredProperties != null && ignoredProperties.contains(mandatoryPropertyDefinition.getName())) {
+ // TODO: skipping for now as validating those from other files requires major effort
+ continue;
+ }
+ messages.add(new ValidationMessage(defaultSeverity,
+ String.format(MESSAGE_MANDATORY_PROPERTY_MISSING, mandatoryPropertyDefinition.getName(),
+ effectiveNodeTypeToString(ntManagerProvider.getNameResolver(),
+ newNodeNameAndType.getEffectiveNodeType()))));
+ }
+ }
+
+ currentNodeNameAndType = newNodeNameAndType;
+ } catch (NoSuchNodeTypeException | IllegalNameException | NamespaceException e) {
+ // log each unknown node type/namespace only once!
+ if (!loggedUnknownNodeTypeMessages.contains(e.getMessage())) {
+ messages.add(new ValidationMessage(severityForUnknownNodeTypes,
+ String.format(MESSAGE_UNKNOWN_NODE_TYPE_OR_NAMESPACE, e.getMessage()), e));
+ loggedUnknownNodeTypeMessages.add(e.getMessage());
+ }
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Could not validate nodes/properties against node types: " + e.getMessage(), e);
+ }
+ return messages;
+ }
+
+ @Override
+ public @Nullable Collection<ValidationMessage> validateEnd(@NotNull DocViewNode node, @NotNull NodeContext nodeContext,
+ boolean isRoot) {
+
+ if (nodeContext.equals(protectedNodeContext)) {
+ protectedNodeContext = null;
+ }
+
+ try {
+ if (currentNodeNameAndType != null) {
+ Collection<ValidationMessage> messages = new LinkedList<>();
+ for (QNodeDefinition mandatoryNodeType : currentNodeNameAndType.getEffectiveNodeType().getMandatoryQNodeDefinitions()) {
+ boolean foundRequiredChildNode = currentNodeNameAndType.getChildren().stream()
+ .anyMatch(childNamesAndTypes -> childNamesAndTypes.fulfillsNodeDefinition(mandatoryNodeType));
+ if (!foundRequiredChildNode) {
+ try {
+ messages.add(new ValidationMessage(defaultSeverity, String.format(MESSAGE_MANDATORY_CHILD_NODE_MISSING,
+ nodeDefinitionToString(ntManagerProvider.getNameResolver(), mandatoryNodeType))));
+ } catch (NamespaceException e) {
+ throw new IllegalStateException("Could not give out node types and name for " + mandatoryNodeType, e);
+ }
+ }
+ }
+ return messages;
+ } else {
+ return null;
+ }
+ } finally {
+ if (currentNodeNameAndType != null) {
+ currentNodeNameAndType = currentNodeNameAndType.getParent();
+ }
+ }
+ }
+
+ static String effectiveNodeTypeToString(NameResolver nameResolver, EffectiveNodeType nodeType) throws NamespaceException {
+ return joinAsQualifiedJcrName(nameResolver, nodeType.getMergedNodeTypes());
+ }
+
+ static String nodeDefinitionToString(NameResolver nameResolver, QNodeDefinition nodeDefinition) throws NamespaceException {
+ return nameResolver.getJCRName(nodeDefinition.getName()) + " ["
+ + joinAsQualifiedJcrName(nameResolver, nodeDefinition.getRequiredPrimaryTypes()) + "]";
+ }
+
+ private static String joinAsQualifiedJcrName(NameResolver nameResolver, Name[] names) throws NamespaceException {
+ StringBuilder types = new StringBuilder();
+ String delimiter = "";
+ for (Name name : names) {
+ types.append(delimiter).append(nameResolver.getJCRName(name));
+ delimiter = ", ";
+ }
+ return types.toString();
+ }
+
+ private static QPropertyDefinition getPropertyDefinition(Name name, int type, EffectiveNodeType effectiveNodeType,
+ ItemDefinitionProvider itemDefinitionProvider, boolean multiValued)
+ throws NoSuchNodeTypeException, ConstraintViolationException {
+ QPropertyDefinition def;
+ try {
+ def = itemDefinitionProvider.getQPropertyDefinition(effectiveNodeType.getAllNodeTypes(), name, type,
+ multiValued);
+ } catch (ConstraintViolationException e) {
+ if (type != PropertyType.UNDEFINED) {
+ def = itemDefinitionProvider.getQPropertyDefinition(effectiveNodeType.getAllNodeTypes(), name, PropertyType.UNDEFINED,
+ multiValued);
+ } else {
+ throw e;
+ }
+ }
+ return def;
+ }
+
+ private static void validateValueConstraints(Value value, QPropertyDefinition def, ValueFactory valueFactory,
+ QValueFactory qValueFactory, NamePathResolver namePathResolver) throws ValueFormatException, RepositoryException {
+ final Value v;
+ if (def.getRequiredType() != 0 && def.getRequiredType() != value.getType()) {
+ v = ValueHelper.convert(value, def.getRequiredType(), valueFactory);
+ } else {
+ v = value;
+ }
+ QValue qValue = ValueFormat.getQValue(v, namePathResolver, qValueFactory);
+ ValueConstraint.checkValueConstraints(def, new QValue[] { qValue });
+ }
+
+ String getPropertyConstraintViolation(DocViewProperty property, EffectiveNodeType effectiveNodeType, boolean allowProtected)
+ throws RepositoryException {
+ Name name = ntManagerProvider.getNameResolver().getQName(property.name);
+
+ try {
+ if (property.isMulti) {
+ return getPropertyConstraintViolation(name, docViewPropertyValueFactory.getValues(property), effectiveNodeType,
+ ntManagerProvider.getItemDefinitionProvider(), ntManagerProvider.getJcrValueFactory(),
+ ntManagerProvider.getQValueFactory(), ntManagerProvider.getNamePathResolver(), allowProtected);
+ } else {
+ return getPropertyConstraintViolation(name, docViewPropertyValueFactory.getValue(property), effectiveNodeType,
+ ntManagerProvider.getItemDefinitionProvider(), ntManagerProvider.getJcrValueFactory(),
+ ntManagerProvider.getQValueFactory(), ntManagerProvider.getNamePathResolver(), allowProtected);
+ }
+ } catch (RepositoryException e) {
+ throw new RepositoryException(String.format(MESSAGE_PROPERTY_ERROR, property.name, e.getMessage()), e);
+ }
+ }
+
+ static String getPropertyConstraintViolation(Name name, Value value, EffectiveNodeType effectiveNodeType,
+ ItemDefinitionProvider itemDefinitionProvider, ValueFactory valueFactory, QValueFactory qValueFactory,
+ NamePathResolver namePathResolver, boolean allowProtected) throws RepositoryException {
+ QPropertyDefinition def;
+ try {
+ def = getPropertyDefinition(name, value.getType(), effectiveNodeType, itemDefinitionProvider, false);
+ } catch (ConstraintViolationException t) {
+ return "No property definition found for name!";
+ }
+
+ if (def.isProtected() && !allowProtected && !ALLOWED_PROTECTED_PROPERTIES.contains(name)) {
+ return "Property is protected!";
+ }
+
+ // single values are valid for multi and single value
+ try {
+ validateValueConstraints(value, def, valueFactory, qValueFactory, namePathResolver);
+ } catch (ConstraintViolationException e) {
+ return "Property value does not satisfy constraints: " + e.getLocalizedMessage();
+ } catch (ValueFormatException e) {
+ return "Cannot convert property into type '" + def.getRequiredType() + "': " + e.getLocalizedMessage();
+ }
+ return null;
+ }
+
+ static String getPropertyConstraintViolation(Name name, Value[] values, EffectiveNodeType effectiveNodeType,
+ ItemDefinitionProvider itemDefinitionProvider, ValueFactory valueFactory, QValueFactory qValueFactory,
+ NamePathResolver namePathResolver, boolean allowProtected) throws RepositoryException {
+ QPropertyDefinition def;
+ int type = values.length > 0 ? values[0].getType() : PropertyType.UNDEFINED;
+ try {
+ def = getPropertyDefinition(name, type, effectiveNodeType, itemDefinitionProvider, true);
+ } catch (ConstraintViolationException t) {
+ return "No property definition found for name!";
+ }
+ if (def.isProtected() && !allowProtected && !ALLOWED_PROTECTED_PROPERTIES.contains(name)) {
+ return "Property is protected!";
+ }
+ if (!def.isMultiple()) {
+ return "Property must be single-value!";
+ }
+ for (Value value : values) {
+ try {
+ validateValueConstraints(value, def, valueFactory, qValueFactory, namePathResolver);
+ } catch (ConstraintViolationException e) {
+ return "Property value does not satisfy constraints: " + e.getLocalizedMessage();
+ } catch (ValueFormatException e) {
+ return "Cannot convert property into type '" + def.getRequiredType() + "': " + e.getLocalizedMessage();
+ }
+ }
+ return null;
+ }
+
+ static String getChildNodeConstraintViolation(DocViewNode node, EffectiveNodeType nodeType,
+ NodeTypeDefinitionProvider nodeTypeDefinitionProvider,
+ NameResolver nameResolver, ItemDefinitionProvider itemDefinitionProvider, boolean allowProtected)
+ throws RepositoryException {
+ Name nodeName = nameResolver.getQName(node.name);
+ QNodeTypeDefinition nodeTypeDefinition = nodeTypeDefinitionProvider.getNodeTypeDefinition(nameResolver.getQName(node.primary));
+ if (nodeTypeDefinition.isAbstract()) {
+ return "Not allowed to add node with abstract node type as primary type";
+ }
+ if (nodeTypeDefinition.isMixin()) {
+ return "Not allowed to add node with a mixin as primary node type";
+ }
+ try {
+ QNodeDefinition nd = itemDefinitionProvider.getQNodeDefinition(nodeType, nodeName, nodeTypeDefinition.getName());
+
+ if (!allowProtected && nd.isProtected()) {
+ return "Node is protected and can not be manually added";
+ }
+
+ if (nd.isAutoCreated()) {
+ return "Node is auto-created and can not be manually added";
+ }
+ } catch (ConstraintViolationException e) {
+ return "Could not find matching child node definition in parent's node type";
+ }
+
+ return null;
+ }
+
+ @Override
+ public @Nullable Collection<ValidationMessage> done() {
+ return null;
+ }
+
+}
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeValidatorFactory.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeValidatorFactory.java
new file mode 100644
index 0000000..be0ebb9
--- /dev/null
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeValidatorFactory.java
@@ -0,0 +1,155 @@
+/*
+ * 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.validation.spi.impl.nodetype;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.jar.Manifest;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.commons.cnd.ParseException;
+import org.apache.jackrabbit.jcr2spi.nodetype.EffectiveNodeType;
+import org.apache.jackrabbit.vault.validation.spi.ValidationContext;
+import org.apache.jackrabbit.vault.validation.spi.ValidationMessageSeverity;
+import org.apache.jackrabbit.vault.validation.spi.Validator;
+import org.apache.jackrabbit.vault.validation.spi.ValidatorFactory;
+import org.apache.jackrabbit.vault.validation.spi.ValidatorSettings;
+import org.apache.jackrabbit.vault.validation.spi.util.classloaderurl.URLFactory;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.kohsuke.MetaInfServices;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@MetaInfServices
+public class NodeTypeValidatorFactory implements ValidatorFactory {
+
+ public static final String OPTION_CNDS = "cnds";
+ /** The default node type to assume if no other node type is given */
+ public static final String OPTION_DEFAULT_NODE_TYPES = "defaultNodeType";
+ public static final String OPTION_SEVERITY_FOR_UNKNOWN_NODETYPES = "severityForUnknownNodetypes";
+
+ static final @NotNull String DEFAULT_DEFAULT_NODE_TYPE = JcrConstants.NT_FOLDER;
+
+ static final @NotNull ValidationMessageSeverity DEFAULT_SEVERITY_FOR_UNKNOWN_NODETYPE = ValidationMessageSeverity.WARN;
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(NodeTypeValidatorFactory.class);
+
+ @Override
+ public @Nullable Validator createValidator(@NotNull ValidationContext context, @NotNull ValidatorSettings settings) {
+
+ String cndUrls = settings.getOptions().get(OPTION_CNDS);
+ // either load map from classloader, from filesystem or from generic url
+ if (StringUtils.isBlank(cndUrls)) {
+ cndUrls = this.getClass().getClassLoader().getResource("default-nodetypes.cnd").toString();
+ LOGGER.warn("Using default nodetypes, consider specifying the nodetypes from the distribution you use!");
+ }
+
+ final String defaultNodeType;
+ if (settings.getOptions().containsKey(OPTION_DEFAULT_NODE_TYPES)) {
+ defaultNodeType = settings.getOptions().get(OPTION_DEFAULT_NODE_TYPES);
+ } else {
+ defaultNodeType = DEFAULT_DEFAULT_NODE_TYPE;
+ }
+
+ final @NotNull ValidationMessageSeverity severityForUnknownNodetypes;
+ if (settings.getOptions().containsKey(OPTION_SEVERITY_FOR_UNKNOWN_NODETYPES)) {
+ String optionValue = settings.getOptions().get(OPTION_SEVERITY_FOR_UNKNOWN_NODETYPES);
+ severityForUnknownNodetypes = ValidationMessageSeverity.valueOf(optionValue.toUpperCase());
+ } else {
+ severityForUnknownNodetypes = DEFAULT_SEVERITY_FOR_UNKNOWN_NODETYPE;
+ }
+
+ try {
+ NodeTypeManagerProvider ntManagerProvider = null;
+ ntManagerProvider = new NodeTypeManagerProvider();
+ for (String cndUrl : resolveJarUrls(cndUrls.split(","))) {
+ try (Reader reader = new InputStreamReader(URLFactory.createURL(cndUrl).openStream(), StandardCharsets.US_ASCII)) {
+ LOGGER.info("Register additional node types from {}", cndUrl);
+ ntManagerProvider.registerNodeTypes(reader);
+ } catch (RepositoryException | IOException | ParseException e) {
+ throw new IllegalArgumentException("Error loading node types from CND at " + cndUrl, e);
+ }
+ }
+ EffectiveNodeType defaultEffectiveNodeType = ntManagerProvider.getEffectiveNodeTypeProvider()
+ .getEffectiveNodeType(ntManagerProvider.getNameResolver().getQName(defaultNodeType));
+ return new NodeTypeValidator(context.getFilter(), ntManagerProvider, defaultEffectiveNodeType, settings.getDefaultSeverity(),
+ severityForUnknownNodetypes);
+ } catch (IOException | RepositoryException | ParseException e) {
+ throw new IllegalArgumentException("Error loading default node type " + defaultNodeType, e);
+ }
+ }
+
+ /**
+ * Resolve URLs pointing to JARs with META-INF/MANIFEST carrying a {@code Sling-Nodetypes} header
+ * @param urls
+ * @return
+ */
+ static List<String> resolveJarUrls(String... urls) {
+ List<String> resolvedUrls = new LinkedList<>();
+ for (String url : urls) {
+ if (url.endsWith(".jar")) {
+ // https://docs.oracle.com/javase/7/docs/api/java/net/JarURLConnection.html
+ URL jarUrl;
+ try {
+ jarUrl = URLFactory.createURL("jar:" + url + "!/");
+ JarURLConnection jarConnection = (JarURLConnection)jarUrl.openConnection();
+ Manifest manifest = jarConnection.getManifest();
+ String slingNodetypes = manifest.getMainAttributes().getValue("Sling-Nodetypes");
+ // split by "," and generate new JAR Urls
+ if (slingNodetypes == null) {
+ LOGGER.warn("No 'Sling-Nodetypes' header found in manifest of '{}'", jarUrl);
+ } else {
+ for (String nodetype : slingNodetypes.split(",")) {
+ resolvedUrls.add(jarUrl.toString() + nodetype);
+ }
+ }
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Could not read from JAR " + url, e);
+ }
+ } else {
+ resolvedUrls.add(url);
+ }
+ }
+ return resolvedUrls;
+ }
+
+ @Override
+ public boolean shouldValidateSubpackages() {
+ return false;
+ }
+
+ @Override
+ public @NotNull String getId() {
+ return ValidatorFactory.ID_PREFIX_JACKRABBIT + "nodetypes";
+ }
+
+ @Override
+ public int getServiceRanking() {
+ return 0;
+ }
+
+}
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/package-info.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/package-info.java
index c72bdef..5a64445 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/package-info.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/package-info.java
@@ -18,7 +18,7 @@
/**
* The FileVault validation framework SPI. Provides classes/interfaces to implement validators on FileVault packages.
*/
-@Version("1.1.0")
+@Version("1.2.0")
package org.apache.jackrabbit.vault.validation.spi;
import org.osgi.annotation.versioning.Version;
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/util/classloaderurl/ClassLoaderUrlConnection.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/util/classloaderurl/ClassLoaderUrlConnection.java
new file mode 100644
index 0000000..5c1e569
--- /dev/null
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/util/classloaderurl/ClassLoaderUrlConnection.java
@@ -0,0 +1,45 @@
+/*
+ * 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.validation.spi.util.classloaderurl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+
+public class ClassLoaderUrlConnection extends URLConnection {
+ private final ClassLoader classLoader;
+
+ protected ClassLoaderUrlConnection(ClassLoader classLoader, URL url) {
+ super(url);
+ this.classLoader = classLoader;
+ }
+
+ @Override
+ public void connect() throws IOException {
+
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ InputStream input = classLoader.getResourceAsStream(url.getFile());
+ if (input == null) {
+ throw new IOException("Could not load resource '" + url.getFile() + "' from classLoader '" + classLoader + "'");
+ }
+ return input;
+ }
+}
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/package-info.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/util/classloaderurl/ThreadContextClassLoaderURLStreamHandler.java
similarity index 63%
copy from vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/package-info.java
copy to vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/util/classloaderurl/ThreadContextClassLoaderURLStreamHandler.java
index c72bdef..3ca3713 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/package-info.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/util/classloaderurl/ThreadContextClassLoaderURLStreamHandler.java
@@ -14,11 +14,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.jackrabbit.vault.validation.spi.util.classloaderurl;
-/**
- * The FileVault validation framework SPI. Provides classes/interfaces to implement validators on FileVault packages.
- */
-@Version("1.1.0")
-package org.apache.jackrabbit.vault.validation.spi;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+public class ThreadContextClassLoaderURLStreamHandler extends URLStreamHandler {
+
+ @Override
+ protected URLConnection openConnection(URL url) throws IOException {
+ return new ClassLoaderUrlConnection(Thread.currentThread().getContextClassLoader(), url);
+ }
-import org.osgi.annotation.versioning.Version;
+}
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/package-info.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/util/classloaderurl/URLFactory.java
similarity index 52%
copy from vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/package-info.java
copy to vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/util/classloaderurl/URLFactory.java
index c72bdef..517168a 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/package-info.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/util/classloaderurl/URLFactory.java
@@ -14,11 +14,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.jackrabbit.vault.validation.spi.util.classloaderurl;
-/**
- * The FileVault validation framework SPI. Provides classes/interfaces to implement validators on FileVault packages.
- */
-@Version("1.1.0")
-package org.apache.jackrabbit.vault.validation.spi;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+public class URLFactory {
+ public static final String TCCL_PROTOCOL_PREFIX = "tccl:";
+
+ private URLFactory() {
+
+ }
-import org.osgi.annotation.versioning.Version;
+ public static URL createURL(String spec) throws MalformedURLException {
+ final URL url;
+ // which URLHandler to take
+ if (spec.startsWith(TCCL_PROTOCOL_PREFIX)) {
+ // use custom UrlStreamHandler
+ url = new URL(null, spec, new ThreadContextClassLoaderURLStreamHandler());
+ } else {
+ // use default UrlStreamHandler
+ url = new URL(spec);
+ }
+ return url;
+ }
+}
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/package-info.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/util/classloaderurl/package-info.java
similarity index 81%
copy from vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/package-info.java
copy to vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/util/classloaderurl/package-info.java
index c72bdef..c23cd0a 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/package-info.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/util/classloaderurl/package-info.java
@@ -14,11 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-/**
- * The FileVault validation framework SPI. Provides classes/interfaces to implement validators on FileVault packages.
- */
-@Version("1.1.0")
-package org.apache.jackrabbit.vault.validation.spi;
+@Version("1.0.0")
+package org.apache.jackrabbit.vault.validation.spi.util.classloaderurl;
import org.osgi.annotation.versioning.Version;
diff --git a/vault-validation/src/main/resources/default-nodetypes.cnd b/vault-validation/src/main/resources/default-nodetypes.cnd
new file mode 100644
index 0000000..bb31f77
--- /dev/null
+++ b/vault-validation/src/main/resources/default-nodetypes.cnd
@@ -0,0 +1,446 @@
+/*
+ * 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.
+ */
+// all node types defined by https://docs.adobe.com/docs/en/spec/jcr/2.0/3_Repository_Model.html#3.7.11%20Standard%20Application%20Node%20Types
+// as well as some Jackrabbit/Oak specific ones
+<'nt'='http://www.jcp.org/jcr/nt/1.0'>
+<'oak'='http://jackrabbit.apache.org/oak/ns/1.0'>
+<'jcr'='http://www.jcp.org/jcr/1.0'>
+<'mix'='http://www.jcp.org/jcr/mix/1.0'>
+<'rep'='internal'>
+<'vlt'='http://www.day.com/jcr/vault/1.0'>
+
+[oak:Unstructured]
+ - * (undefined) multiple
+ - * (undefined)
+ + * (nt:base) = oak:Unstructured version
+
+[nt:linkedFile] > nt:hierarchyNode
+ primaryitem jcr:content
+ - jcr:content (reference) mandatory
+
+[rep:Group] > rep:Authorizable, rep:MemberReferences
+ + rep:members (rep:Members) = rep:Members protected multiple version
+ + rep:membersList (rep:MemberReferencesList) = rep:MemberReferencesList protected
+
+[mix:lifecycle]
+ mixin
+ - jcr:lifecyclePolicy (reference) protected initialize
+ - jcr:currentLifecycleState (string) protected initialize
+
+[rep:User] > rep:Authorizable, rep:Impersonatable
+ - rep:password (string) protected
+ - rep:disabled (string) protected
+ + rep:pwd (rep:Password) = rep:Password protected
+
+[rep:Privileges]
+ - rep:next (long) mandatory protected multiple
+ + * (rep:Privilege) = rep:Privilege protected abort
+
+[nt:activity] > mix:referenceable
+ - jcr:activityTitle (string) mandatory autocreated protected
+
+[nt:childNodeDefinition]
+ - jcr:name (name) protected
+ - jcr:autoCreated (boolean) mandatory protected
+ - jcr:mandatory (boolean) mandatory protected
+ - jcr:onParentVersion (string) mandatory protected < 'COPY', 'VERSION', 'INITIALIZE', 'COMPUTE', 'IGNORE', 'ABORT'
+ - jcr:protected (boolean) mandatory protected
+ - jcr:requiredPrimaryTypes (name) = 'nt:base' mandatory protected multiple
+ - jcr:defaultPrimaryType (name) protected
+ - jcr:sameNameSiblings (boolean) mandatory protected
+
+[rep:PropertyDefinition] > nt:propertyDefinition
+ - rep:declaringNodeType (name) mandatory protected
+
+[rep:CugPolicy] > rep:Policy
+ - rep:principalNames (string) mandatory protected multiple ignore
+
+[nt:configuration] > mix:versionable
+ - jcr:root (reference) mandatory autocreated protected
+
+[mix:simpleVersionable]
+ mixin
+ - jcr:isCheckedOut (boolean) = 'true' mandatory autocreated protected ignore
+
+[mix:mimeType]
+ mixin
+ - jcr:mimeType (string)
+ - jcr:encoding (string)
+
+[nt:query]
+ - jcr:statement (string)
+ - jcr:language (string)
+
+[nt:nodeType]
+ - jcr:nodeTypeName (name) mandatory protected
+ - jcr:supertypes (name) protected multiple
+ - jcr:isAbstract (boolean) mandatory protected
+ - jcr:isQueryable (boolean) mandatory protected
+ - jcr:isMixin (boolean) mandatory protected
+ - jcr:hasOrderableChildNodes (boolean) mandatory protected
+ - jcr:primaryItemName (name) protected
+ + jcr:propertyDefinition (nt:propertyDefinition) = nt:propertyDefinition protected multiple
+ + jcr:childNodeDefinition (nt:childNodeDefinition) = nt:childNodeDefinition protected multiple
+
+[rep:system]
+ orderable
+ + jcr:versionStorage (rep:versionStorage) = rep:versionStorage mandatory protected abort
+ + jcr:nodeTypes (rep:nodeTypes) = rep:nodeTypes mandatory protected abort
+ + jcr:activities (rep:Activities) = rep:Activities mandatory protected abort
+ + jcr:configurations (rep:Configurations) = rep:Configurations protected abort
+ + * (nt:base) = nt:base ignore
+ + rep:privileges (rep:Privileges) = rep:Privileges protected abort
+
+[rep:Password]
+ - * (undefined) protected
+ - * (undefined) protected multiple
+
+[nt:unstructured]
+ orderable
+ - * (undefined) multiple
+ - * (undefined)
+ + * (nt:base) = nt:unstructured multiple version
+
+[mix:atomicCounter]
+ mixin
+ - oak:counter (long) = '0' autocreated protected
+
+[rep:ACE]
+ - rep:principalName (string) mandatory protected
+ - rep:privileges (name) mandatory protected multiple
+ - rep:nodePath (path) protected
+ - rep:glob (string) protected
+ - * (undefined) protected
+ + rep:restrictions (rep:Restrictions) = rep:Restrictions protected
+
+[rep:versionStorage]
+ - * (undefined) protected abort
+ - * (undefined) protected multiple abort
+ + * (nt:versionHistory) = nt:versionHistory protected abort
+ + * (rep:versionStorage) = rep:versionStorage protected abort
+
+[mix:indexable]
+ mixin
+ + oak:index (nt:base) = nt:unstructured
+
+[rep:Authorizable] > mix:referenceable, nt:hierarchyNode
+ abstract
+ - rep:principalName (string) mandatory protected
+ - rep:authorizableId (string) protected
+ - * (undefined)
+ - * (undefined) multiple
+ + * (nt:base) = nt:unstructured version
+
+[nt:frozenNode] > mix:referenceable
+ orderable
+ - jcr:frozenPrimaryType (name) mandatory autocreated protected abort
+ - jcr:frozenMixinTypes (name) protected multiple abort
+ - jcr:frozenUuid (string) mandatory autocreated protected abort
+ - * (undefined) protected abort
+ - * (undefined) protected multiple abort
+ + * (nt:base) protected multiple abort
+
+[mix:etag]
+ mixin
+ - jcr:etag (string) autocreated protected
+
+[rep:ChildNodeDefinition] > nt:childNodeDefinition
+ - rep:declaringNodeType (name) mandatory protected
+
+[nt:version] > mix:referenceable
+ - jcr:created (date) mandatory autocreated protected abort
+ - jcr:predecessors (reference) protected multiple abort < 'nt:version'
+ - jcr:successors (reference) protected multiple abort < 'nt:version'
+ - jcr:activity (reference) protected abort < 'nt:activity'
+ + jcr:frozenNode (nt:frozenNode) protected abort
+
+[rep:MemberReferencesList]
+ + * (rep:MemberReferences) = rep:MemberReferences protected
+
+[nt:versionLabels]
+ - * (reference) protected abort < 'nt:version'
+
+[mix:versionable] > mix:referenceable, mix:simpleVersionable
+ mixin
+ - jcr:versionHistory (reference) mandatory protected ignore < 'nt:versionHistory'
+ - jcr:baseVersion (reference) mandatory protected ignore < 'nt:version'
+ - jcr:predecessors (reference) mandatory protected multiple ignore < 'nt:version'
+ - jcr:mergeFailed (reference) protected multiple abort < 'nt:version'
+ - jcr:activity (reference) protected < 'nt:activity'
+ - jcr:configuration (reference) protected ignore < 'nt:configuration'
+
+[rep:PrincipalAccessControl] > rep:AccessControl
+ + rep:policy (rep:Policy) protected ignore
+
+[rep:Policy]
+ abstract
+
+[rep:Configurations]
+ + * (nt:configuration) = nt:configuration abort
+ + * (rep:Configurations) = rep:Configurations abort
+
+[rep:Activities]
+ + * (nt:activity) = nt:activity protected abort
+ + * (rep:Activities) = rep:Activities protected abort
+
+[rep:Token] > mix:referenceable
+ - rep:token.key (string) mandatory protected
+ - rep:token.exp (date) mandatory protected
+ - * (undefined) protected
+ - * (undefined) protected multiple
+
+[rep:Impersonatable]
+ mixin
+ - rep:impersonators (string) protected multiple
+
+[nt:hierarchyNode] > mix:created
+ abstract
+
+[vlt:PackageDefinition] > nt:unstructured
+ orderable
+ - artifactId (string)
+ - jcr:created (date)
+ - jcr:createdBy (string)
+ - jcr:lastModified (date)
+ - lastUnpackedBy (string)
+ - jcr:description (string)
+ - groupId (string)
+ - lastUnpacked (date)
+ - version (string)
+ - jcr:lastModifiedBy (string)
+ - dependencies (string) multiple
+ + thumbnail (nt:base) = nt:unstructured
+ + filter (nt:base) = nt:unstructured
+
+[nt:resource] > mix:lastModified, mix:mimeType, mix:referenceable
+ primaryitem jcr:data
+ - jcr:data (binary) mandatory
+
+[nt:file] > nt:hierarchyNode
+ primaryitem jcr:content
+ + jcr:content (nt:base) mandatory
+
+[rep:VersionablePaths]
+ mixin
+ - * (path) protected abort
+
+[mix:lockable]
+ mixin
+ - jcr:lockOwner (string) protected ignore
+ - jcr:lockIsDeep (boolean) protected ignore
+
+[rep:MergeConflict]
+ mixin primaryitem rep:ours
+ + rep:ours (rep:Unstructured) protected ignore
+
+[oak:QueryIndexDefinition] > oak:Unstructured
+ - type (string) mandatory
+ - async (string)
+ - reindex (boolean) ignore
+
+[nt:base]
+ abstract
+ - jcr:primaryType (name) mandatory autocreated protected compute
+ - jcr:mixinTypes (name) protected multiple compute
+
+[rep:Cache] > rep:UnstructuredProtected
+ - rep:expiration (long) protected ignore
+
+[mix:title]
+ mixin
+ - jcr:title (string)
+ - jcr:description (string)
+
+[rep:root] > nt:unstructured
+ + jcr:system (rep:system) = rep:system mandatory ignore
+
+[nt:address]
+ - jcr:protocol (string)
+ - jcr:host (string)
+ - jcr:port (string)
+ - jcr:repository (string)
+ - jcr:workspace (string)
+ - jcr:path (path)
+ - jcr:id (weakreference)
+
+[rep:Unstructured]
+ - * (undefined) multiple ignore
+ - * (undefined) ignore
+ + * (nt:base) = rep:Unstructured ignore
+
+[rep:ACL] > rep:Policy
+ orderable
+ + * (rep:ACE) = rep:GrantACE protected ignore
+
+[rep:Privilege]
+ - rep:isAbstract (boolean) protected
+ - rep:aggregates (name) protected multiple
+ - rep:bits (long) mandatory protected multiple
+
+[rep:MemberReferences]
+ - rep:members (weakreference) protected multiple < 'rep:Authorizable'
+
+[rep:PropertyDefinitions]
+ + * (rep:PropertyDefinition) = rep:PropertyDefinition protected
+
+[mix:referenceable]
+ mixin
+ - jcr:uuid (string) mandatory autocreated protected initialize
+
+[mix:lastModified]
+ mixin
+ - jcr:lastModified (date) autocreated
+ - jcr:lastModifiedBy (string) autocreated
+
+[rep:Members]
+ orderable
+ - * (weakreference) protected < 'rep:Authorizable'
+ + * (rep:Members) = rep:Members protected multiple
+
+[vlt:FullCoverage]
+ mixin
+
+[rep:Permissions]
+ - * (undefined) protected ignore
+ - * (undefined) protected multiple ignore
+ + * (rep:Permissions) = rep:Permissions protected ignore
+
+[mix:created]
+ mixin
+ - jcr:created (date) autocreated protected
+ - jcr:createdBy (string) autocreated protected
+
+[nt:folder] > nt:hierarchyNode
+ + * (nt:hierarchyNode) version
+
+[nt:propertyDefinition]
+ - jcr:name (name) protected
+ - jcr:autoCreated (boolean) mandatory protected
+ - jcr:mandatory (boolean) mandatory protected
+ - jcr:onParentVersion (string) mandatory protected < 'COPY', 'VERSION', 'INITIALIZE', 'COMPUTE', 'IGNORE', 'ABORT'
+ - jcr:protected (boolean) mandatory protected
+ - jcr:requiredType (string) mandatory protected < 'STRING', 'URI', 'BINARY', 'LONG', 'DOUBLE', 'DECIMAL', 'BOOLEAN', 'DATE', 'NAME', 'PATH', 'REFERENCE', 'WEAKREFERENCE', 'UNDEFINED'
+ - jcr:valueConstraints (string) protected multiple
+ - jcr:defaultValues (undefined) protected multiple
+ - jcr:multiple (boolean) mandatory protected
+ - jcr:availableQueryOperators (name) mandatory protected multiple
+ - jcr:isFullTextSearchable (boolean) mandatory protected
+ - jcr:isQueryOrderable (boolean) mandatory protected
+
+[rep:NodeType] > nt:nodeType
+ - rep:supertypes (name) autocreated protected multiple
+ - rep:primarySubtypes (name) autocreated protected multiple
+ - rep:mixinSubtypes (name) autocreated protected multiple
+ - rep:mandatoryProperties (name) autocreated protected multiple
+ - rep:mandatoryChildNodes (name) autocreated protected multiple
+ - rep:protectedProperties (name) autocreated protected multiple
+ - rep:protectedChildNodes (name) autocreated protected multiple
+ - rep:hasProtectedResidualProperties (boolean) autocreated protected
+ - rep:hasProtectedResidualChildNodes (boolean) autocreated protected
+ - rep:namedSingleValuedProperties (name) autocreated protected multiple
+ + rep:namedPropertyDefinitions (rep:NamedPropertyDefinitions) = rep:NamedPropertyDefinitions protected
+ + rep:residualPropertyDefinitions (rep:PropertyDefinitions) = rep:PropertyDefinitions protected
+ + rep:namedChildNodeDefinitions (rep:NamedChildNodeDefinitions) = rep:NamedChildNodeDefinitions protected
+ + rep:residualChildNodeDefinitions (rep:ChildNodeDefinitions) = rep:ChildNodeDefinitions protected
+
+[mix:shareable] > mix:referenceable
+ mixin
+
+[rep:AccessControl]
+ + * (rep:AccessControl) protected ignore
+ + * (rep:PrincipalAccessControl) protected ignore
+
+[rep:SystemUser] > rep:User
+
+[mix:language]
+ mixin
+ - jcr:language (string)
+
+[rep:VersionReference]
+ mixin
+ - rep:versions (reference) protected multiple
+
+[rep:PermissionStore]
+ - rep:accessControlledPath (string) protected ignore
+ - rep:numPermissions (long) protected ignore
+ - rep:modCount (long) protected ignore
+ + * (rep:PermissionStore) = rep:PermissionStore protected ignore
+ + * (rep:Permissions) = rep:Permissions protected ignore
+
+[rep:UnstructuredProtected]
+ abstract
+ - * (undefined) protected multiple ignore
+ - * (undefined) protected ignore
+ + * (rep:UnstructuredProtected) protected ignore
+
+[oak:Resource] > mix:lastModified, mix:mimeType
+ primaryitem jcr:data
+ - jcr:data (binary) mandatory
+
+[rep:NamedPropertyDefinitions]
+ + * (rep:PropertyDefinitions) = rep:PropertyDefinitions protected
+
+[rep:nodeTypes]
+ + * (nt:nodeType) = nt:nodeType protected abort
+
+[rep:AccessControllable]
+ mixin
+ + rep:policy (rep:Policy) protected ignore
+
+[rep:NamedChildNodeDefinitions]
+ + * (rep:ChildNodeDefinitions) = rep:ChildNodeDefinitions protected
+
+[nt:versionHistory] > mix:referenceable
+ - jcr:versionableUuid (string) mandatory autocreated protected abort
+ - jcr:copiedFrom (weakreference) protected abort < 'nt:version'
+ + jcr:rootVersion (nt:version) = nt:version mandatory autocreated protected abort
+ + jcr:versionLabels (nt:versionLabels) = nt:versionLabels mandatory autocreated protected abort
+ + * (nt:version) = nt:version protected abort
+
+[rep:RetentionManageable]
+ mixin
+ - rep:hold (undefined) protected multiple ignore
+ - rep:retentionPolicy (undefined) protected ignore
+
+[rep:RepoAccessControllable]
+ mixin
+ + rep:repoPolicy (rep:Policy) protected ignore
+
+[rep:GrantACE] > rep:ACE
+
+[rep:ChildNodeDefinitions]
+ + * (rep:ChildNodeDefinition) = rep:ChildNodeDefinition protected
+
+[vlt:HierarchyNode] > nt:hierarchyNode
+ mixin
+
+[nt:versionedChild]
+ - jcr:childVersionHistory (reference) mandatory autocreated protected abort < 'nt:versionHistory'
+
+[vlt:Package]
+ orderable mixin
+ + vlt:definition (nt:base) = vlt:PackageDefinition
+
+[rep:DenyACE] > rep:ACE
+
+[rep:AuthorizableFolder] > nt:hierarchyNode
+ + * (rep:Authorizable) = rep:User version
+ + * (rep:AuthorizableFolder) = rep:AuthorizableFolder version
+
+[rep:Restrictions]
+ - * (undefined) protected
+ - * (undefined) protected multiple
diff --git a/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/ValidationExecutorTest.java b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/ValidationExecutorTest.java
index 606456f..4477a3f 100644
--- a/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/ValidationExecutorTest.java
+++ b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/ValidationExecutorTest.java
@@ -247,9 +247,13 @@ public class ValidationExecutorTest {
}
public static void assertViolation(Collection<? extends ValidationMessage> messages, ValidationMessageSeverity thresholdSeverity, ValidationMessage... violations) {
- List<ValidationMessage> filteredMessages = messages.stream()
- .filter(m -> m.getSeverity().ordinal() >= thresholdSeverity.ordinal()).collect(Collectors.toList());
- Assert.assertThat(filteredMessages, Matchers.contains(violations));
+ if (messages == null) {
+ Assert.fail("No violations found at all!");
+ } else {
+ List<ValidationMessage> filteredMessages = messages.stream()
+ .filter(m -> m.getSeverity().ordinal() >= thresholdSeverity.ordinal()).collect(Collectors.toList());
+ Assert.assertThat(filteredMessages, Matchers.contains(violations));
+ }
}
public static void assertViolation(Collection<? extends ValidationMessage> messages, ValidationMessage... violations) {
diff --git a/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/DocumentViewParserValidatorTest.java b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/DocumentViewParserValidatorTest.java
new file mode 100644
index 0000000..6dbdb9f
--- /dev/null
+++ b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/DocumentViewParserValidatorTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.validation.spi.impl;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+
+public class DocumentViewParserValidatorTest {
+
+ @Test
+ public void testGetDocumentViewXmlRootPathFromContentXml() throws IOException {
+ Path filePath = Paths.get("test", "parent", ".content.xml");
+ Assert.assertEquals(Paths.get("test", "parent"), DocumentViewParserValidator.getDocumentViewXmlRootPath(null, filePath));
+ }
+
+ @Test
+ public void testGetDocumentViewXmlRootPathFromContentXmlBelowDotDir() throws IOException {
+ // http://jackrabbit.apache.org/filevault/vaultfs.html#Extended_File_aggregates
+ Path filePath = Paths.get("test", "parent.dir", ".content.xml");
+ Assert.assertEquals(Paths.get("test", "parent"), DocumentViewParserValidator.getDocumentViewXmlRootPath(null, filePath));
+ }
+}
diff --git a/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/PrimaryNodeTypeValidatorTest.java b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/PrimaryNodeTypeValidatorTest.java
deleted file mode 100644
index 6b4467c..0000000
--- a/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/PrimaryNodeTypeValidatorTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.vault.validation.spi.impl;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Paths;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.PropertyType;
-
-import org.apache.jackrabbit.vault.fs.config.ConfigurationException;
-import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
-import org.apache.jackrabbit.vault.util.DocViewNode;
-import org.apache.jackrabbit.vault.util.DocViewProperty;
-import org.apache.jackrabbit.vault.validation.AnyValidationMessageMatcher;
-import org.apache.jackrabbit.vault.validation.ValidationExecutorTest;
-import org.apache.jackrabbit.vault.validation.spi.ValidationMessage;
-import org.apache.jackrabbit.vault.validation.spi.ValidationMessageSeverity;
-import org.apache.jackrabbit.vault.validation.spi.impl.PrimaryNodeTypeValidator;
-import org.junit.Assert;
-import org.junit.Test;
-
-
-public class PrimaryNodeTypeValidatorTest {
-
- private PrimaryNodeTypeValidator validator;
-
- @Test
- public void testNodeTypes() throws IOException, ConfigurationException {
- DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
- try (InputStream input = this.getClass().getResourceAsStream("/filter.xml")) {
- filter.load(input);
- }
- validator = new PrimaryNodeTypeValidator(ValidationMessageSeverity.ERROR, filter);
- Map<String, DocViewProperty> props = new HashMap<>();
- props.put("prop1", new DocViewProperty("prop1", new String[] { "value1" } , false, PropertyType.STRING));
-
- // order node only (no other property)
- DocViewNode node = new DocViewNode("jcr:root", "jcr:root", null, Collections.emptyMap(), null, null);
- Assert.assertThat(validator.validate(node, "/apps/test", Paths.get("/some/path"), false), AnyValidationMessageMatcher.noValidationInCollection());
-
- // primary node type set with additional properties
- node = new DocViewNode("jcr:root", "jcr:root", null, props, null, "nt:unstructured");
- Assert.assertThat(validator.validate(node, "/apps/test", Paths.get("/some/path"), false), AnyValidationMessageMatcher.noValidationInCollection());
-
- // missing node type but not contained in filter (with properties)
- node = new DocViewNode("jcr:root", "jcr:root", null, props, null, null);
- Assert.assertThat(validator.validate(node, "/apps/test2/invalid", Paths.get("/some/path"), false), AnyValidationMessageMatcher.noValidationInCollection());
-
- // missing node type and contained in filter (with properties)
- ValidationExecutorTest.assertViolation(
- validator.validate(node, "/apps/test", Paths.get("/some/path"), false),
- new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(PrimaryNodeTypeValidator.MESSAGE_MISSING_PRIMARY_TYPE, "/apps/test")));
- }
-}
diff --git a/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeValidatorTest.java b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeValidatorTest.java
new file mode 100644
index 0000000..b0a7220
--- /dev/null
+++ b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/spi/impl/nodetype/NodeTypeValidatorTest.java
@@ -0,0 +1,209 @@
+/*
+ * 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.validation.spi.impl.nodetype;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.commons.cnd.ParseException;
+import org.apache.jackrabbit.jcr2spi.nodetype.EffectiveNodeType;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
+import org.apache.jackrabbit.vault.fs.config.ConfigurationException;
+import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
+import org.apache.jackrabbit.vault.util.DocViewNode;
+import org.apache.jackrabbit.vault.util.DocViewProperty;
+import org.apache.jackrabbit.vault.validation.AnyValidationMessageMatcher;
+import org.apache.jackrabbit.vault.validation.ValidationExecutorTest;
+import org.apache.jackrabbit.vault.validation.spi.NodeContext;
+import org.apache.jackrabbit.vault.validation.spi.ValidationMessage;
+import org.apache.jackrabbit.vault.validation.spi.ValidationMessageSeverity;
+import org.apache.jackrabbit.vault.validation.spi.util.NodeContextImpl;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class NodeTypeValidatorTest {
+
+ private NodeTypeValidator validator;
+ private DefaultWorkspaceFilter filter;
+
+ @Before
+ public void setUp() throws IOException, ConfigurationException, RepositoryException, ParseException {
+ filter = new DefaultWorkspaceFilter();
+ try (InputStream input = this.getClass().getResourceAsStream("/filter.xml")) {
+ filter.load(input);
+ }
+ validator = createValidator(filter, JcrConstants.NT_FOLDER);
+ }
+
+ static NodeTypeValidator createValidator(WorkspaceFilter filter, String defaultNodeType)
+ throws IOException, RepositoryException, ParseException {
+ NodeTypeManagerProvider ntManagerProvider = new NodeTypeManagerProvider();
+ EffectiveNodeType defaultEffectiveNodeType = ntManagerProvider.getEffectiveNodeTypeProvider()
+ .getEffectiveNodeType(ntManagerProvider.getNameResolver().getQName(defaultNodeType));
+ return new NodeTypeValidator(filter, ntManagerProvider, defaultEffectiveNodeType, ValidationMessageSeverity.ERROR,
+ ValidationMessageSeverity.WARN);
+ }
+
+ @Test
+ @Ignore
+ public void testValidateComplexUnstructuredNodeTypes() throws IOException, RepositoryException, ParseException, ConfigurationException {
+ NodeContext nodeContext = new NodeContextImpl("/apps/test/node4", Paths.get("node4"), Paths.get(""));
+
+ Map<String, DocViewProperty> props = new HashMap<>();
+ DocViewProperty property = new DocViewProperty("{}prop1", new String[] { "value1" }, false, PropertyType.STRING);
+ props.put("{}prop1", property);
+ props.put(NameConstants.JCR_PRIMARYTYPE.toString(), new DocViewProperty(NameConstants.JCR_PRIMARYTYPE.toString(),
+ new String[] { "nt:unstructured" }, false, PropertyType.STRING));
+
+ // no primary type
+ DocViewNode node = new DocViewNode("jcr:root", "jcr:root", null, props, null, "sling:Folder");
+ Assert.assertNull(validator.validate(node, nodeContext, false));
+
+ props.put(NameConstants.JCR_PRIMARYTYPE.toString(),
+ new DocViewProperty(NameConstants.JCR_PRIMARYTYPE.toString(), new String[] { "value1" }, false, PropertyType.STRING));
+ node = new DocViewNode("test", "test", null, props, null, "nt:folder");
+ ValidationExecutorTest.assertViolation(validator.validate(node, nodeContext, false),
+ new ValidationMessage(ValidationMessageSeverity.ERROR,
+ String.format(NodeTypeValidator.MESSAGE_PROPERTY_NOT_ALLOWED, property, "nt:folder",
+ "No property definition found for name!")));
+ }
+
+ @Test
+ public void testInvalidChildNodeTypeBelowDefault() {
+ NodeContext nodeContext = new NodeContextImpl("/apps/test/node4", Paths.get("node4"), Paths.get(""));
+
+ Map<String, DocViewProperty> props = new HashMap<>();
+ props.put(NameConstants.JCR_PRIMARYTYPE.toString(), new DocViewProperty(NameConstants.JCR_PRIMARYTYPE.toString(),
+ new String[] { JcrConstants.NT_UNSTRUCTURED }, false, PropertyType.STRING));
+ // nt:unstructured below nt:folder is not allowed
+ DocViewNode node = new DocViewNode("jcr:root", "jcr:root", null, props, null, JcrConstants.NT_UNSTRUCTURED);
+ ValidationExecutorTest.assertViolation(validator.validate(node, nodeContext, false),
+ new ValidationMessage(ValidationMessageSeverity.ERROR,
+ String.format(NodeTypeValidator.MESSAGE_CHILD_NODE_OF_NOT_CONTAINED_PARENT_POTENTIALLY_NOT_ALLOWED,
+ "jcr:root [nt:unstructured]", JcrConstants.NT_FOLDER,
+ "Could not find matching child node definition in parent's node type")));
+ }
+
+ @Test
+ public void testMissingMandatoryChildNode() {
+ NodeContext nodeContext = new NodeContextImpl("/apps/test/node4", Paths.get("node4"), Paths.get(""));
+
+ Map<String, DocViewProperty> props = new HashMap<>();
+ props.put(NameConstants.JCR_PRIMARYTYPE.toString(), new DocViewProperty(NameConstants.JCR_PRIMARYTYPE.toString(),
+ new String[] { JcrConstants.NT_FILE }, false, PropertyType.STRING));
+ DocViewNode node = new DocViewNode("jcr:root", "jcr:root", null, props, null, JcrConstants.NT_FILE);
+ Assert.assertThat(validator.validate(node, nodeContext, false), AnyValidationMessageMatcher.noValidationInCollection());
+
+ ValidationExecutorTest.assertViolation(validator.validateEnd(node, nodeContext, false),
+ new ValidationMessage(ValidationMessageSeverity.ERROR,
+ String.format(NodeTypeValidator.MESSAGE_MANDATORY_CHILD_NODE_MISSING,
+ "jcr:content [nt:base]")));
+ }
+
+ @Test
+ public void testNotAllowedProperty() {
+ NodeContext nodeContext = new NodeContextImpl("/apps/test/node4", Paths.get("node4"), Paths.get(""));
+
+ Map<String, DocViewProperty> props = new HashMap<>();
+ DocViewProperty prop = new DocViewProperty("{}invalid-prop", new String[] { "some-value" }, false, PropertyType.STRING);
+ props.put("{}invalid-prop", prop);
+ props.put(NameConstants.JCR_PRIMARYTYPE.toString(), new DocViewProperty(NameConstants.JCR_PRIMARYTYPE.toString(),
+ new String[] { JcrConstants.NT_FILE }, false, PropertyType.STRING));
+ // nt:file is only supposed to have jcr:created property
+ DocViewNode node = new DocViewNode("jcr:root", "jcr:root", null, props, null, JcrConstants.NT_FILE);
+ ValidationExecutorTest.assertViolation(validator.validate(node, nodeContext, false),
+ new ValidationMessage(ValidationMessageSeverity.ERROR,
+ String.format(NodeTypeValidator.MESSAGE_PROPERTY_NOT_ALLOWED, prop, JcrConstants.NT_FILE,
+ "No property definition found for name!")));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testPropertyWitInconvertibleValue() {
+ NodeContext nodeContext = new NodeContextImpl("/apps/test/node4", Paths.get("node4"), Paths.get(""));
+
+ Map<String, DocViewProperty> props = new HashMap<>();
+ DocViewProperty prop = new DocViewProperty(Property.JCR_CREATED, new String[] { "some-invalid-value" }, true, PropertyType.DATE);
+ props.put(Property.JCR_CREATED, prop);
+ props.put(NameConstants.JCR_PRIMARYTYPE.toString(), new DocViewProperty(NameConstants.JCR_PRIMARYTYPE.toString(),
+ new String[] { JcrConstants.NT_FILE }, false, PropertyType.STRING));
+ // nt:file is only supposed to have jcr:created property
+ DocViewNode node = new DocViewNode("jcr:root", "jcr:root", null, props, null, JcrConstants.NT_FILE);
+ validator.validate(node, nodeContext, false);
+ }
+
+ @Test
+ public void testUnknownNamespace() {
+ NodeContext nodeContext = new NodeContextImpl("/apps/test/node4", Paths.get("node4"), Paths.get(""));
+
+ Map<String, DocViewProperty> props = new HashMap<>();
+ DocViewProperty prop = new DocViewProperty("{}invalid-prop", new String[] { "some-value" }, false, PropertyType.STRING);
+ props.put("{}invalid-prop", prop);
+ props.put(NameConstants.JCR_PRIMARYTYPE.toString(), new DocViewProperty(NameConstants.JCR_PRIMARYTYPE.toString(),
+ new String[] { "sling:Folder" }, false, PropertyType.STRING));
+ // nt:file is only supposed to have jcr:created property
+ DocViewNode node = new DocViewNode("jcr:root", "jcr:root", null, props, null, "sling:Folder");
+ ValidationExecutorTest.assertViolation(validator.validate(node, nodeContext, false),
+ new ValidationMessage(ValidationMessageSeverity.WARN,
+ String.format(NodeTypeValidator.MESSAGE_UNKNOWN_NODE_TYPE_OR_NAMESPACE,
+ "sling: is not a registered namespace prefix.")));
+ }
+
+ @Test
+ public void testExistenceOfPrimaryNodeTypes() throws IOException, ConfigurationException, RepositoryException, ParseException {
+ validator = createValidator(filter, NodeType.NT_UNSTRUCTURED);
+ Map<String, DocViewProperty> props = new HashMap<>();
+ props.put("{}prop1", new DocViewProperty("{}prop1", new String[] { "value1" }, false, PropertyType.STRING));
+
+ // order node only (no other property)
+ DocViewNode node = new DocViewNode("jcr:root", "jcr:root", null, Collections.emptyMap(), null, null);
+ Assert.assertThat(validator.validate(node, new NodeContextImpl("/apps/test", Paths.get("/some/path"), Paths.get("")), false),
+ AnyValidationMessageMatcher.noValidationInCollection());
+
+ // missing node type but not contained in filter (with properties)
+ node = new DocViewNode("jcr:root", "jcr:root", null, props, null, null);
+ Assert.assertThat(
+ validator.validate(node, new NodeContextImpl("/apps/test2/invalid", Paths.get("/some/path"), Paths.get("")), false),
+ AnyValidationMessageMatcher.noValidationInCollection());
+
+ // missing node type and contained in filter (with properties)
+ ValidationExecutorTest.assertViolation(
+ validator.validate(node, new NodeContextImpl("/apps/test", Paths.get("/some/path"), Paths.get("")), false),
+ new ValidationMessage(ValidationMessageSeverity.ERROR,
+ String.format(NodeTypeValidator.MESSAGE_MISSING_PRIMARY_TYPE, "/apps/test")));
+
+ // primary node type set with additional properties
+ props.put(NameConstants.JCR_PRIMARYTYPE.toString(), new DocViewProperty(NameConstants.JCR_PRIMARYTYPE.toString(),
+ new String[] { "nt:unstructured" }, false, PropertyType.STRING));
+ node = new DocViewNode("jcr:root", "jcr:root", null, props, null, "nt:unstructured");
+ Assert.assertThat(validator.validate(node, new NodeContextImpl("/apps/test", Paths.get("/some/path"), Paths.get("")), false),
+ AnyValidationMessageMatcher.noValidationInCollection());
+
+ }
+}