Miscellaneous Tips, Tricks, and Things to Avoid
This section contains the following topics:
• Working with Access Permission
• Rule of Thumb: One Validator per Action/Attribute/Component
• Do Not Inflate WTReferences
• Handle Null Values
• Use the @Override Annotation
• UIValidationCriteria.toString()
Working with Access Permissions
There is an attribute on UIValidationCriteria that can be used to store access permissions retrieved by one validator or filter, to be used by a subsequent validator or filter. For example, suppose actionA, actionB, and actionC are all in the same action model, and all need to know whether or not the user has modify permissions in the current container. If the validator or filter for actionA is invoked first, it can query the AccessControlManager for the permissions and then store them in the UIValidationCriteria, so that the validators or filters for actionB and actionC do not need to perform the same query again.
In general, if you need to check access permissions in a validator or filter, you should do the following:
• Check to see if the permissions are already stored in the UIValidationCriteria by calling the new UIValidationCriteria.getCachedAccessPermissions() method.
• If getCachedAccessPermissions() returns a non-null value, you can use those permissions for your validation checks
• If getCachedAccessPermissions() returns null, then you can query the AccessControlManager to get the permissions you need, and then store them in the UIValidationCriteria for subsequent validators by calling the setCachedAccessPermissions() method.
The intent of the cachedAccessPermissions attribute in UIValidationCriteira is that it stores the result of AccessControlManager.getPermissions().
So in other words, you should write your validator or filter code to look like this:
// check to see if the access permissions have already been
calculated...
WTKeyedHashMap accessPermissions =
criteria.getCachedAccessPermissions();
// if the access permissions have not been calculated yet, get the
permissions and cache them for other
// validators to use
if (accessPermissions == null){
accessPermissions = AccessControlManager.getPermissions(...);
criteria.setCachedAccessPermissions(accessPermissions);
}
Additional notes regarding access permissions:
• wt.access.AccessControlManager has the following APIs available in single- and multi-object variants:
◦ hasAccess - returns true if the principal has the given access permission, otherwise returns false
◦ checkAccess - throws a NotAuthorizedException and emits an event for auditing purposes if the principal does not have the given access permission
◦ getPermissions - returns the set of permissions (AccessPermissionSet) granted to a principal
• Use hasAccess or getPermissions to evaluate a users rights and continue (e.g., UI action validation code disables action based on the user’s rights)
• One way of checking to see if a user has access is to use one of the hasAccess APIs. Another way would be to have anyone that needs to check permissions for an object (or objects) call one of the two getPermissions APIs, and store the result in the UIValidationCriteria, for use by other validators that also need to check permissions for the same object(s) (assuming the domain, type & state of the object and the current principal remain the same), rather than calling hasAccess to evaluate access rights for each permission check. Even if the result was not stored, the getPermissions API would be useful for any validator that needs to check multiple permissions. AccessPermissionSet is a collection, and has a method to check if a specified permission is in the set:
boolean includes(AccessPermission permission)
// Returns true if permissions in this set include rights for
the specified permission.
• The checkAccess APIs should NOT be used to see if the user has a specified permission, unless the intent is that an exception is to be propagated to the end user. If the NotAuthorizedException is caught and does not result in a user's action failing due to the lack of access rights, auditing of the exception should be disabled. See the Javadoc for more information.
Rule of Thumb: One Validator per Action/Attribute/Component
Don’t fall into the trap of having one validator registered for multiple unrelated actions. The result is lots of if/else branching and some very large methods. This can make maintenance difficult, and makes it much easier to introduce regressions.
In general, the only times where you would use a single validator for multiple actions/components would be if those components share the exact same validation logic, or if you have an either/or scenario. What we mean by an either-or scenario is that in a given action menu, either actionA or actionB should appear, but never both. For all other cases, the best practice is to register one validator per action/component.
Do Not Inflate WTReferences
You may be tempted to write validation code like this. Don't do it.
@Override
public UIValidationResultSet performFullPreValidation
(UIValidationKey validationKey,
UIValidationCriteria validationCriteria, Locale locale)
throws WTException
{
Persistable contextObject =
validationCriteria.getContextObject().getObject();
WTContainer container =
validationCriteria.getParentContainer().getReferencedContainer();
if (!contextObject instanceof WTPart){
...
}
if (!container instanceof PDMLinkProduct){
...
}
}
The code above is performing a relatively costly operation of inflating the WTReferences held in the UIValidationCriteria to Persistables. There may be cases where inflating those objects is not avoidable, but there are also many cases where it can be avoided. If all you really need to know is if the context object or parent container is an instance of some class, you can check the instance like this:
@Override
public UIValidationResultSet performFullPreValidation
(UIValidationKey validationKey,
UIValidationCriteria validationCriteria, Locale locale)
throws WTException
{
WTReference contextObjectRef =
validationCriteria.getContextObject();
WTContainerRef containerRef =
validationCriteria.getParentContainer();
if
(!WTPart.class.isAssignableFrom(contextObjectRef.getReferencedClas
s())){
...
}
if (!(containerRef.getReferencedContainerReadOnly() instanceof PDMLinkProduct){
...
}
}
Handle Null Values
There may be cases where you are writing a validator or filter for a specific action on a specific page in a product where you need one of the attributes from UIValidationCriteria to perform your business logic. For example, suppose you're working on a 3rd level table on an info page, and in that table's toolbar, you don't want an action to appear if you're on an info page for a Generic Part. So you do the following (do not duplicate this code, but for the sake of an example...):
if
(((WTPart)validationCriteria.getContextObject().getObject()).getGe
nericType()
.equals(GenericType.GENERIC)){
...
And that may work fine in your test cases where you're only testing an action on part details pages. But what if that action also appears in the toolbar of one of the tables on the home page, or on the products list page? Then the code above will throw a NullPointerException from those pages, since validationCriteria.getContextObject() will (correctly) return null.
There are a few things you can do to avoid this scenario. The first is to check and make sure that the values you're getting from UIValidationCriteria are not null. If a value is null, log a warning, and call super.[whatever method you're implementing](key, criteria, locale);.
The other thing you can do is when performing comparisons, use the .equals operation on the "expected" value. For example:
if
(ComponentType.WIZARD.equals(validationCriteria.getComponentType()
)
NOT
if
(validationCriteria.getComponentType().equals(ComponentType.WIZARD
))
In general, just because a null value doesn't allow validation to proceed in your use case, that doesn't mean it should be a showstopper in every use case.
Use the @Override Annotation
It’s highly encouraged to use the Override annotation, whenever you over-ride methods of the ootb delivered classes.
UIValidationCriteria.toString()
UIValidationCriteria, the toString() is not over-ridden to give information of its content. You can use the following method for logging.
public String toString_heavy(Logger logger, Level level)
If the Level of the Logger matches with the Level provided, the method will return the information. Since the method performs some costly calculations, it’s suggested to use this it wisely.