/** * Creates a new {@linkplain TypeEditor} instance * * @param callback the callback to use when a constraint violation is found. The client must * check the results of the callback invocations if the specified callback does not * immediately propagate constraint violations as checked exceptions. * @param typesToCheck the types to check for. If {@code null}, this node is checked. Otherwise * it is checked if its primary type or one of it's mixin types is contained in this parameters * @param types the {@code /jcr:system/jcr:nodeTypes} node * @param primary the node's primary type * @param mixins the node's mixins * @param builder a builder containing the current state of the node to check. May be used to set * a default primary type if none is set * @return a new TypeEditor instance * @throws CommitFailedException when the primary type of mixin definition is incorrect */ public static TypeEditor create(@NotNull ConstraintViolationCallback callback, Set<String> typesToCheck, @NotNull NodeState types, String primary, Iterable<String> mixins, @NotNull NodeBuilder builder) throws CommitFailedException { return new TypeEditor(callback, typesToCheck, types, primary, mixins, builder); }
@Override public void enter(NodeState before, NodeState after) throws CommitFailedException { if (checkThisNode && validate) { // when adding a new node, or changing node type on an existing // node, we need to reverify type constraints checkNodeTypeConstraints(after); checkThisNode = false; } }
@Override public void propertyChanged(PropertyState before, PropertyState after) throws CommitFailedException { if (checkThisNode) { checkPropertyTypeConstraints(after); } }
@Override public TypeEditor childNodeChanged( String name, NodeState before, NodeState after) throws CommitFailedException { String primary = after.getName(JCR_PRIMARYTYPE); Iterable<String> mixins = after.getNames(JCR_MIXINTYPES); if (primary == null && effective != null) { // no primary type defined, find and apply a default type primary = effective.getDefaultType(name); if (primary != null) { builder.setProperty(JCR_PRIMARYTYPE, primary, NAME); } else { constraintViolation( 4, "No default primary type available " + " for child node " + name); } } // if node type didn't change no need to validate child node boolean validate = primaryChanged(before, primary) || mixinsChanged(before, mixins); NodeBuilder childBuilder = builder.getChildNode(name); TypeEditor editor = new TypeEditor(this, name, primary, mixins, childBuilder, validate); if (checkThisNode && validate && !effective.isValidChildNode(name, editor.getEffective())) { constraintViolation( 1, "No matching definition found for child node " + name + " with effective type " + editor.getEffective()); } return editor; }
private void checkNodeTypeConstraints(NodeState after) throws CommitFailedException { EffectiveType effective = getEffective(); Set<String> properties = effective.getMandatoryProperties(); for (PropertyState ps : after.getProperties()) { properties.remove(ps.getName()); checkPropertyTypeConstraints(ps); } // verify the presence of all mandatory items if (!properties.isEmpty()) { constraintViolation(21, "Mandatory property " + properties.iterator().next() + " not found in a new node"); } List<String> names = Lists.newArrayList(after.getChildNodeNames()); for (String child : effective.getMandatoryChildNodes()) { if (!names.remove(child)) { constraintViolation(25, "Mandatory child node " + child + " not found in a new node"); } } if (!names.isEmpty()) { for (String name : names) { NodeState child = after.getChildNode(name); String primary = child.getName(JCR_PRIMARYTYPE); Iterable<String> mixins = child.getNames(JCR_MIXINTYPES); NodeBuilder childBuilder = builder.getChildNode(name); TypeEditor editor = new TypeEditor(this, name, primary, mixins, childBuilder, false); if (!effective.isValidChildNode(name, editor.getEffective())) { constraintViolation(25, "Unexpected child node " + name + " found in a new node"); } } } }
private void checkPropertyTypeConstraints(PropertyState after) throws CommitFailedException { if (NodeStateUtils.isHidden(after.getName())) { return; } NodeState definition = effective.getDefinition(after); if (definition == null) { constraintViolation(4, "No matching property definition found for " + after); } else if (JCR_UUID.equals(after.getName()) && effective.isNodeType(MIX_REFERENCEABLE)) { // special handling for the jcr:uuid property of mix:referenceable // TODO: this should be done in a pluggable extension if (!isValidUUID(after.getValue(Type.STRING))) { constraintViolation(12, "Invalid UUID value in the jcr:uuid property"); } } else { int requiredType = getRequiredType(definition); if (requiredType != PropertyType.UNDEFINED) { checkRequiredType(after, requiredType); checkValueConstraints(definition, after, requiredType); } } }
@Test public void removeNonMandatoryChildNode() throws CommitFailedException { EffectiveType effective = createControl().createMock(EffectiveType.class); expect(effective.isMandatoryChildNode("mandatory")).andReturn(false); replay(effective); TypeEditor editor = new TypeEditor(effective); editor.childNodeDeleted("mandatory", EMPTY_NODE); }
@Test(expected = CommitFailedException.class) public void removeMandatoryProperty() throws CommitFailedException { EffectiveType effective = createControl().createMock(EffectiveType.class); expect(effective.isMandatoryProperty("mandatory")).andReturn(true); expect(effective.getDirectTypeNames()).andReturn(Collections.emptyList()); replay(effective); TypeEditor editor = new TypeEditor(effective); editor.propertyDeleted(PropertyStates.createProperty("mandatory", "")); }
@Override public Editor childNodeDeleted(String name, NodeState before) throws CommitFailedException { if (checkThisNode && effective.isMandatoryChildNode(name)) { constraintViolation(26, "Mandatory child node " + name + " can not be removed"); } return null; // no further checking needed for the removed subtree }
constraintViolation(1, "The primary type " + primary + " does not exist"); } else if (type.getBoolean(JCR_ISMIXIN)) { constraintViolation(2, "Mixin type " + primary + " used as the primary type"); } else { if (type.getBoolean(JCR_IS_ABSTRACT)) { + getPath()); } else { constraintViolation(2, "Abstract type " + primary + " used as the primary type"); type = types.getChildNode(mixin); if (!type.exists()) { constraintViolation(5, "The mixin type " + mixin + " does not exist"); } else if (!type.getBoolean(JCR_ISMIXIN)) { constraintViolation(6, "Primary type " + mixin + " used as a mixin type"); } else if (type.getBoolean(JCR_IS_ABSTRACT)) { constraintViolation(7, "Abstract type " + mixin + " used as a mixin type"); } else { list.add(type);
private String getPath() { if (parent == null) { return "/"; } else if (parent.parent == null) { return '/' + nodeName; } else { return parent.getPath() + '/' + nodeName; } }
TypeEditor( ConstraintViolationCallback callback, Set<String> typesToCheck, NodeState types, String primary, Iterable<String> mixins, NodeBuilder builder) throws CommitFailedException { this.callback = checkNotNull(callback); this.typesToCheck = typesToCheck; this.checkThisNode = typesToCheck == null || typesToCheck.contains(primary) || any(mixins, in(typesToCheck)); this.parent = null; this.nodeName = null; this.types = checkNotNull(types); this.effective = createEffectiveType(null, null, primary, mixins); this.builder = checkNotNull(builder); this.validate = false; }
@Override public Editor childNodeAdded(String name, NodeState after) throws CommitFailedException { // TODO: add any auto-created items that are still missing return childNodeChanged(name, MISSING_NODE, after); }
@Override public TypeEditor childNodeChanged( String name, NodeState before, NodeState after) throws CommitFailedException { String primary = after.getName(JCR_PRIMARYTYPE); Iterable<String> mixins = after.getNames(JCR_MIXINTYPES); if (primary == null && effective != null) { // no primary type defined, find and apply a default type primary = effective.getDefaultType(name); if (primary != null) { builder.setProperty(JCR_PRIMARYTYPE, primary, NAME); } else { constraintViolation( 4, "No default primary type available " + " for child node " + name); } } // if node type didn't change no need to validate child node boolean validate = primaryChanged(before, primary) || mixinsChanged(before, mixins); NodeBuilder childBuilder = builder.getChildNode(name); TypeEditor editor = new TypeEditor(this, name, primary, mixins, childBuilder, validate); if (checkThisNode && validate && !effective.isValidChildNode(name, editor.getEffective())) { constraintViolation( 1, "No matching definition found for child node " + name + " with effective type " + editor.getEffective()); } return editor; }
private void checkNodeTypeConstraints(NodeState after) throws CommitFailedException { EffectiveType effective = getEffective(); Set<String> properties = effective.getMandatoryProperties(); for (PropertyState ps : after.getProperties()) { properties.remove(ps.getName()); checkPropertyTypeConstraints(ps); } // verify the presence of all mandatory items if (!properties.isEmpty()) { constraintViolation(21, "Mandatory property " + properties.iterator().next() + " not found in a new node"); } List<String> names = Lists.newArrayList(after.getChildNodeNames()); for (String child : effective.getMandatoryChildNodes()) { if (!names.remove(child)) { constraintViolation(25, "Mandatory child node " + child + " not found in a new node"); } } if (!names.isEmpty()) { for (String name : names) { NodeState child = after.getChildNode(name); String primary = child.getName(JCR_PRIMARYTYPE); Iterable<String> mixins = child.getNames(JCR_MIXINTYPES); NodeBuilder childBuilder = builder.getChildNode(name); TypeEditor editor = new TypeEditor(this, name, primary, mixins, childBuilder, false); if (!effective.isValidChildNode(name, editor.getEffective())) { constraintViolation(25, "Unexpected child node " + name + " found in a new node"); } } } }
private void checkPropertyTypeConstraints(PropertyState after) throws CommitFailedException { if (NodeStateUtils.isHidden(after.getName())) { return; } NodeState definition = effective.getDefinition(after); if (definition == null) { constraintViolation(4, "No matching property definition found for " + after); } else if (JCR_UUID.equals(after.getName()) && effective.isNodeType(MIX_REFERENCEABLE)) { // special handling for the jcr:uuid property of mix:referenceable // TODO: this should be done in a pluggable extension if (!isValidUUID(after.getValue(Type.STRING))) { constraintViolation(12, "Invalid UUID value in the jcr:uuid property"); } } else { int requiredType = getRequiredType(definition); if (requiredType != PropertyType.UNDEFINED) { checkRequiredType(after, requiredType); checkValueConstraints(definition, after, requiredType); } } }
@Test(expected = CommitFailedException.class) public void removeMandatoryChildNode() throws CommitFailedException { EffectiveType effective = createControl().createMock(EffectiveType.class); expect(effective.isMandatoryChildNode("mandatory")).andReturn(true); expect(effective.getDirectTypeNames()).andReturn(Collections.emptyList()); replay(effective); TypeEditor editor = new TypeEditor(effective); editor.childNodeDeleted("mandatory", EMPTY_NODE); }
@Test public void removeNonMandatoryProperty() throws CommitFailedException { EffectiveType effective = createControl().createMock(EffectiveType.class); expect(effective.isMandatoryProperty("mandatory")).andReturn(false); replay(effective); TypeEditor editor = new TypeEditor(effective); editor.propertyDeleted(PropertyStates.createProperty("mandatory", "")); }
@Override public Editor childNodeDeleted(String name, NodeState before) throws CommitFailedException { if (checkThisNode && effective.isMandatoryChildNode(name)) { constraintViolation(26, "Mandatory child node " + name + " can not be removed"); } return null; // no further checking needed for the removed subtree }
constraintViolation(1, "The primary type " + primary + " does not exist"); } else if (type.getBoolean(JCR_ISMIXIN)) { constraintViolation(2, "Mixin type " + primary + " used as the primary type"); } else { if (type.getBoolean(JCR_IS_ABSTRACT)) { + getPath()); } else { constraintViolation(2, "Abstract type " + primary + " used as the primary type"); type = types.getChildNode(mixin); if (!type.exists()) { constraintViolation(5, "The mixin type " + mixin + " does not exist"); } else if (!type.getBoolean(JCR_ISMIXIN)) { constraintViolation(6, "Primary type " + mixin + " used as a mixin type"); } else if (type.getBoolean(JCR_IS_ABSTRACT)) { constraintViolation(7, "Abstract type " + mixin + " used as a mixin type"); } else { list.add(type);