Sequence generators

Sequence generators

The main goal of sequence generators is to provide ability to create a unique login or employee id during bulk operation. The most famous use case is creating logins for namesakes during bulk user creation. Due to operations are concurrent it may be difficult to track duplicates against Database. The Sequence generator feature provides across entire installation thread-safe method to have new unique number for the named sequence.

Each sequence is a pair of String and Integer value where String is a name of sequence and Integer is current sequence value.

Sequence generators UI

There is an GUI at webconsole -> Administrator -> Sequence Generators where administrator can manage and view current generators. There are following operations are available:

  • Create new Sequence Generator with some provided initial value.
  • Delete Sequence Generator.

Please, be sure that you understand the problems related to the deletion of Sequence Generator. It may lead the situation of duplicates creation. Please, don't use 'Delete' operation in case you are not sure about the consequences.

Sequence generators API

The usage of the 'Sequence generators' is based on the method call from the Groovy Scripts.

Synchronization Transformation

From the Synchronization Transformation script method getNextValueInSeries can be used

/**
* Method returns next number in series and move it to the offset value.
*
* @param offset - the offset to move value from the key.
* @param key - the key of the counter that will be used.
* @return - moved to the offset value
*/
protected Long getNextValueInSeries(String key,long offset);

Let's review an example of method usage below:

/* 1 */ Long valueFromSerial=getNextValueInSeries("test.user",0);
/* 2 */ Long nextValueFromSerial=getNextValueInSeries("test.user",0);

in the line 1 call of the method getNextValueInSeries will return the next value of sequence name 'test.user' by adding 0 as an offset. If 'test.user' sequence exists then the next value will be returned by method with offset 0. If 'test.user' sequence has not existed before this call, such sequence will be created with initial value = 1 (+ offset) and variable

valueFromSerial will get the Value.

There are some special use cases where offset should be used, but in 99% cases offset should be 0.

Theoretically the value of 'nextValueFromSerial' will be one more than value of valueFromSerial. And this is true for the single thread use-case, however in the async systems like OpenIAM this is not 'true'. In this case please don't rely on the difference of value of several values returned by getNextValueInSeries, due to it's not predictable because of multithreading.

To simplify the usage of the sequence generator the following method can be used:

/**
* Method returns formatted string for next number value in the row.
* <p>
* For example getNextFormattedValueInSeries("%05d", "EMPLOYEE_ID")
* for the 1st invocation will return "00000",
* for the 2nd invocation "00001"
* for the 3rd invocation "00002"
*
* @param format - format of the String
* @param key - the key of the counter that will be used.
* @return - String of value;
*/
protected String getNextFormattedValueInSeries(String format, String key);

Let's check the following code examples: Imaging that sequence "EMPLOYEE_ID" has never been existed before and this is single-thread use case.

String currentEmployeeId=getNextFormattedValueInSeries("EMPLN%d","EMPLOYEE_ID");

The value of currentEmployeeId will be equals to "EMPLN1" Next call will return String "EMPLN2", next "EMPLN3" and etc..

let's use another format

String currentEmployeeId = getNextFormattedValueInSeries("EMPLN%4d","EMPLOYEE_ID");

The value of currentEmployeeId will be equals to "EMPLN0001" Next call will return String "EMPLN0002", next "EMPLN0003" and etc..

Please read article about String format in Java if required.

Groovy for Policy Map

Since 4.2.1.2 all groovy scripts which extends AbstractUserPrincipalGenerator or AbstractPrincipalGenerator uses Sequence generator to track duplicate logins

By default method

protected long count(String login) ;

is used to get next value for login where login is key for sequence.

The default login is firstname.lastname. If such login exists the sequence generator will be used. By default, the following Logins are generating (for OpenIAM managed system):

  1. firstname.lastname
  2. firstname.lastname1
  3. firstname.lastname2

To overwrite this behavior you can do the following:

Use another firstname and last name separator:

