in source/com.microsoft.tfs.core/src/com/microsoft/tfs/core/clients/workitem/internal/rules/RuleEngine.java [429:906]
private void runDenyWriteRule(final Rule rule) {
final IRuleTargetField field = target.getRuleTargetField(rule.getThenFldID());
/*
* The unless and thennot flags determine semantics for the rule.
* Ignoring the reverse flag (which is not used in TFS v1), a denywrite
* rule can be interpreted as: deny write (unless ? UNLESS : WHEN) the
* condition is (thennot ? FALSE : TRUE).
*
* Note that (unless==true && thennot==true) is the same condition as
* (unless==false && thennot==false). Likewise, (unless==true &&
* thennot==false) is the same condition as (unless==false &&
* thennot==true).
*
* Therefore, there are only two states. If (unless == thennot), deny
* write if the condition is true. If (unless != thennot), deny write if
* the condition is false.
*/
final boolean not = rule.isFlagThenNot();
final boolean unless = rule.isFlagUnless();
String logRuleEffect = "no effect"; //$NON-NLS-1$
if (SpecialConstantIDs.isSpecialConstantID(rule.getThenConstID())) {
switch (rule.getThenConstID()) {
case SpecialConstantIDs.CONST_EMPTY_VALUE:
// triggered by:
// * <REQUIRED /> (unless==true, thennot==true)
// * <CANNOTLOSEVALUE /> (unless==true, thennot==true)
// * <EMPTY /> (unless==true, thennot==false)
// * lots of in-the-box rules (unless==false,
// thennot==false)
/*
* ThenImplicitUnchanged will be true if there was an
* <ALLOWEXISTINGVALUE /> rule at in scope as of a <REQUIRED
* />, <CANNOTLOSEVALUE />, or <EMPTY /> rule.
* ThenImplicitUnchanged is ignored by this type of rule in
* Visual Studio's implementation.
*/
/*
* empty is true if the field has no value and the field is
* not set to be server computed
*/
final boolean empty = (field.getValue() == null) && (field.getServerComputedType() == null);
if ((not != unless)) {
/*
* This is a rare case where a data validation rule can
* end up behaving like a value providing rule. In this
* case, the field holds a value but a data validation
* rule says that it must be empty.
*
* We could simply set the field invalid:
* field.setStatus(FieldStatus.INVALID_NOT_EMTPY);
* logRuleEffect = "INVALID_NOT_EMTPY";
*
* However, that's not very user friendly (it's also not
* what the Microsoft implementation does). A more
* user-friendly approach will be to set the field's
* value to empty (if it is not already empty), and then
* set the field to read-only so the user can't set a
* value which would then be invalid. For most data
* validation rules we can't do this, since we only know
* "what's wrong", and not "how to fix it". This is a
* rare case where we do know "how to fix it".
*/
if (!empty) {
field.setValueFromRule(null);
field.setReadOnly(true);
logRuleEffect = "cleared value and set readonly true"; //$NON-NLS-1$
} else {
field.setReadOnly(true);
logRuleEffect = "set readonly true"; //$NON-NLS-1$
}
} else if ((not == unless) && empty) {
setInvalidStatus(field, FieldStatus.INVALID_EMPTY);
logRuleEffect = "INVALID_EMTPY"; //$NON-NLS-1$
}
break;
case SpecialConstantIDs.CONST_SAME_AS_OLD_VALUE:
// triggered by:
// * <READONLY /> (unless==true, thennot==false)
// * multiple in-the-box rules (unless==true,
// thennot==false)
/*
* ThenImplicitUnchanged will be true if there was an
* <ALLOWEXISTINGVALUE /> rule at in scope as of a <READONLY
* /> rule. ThenImplicitUnchanged is ignored by this type of
* rule in Visual Studio's implementation.
*/
if (not != unless) {
field.setReadOnly(true);
field.unsetNewValue();
logRuleEffect = "unset new value and set readonly true"; //$NON-NLS-1$
} else {
/*
* should never happen in TFS WIT v1
*/
throw new UnhandledRuleStateException(
rule,
"unless==thennot for denywrite ConstSameAsOldValue"); //$NON-NLS-1$
}
break;
case SpecialConstantIDs.CONST_WAS_EMPTY_OR_SAME_AS_OLD_VALUE:
// triggered by:
// <FROZEN /> (unless==true, thennot==false)
/*
* ThenImplicitUnchanged will be true if there was an
* <ALLOWEXISTINGVALUE /> rule at in scope as of a <FROZEN
* /> rule. ThenImplicitUnchanged is ignored by this type of
* rule in Visual Studio's implementation.
*/
final boolean wasEmpty = (field.getOriginalValue() == null);
final boolean sameAsOldValue = isSameAsOldValue(field);
final boolean wasEmptyOrSameAsOldValue = wasEmpty | sameAsOldValue;
if (not != unless) {
if (!wasEmptyOrSameAsOldValue) {
setInvalidStatus(field, FieldStatus.INVALID_NOT_EMPTY_OR_OLD_VALUE);
logRuleEffect = "INVALID_NOT_EMPTY_OR_OLD_VALUE"; //$NON-NLS-1$
}
} else {
/*
* Should never happen in TFS WIT v1 Presumably this
* case is what FieldStatus.INVALID_EMPTY_OR_OLD_VALUE
* is for
*/
throw new UnhandledRuleStateException(
rule,
"unless==thennot for denywrite ConstWasEmptyOrSameAsOldValue"); //$NON-NLS-1$
}
break;
case SpecialConstantIDs.CONST_CURRENT_USER:
// only triggered by in-the-box rules:
// * [Created By] should default to the person doing the
// action (Will cause OM to set it to the right value)
// (unless==false, thennot==false)
// * If [Created By] became non-empty (-10014) then it
// should be equal to Person doing action (unless==true,
// thennot==false)
/*
* seems that we can ignore this as things are working fine
* without it
*/
break;
case SpecialConstantIDs.CONST_OLD_VALUE_PLUS_ONE:
// only triggered by an in-the-box rule:
// * The Rev of a new item must be increased by one from
// the old version (unless==true, thennot==false).
/*
* So it will only ever show up for ThenFld == System.Rev.
* We can safely ignore as this is really more of a
* server-side concern than a client-side concern - the rev
* field is read-only as far as the client is concerned.
*/
break;
case SpecialConstantIDs.CONST_SERVER_DATE_TIME:
// triggered by:
// * <SERVERDEFAULT from="clock" /> (unless==true,
// thennot==false)
// * multiple in-the-box-rules (unless==true,
// thennot==false)
/*
* ThenImplicitUnchanged will be true if there was an
* <ALLOWEXISTINGVALUE /> rule at in scope as of a
* <SERVERDEFAULT /> rule. Unknown what Visual Studio's
* implementation does if this flag is set - it likely
* ignores it.
*/
/*
* As far as the WITD triggering, we can ignore as this is
* handled automatically by our OM without the need for a
* denywrite rule. Not sure if we need to care about the
* in-the-box rules - seems unlikely but probably need to
* review them to be sure.
*/
/*
* If the field is empty, apply "make true" logic since this
* field can only have one value.
*
* TODO: this is a temporary or at least partial fix for the
* Dev11 BUILD release. We have a user story to investigate
* the .NET rule engine's "make true" logic.
*/
if (field.getValue() == null && field.getServerComputedType() == null) {
field.setServerComputed(ServerComputedFieldType.DATE_TIME);
}
break;
case SpecialConstantIDs.CONST_VALUE_IN_OTHER_FIELD:
// triggered by:
// * <NOTSAMEAS field="x" /> (unless==true, thennot==true)
/*
* ThenImplicitUnchanged will be true if there was an
* <ALLOWEXISTINGVALUE /> rule at in scope as of a
* <NOTSAMEAS /> rule. ThenImplicitUnchanged is ignored by
* this type of rule in Visual Studio's implementation.
*/
if (rule.getIf2ConstID() != SpecialConstantIDs.CONST_VALUE_IN_OTHER_FIELD) {
throw new UnhandledRuleStateException(
rule,
MessageFormat.format(
"denywrite ConstValueInOtherField rule with If2ConstID={0}", //$NON-NLS-1$
Integer.toString(rule.getIf2ConstID())));
}
final int otherFieldID = rule.getIf2FldID();
if (otherFieldID == 0) {
throw new UnhandledRuleStateException(
rule,
MessageFormat.format(
"denywrite ConstValueInOtherField with If2FldID={0}", //$NON-NLS-1$
Integer.toString(rule.getIf2FldID())));
}
final Object otherFieldValue = target.getRuleTargetField(otherFieldID).getValue();
final Object targetFieldValue = field.getValue();
final boolean fieldValuesEqual = fieldValuesEqual(targetFieldValue, otherFieldValue);
if (unless == not) {
if (fieldValuesEqual) {
setInvalidStatus(field, FieldStatus.INVALID_VALUE_IN_OTHER_FIELD);
logRuleEffect = "INVALID_VALUE_IN_OTHER_FIELD (" + otherFieldID + ")"; //$NON-NLS-1$ //$NON-NLS-2$
}
} else {
/*
* Should never happen in TFS WIT v1. Presumably this
* case is what
* FieldStatus.INVALID_VALUE_NOT_IN_OTHER_FIELD is for.
*/
throw new UnhandledRuleStateException(
rule,
"unless!=thennot for denywrite ConstValueInOtherField"); //$NON-NLS-1$
}
break;
case SpecialConstantIDs.CONST_SERVER_CURRENT_USER:
// triggered by:
// * <SERVERDEFAULT from="currentuser" /> (unless==true,
// thennot==false)
/*
* ThenImplicitUnchanged will be true if there was an
* <ALLOWEXISTINGVALUE /> rule at in scope as of a
* <SERVERDEFAULT /> rule. Unknown what Visual Studio's
* implementation does if this flag is set - it likely
* ignores it.
*/
/*
* currently do-nothing as this is handled automatically
* without the need for a denywrite rule
*/
break;
case SpecialConstantIDs.CONST_SERVER_RANDOM_GUID:
break;
case SpecialConstantIDs.CONST_GREATER_THAN_OLD_VALUE:
// only triggered by an in-the-box rule:
// * [Changed Date] is greater than [Changed Date] of the
// previous revision (unless==true, thennot==false)
/*
* can safely ignore this case, as this is a server-side
* concern. the client side doesn't modify the changed date
*/
break;
case SpecialConstantIDs.CONST_DELETED_TREE_LOCATION:
// only triggered by in-the-box rules:
// * WorkItem cannot be in an invalid location
// (unless==false, thennot==false), ThenFld == System.AreaId
// * WorkItem cannot have an invalid IterationID
// (unless==false, thennot==false), ThenFld =
// System.IterationId
/*
* This rule will only be applied to one of the tree id
* fields (System.AreaId or System.IterationId). The rule
* must check if the field value is the id of a deleted
* node.
*
* Not sure if this rule will ever get triggered - is it
* possible to set area id / iteration id to a deleted node
* using TEE?
*/
break;
case SpecialConstantIDs.CONST_ADMIN_ONLY_TREE_LOCATION:
// only triggered by an in-the-box rule:
// * WorkItem cannot be written when in an admin only tree
// location (unless==false, thennot==false), ThenFld ==
// System.AreaId
/*
* This rule will only be applied to one of the tree id
* fields (System.AreaId or System.IterationId). The rule
* must check if the field value is the id of a admin node.
*
* Need to check on the semantics of that and what is
* considered an admin node. In fact, I think there is a
* good chance that TFS doesn't even support the concept of
* "admin-only" tree nodes, and the in-the-box rule that
* triggers this case is simply left over from product
* studio.
*/
break;
case SpecialConstantIDs.CONST_NOT_GREATER_THAN_SERVER_TIME:
// New rule in Dev11. "Server time" is terminology used to
// refer to "Authorized Date". This rule is specifically
// created to ensure that "Changed date" is not set to an
// invalid value. The VS WIT OM does nothing for this rule,
// it is enforced on the server.
break;
default:
/*
* All of the special constants that are possible in TFS WIT
* should be handled by the above cases.
*/
throw new UnhandledSpecialConstantIDException(
rule.getThenConstID(),
rule,
"deny write rule ThenConstID"); //$NON-NLS-1$
}
} else {
// triggered by:
// * <ALLOWEDVALUES /> (unless==true, thennot==false,
// ThenImplicitEmpty==true)
// * <PROHIBITEDVALUES /> (unless==true, thennot==true)
// * <VALIDUSER /> (unless==true, thennot==false,
// ThenImplicitEmpty==true)
// * <MATCH /> (unless==true, thennot==false,
// ThenImplicitEmpty==true, ThenLike==true)
// provisioning-generated rules:
// * WITImporter.ProcessDefaultState (unless==true,
// thennot==false)
// * WITImporter.ProcessReasons (unless==true, thennot==false)
// * WITImporter.ProcessStates (unless==true, thennot==false)
// * WITImporter.ProcessWorkItemType (unless==true,
// thennot==false)
// * WITImporter.RestrictTransition (unless==false,
// thennot==false)
/*
* ThenImplicitUnchanged will be true if there was an
* <ALLOWEXISTINGVALUE /> rule at in scope as of the <ALLOWEDVALUES
* />, <VALIDUSER />, or <MATCH /> rule (ThenImplicitUnchanged is
* disabled by the importer for <PROHIBITEDVALUES /> rules). If this
* flag is set, existing values should be allowed, even if they
* would not otherwise pass this rule.
*/
final IConstantSet constantSet = getConstantSetFromThenFields(rule);
final boolean allowedValues = (unless != not);
/*
* update the picklist if the rule is not a pattern match rule
*/
if (!rule.isFlagThenLike()) {
final Set<String> listValues = constantSet.getValues();
final IFieldPickListSupport pickList = field.getPickListSupport();
if (allowedValues) {
pickList.addAllowedValues(listValues);
logRuleEffect = "allowed values (" + listValues.size() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
} else {
field.getPickListSupport().addProhibitedValues(listValues);
logRuleEffect = "prohibited values (" + listValues.size() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
}
}
if (rule.isFlagThenImplicitEmpty() && field.getValue() == null) {
/*
* This rule has no effect, since ThenImplicitEmpty is set and
* the field is empty.
*/
logRuleEffect += " (no denywrite - ThenImplicitEmpty)"; //$NON-NLS-1$
} else if (rule.isFlagThenImplicitUnchanged() && isSameAsOldValue(field)) {
/*
* This rule has no effect, since ThenImplicitUnchanged is set
* and the field has not changed.
*/
logRuleEffect += " (no denywrite - ThenImplicitUnchanged)"; //$NON-NLS-1$
} else {
if (rule.isFlagThenLike()) {
if (!allowedValues) {
/*
* The unless == thennot state should never occur for
* pattern match rules in TFS WIT v1.
*/
throw new UnhandledRuleStateException(
rule,
"pattern match unless=" //$NON-NLS-1$
+ unless
+ " thennot=" //$NON-NLS-1$
+ not);
}
final boolean matches = constantSet.patternMatch(
field.getValue(),
"ruleid:" //$NON-NLS-1$
+ rule.getRuleID()
+ ",fldid:" //$NON-NLS-1$
+ rule.getThenFldID());
if (!matches) {
setInvalidStatus(field, FieldStatus.INVALID_FORMAT);
logRuleEffect = "INVALID_FORMAT"; //$NON-NLS-1$
} else {
/*
* This rule has no effect, since the current value in
* the field matches the pattern.
*/
logRuleEffect += " (pattern matches)"; //$NON-NLS-1$
}
} else {
final Object fieldValue = field.getValue();
final String fieldValueAsString = translateFieldValueIntoString(field.getValue());
/*
* The value is considered in the list if non-null (constant
* sets never contain empty values), the conversion to a
* string succeeded, and the constant set contains the
* value.
*/
final boolean valueInList =
fieldValue != null && fieldValueAsString != null && constantSet.contains(fieldValueAsString);
if ((allowedValues && !valueInList) || (!allowedValues && valueInList)) {
setInvalidStatus(field, FieldStatus.INVALID_LIST_VALUE);
logRuleEffect += ", INVALID_LIST_VALUE"; //$NON-NLS-1$
} else {
logRuleEffect += " (no denywrite effect - value " //$NON-NLS-1$
+ (valueInList ? "is" : "is not") //$NON-NLS-1$ //$NON-NLS-2$
+ " in list)"; //$NON-NLS-1$
}
}
}
}
log.trace(MessageFormat.format(
"applied DW rule [{0}] in area [{1}] to field [{2}]: {3}", //$NON-NLS-1$
Integer.toString(rule.getRuleID()),
Integer.toString(rule.getAreaID()),
getFieldNameForTrace(rule.getThenFldID()),
logRuleEffect));
}