cancel
Showing results for 
Search instead for 
Did you mean: 

Restrict group assignment for bpm:groupAssignees aspect

esheehan
Champ in-the-making
Champ in-the-making
We've got a workflow model with some tasks that include the bpm:groupAssignees aspect and we'd like to restrict the list of groups available to this association to some list. When a user opens this task during the course of the workflow the widget that allows them to select the groups for this association should only display allowed groups.

Is it possible to use constraints to restrict the allowable associations so that these restrictions are enforced at the data model level? Or will we have to just allow the data model any association with a usr:authorityContainer node and use a custom control that only displays the groups we want to allow users to select?

I've seen some other topics about restricting user selection in tasks. There was some hand waving about pooled tasks and custom component generators, but I saw no definitive solution. Any guidance would be appreciated.


-Evan
1 REPLY 1

esheehan
Champ in-the-making
Champ in-the-making
The solution we devised for this problem was to override the UIAssociationEditor component to filter the options for a usr:authorityContainer association in the getAvailableOptions method. We found a way to communicate the list of allowed group names to the component using the constraints tag in the task model and a dummy property in the aspect that contains the association definition.

Task model definition:

<constraints>
    <constraint name="foo:groupConstraints" type="LIST">
        <parameter name="allowedValues">
            <list>
                <value>group1</value>
                <value>group2</value>
                <value>group3</value>
            </list>
        </parameter>
    </constraint>
<constraints>

<aspects>
    <aspect name="foo:restrictedGroupAssignees">
        <properties>
            <property name="foo:restrictedGroupAssigneesConstraints">
                <type>d:text</type>
                <constraints>
                    <constraint ref="foo:groupConstraints" />
                </constraints>
            </property>
        </properties>
        <associations>
            <association name="foo:restrictedGroupAssignees">
                <source>
                    <mandatory>false</mandatory>
                    <many>false</many>
                </source>
                <target>
                    <class>usr:authorityContainer</class>
                    <mandatory>true</mandatory>
                    <many>true</many>
                </target>
            </association>
        </associations>
    </aspect>
</aspects>
The foo:restrictedGroupAssigneesConstraints property is a dummy property that is used to associate the constraint foo:groupConstraints with the association in the aspect. When the overridden getAvailableOptions in our UIFilteredAssociationEditor class is called it looks up the aspect that contains the association, looks for a property by the name <aspect name> + "Constraints" and reads the constraints belonging to that property for use in filtering the association options.

UIFilteredAssociationEditor.getAvailableOptions:

// Look for a property called "constraints" defined alongside the
// association to find the filter constraints.
AssociationDefinition assocDef = getAssociationDefinition(context);
String assocType = assocDef.getTargetClass().getName().toString();

// For now we only filter authorityContainer associations.
if (assocType.equals(ContentModel.TYPE_AUTHORITY_CONTAINER.toString())) {

    // Look up the aspect or type that contains this association definition
    ClassDefinition classDef = assocDef.getSourceClass();
    Map<QName, PropertyDefinition> props = classDef.getProperties();

    // Look up the dummy property containing the constraints by appending "Constraints" to the
    // name of the aspect or type
    PropertyDefinition constraintProp = props.get(QName.createQName(Constants.WORKFLOW_NS,
            String.format("%sConstraints", classDef.getName().getLocalName())));

    List<ConstraintDefinition> constraintList = constraintProp.getConstraints();

    // Generate a list of values listed in all the constraints
    ArrayList<String> allowedNames = new ArrayList<String>();

    for (ConstraintDefinition def : constraintList) {
        Constraint constraint = def.getConstraint();

        if (constraint.getType().equals("LIST")) {
            ListOfValuesConstraint listConstraint = (ListOfValuesConstraint) constraint;

            // Alfresco prepends "GROUP_" to all its group names. We don't want to define our
            // constraints that way, so we prepend the string programmatically here.
            for (String name : listConstraint.getAllowedValues()) {
                allowedNames.add(String.format("GROUP_%s", name));
            }
        }
    }

    // Apply constraints here
}
In our case we removed every group from the availableOptions property whose usr:authorityName didn't match one of the strings we retrieved from the constraints. We hardwired our constraint code to only fire if the association is to a usr:authorityContainer so that we could assume the presence of the usr:authorityName property for comparing the constraint values.

The last piece of the puzzle was getting Alfresco to use our new UIFilteredAssociationEditor class. This turns out to be very simple. We simply created a custom generator that created a UIFilteredAssociationEditor instead of a UIAssociationEditor and applied that generator as needed via the component-generator attribute on the show-association tag in the property sheet definition for any tasks requiring a filtered group association.

web-client-config-custom.xml:

<show-association name="foo:restrictedGroupAssignees" component-generator="FilteredAssociationGenerator" />

You'll also need to add the generator and the faces component to the face configuration.

faces-config-custom.xml:

    <managed-bean>
        <description>
            Bean that generates an association component with a filter
        </description>
        <managed-bean-name>FilteredAssociationGenerator</managed-bean-name>
        <managed-bean-class>your.package.here.FilteredAssociationGenerator</managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
    </managed-bean>

    <component>
        <component-type>your.package.here.faces.FilteredAssociationEditor</component-type>
        <component-class>your.package.here.UIFilteredAssociationEditor</component-class>
    </component>
With these classes in place we can now define any constraint set for a usr:authorityContainer association in a task using Alfresco's content model.


-Evan
Zia Consulting, Inc.