As I’ve covered in previous posts covering the basics of Teamcenter’s Conditions and discussing how to use Conditions in your ITK, I’ve been playing around with Conditions in Teamcenter lately and have generally found them to be useful and promising. But not everything has been wine and roses. There are some warts, some deficiencies, and some straight up errors with how they work. I feel that I would be remiss if I did not spend some time discussing the problems and pitfalls I’ve found.
WARNING: Don’t redefine parameter type
I could go into a long story about how I managed to cause a lot of grief for our admin, but I’ll cut to the chase: Once you define a condition, don’t go changing the parameter type. For example, if you create and deploy a Condition that works on Items, don’t change it to ItemRevision later. There is a known bug and that change will not be deployed to the server. The BMIDE will not tell you that there is any problem and you’ll appear to be able to deploy the change, but it won’t actually take. You’re better off either deleting the condition and then re-defining it, Or creating a new condition with a different name.
They’re tempting to overuse
This not a problem with Conditions, per se but with over-eager developers. Once you start using conditions, particularly within ITK, you may find yourself using them for every condition you need to check. If you’re not careful you could end up with a whole lot of very trivial Condition objects in the database, many of which might even be unused. There is only one “namespace” for conditions, and it is easy to clutter it up. So try to use some judgement and common sense about it.
If it’d take you two lines of code to do the same check as what the condition will give you, you’re probably better off using the ITK. You should also consider if the check involves any business logic or not. For example, you can probably create a Condition to check if a revision is an assembly or not. But the definition of assembly isn’t really something that depends on your business logic and it’s something easily enough to determine strictly with ITK. On the other hand, checking if an object has one of a particular set of release statuses to determine if it’s valid for some operation is dependent on your business logic and is conceivably something that could change over time, so for that I would give consideration to using a Condition.
Probably the first frustration that users experience is that the hooks in the BMIDE to use Conditions have very specific signatures. Usually the BMIDE will only let you use Conditions that take the
UserSession object as input. This means that most Conditions used to configure the BMIDE can only check things like the current Group, Role, or Project of the user. For example, if you want to check if the current user has membership in ProjectX, even though they’re not logged into it right now, you can’t. You’d have to find some other way of checking for group membership.
Can’t Check for unset values
Another snag I ran into is that you can’t test for unset attribute values unless they’re a reference to another Business Object, i.e. tag_t references. In database SQL terms I want to test if any attribute
IS NULL or
IS NOT NULL. For example, say I had a Drawing item with a string attribute, checker_name, and an initial value of NULL. I might want to define a condition to determine if a checker has been assigned yet or not,
hasChecker(DrawingRevision d) := d.checker_name != NULL # Error!
Unfortunately this isn’t possible because the BMIDE complains that the types don’t match. NULL can currently only be compared to object references (i.e. tag_t attributes), but checker in this example is a string attribute. Since NULL is a valid value in the underlying database tables, we should be able to test for it.
One note: I could test
d.checker_name != "", but an empty string is not the same thing as NULL.
There are only three functions that you can use to manipulate the values you’re examining and apparently there’s no way for customers to add their own. The three functions provided are INLIST, which allows you to search for a value in a list, and ToUpper and ToLower, which transform a string value into upper or lowercase. It certainly didn’t take me long to come up with several functions that I wish I had or could define myself, for example:
- Length(string or list)– return the number of characters in a string or elements in a list.
Example: Check if an string property has a minimum number of characters
minimumLength(Workspace Object obj) := Function::Length(obj.object_name) > 5 # Error: No such Length function exists
Example: Check if an object has multiple statuses
hasMultipleStatuses(WorkspaceObject obj) := Function::Length(obj.release_status_list) > 1
- ANY(Condition, list) – return true if the condition is true for anyvalue in the list
Example: Check if any revision of an item has multiple statuses
hasRevWithMultipleStatuses(Item item) := Function::Any(Condition::hasMultipleStatuses, item.item_revision_list)
- ALL(Condition, list) – return True if the condition is true for allvalues in the list.
Example: Check if all revisions of an item have multiple statuses
allRevsHaveMultipleStatuses(Item item) := Function::All(Condition::hasMultipleStatuses, item.item_revision_list) # Error - no such function
No access to Constants
Conditions can’t access Global or Business Object Constants. I had hoped to create a Business Object Constant, valid_statuses, which defined a list of status names. Then I wanted to test if an object had a valid status by defining a condition something like,
hasValidStatus(WorkspaceObject o) := Function::INLIST(o.last_release_status, o.valid_statuses)
valid_statuses is the value of the Business Object Constant. It seems that your options are to either explicitly list the statuses in your condition definition,
hasValidStatus(WorkspaceObject o) := o.last_release_status=”StatusA” OR o.last_release_status=”StatusB”
Or, to define an Business Object Operation and then use that in your condition,
hasValidStatus(WorkspaceObject o) := o.hasValidStatus()
This is the approach used by the Foundation Condition
isOtherSideLatestMature which invokes the
isLatestRevisionMature() operation which evidently looks up the MaturityStatuses constant.
No “Dynamic Casting”
If you’re checking a revision object you can use the
items_tag to get back to the parent Item and evaluate its properties.
# test if revision has the same name as it's parent item: hasItemsName(ItemRevision revision) := revision.object_name = revision.items_tag.object_name
Now, you might expect, or at least I would, that If I had my own custom Item Type with its own custom attribute, that I could do the same thing for my custom Item type’s properties:
myRevHasSamePropertyAsItem(MyItemRevision my_revision) := my_revision.my_rev_attribute = my_revision.items_tag.my_item_attribute # Error!
As it turns out, items_tag cannot access properties defined on subtypes of Item. The reason is simple enough: items_tag is defined as a property of ItemRevision which refers to an object of type Item. So even though there’s no way for the items_tag of a MyItemRevision object to refer to anything but MyItem, you’re limited to accessing the properties defined for the base type, Item, and there isn’t any way to “cast” (to borrow a programming term) the reference to its proper type.
Small Beer: Minor, but Annoying Issues
In my examples above I used line breaks and
# comments to clarify my examples. Neither is actually possible in real conditions, however. Line breaks will get you an error from the BMIDE and there is no such thing as a comment in the condition definition.
I won’t cry in my beer over it, but it is annoying.
This wraps up the series on Conditions for now. I hope the information will be useful. Have you tried using Conditions for anything yet? What have you found, good, bad or indifferent, about them? Please share you experiences with us.