/* * 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.ISO8601; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.ValueFormatException; 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 java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Utility class to locate item definitions in the NodeTyeManager. */ public class NodeTypeUtil { public static final int ANY_PROPERTY_TYPE = -1; /** * Locate a non-protected child node def declared by a non-abstract node type * parsing all node types * * @param session the session to access the node types * @param regardDefaultPrimaryType if true, the default primary type of the * returned <code>NodeDef</code> is * according to param <code>defaultPrimaryType</code>. * If false, the returned <code>NodeDef</code> * might have a default primary type or * not. * @param defaultPrimaryType if <code>regardDefaultPrimaryType</code> * is true: if true, the returned * <code>NodeDef</code> has a default * primary type, else not * @param residual if true, the returned <code>NodeDef</code> * is of the residual name "*", else not * @return * @throws RepositoryException */ public static NodeDefinition locateChildNodeDef(Session session, boolean regardDefaultPrimaryType, boolean defaultPrimaryType, boolean residual) throws RepositoryException { NodeTypeManager manager = session.getWorkspace().getNodeTypeManager(); NodeTypeIterator types = manager.getAllNodeTypes(); boolean skip = false; while (types.hasNext()) { NodeType type = types.nextNodeType(); // node types with more than one residual child node definition // will cause trouble in test cases. the implementation // might pick another definition than the definition returned by // this method, when a child node is set. NodeDefinition[] childDefs = type.getChildNodeDefinitions(); int residuals = 0; for (int i = 0; i < childDefs.length; i++) { if (childDefs[i].getName().equals("*")) { residuals++; } } if (residuals > 1) { // more than one residual, not suitable for tests continue; } NodeDefinition nodeDefs[] = type.getDeclaredChildNodeDefinitions(); for (int i = 0; i < nodeDefs.length; i++) { NodeDefinition nodeDef = nodeDefs[i]; if (nodeDef.getDeclaringNodeType().isAbstract()) { continue; } if (nodeDef.isProtected()) { continue; } if (nodeDef.getRequiredPrimaryTypes().length > 1) { // behaviour of implementations that support multiple multiple inheritance // of primary node types is not specified continue; } if (regardDefaultPrimaryType) { if (defaultPrimaryType && nodeDef.getDefaultPrimaryType() == null) { continue; } if (!defaultPrimaryType && nodeDef.getDefaultPrimaryType() != null) { continue; } } if (residual && !nodeDef.getName().equals("*")) { continue; } if (!residual) { // if another child node def is a residual definition // skip the current node type NodeDefinition nodeDefsAll[] = type.getChildNodeDefinitions(); for (int j = 0; j < nodeDefsAll.length; j++) { if (nodeDefsAll[j].getName().equals("*")) { skip = true; break; } } if (skip) { // break the loop of the current child not defs skip = false; break; } } return nodeDef; } } return null; } /** * Locate all non-protected child node def declared by a non-abstract node type * parsing all node types * * @param session the session to access the node types * @param regardDefaultPrimaryType if true, the default primary type of the * returned <code>NodeDef</code> is * according to param <code>defaultPrimaryType</code>. * If false, the returned <code>NodeDef</code> * might have a default primary type or * not. * @param defaultPrimaryType if <code>regardDefaultPrimaryType</code> * is true: if true, the returned * <code>NodeDef</code> has a default * primary type, else not * @param residual if true, the returned <code>NodeDef</code> * is of the residual name "*", else not * @return * @throws RepositoryException */ public static List<NodeDefinition> locateAllChildNodeDef(Session session, boolean regardDefaultPrimaryType, boolean defaultPrimaryType, boolean residual) throws RepositoryException { List<NodeDefinition> nodeTypes = new ArrayList<NodeDefinition>(); NodeTypeManager manager = session.getWorkspace().getNodeTypeManager(); NodeTypeIterator types = manager.getAllNodeTypes(); boolean skip = false; while (types.hasNext()) { NodeType type = types.nextNodeType(); // node types with more than one residual child node definition // will cause trouble in test cases. the implementation // might pick another definition than the definition returned by // this method, when a child node is set. NodeDefinition[] childDefs = type.getChildNodeDefinitions(); int residuals = 0; for (int i = 0; i < childDefs.length; i++) { if (childDefs[i].getName().equals("*")) { residuals++; } } if (residuals > 1) { // more than one residual, not suitable for tests continue; } NodeDefinition nodeDefs[] = type.getDeclaredChildNodeDefinitions(); for (int i = 0; i < nodeDefs.length; i++) { NodeDefinition nodeDef = nodeDefs[i]; if (nodeDef.getDeclaringNodeType().isAbstract()) { continue; } if (nodeDef.isProtected()) { continue; } if (nodeDef.getRequiredPrimaryTypes().length > 1) { // behaviour of implementations that support multiple multiple inheritance // of primary node types is not specified continue; } if (regardDefaultPrimaryType) { if (defaultPrimaryType && nodeDef.getDefaultPrimaryType() == null) { continue; } if (!defaultPrimaryType && nodeDef.getDefaultPrimaryType() != null) { continue; } } if (residual && !nodeDef.getName().equals("*")) { continue; } if (!residual) { // if another child node def is a residual definition // skip the current node type NodeDefinition nodeDefsAll[] = type.getChildNodeDefinitions(); for (int j = 0; j < nodeDefsAll.length; j++) { if (nodeDefsAll[j].getName().equals("*")) { skip = true; break; } } if (skip) { // break the loop of the current child not defs skip = false; break; } } nodeTypes.add(nodeDef); } } return nodeTypes; } /** * Locate a child node def parsing all node types * * @param session the session to access the node types * @param isProtected if true, the returned <code>NodeDef</code> is * protected, else not * @param mandatory if true, the returned <code>NodeDef</code> is * mandatory, else not * @return the first <code>NodeDef</code> found fitting the requirements */ public static NodeDefinition locateChildNodeDef(Session session, boolean isProtected, boolean mandatory) throws RepositoryException { NodeTypeManager manager = session.getWorkspace().getNodeTypeManager(); NodeTypeIterator types = manager.getAllNodeTypes(); while (types.hasNext()) { NodeType type = types.nextNodeType(); NodeDefinition nodeDefs[] = type.getDeclaredChildNodeDefinitions(); for (int i = 0; i < nodeDefs.length; i++) { NodeDefinition nodeDef = nodeDefs[i]; if (nodeDef.getName().equals("*")) { continue; } if (isProtected && !nodeDef.isProtected()) { continue; } if (!isProtected && nodeDef.isProtected()) { continue; } if (mandatory && !nodeDef.isMandatory()) { continue; } if (!mandatory && nodeDef.isMandatory()) { continue; } return nodeDef; } } return null; } /** * Locate a property def parsing all node types * * @param session the session to access the node types * @param propertyType the type of the returned property. -1 indicates to * return a property of any type but not UNDEFIEND * @param multiple if true, the returned <code>PropertyDef</code> is * multiple, else not * @param isProtected if true, the returned <code>PropertyDef</code> is * protected, else not * @param residual if true, the returned <code>PropertyDef</code> is of * the residual name "*", else not * @return the first <code>PropertyDef</code> found fitting the * requirements */ public static PropertyDefinition locatePropertyDef(Session session, int propertyType, boolean multiple, boolean isProtected, boolean constraints, boolean residual) throws RepositoryException { NodeTypeManager manager = session.getWorkspace().getNodeTypeManager(); NodeTypeIterator types = manager.getAllNodeTypes(); while (types.hasNext()) { NodeType type = types.nextNodeType(); PropertyDefinition propDefs[] = type.getDeclaredPropertyDefinitions(); for (int i = 0; i < propDefs.length; i++) { PropertyDefinition propDef = propDefs[i]; if (propertyType != ANY_PROPERTY_TYPE && propDef.getRequiredType() != propertyType) { continue; } if (propertyType == ANY_PROPERTY_TYPE && propDef.getRequiredType() == PropertyType.UNDEFINED) { continue; } if (multiple && !propDef.isMultiple()) { continue; } if (!multiple && propDef.isMultiple()) { continue; } if (isProtected && !propDef.isProtected()) { continue; } if (!isProtected && propDef.isProtected()) { continue; } String vc[] = propDef.getValueConstraints(); if (!constraints && vc != null && vc.length > 0) { continue; } if (constraints) { // property def with constraints requested if (vc == null || vc.length == 0) { // property def has no constraints continue; } } if (!residual && propDef.getName().equals("*")) { continue; } if (residual && !propDef.getName().equals("*")) { continue; } // also skip property residual property definition if there // is another residual definition if (residual) { // check if there is another residual property def if (getNumResidualPropDefs(type) > 1) { continue; } } if (!residual) { // if not looking for a residual property def then there // must not be any residual definition at all on the node // type if (getNumResidualPropDefs(type) > 0) { continue; } } return propDef; } } return null; } /** * Returns the number of residual property definitions of <code>type</code> * including its base types. * @param type the node type * @return the number of residual property definitions. */ private static int getNumResidualPropDefs(NodeType type) { PropertyDefinition[] pDefs = type.getPropertyDefinitions(); int residuals = 0; for (int j = 0; j < pDefs.length; j++) { PropertyDefinition pDef = pDefs[j]; if (pDef.getName().equals("*")) { residuals++; } } return residuals; } /** * Locate a property def parsing all node types * * @param session the session to access the node types * @param isProtected if true, the returned <code>PropertyDef</code> is * protected, else not * @param mandatory if true, the returned <code>PropertyDef</code> is * mandatory, else not * @return the first <code>PropertyDef</code> found fitting the * requirements */ public static PropertyDefinition locatePropertyDef(Session session, boolean isProtected, boolean mandatory) throws RepositoryException { NodeTypeManager manager = session.getWorkspace().getNodeTypeManager(); NodeTypeIterator types = manager.getAllNodeTypes(); while (types.hasNext()) { NodeType type = types.nextNodeType(); PropertyDefinition propDefs[] = type.getDeclaredPropertyDefinitions(); for (int i = 0; i < propDefs.length; i++) { PropertyDefinition propDef = propDefs[i]; if (propDef.getName().equals("*")) { continue; } if (isProtected && !propDef.isProtected()) { continue; } if (!isProtected && propDef.isProtected()) { continue; } if (mandatory && !propDef.isMandatory()) { continue; } if (!mandatory && propDef.isMandatory()) { continue; } return propDef; } } return null; } /** * Returns a name that is not defined by the nodeType's child node def */ public static String getUndefinedChildNodeName(NodeType nodeType) { NodeDefinition nodeDefs[] = nodeType.getChildNodeDefinitions(); StringBuffer s = new StringBuffer("X"); for (int i = 0; i < nodeDefs.length; i++) { s.append(nodeDefs[i].getName()); } String undefinedName = s.toString(); undefinedName = undefinedName.replaceAll("\\*", ""); undefinedName = undefinedName.replaceAll(":", ""); return undefinedName; } /** * Returns a node type that is nor legalType nor a sub type of of */ public static String getIllegalChildNodeType(NodeTypeManager manager, String legalType) throws RepositoryException { NodeTypeIterator types = manager.getAllNodeTypes(); while (types.hasNext()) { NodeType type = types.nextNodeType(); if (!type.getName().equals(legalType)) { NodeType superTypes[] = type.getSupertypes(); boolean isSubType = false; for (int i = 0; i < superTypes.length; i++) { String name = superTypes[i].getName(); if (name.equals(legalType)) { isSubType = true; break; } } if (!isSubType) { return type.getName(); } } } return null; } /** * Returns any value of the requested type */ public static Value getValueOfType(Session session, int type) throws ValueFormatException, UnsupportedOperationException, RepositoryException { switch (type) { case (PropertyType.BINARY): // note: If binary is not UTF-8 behavior is implementation-specific return session.getValueFactory().createValue("abc", PropertyType.BINARY); case (PropertyType.BOOLEAN): return session.getValueFactory().createValue(true); case (PropertyType.DATE): return session.getValueFactory().createValue(Calendar.getInstance()); case (PropertyType.DOUBLE): return session.getValueFactory().createValue(1.0); case (PropertyType.LONG): return session.getValueFactory().createValue(1); case (PropertyType.NAME): return session.getValueFactory().createValue("abc", PropertyType.NAME); case (PropertyType.PATH): return session.getValueFactory().createValue("/abc", PropertyType.PATH); default: // STRING and UNDEFINED // note: REFERENCE is not testable since its format is implementation-specific return session.getValueFactory().createValue("abc"); } } /** * Returns a value according to the value contraints of a * <code>PropertyDefinition</code> * * @param propDef The <code>PropertyDefinition</code> whose constraints * will be regarded * @param satisfied If true, the returned <code>Value</code> will satisfying * the constraints - If false, the returned * <code>Value</code> will not satisfying the constraints. * @return Depending on param <code>satisfied</code> a <code>Value</code> * satisfying or not satistying the constraints of * <code>propDef</code> will be returned. Null will be returned if * no accordant <code>Value</code> could be build. */ public static Value getValueAccordingToValueConstraints(Session session, PropertyDefinition propDef, boolean satisfied) throws ValueFormatException, RepositoryException { int type = propDef.getRequiredType(); String constraints[] = propDef.getValueConstraints(); if (constraints == null || constraints.length == 0) { return null; } switch (type) { case (PropertyType.BINARY): { long absMin = 0; long absMax = 0; // indicate if absMin and absMax are already set boolean absMinSet = false; boolean absMaxSet = false; // boundless vars indicate min/max without bounds, // if constraint is e.g.(min,) or [,max] boolean maxBoundless = false; boolean minBoundless = false; // find smallest min and largest max value for (int i = 0; i < constraints.length; i++) { if (!minBoundless) { String minStr = getConstraintMin(constraints[i]); if (minStr == null) { minBoundless = true; } else { long min = Long.valueOf(minStr).longValue(); if (!absMinSet) { absMin = min; absMinSet = true; } else if (min < absMin) { absMin = min; } } } if (!maxBoundless) { String maxStr = getConstraintMax(constraints[i]); if (maxStr == null) { maxBoundless = true; } else { long max = Long.valueOf(maxStr).longValue(); if (!absMaxSet) { absMax = max; absMaxSet = true; } else if (max > absMax) { absMin = max; } } } } if (satisfied) { // build a binary value absMin < size > absMax StringBuffer content = new StringBuffer(); for (int i = 0; i <= absMin + 1; i++) { content.append("X"); } if (!maxBoundless && content.length() >= absMax) { return null; } else { return session.getValueFactory().createValue(content.toString(), PropertyType.BINARY); } } else { if (!minBoundless && absMin > 1) { // return a value of size < absMin return session.getValueFactory().createValue("0", PropertyType.BINARY); } else if (!maxBoundless) { // build a binary value of size > absMax StringBuffer content = new StringBuffer(); for (int i = 0; i <= absMax; i = i + 10) { content.append("0123456789"); } return session.getValueFactory().createValue(content.toString(), PropertyType.BINARY); } else { return null; } } } case (PropertyType.BOOLEAN): { if (constraints.length > 1) { return null; // silly constraint } boolean value = Boolean.valueOf(constraints[0]).booleanValue(); if (satisfied) { return session.getValueFactory().createValue(value); } else { return session.getValueFactory().createValue(!value); } } case (PropertyType.DATE): { Calendar absMin = null; Calendar absMax = null; // boundless vars indicate min/max without bounds, // if constraint is e.g.(min,) or [,max] boolean maxBoundless = false; boolean minBoundless = false; // find smallest min and largest max value for (int i = 0; i < constraints.length; i++) { if (!minBoundless) { String minStr = getConstraintMin(constraints[i]); if (minStr == null) { minBoundless = true; } else { Calendar min = ISO8601.parse(minStr); if (absMin == null || min.before(absMin)) { absMin = min; } } } if (!maxBoundless) { String maxStr = getConstraintMax(constraints[i]); if (maxStr == null) { maxBoundless = true; } else { Calendar max = ISO8601.parse(maxStr); if (absMax == null || max.after(absMax)) { absMax = max; } } } } if (satisfied) { if (absMin != null) { absMin.setTimeInMillis(absMin.getTimeInMillis() + 1); if (absMin.after(absMax)) { return null; } return session.getValueFactory().createValue(absMin); } else if (absMax != null) { absMax.setTimeInMillis(absMax.getTimeInMillis() - 1); if (absMax.before(absMin)) { return null; } return session.getValueFactory().createValue(absMax); } else { // neither min nor max set: return "now" return session.getValueFactory().createValue(Calendar.getInstance()); } } else { if (!minBoundless) { absMin.setTimeInMillis(absMin.getTimeInMillis() - 1); return session.getValueFactory().createValue(absMin); } else if (!maxBoundless) { absMax.setTimeInMillis(absMax.getTimeInMillis() + 1); return session.getValueFactory().createValue(absMax); } else { return null; } } } case (PropertyType.DOUBLE): { double absMin = 0; double absMax = 0; // indicate if absMin and absMax are already set boolean absMinSet = false; boolean absMaxSet = false; // boundless vars indicate min/max without bounds, // if constraint is e.g.(min,) or [,max] boolean maxBoundless = false; boolean minBoundless = false; // find smallest min and largest max value for (int i = 0; i < constraints.length; i++) { if (!minBoundless) { String minStr = getConstraintMin(constraints[i]); if (minStr == null) { minBoundless = true; } else { double min = Double.valueOf(minStr).doubleValue(); if (!absMinSet) { absMin = min; absMinSet = true; } else if (min < absMin) { absMin = min; } } } if (!maxBoundless) { String maxStr = getConstraintMax(constraints[i]); if (maxStr == null) { maxBoundless = true; } else { double max = Double.valueOf(maxStr).doubleValue(); if (!absMaxSet) { absMax = max; absMaxSet = true; } else if (max > absMax) { absMax = max; } } } } if (satisfied) { if (minBoundless) { return session.getValueFactory().createValue(absMax - 1.0); } else if (maxBoundless) { return session.getValueFactory().createValue(absMin + 1.0); } else if (absMin < absMax) { double d = (absMin + absMax) / 2; return session.getValueFactory().createValue(d); } else { return null; } } else { if (!minBoundless) { return session.getValueFactory().createValue(absMin - 1.0); } else if (!maxBoundless) { return session.getValueFactory().createValue(absMax + 1.0); } else { return null; } } } case (PropertyType.LONG): { long absMin = 0; long absMax = 0; // indicate if absMin and absMax are already set boolean absMinSet = false; boolean absMaxSet = false; // boundless vars indicate min/max without bounds, // if constraint is e.g.(min,) or [,max] boolean maxBoundless = false; boolean minBoundless = false; // find smallest min and largest max value for (int i = 0; i < constraints.length; i++) { if (!minBoundless) { String minStr = getConstraintMin(constraints[i]); if (minStr == null) { minBoundless = true; } else { long min = Long.valueOf(minStr).longValue(); if (!absMinSet) { absMin = min; absMinSet = true; } else if (min < absMin) { absMin = min; } } } if (!maxBoundless) { String maxStr = getConstraintMax(constraints[i]); if (maxStr == null) { maxBoundless = true; } else { long max = Long.valueOf(maxStr).longValue(); if (!absMaxSet) { absMax = max; absMaxSet = true; } else if (max > absMax) { absMax = max; } } } } if (satisfied) { if (minBoundless) { return session.getValueFactory().createValue(absMax - 1); } else if (maxBoundless) { return session.getValueFactory().createValue(absMin + 1); } else if (absMin < absMax - 1) { long x = (absMin + absMax) / 2; return session.getValueFactory().createValue(x); } else { return null; } } else { if (!minBoundless) { return session.getValueFactory().createValue(absMin - 1); } else if (!maxBoundless) { return session.getValueFactory().createValue(absMax + 1); } else { return null; } } } case (PropertyType.NAME): { if (satisfied) { // not in use so far return null; } else { // build a name that is for sure not part of the constraints StringBuffer name = new StringBuffer("X"); for (int i = 0; i < constraints.length; i++) { name.append(constraints[i].replaceAll(":", "")); } return session.getValueFactory().createValue(name.toString(), PropertyType.NAME); } } case (PropertyType.PATH): { if (satisfied) { // not in use so far return null; } else { // build a path that is for sure not part of the constraints StringBuffer path = new StringBuffer("X"); for (int i = 0; i < constraints.length; i++) { path.append(constraints[i]); } String pathStr = path.toString(); // replace colon to avoid /a/x:b + y:c => /a/x:b:y:c // where x:b:y:c is not a legal path element pathStr = pathStr.replaceAll(":", ""); pathStr = pathStr.replaceAll("\\*", ""); pathStr = pathStr.replaceAll("//", "/"); return session.getValueFactory().createValue(pathStr, PropertyType.PATH); } } case (PropertyType.UNDEFINED): { return null; } default: { if (satisfied) { // not in use so far return null; } else { // build a string that will probably not satisfy the constraints StringBuffer value = new StringBuffer("X"); for (int i = 0; i < constraints.length; i++) { value.append(constraints[i]); } // test if value does not match any of the constraints for (int i = 0; i < constraints.length; i++) { Pattern pattern = Pattern.compile(constraints[i]); Matcher matcher = pattern.matcher(value); if (matcher.matches()) { return null; } } return session.getValueFactory().createValue(value.toString()); } } } } // ------------------------< internal >------------------------------------- /** * Get the min value (as string) of a numeric/date constraint string */ private static String getConstraintMin(String constraint) { String min = constraint.substring(0, constraint.indexOf(",")); min = min.replaceAll("\\(", ""); min = min.replaceAll("\\[", ""); min = min.replaceAll(" ", ""); if (min.equals("")) { min = null; } return min; } /** * Get the max value (as string) of a numeric/date constraint string */ private static String getConstraintMax(String constraint) { String max = constraint.substring(constraint.indexOf(",") + 1); max = max.replaceAll("\\)", ""); max = max.replaceAll("\\]", ""); max = max.replaceAll(" ", ""); if (max.equals("")) { max = null; } return max; } }