import org.openiam.api.connector.model.ConnectorAttribute
import org.openiam.idm.processor.groovy.AbstractUserPrincipalGenerator
import org.openiam.idm.provisioning.diff.model.user.ProvisionUserObjectDiff
import org.openiam.exception.BasicDataServiceException
class UserPrincipal extends AbstractUserPrincipalGenerator {
@Override
public void perform(final ConnectorAttribute attribute, final ProvisionUserObjectDiff diffObject) throws BasicDataServiceException {
perform(attribute, diffObject, "-");
}
}

Output will be like:

  1. firstname-lastname
  2. firstname-lastname1
  3. firstname-lastname2

Use another logic to generate base part of Login

The following example demonstrates how to generate Login as two 1st letters of lastName concatenates with value of first name

import org.apache.commons.collections4.CollectionUtils;
import org.openiam.api.connector.model.ConnectorAttribute;
import org.openiam.api.connector.model.StringOperationalConnectorValue;
import org.openiam.base.AttributeOperationEnum;
import org.openiam.base.request.BaseSearchServiceRequest;
import org.openiam.base.request.IdServiceRequest;
import org.openiam.base.response.data.BooleanResponse;
import org.openiam.base.response.data.LongDataResponse;
import org.openiam.base.ws.MatchType;
import org.openiam.base.ws.ResponseCode;
import org.openiam.base.ws.SearchParam;
import org.openiam.exception.BasicDataServiceException;
import org.openiam.idm.provisioning.diff.model.user.ProvisionUserObjectDiff;
import org.openiam.idm.searchbeans.LoginSearchBean;
import org.openiam.idm.srvc.auth.dto.Login;
import org.openiam.idm.processor.groovy.AbstractUserPrincipalGenerator
class UserPrincipal extends AbstractUserPrincipalGenerator {
public void perform(final ConnectorAttribute attribute, final ProvisionUserObjectDiff diffObject, String splitString) throws BasicDataServiceException {
String origLogin = String.format("%s%s", diffObject.getLastNameDiff().getValue().substring(0, 2), diffObject.getFirstNameDiff().getValue());
String login = origLogin;
while (loginExistsV4211(login)) {
long count = count(origLogin);
login = origLogin + count;
}
attribute.addValue(new StringOperationalConnectorValue(login, AttributeOperationEnum.ADD));
}
}

Output will be like:

  1. lafirstname
  2. lafirstname1
  3. lafirstname2

Add leading zeros to the counting part of the login

The following example demonstrates how to generate Login as two 1st letters of lastName concatenates with value of first name The counting part will contain 4 characters constantly (leading zeros will be added)

import org.apache.commons.collections4.CollectionUtils;
import org.openiam.api.connector.model.ConnectorAttribute;
import org.openiam.api.connector.model.StringOperationalConnectorValue;
import org.openiam.base.AttributeOperationEnum;
import org.openiam.base.request.BaseSearchServiceRequest;
import org.openiam.base.request.IdServiceRequest;
import org.openiam.base.response.data.BooleanResponse;
import org.openiam.base.response.data.LongDataResponse;
import org.openiam.base.ws.MatchType;
import org.openiam.base.ws.ResponseCode;
import org.openiam.base.ws.SearchParam;
import org.openiam.exception.BasicDataServiceException;
import org.openiam.idm.provisioning.diff.model.user.ProvisionUserObjectDiff;
import org.openiam.idm.searchbeans.LoginSearchBean;
import org.openiam.idm.srvc.auth.dto.Login;
import org.openiam.idm.processor.groovy.AbstractUserPrincipalGenerator
class UserPrincipal extends AbstractUserPrincipalGenerator {
public void perform(final ConnectorAttribute attribute, final ProvisionUserObjectDiff diffObject, String splitString) throws BasicDataServiceException {
String origLogin = String.format("%s%s", diffObject.getLastNameDiff().getValue().substring(0, 2), diffObject.getFirstNameDiff().getValue());
String login = origLogin;
while (loginExistsV4211(login)) {
long count = count(origLogin);
login = String.format("%s%4d", origLogin, count);
}
attribute.addValue(new StringOperationalConnectorValue(login, AttributeOperationEnum.ADD));
}
}

Output will be like:

  1. lafirstname
  2. lafirstname0001
  3. lafirstname0002