Mass signing and encrypting
Available since v3.0.2 Live Sample GitHub
Mass signing and encrypting is a mass operation that signs and encrypts the files of the selected rows with CryptoPro.
All operation types of Signing and encrypting are available: signing, encrypting, signing and encrypting, encrypting and signing.
To use this functionality, the user must have the required software CryptoPro installed.
How does it look?
[:material-play-circle: Live Sample](http://code-samples.cxbox.org/ui/#/screen/myexample3713/view/myexample3713massencryptgh){:target="_blank"}
[:fontawesome-brands-github: GitHub](https://github.com/CX-Box/cxbox-code-samples/tree/main/src/main/java/org/demo/documentation/feature/encryptsign/encrypt/mass){:target="_blank"}
<video controls width="800">
<source src="/widget/type/property/massoperations/sign/04_encrypt_gh_3713.mp4" type="video/mp4">
</video>
[:material-play-circle: Live Sample](http://code-samples.cxbox.org/ui/#/screen/myexample3714/view/myexample3714masssignencryptgh){:target="_blank"}
[:fontawesome-brands-github: GitHub](https://github.com/CX-Box/cxbox-code-samples/tree/main/src/main/java/org/demo/documentation/feature/encryptsign/signencrypt/mass){:target="_blank"}
<video controls width="800">
<source src="/widget/type/property/massoperations/sign/06_sign_encrypt_gh_3714.mp4" type="video/mp4">
</video>
Basic
It saves the user from signing or encrypting documents one by one: the certificates are selected once, and the files of all selected rows are processed in one run. For example:
- Sign — sign a batch of contracts, acts or invoices with an electronic signature.
- Encrypt — encrypt the documents of the selected rows before sending them to a counterparty.
- Sign and encrypt / Encrypt and sign — prepare a batch of documents for sending in one action, in the required order of operations.
The user sees the result for each row and can repeat the operation for the rows that failed, see Retry for failed rows.
This function is available for widgets:
After clicking on the mass action, the user goes through the steps:
- Select rows
- Review rows
- Certificate selection
- Confirm operation (this step is optional)
- View results
Specifics
- The mass action has the same settings as the action for one document: an item in
options.cryptoGeneratorwith the name of the mass action. - The user selects the certificates once for all rows.
- The files are processed row by row: the file of a row is signed or encrypted, and the new files are saved to the file storage. When all rows are processed, the action is sent once for all of them.
- A row that cannot be processed is skipped, the other rows are processed: a row without a file, a CryptoPro error, a file that was not saved.
- In GroupingHierarchy only rows with records can be selected: a group row without its own record has a disabled checkbox. In the samples the rows are grouped by status.
- The file preview works on all steps. Its arrows go through the rows, the active row does not change.
- The postAction of the mass action runs when the user clicks Close on Step 5.
- The general rules of mass operations apply: the action is executed using the ID of the first selected row, and on the server it runs in a single transaction — if an error occurs, the changes are rolled back for all rows.
How to add?
Example
Step 1 Add a mass action to the corresponding Service.
data.getMassIds_()gives only the rows processed on the frontend. The files of a row are in its options:mass.getOption(MassOptionType.SIGNATURE_FILE_ID)and other keys ofMassOptionType.- The rows that failed on the frontend get their errors in the result automatically. If the handler returns its own result for such a row, its own result is shown.
Step 2 Add an item with the name of the mass action to options.cryptoGenerator and the action to actionGroups in the .widget configuration options.
The settings are the same for List and GroupingHierarchy; GroupingHierarchy also needs options.groupingHierarchy.fields.
List: Live Sample
GroupingHierarchy: Live Sample
@Override
public Actions<MyexampleDTO> getActions() {
return Actions.<MyexampleDTO>builder()
.action(act -> act
.action("massDocumentSign", "Mass Document Sign")
.massInvoker((bc, data, ids) -> {
// only the rows signed on the frontend, the others get the frontend errors automatically
var massResult = data.getMassIds_().stream()
.map(mass -> {
Myexample entity = myexampleRepository.getReferenceById(Long.parseLong(mass.getId()));
entity.setFileSignId(mass.getOption(MassOptionType.SIGNATURE_FILE_ID));
entity.setFileSign(mass.getOption(MassOptionType.SIGNATURE_FILE_NAME));
entity.setStatus(StatusSignEnum.SIGNED);
return MassDTO.success(mass.getId());
})
.collect(Collectors.toSet());
data.getMassErrors_().forEach(error ->
log.warn("Row {} was not signed: {}", error.getId(), error.getErrorMessage()));
return new MassActionResultDTO<MyexampleDTO>(massResult);
})
)
.build();
}
{
"name": "MyExampleMassList",
"title": "",
"type": "List",
"bc": "myexamplemass",
"fields": [
{
"title": "File",
"key": "file",
"type": "fileUpload",
"fileIdKey": "fileId",
"preview": {
"enabled": true
}
},
{
"title": "Status",
"key": "status",
"type": "dictionary",
"bgColorKey": "color"
},
{
"title": "File Signed",
"key": "fileSignRO",
"type": "fileUpload",
"fileIdKey": "fileSignId"
}
],
"options": {
"cryptoGenerator": [
{
"type": "sign",
"actionName": "massDocumentSign",
"documentFileIdKey": "fileId",
"documentFileNameKey": "file",
"signatureFileIdKey": "fileSignId",
"signatureFileNameKey": "fileSign"
}
],
"actionGroups": {
"include": [
"massDocumentSign"
]
}
}
}
List: Live Sample
GroupingHierarchy: Live Sample
@Override
public Actions<MyexampleDTO> getActions() {
return Actions.<MyexampleDTO>builder()
.action(act -> act
.action("massDocumentEncrypt", "Mass Document Encrypt")
.massInvoker((bc, data, ids) -> {
// only the rows processed on the frontend, the others get the frontend errors automatically
var massResult = data.getMassIds_().stream()
.map(mass -> {
Myexample entity = myexampleRepository.getReferenceById(Long.parseLong(mass.getId()));
entity.setFileEncryptId(mass.getOption(MassOptionType.ENCRYPTED_FILE_ID));
entity.setFileEncrypt(mass.getOption(MassOptionType.ENCRYPTED_FILE_NAME));
entity.setStatus(StatusEncryptEnum.ENCRYPT);
return MassDTO.success(mass.getId());
})
.collect(Collectors.toSet());
return new MassActionResultDTO<MyexampleDTO>(massResult);
})
)
.build();
}
{
"name": "MyExampleMassList",
"title": "",
"type": "List",
"bc": "myexamplemass",
"fields": [
{
"title": "File",
"key": "file",
"type": "fileUpload",
"fileIdKey": "fileId",
"preview": {
"enabled": true
}
},
{
"title": "Status",
"key": "status",
"type": "dictionary",
"bgColorKey": "color"
},
{
"title": "File Encrypted",
"key": "fileEncrypt",
"type": "fileUpload",
"fileIdKey": "fileEncryptId"
}
],
"options": {
"cryptoGenerator": [
{
"type": "encrypt",
"actionName": "massDocumentEncrypt",
"documentFileIdKey": "fileId",
"documentFileNameKey": "file",
"encryptedFileIdKey": "fileEncryptId",
"encryptedFileNameKey": "fileEncrypt"
}
],
"actionGroups": {
"include": [
"massDocumentEncrypt"
]
}
}
}
List: Live Sample
GroupingHierarchy: Live Sample
@Override
public Actions<MyexampleDTO> getActions() {
return Actions.<MyexampleDTO>builder()
.action(act -> act
.action("massDocumentSignEncrypt", "Mass Document Sign Encrypt")
.massInvoker((bc, data, ids) -> {
// only the rows processed on the frontend, the others get the frontend errors automatically
var massResult = data.getMassIds_().stream()
.map(mass -> {
Myexample entity = myexampleRepository.getReferenceById(Long.parseLong(mass.getId()));
entity.setFileSignId(mass.getOption(MassOptionType.SIGNATURE_FILE_ID));
entity.setFileSign(mass.getOption(MassOptionType.SIGNATURE_FILE_NAME));
entity.setFileEncryptId(mass.getOption(MassOptionType.ENCRYPTED_FILE_ID));
entity.setFileEncrypt(mass.getOption(MassOptionType.ENCRYPTED_FILE_NAME));
entity.setStatus(StatusSignEncryptEnum.SIGN_ENCRYPT);
return MassDTO.success(mass.getId());
})
.collect(Collectors.toSet());
return new MassActionResultDTO<MyexampleDTO>(massResult);
})
)
.build();
}
{
"name": "MyExampleMassList",
"title": "",
"type": "List",
"bc": "myexamplemass",
"fields": [
{
"title": "File",
"key": "file",
"type": "fileUpload",
"fileIdKey": "fileId",
"preview": {
"enabled": true
}
},
{
"title": "Status",
"key": "status",
"type": "dictionary",
"bgColorKey": "color"
},
{
"title": "File Encrypted",
"key": "fileEncryptRO",
"type": "fileUpload",
"fileIdKey": "fileEncryptId"
},
{
"title": "File Signed",
"key": "fileSignRO",
"type": "fileUpload",
"fileIdKey": "fileSignId"
}
],
"options": {
"cryptoGenerator": [
{
"type": "signAndEncrypt",
"actionName": "massDocumentSignEncrypt",
"documentFileIdKey": "fileId",
"documentFileNameKey": "file",
"signatureFileIdKey": "fileSignId",
"signatureFileNameKey": "fileSign",
"encryptedFileIdKey": "fileEncryptId",
"encryptedFileNameKey": "fileEncrypt"
}
],
"actionGroups": {
"include": [
"massDocumentSignEncrypt"
]
}
}
}
List: Live Sample
GroupingHierarchy: Live Sample
@Override
public Actions<MyexampleDTO> getActions() {
return Actions.<MyexampleDTO>builder()
.action(act -> act
.action("massDocumentEncryptSign", "Mass Document Encrypt Sign")
.massInvoker((bc, data, ids) -> {
// only the rows processed on the frontend, the others get the frontend errors automatically
var massResult = data.getMassIds_().stream()
.map(mass -> {
Myexample entity = myexampleRepository.getReferenceById(Long.parseLong(mass.getId()));
entity.setFileEncryptId(mass.getOption(MassOptionType.ENCRYPTED_FILE_ID));
entity.setFileEncrypt(mass.getOption(MassOptionType.ENCRYPTED_FILE_NAME));
entity.setFileSignId(mass.getOption(MassOptionType.SIGNATURE_FILE_ID));
entity.setFileSign(mass.getOption(MassOptionType.SIGNATURE_FILE_NAME));
entity.setStatus(StatusEncryptSignEnum.SIGN_ENCRYPT);
return MassDTO.success(mass.getId());
})
.collect(Collectors.toSet());
return new MassActionResultDTO<MyexampleDTO>(massResult);
})
)
.build();
}
{
"name": "MyExampleMassList",
"title": "",
"type": "List",
"bc": "myexamplemass",
"fields": [
{
"title": "File",
"key": "file",
"type": "fileUpload",
"fileIdKey": "fileId",
"preview": {
"enabled": true
}
},
{
"title": "Status",
"key": "status",
"type": "dictionary",
"bgColorKey": "color"
},
{
"title": "File Encrypted",
"key": "fileEncryptRO",
"type": "fileUpload",
"fileIdKey": "fileEncryptId"
},
{
"title": "File Signed",
"key": "fileSignRO",
"type": "fileUpload",
"fileIdKey": "fileSignId"
}
],
"options": {
"cryptoGenerator": [
{
"type": "encryptAndSign",
"actionName": "massDocumentEncryptSign",
"documentFileIdKey": "fileId",
"documentFileNameKey": "file",
"signatureFileIdKey": "fileSignId",
"signatureFileNameKey": "fileSign",
"encryptedFileIdKey": "fileEncryptId",
"encryptedFileNameKey": "fileEncrypt"
}
],
"actionGroups": {
"include": [
"massDocumentEncryptSign"
]
}
}
}
To react to the rows that failed on the frontend, read data.getMassErrors_():
Step 1. Select rows
How does it look?
The step works as Select rows of mass operations: checkboxes in the first column, chips of the selected rows, Clear and Select from file.
Available actions:
- Next — moves the user to Step 2.
- Cancel — exits mass-operation mode.
Step 2. Review rows
How does it look?

The step works as Review rows of mass operations: only the selected rows are shown, read-only.
Available actions:
- Next — moves the user to Step 3.
- Back — returns the user to Step 1.
- Cancel — exits mass-operation mode.
Step 3. Certificate selection
How does it look?
If the CryptoPro browser plugin is not installed, the step shows a message with a link to the setup instructions, and only Back is available.

- The user selects the certificates, as in the popup for one document: for signing, for encryption or both, by the operation type.
-
Available actions:
-
While the files are processed, the step shows a progress bar and Processed N of M. Back is not available.
- Stop and send processed - stops the processing at once. The processed rows are sent, the current row and the rows after it get the error "Interrupted". If no row is processed yet, nothing is sent and the step is shown again.
-
If the action fails on the server, the user can go back and run the step again. A row that is already processed with the same certificates and the same file is not processed again until the mass operation is closed.
Step 4. Confirm operation
The step is shown only when the mass action has a confirmation, as in Mass operations with confirmation.
- The confirmation is shown with its own buttons.
- After the yes button, the files are processed on this step, as on Step 3: a progress bar, Processed N of M, Stop and send processed. Then the action is sent.
- The no button closes the mass operation. Nothing is signed or encrypted.
- Back returns the user to Step 3.
The title and the text of the yes and no buttons are customized as in Mass operations with confirmation. The values entered in the confirmation form are applied to all selected rows; an empty field means "clear".
Step 5. View results
The step works as View results of mass operations.
A row that was not processed has the status Fail and the error:
- "There is no file in this row" - the row has no file;
- the text of the CryptoPro error;
- "Interrupted" - the processing was stopped with Stop and send processed before this row.
The widget header displays the number of processed rows, successful and failed. Export and Retry → Export failed work as in mass operations.
The postAction of the mass action runs when the user clicks Close.
Retry for failed rows
The operation is repeated for the failed rows as in mass operations:
- Export — on Step 5 click Retry → Export failed. An Excel file with the failed rows is downloaded.
- Fix the cause — add a file to the rows with "There is no file in this row", or fix the CryptoPro error (for example, select another certificate). Rows with "Interrupted" need no fix.
- Upload — start the mass operation again and on Step 1 select Select from file in the gear menu. Rows are matched only by ID: the exported file keeps it in a hidden column, so the file does not need to be edited.
- Run again — select the certificates and go through the steps as usual.


