/* * 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.test.api.nodetype; import org.apache.jackrabbit.test.AbstractJCRTest; import org.apache.jackrabbit.test.NotExecutableException; import org.apache.jackrabbit.test.api.PropertyUtil; import javax.jcr.Session; import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.PropertyType; import javax.jcr.Value; import javax.jcr.NodeIterator; import javax.jcr.NamespaceException; import javax.jcr.nodetype.NodeTypeManager; import javax.jcr.nodetype.NodeTypeIterator; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.PropertyDefinition; import java.util.regex.Pattern; import java.util.regex.Matcher; /** * Tests if property definitions are properly defined. * */ public class PropertyDefTest extends AbstractJCRTest { // format: '(<min>, <max>)', '[<min>, <max>]', '(, <max>)' etc. private static final Pattern CONSTRAINTSPATTERN_BINARY = Pattern.compile("([\\(\\[]) *\\d* *, *\\d* *([\\)\\]])"); // format: '(<min>, <max>)', '[<min>, <max>]', '(, <max>)' etc. private static final Pattern CONSTRAINTSPATTERN_LONG = Pattern.compile("([\\(\\[]) *(\\-?\\d*)? *, *(\\-?\\d*)? *([\\)\\]])"); // format: '(<min>, <max>)', '[<min>, <max>]', '(, <max>)' etc. private static final Pattern CONSTRAINTSPATTERN_DOUBLE = Pattern.compile("([\\(\\[]) *(\\-?\\d+\\.?\\d*)? *, *(\\-?\\d+\\.?\\d*)? *([\\)\\]])"); // format: '(<min>, <max>)', '[<min>, <max>]', '(, <max>)' etc. private static final Pattern CONSTRAINTSPATTERN_DATE = Pattern.compile("([\\(\\[]) *(" + PropertyUtil.PATTERNSTRING_DATE + ")? *, *" + "(" + PropertyUtil.PATTERNSTRING_DATE + ")? *([\\)\\]])"); private static final Pattern CONSTRAINTSPATTERN_PATH = Pattern.compile(PropertyUtil.PATTERNSTRING_PATH_WITHOUT_LAST_SLASH + "(/|/\\*)?"); /** * The session we use for the tests */ private Session session; /** * The node type manager of the session */ private NodeTypeManager manager; /** * If <code>true</code> indicates that the test found a mandatory property */ private boolean foundMandatoryProperty = false; /** * Sets up the fixture for the test cases. */ protected void setUp() throws Exception { isReadOnly = true; super.setUp(); session = getHelper().getReadOnlySession(); manager = session.getWorkspace().getNodeTypeManager(); // re-fetch testRootNode with read-only session testRootNode = (Node) session.getItem(testRoot); } /** * Releases the session aquired in {@link #setUp()}. */ protected void tearDown() throws Exception { if (session != null) { session.logout(); session = null; } manager = null; super.tearDown(); } /** * Test getDeclaringNodeType() returns the node type which is defining the * requested property def. Test runs for all existing node types. */ public void testGetDeclaringNodeType() throws RepositoryException { NodeTypeIterator types = manager.getAllNodeTypes(); // loop all node types while (types.hasNext()) { NodeType currentType = types.nextNodeType(); PropertyDefinition defsOfCurrentType[] = currentType.getPropertyDefinitions(); // loop all property defs of each node type for (int i = 0; i < defsOfCurrentType.length; i++) { PropertyDefinition def = defsOfCurrentType[i]; NodeType type = def.getDeclaringNodeType(); // check if def is part of the property defs of the // declaring node type PropertyDefinition defs[] = type.getPropertyDefinitions(); boolean hasType = false; for (int j = 0; j < defs.length; j++) { if (defs[j].getName().equals(def.getName())) { hasType = true; break; } } assertTrue("getDeclaringNodeType() must return the node " + "which defines the corresponding property def.", hasType); } } } /** * Tests if auto create properties are not a residual set definition * (getName() does not return "*") */ public void testIsAutoCreate() throws RepositoryException { NodeTypeIterator types = manager.getAllNodeTypes(); // loop all node types while (types.hasNext()) { NodeType type = types.nextNodeType(); PropertyDefinition defs[] = type.getPropertyDefinitions(); for (int i = 0; i < defs.length; i++) { if (defs[i].isAutoCreated()) { assertFalse("An auto create property must not be a " + "residual set definition.", defs[i].getName().equals("*")); } } } } /** * This test checks if item definitions with mandatory constraints are * respected. * <p> * If the default workspace does not contain a node with a node type * definition that specifies a mandatory property a {@link * org.apache.jackrabbit.test.NotExecutableException} is thrown. */ public void testIsMandatory() throws RepositoryException, NotExecutableException { traverse(testRootNode); if (!foundMandatoryProperty) { throw new NotExecutableException("Workspace does not contain any node with a mandatory property definition"); } } /** * Tests if isRequiredType() returns a valid PropertyType. * <p> * The test runs for all available node types. */ public void testIsRequiredType() throws RepositoryException { NodeTypeIterator types = manager.getAllNodeTypes(); // loop all node types while (types.hasNext()) { NodeType type = types.nextNodeType(); PropertyDefinition defs[] = type.getPropertyDefinitions(); for (int i = 0; i < defs.length; i++) { switch (defs[i].getRequiredType()) { case PropertyType.STRING: case PropertyType.BINARY: case PropertyType.DATE: case PropertyType.LONG: case PropertyType.DOUBLE: case PropertyType.NAME: case PropertyType.PATH: case PropertyType.REFERENCE: case PropertyType.BOOLEAN: case PropertyType.UNDEFINED: case PropertyType.WEAKREFERENCE: case PropertyType.DECIMAL: case PropertyType.URI: // success break; default: fail("getRequiredType() returns an " + "invalid PropertyType."); } } } } /** * Tests if value constraints match the pattern specified by the required * property type. * <p> * The test runs for all value constraints of all properties of all * available node types. */ public void testGetValueConstraints() throws RepositoryException { NodeTypeIterator types = manager.getAllNodeTypes(); // loop all node types while (types.hasNext()) { NodeType type = types.nextNodeType(); PropertyDefinition defs[] = type.getPropertyDefinitions(); for (int i = 0; i < defs.length; i++) { PropertyDefinition def = defs[i]; String constraints[] = def.getValueConstraints(); if (constraints != null) { for (int j = 0; j < constraints.length; j++) { Matcher matcher; switch (defs[i].getRequiredType()) { case PropertyType.STRING: case PropertyType.UNDEFINED: // any value matches break; case PropertyType.BINARY: matcher = CONSTRAINTSPATTERN_BINARY.matcher(constraints[j]); assertTrue("Value constraint does not match " + "the pattern of PropertyType.BINARY ", matcher.matches()); break; case PropertyType.DATE: matcher = CONSTRAINTSPATTERN_DATE.matcher(constraints[j]); assertTrue("Value constraint does not match " + "the pattern of PropertyType.DATE ", matcher.matches()); break; case PropertyType.LONG: matcher = CONSTRAINTSPATTERN_LONG.matcher(constraints[j]); assertTrue("Value constraint does not match " + "the pattern of PropertyType.LONG", matcher.matches()); break; case PropertyType.DOUBLE: matcher = CONSTRAINTSPATTERN_DOUBLE.matcher(constraints[j]); assertTrue("Value constraint does not match " + "the pattern of PropertyType.DOUBLE", matcher.matches()); break; case PropertyType.NAME: matcher = PropertyUtil.PATTERN_NAME.matcher(constraints[j]); assertTrue("Value constraint does not match " + "the pattern of PropertyType.NAME", matcher.matches()); checkPrefix(constraints[j]); break; case PropertyType.PATH: matcher = CONSTRAINTSPATTERN_PATH.matcher(constraints[j]); assertTrue("Value constraint does not match " + "the pattern of PropertyType.PATH", matcher.matches()); String elems[] = constraints[j].split("/"); for (int k = 0; k < elems.length; k++) { checkPrefix(elems[k]); } break; case PropertyType.REFERENCE: matcher = PropertyUtil.PATTERN_NAME.matcher(constraints[j]); assertTrue("Value constraint does not match " + "the pattern of PropertyType.REFERENCE", matcher.matches()); checkPrefix(constraints[j]); break; case PropertyType.BOOLEAN: assertTrue("Value constraint does not match " + "the pattern of PropertyType.BOOLEAN", constraints[j].equals("true") || constraints[j].equals("false")); break; } } } } } } /** * Tests if single-valued properties do have not more than one default * value. * <p> * The test runs for all default values of all properties of all available * node types. */ public void testGetDefaultValues() throws RepositoryException { NodeTypeIterator types = manager.getAllNodeTypes(); // loop all node types while (types.hasNext()) { NodeType type = types.nextNodeType(); PropertyDefinition defs[] = type.getPropertyDefinitions(); for (int i = 0; i < defs.length; i++) { PropertyDefinition def = defs[i]; Value values[] = def.getDefaultValues(); if (values != null) { for (int j = 0; j < values.length; j++) { if (!def.isMultiple()) { assertEquals( "Single-valued property " + type.getName() +"/" + def.getName() + " must not have more than one default value.", 1, values.length); } } } } } } // ---------------------------------< internal >---------------------------- /** * Traverses the node hierarchy and applies * {@link #checkMandatoryConstraint(javax.jcr.Node, javax.jcr.nodetype.NodeType)} * to all descendant nodes of <code>parentNode</code>. */ private void traverse(Node parentNode) throws RepositoryException { NodeIterator nodes = parentNode.getNodes(); while (nodes.hasNext()) { Node node = nodes.nextNode(); NodeType primeType = node.getPrimaryNodeType(); checkMandatoryConstraint(node, primeType); NodeType mixins[] = node.getMixinNodeTypes(); for (int i = 0; i < mixins.length; i++) { checkMandatoryConstraint(node, mixins[i]); } traverse(node); } } /** * Checks if mandatory property definitions are respected. */ private void checkMandatoryConstraint(Node node, NodeType type) throws RepositoryException { // test if node contains all mandatory properties of current type PropertyDefinition propDefs[] = type.getPropertyDefinitions(); for (int i = 0; i < propDefs.length; i++) { PropertyDefinition propDef = propDefs[i]; if (propDef.isMandatory()) { foundMandatoryProperty = true; String name = propDef.getName(); assertTrue("Node " + node.getPath() + " does not contain " + "value for mandatory property: " + name, node.hasProperty(name)); // todo check back with latest spec! /* try { Property p = node.getProperty(name); if (propDef.isMultiple()) { // empty array fails assertFalse("The mandatory and multiple property " + p.getName() + " must not be empty.", p.getValues().length == 0); } else { // empty value fails assertNotNull("A mandatory property must have a value", p.getValue()); } } catch (PathNotFoundException e) { fail("Mandatory property " + name + " does not exist."); } */ } } } /** * Checks for NAME, PATH and REFERENCE constraint values if the constraint * is reflecting the namespace mapping in the current <code>Session</code> * * @throws NamespaceException if the prefix of name is not a registered * namespace prefix */ private void checkPrefix(String name) throws NamespaceException, RepositoryException { if (name.indexOf(":") != -1) { String prefix = name.split(":")[0]; // NamespaceException is thrown if fails session.getNamespaceURI(prefix); } } }