/* * 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 java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.Reader; import java.io.StringWriter; import java.util.Arrays; import java.util.Comparator; import java.util.Map; import java.util.HashMap; import java.util.Set; import java.util.HashSet; import java.util.Iterator; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.NodeTypeIterator; import javax.jcr.nodetype.NodeTypeManager; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.version.OnParentVersionAction; import org.apache.jackrabbit.test.AbstractJCRTest; import org.apache.jackrabbit.test.NotExecutableException; /** * <code>PredefinedNodeTypeTest</code> tests if the predefined node types are * implemented correctly. * */ public class PredefinedNodeTypeTest extends AbstractJCRTest { private static final Map<String, String[]> SUPERTYPES = new HashMap<String, String[]>(); static { SUPERTYPES.put("mix:created", new String[]{}); SUPERTYPES.put("mix:etag", new String[]{}); SUPERTYPES.put("mix:language", new String[]{}); SUPERTYPES.put("mix:lastModified", new String[]{}); SUPERTYPES.put("mix:lifecycle", new String[]{}); SUPERTYPES.put("mix:lockable", new String[]{}); SUPERTYPES.put("mix:mimeType", new String[]{}); SUPERTYPES.put("mix:referenceable", new String[]{}); SUPERTYPES.put("mix:shareable", new String[]{"mix:referenceable"}); SUPERTYPES.put("mix:simpleVersionable", new String[]{}); SUPERTYPES.put("mix:title", new String[]{}); SUPERTYPES.put("mix:versionable", new String[]{"mix:referenceable", "mix:simpleVersionable"}); SUPERTYPES.put("nt:activity", new String[]{"nt:base"}); SUPERTYPES.put("nt:address", new String[]{"nt:base"}); SUPERTYPES.put("nt:base", new String[]{}); SUPERTYPES.put("nt:childNodeDefinition", new String[]{"nt:base"}); SUPERTYPES.put("nt:configuration", new String[]{"nt:base"}); SUPERTYPES.put("nt:file", new String[]{"nt:hierarchyNode"}); SUPERTYPES.put("nt:folder", new String[]{"nt:hierarchyNode"}); SUPERTYPES.put("nt:frozenNode", new String[]{"nt:base", "mix:referenceable"}); SUPERTYPES.put("nt:hierarchyNode", new String[]{"nt:base", "mix:created"}); SUPERTYPES.put("nt:linkedFile", new String[]{"nt:hierarchyNode"}); SUPERTYPES.put("nt:nodeType", new String[]{"nt:base"}); SUPERTYPES.put("nt:propertyDefinition", new String[]{"nt:base"}); SUPERTYPES.put("nt:query", new String[]{"nt:base"}); SUPERTYPES.put("nt:resource", new String[]{"nt:base", "mix:lastModified", "mix:mimeType"}); SUPERTYPES.put("nt:unstructured", new String[]{"nt:base"}); SUPERTYPES.put("nt:version", new String[]{"nt:base", "mix:referenceable"}); SUPERTYPES.put("nt:versionedChild", new String[]{"nt:base"}); SUPERTYPES.put("nt:versionHistory", new String[]{"nt:base", "mix:referenceable"}); SUPERTYPES.put("nt:versionLabels", new String[]{"nt:base"}); } /** * The NodeTypeManager of the session */ private NodeTypeManager manager; /** * The read-only session for the test */ private Session session; /** * Sets up the fixture for this test. */ protected void setUp() throws Exception { isReadOnly = true; super.setUp(); session = getHelper().getReadOnlySession(); manager = session.getWorkspace().getNodeTypeManager(); } /** * Releases the session aquired in {@link #setUp()}. */ protected void tearDown() throws Exception { if (session != null) { session.logout(); session = null; } manager = null; super.tearDown(); } /** * Tests if all primary node types are subtypes of node type <code>nt:base</code> */ public void testIfPrimaryNodeTypesAreSubtypesOfNTBase() throws RepositoryException { NodeTypeIterator types = manager.getPrimaryNodeTypes(); while (types.hasNext()) { NodeType type = types.nextNodeType(); assertTrue("Primary node type " + type.getName() + " must inherit nt:base", type.isNodeType("nt:base")); } } /** Test for the predefined mix:lifecycle node type. */ public void testLifecycle() throws NotExecutableException { testPredefinedNodeType("mix:lifecycle", false); } /** Test for the predefined mix:lockable node type. */ public void testLockable() throws NotExecutableException { testPredefinedNodeType("mix:lockable", false); } /** Test for the predefined mix:referenceable node type. */ public void testReferenceable() throws NotExecutableException { testPredefinedNodeType("mix:referenceable", false); } /** Test for the predefined mix:referenceable node type. */ public void testShareable() throws NotExecutableException { testPredefinedNodeType("mix:shareable", false); } /** Test for the predefined mix:versionable node type. */ public void testVersionable() throws NotExecutableException { testPredefinedNodeType("mix:versionable", false); } /** Test for the predefined mix:simpleVersionable node type. */ public void testSimpleVersionable() throws NotExecutableException { testPredefinedNodeType("mix:simpleVersionable", false); } /** Test for the predefined mix:created node type. */ public void testMixCreated() throws NotExecutableException { testPredefinedNodeType("mix:created", true); } /** Test for the predefined mix:lastModified node type. */ public void testMixLastModified() throws NotExecutableException { testPredefinedNodeType("mix:lastModified", true); } /** Test for the predefined mix:etag node type. */ public void testMixETag() throws NotExecutableException { testPredefinedNodeType("mix:etag", false); } /** Test for the predefined mix:title node type. */ public void testMixTitle() throws NotExecutableException { testPredefinedNodeType("mix:title", true); } /** Test for the predefined mix:language node type. */ public void testMixLanguage() throws NotExecutableException { testPredefinedNodeType("mix:language", true); } /** Test for the predefined mix:language node type. */ public void testMixMimeType() throws NotExecutableException { testPredefinedNodeType("mix:mimeType", true); } /** Test for the predefined nt:address node type. */ public void testNtAddress() throws NotExecutableException { testPredefinedNodeType("nt:address", false); } /** Test for the predefined nt:base node type. */ public void testBase() throws NotExecutableException { testPredefinedNodeType("nt:base", false); } /** Test for the predefined nt:unstructured node type. */ public void testUnstructured() throws NotExecutableException { testPredefinedNodeType("nt:unstructured", false); } /** Test for the predefined nt:hierarchyNode node type. */ public void testHierarchyNode() throws NotExecutableException { testPredefinedNodeType("nt:hierarchyNode", false); } /** Test for the predefined nt:file node type. */ public void testFile() throws NotExecutableException { testPredefinedNodeType("nt:file", false); } /** Test for the predefined nt:linkedFile node type. */ public void testLinkedFile() throws NotExecutableException { testPredefinedNodeType("nt:linkedFile", false); } /** Test for the predefined nt:folder node type. */ public void testFolder() throws NotExecutableException { testPredefinedNodeType("nt:folder", false); } /** Test for the predefined nt:nodeType node type. */ public void testNodeType() throws NotExecutableException { testPredefinedNodeType("nt:nodeType", false); } /** Test for the predefined nt:propertyDef node type. */ public void testPropertyDef() throws NotExecutableException { testPredefinedNodeType("nt:propertyDefinition", false); } /** Test for the predefined nt:childNodeDef node type. */ public void testChildNodeDef() throws NotExecutableException { testPredefinedNodeType("nt:childNodeDefinition", false); } /** Test for the predefined nt:versionHistory node type. */ public void testVersionHistory() throws NotExecutableException { testPredefinedNodeType("nt:versionHistory", false); } /** Test for the predefined nt:versionLabels node type. */ public void testVersionLabels() throws NotExecutableException { testPredefinedNodeType("nt:versionLabels", false); } /** Test for the predefined nt:version node type. */ public void testVersion() throws NotExecutableException { testPredefinedNodeType("nt:version", false); } /** Test for the predefined nt:activity node type. */ public void testActivity() throws NotExecutableException { testPredefinedNodeType("nt:activity", false); } /** Test for the predefined nt:configuration node type. */ public void testConfiguration() throws NotExecutableException { testPredefinedNodeType("nt:configuration", false); } /** Test for the predefined nt:frozenNode node type. */ public void testFrozenNode() throws NotExecutableException { testPredefinedNodeType("nt:frozenNode", false); } /** Test for the predefined nt:versionedChild node type. */ public void testVersionedChild() throws NotExecutableException { testPredefinedNodeType("nt:versionedChild", false); } /** Test for the predefined nt:query node type. */ public void testQuery() throws NotExecutableException { testPredefinedNodeType("nt:query", false); } /** Test for the predefined nt:resource node type. */ public void testResource() throws NotExecutableException { testPredefinedNodeType("nt:resource", false); } /** * Tests that the named node type matches the JSR 170 specification. * The test is performed by genererating a node type definition spec * string in the format used by the JSR 170 specification, and comparing * the result with a static spec file extracted from the specification * itself. * <p> * Note that the extracted spec files are not exact copies of the node * type specification in the JSR 170 document. Some formatting and * ordering changes have been made to simplify the test code, but the * semantics remain the same. * * @param name node type name * @param propsVariant whether the properties of this node type may * have implementation variant autocreated and OPV flags. * @throws NotExecutableException if the node type is not supported by * this repository implementation. */ private void testPredefinedNodeType(String name, boolean propsVariant) throws NotExecutableException { try { StringBuffer spec = new StringBuffer(); String resource = "org/apache/jackrabbit/test/api/nodetype/spec/" + name.replace(':', '-') + ".txt"; Reader reader = new InputStreamReader( getClass().getClassLoader().getResourceAsStream(resource)); for (int ch = reader.read(); ch != -1; ch = reader.read()) { spec.append((char) ch); } NodeType type = manager.getNodeType(name); String current = getNodeTypeSpec(type, propsVariant).trim(); if (!System.getProperty("line.separator").equals("\n")) { current = normalizeLineSeparators(current); } String expected = normalizeLineSeparators(spec.toString()).trim(); assertEquals("Predefined node type " + name, expected, current); // check minimum declared supertypes Set<String> declaredSupertypes = new HashSet<String>(); for (Iterator<NodeType> it = Arrays.asList( type.getDeclaredSupertypes()).iterator(); it.hasNext(); ) { NodeType nt = it.next(); declaredSupertypes.add(nt.getName()); } for (Iterator<String> it = Arrays.asList( SUPERTYPES.get(name)).iterator(); it.hasNext(); ) { String supertype = it.next(); assertTrue("Predefined node type " + name + " does not " + "declare supertype " + supertype, declaredSupertypes.contains(supertype)); } } catch (IOException e) { fail(e.getMessage()); } catch (NoSuchNodeTypeException e) { // only nt:base is strictly required if ("nt:base".equals(name)) { fail(e.getMessage()); } else { throw new NotExecutableException("NodeType " + name + " not supported by this repository implementation."); } } catch (RepositoryException e) { fail(e.getMessage()); } } /** * Creates and returns a spec string for the given node type definition. * The returned spec string follows the node type definition format * used in the JSR 170 specification. * * @param type node type definition * @param propsVariant whether the properties of this node type may * have implementation variant autocreated and OPV flags. * @return spec string * @throws RepositoryException on repository errors */ private static String getNodeTypeSpec(NodeType type, boolean propsVariant) throws RepositoryException { String typeName = type.getName(); StringWriter buffer = new StringWriter(); PrintWriter writer = new PrintWriter(buffer); writer.println("NodeTypeName"); writer.println(" " + typeName); writer.println("IsMixin"); writer.println(" " + type.isMixin()); writer.println("HasOrderableChildNodes"); writer.println(" " + type.hasOrderableChildNodes()); writer.println("PrimaryItemName"); writer.println(" " + type.getPrimaryItemName()); NodeDefinition[] nodes = type.getDeclaredChildNodeDefinitions(); Arrays.sort(nodes, NODE_DEF_COMPARATOR); for (int i = 0; i < nodes.length; i++) { writer.print(getChildNodeDefSpec(nodes[i])); } PropertyDefinition[] properties = type.getDeclaredPropertyDefinitions(); Arrays.sort(properties, PROPERTY_DEF_COMPARATOR); for (int i = 0; i < properties.length; i++) { writer.print(getPropertyDefSpec(properties[i], propsVariant)); } return buffer.toString(); } /** * Creates and returns a spec string for the given node definition. * The returned spec string follows the child node definition format * used in the JSR 170 specification. * * @param node child node definition * @return spec string */ private static String getChildNodeDefSpec(NodeDefinition node) { StringWriter buffer = new StringWriter(); PrintWriter writer = new PrintWriter(buffer); writer.println("ChildNodeDefinition"); if (node.getName().equals("*")) { writer.println(" Name \"*\""); } else { writer.println(" Name " + node.getName()); } writer.print(" RequiredPrimaryTypes ["); NodeType[] types = node.getRequiredPrimaryTypes(); Arrays.sort(types, NODE_TYPE_COMPARATOR); for (int j = 0; j < types.length; j++) { if (j > 0) { writer.print(','); } writer.print(types[j].getName()); } writer.println("]"); if (node.getDefaultPrimaryType() != null) { writer.println(" DefaultPrimaryType " + node.getDefaultPrimaryType().getName()); } else { writer.println(" DefaultPrimaryType null"); } writer.println(" AutoCreated " + node.isAutoCreated()); writer.println(" Mandatory " + node.isMandatory()); writer.println(" OnParentVersion " + OnParentVersionAction.nameFromValue(node.getOnParentVersion())); writer.println(" Protected " + node.isProtected()); writer.println(" SameNameSiblings " + node.allowsSameNameSiblings()); return buffer.toString(); } /** * Creates and returns a spec string for the given property definition. * The returned spec string follows the property definition format * used in the JSR 170 specification. * * @param property property definition * @param propsVariant whether the properties of this node type may * have implementation variant autocreated and OPV flags. * @return spec string * @throws RepositoryException on repository errors */ private static String getPropertyDefSpec(PropertyDefinition property, boolean propsVariant) throws RepositoryException { StringWriter buffer = new StringWriter(); PrintWriter writer = new PrintWriter(buffer); writer.println("PropertyDefinition"); if (property.getName().equals("*")) { writer.println(" Name \"*\""); } else { writer.println(" Name " + property.getName()); } String type = PropertyType.nameFromValue(property.getRequiredType()); writer.println(" RequiredType " + type.toUpperCase()); Value[] values = property.getDefaultValues(); if (values != null && values.length > 0) { writer.print(" DefaultValues ["); for (int j = 0; j < values.length; j++) { if (j > 0) { writer.print(','); } writer.print(values[j].getString()); } writer.println("]"); } else { writer.println(" DefaultValues null"); } if (!propsVariant) { writer.println(" AutoCreated " + property.isAutoCreated()); } writer.println(" Mandatory " + property.isMandatory()); String action = OnParentVersionAction.nameFromValue( property.getOnParentVersion()); if (!propsVariant) { writer.println(" OnParentVersion " + action); } writer.println(" Protected " + property.isProtected()); writer.println(" Multiple " + property.isMultiple()); return buffer.toString(); } /** * Replaces platform-dependant line-separators in <code>stringValue</code> * with "\n". * * @param stringValue string to normalize * @return the normalized string */ private String normalizeLineSeparators(String stringValue) { // Replace "\r\n" (Windows format) with "\n" (Unix format) stringValue = stringValue.replaceAll("\r\n", "\n"); // Replace "\r" (Mac format) with "\n" (Unix format) stringValue = stringValue.replaceAll("\r", "\n"); return stringValue; } /** * Comparator for ordering node definition arrays. Node definitions are * ordered by name, with the wildcard item definition ("*") ordered last. */ private static final Comparator<NodeDefinition> NODE_DEF_COMPARATOR = new Comparator<NodeDefinition>() { public int compare(NodeDefinition nda, NodeDefinition ndb) { if (nda.getName().equals("*") && !ndb.getName().equals("*")) { return 1; } else if (!nda.getName().equals("*") && ndb.getName().equals("*")) { return -1; } else { return nda.getName().compareTo(ndb.getName()); } } }; /** * Comparator for ordering property definition arrays. Property definitions * are ordered by name, with the wildcard item definition ("*") ordered * last, and isMultiple flag, with <code>isMultiple==true</code> ordered last. */ private static final Comparator<PropertyDefinition> PROPERTY_DEF_COMPARATOR = new Comparator<PropertyDefinition>() { public int compare(PropertyDefinition pda, PropertyDefinition pdb) { if (pda.getName().equals("*") && !pdb.getName().equals("*")) { return 1; } else if (!pda.getName().equals("*") && pdb.getName().equals("*")) { return -1; } int result = pda.getName().compareTo(pdb.getName()); if (result != 0) { return result; } if (pda.isMultiple() && !pdb.isMultiple()) { return 1; } else if (!pda.isMultiple() && pdb.isMultiple()) { return -1; } else { return 0; } } }; /** * Comparator for ordering node type arrays. Node types are ordered by * name, with all primary node types ordered before mixin node types. */ private static final Comparator<NodeType> NODE_TYPE_COMPARATOR = new Comparator<NodeType>() { public int compare(NodeType nta, NodeType ntb) { if (nta.isMixin() && !ntb.isMixin()) { return 1; } else if (!nta.isMixin() && ntb.isMixin()) { return -1; } else { return nta.getName().compareTo(ntb.getName()); } } }; }