# SDK for Java (sdk4j) - OpenKM > This document is intended for LLM consumption. It consolidates the complete documentation for sdk4j, the official Java SDK for the OpenKM REST API. > Source: https://docs.openkm.com/kcenter/view/sdk4j-5.4/ > Compatibility: sdk4j 5.4 requires OpenKM Professional 8.2.4 or newer. --- ## Overview The OpenKM SDK for Java (sdk4j) is a comprehensive toolkit enabling developers to build applications integrated with OpenKM. It wraps the full OpenKM REST API, providing type-safe Java objects, authentication handling, and broad compatibility across OpenKM REST versions while minimising code modifications. **License**: EULA - OpenKM SDK End User License Agreement, published by OpenKM Knowledge Management System S.L. --- ## Maven Setup Add the OpenKM Maven repository and the sdk4j dependency to your `pom.xml`: ```xml openkm.com OpenKM Maven Repository https://maven.openkm.com com.openkm sdk4j 5.4 ``` ### Packaging: maven-shade-plugin When building an executable JAR, use `maven-shade-plugin` (not `maven-assembly-plugin`) to avoid a "missing MessageBodyWriter" error. Include the following transformer: ```xml META-INF/services/javax.ws.rs.ext.MessageBodyWriter ``` --- ## Package Structure | Package | Description | |---------|-------------| | `com.openkm.sdk4j` | Factory class `OKMWebservicesFactory` that returns an `OKMWebservices` instance | | `com.openkm.sdk4j.bean` | Data transfer objects (POJOs) — unmarshalled REST response objects | | `com.openkm.sdk4j.definition` | Interface definitions (`BaseActivity`, `BaseAuth`, etc.) | | `com.openkm.sdk4j.impl` | Concrete implementations (`ActivityImpl`, `AuthImpl`, etc.) | | `com.openkm.sdk4j.util` | Utilities, including ISO8601 date parsing | | `com.openkm.sdk4j.exception` | Exception classes | --- ## Initialization and Authentication ### Creating a client instance ```java import com.openkm.sdk4j.OKMWebservicesFactory; import com.openkm.sdk4j.impl.OKMWebservices; String host = "http://localhost:8080/openkm"; OKMWebservices ws = OKMWebservicesFactory.getInstance(host); ``` `OKMWebservicesFactory.getInstance(String host)` is a static factory method that creates and returns a new `OKMWebservices` object configured for the given host URL. ### Login with username and password ```java ws.login(user, password); ``` ### Login with username, password, expiration and IP restriction ```java ws.login(user, password, expiration, restrictIp); ``` ### Login with a Personal Access Token ```java ws.login(personalAccessToken); ``` All `login()` variants obtain an authorization token from the server and automatically propagate it to every service implementation inside the `OKMWebservices` object. The token is stored internally; you do not need to manage it manually. ### Logout ```java ws.logout(); ``` Invalidates the session and resets the internal authorization token to `null`. ### Refresh token ```java String newToken = ws.getRefreshToken(); ``` Retrieves a refreshed token and updates all service implementations automatically. ### Setting authorization token manually ```java ws.setAuthorizationToken(authorizationToken); ``` Useful when reusing a previously obtained token across sessions. --- ## OKMWebservices — Service Registry `OKMWebservices` is the central object. It holds one public field per service area. All fields are initialized in the constructor. ```java public class OKMWebservices implements Serializable { public AuthImpl auth; public CronTabImpl cronTab; public FolderImpl folder; public PropertyGroupImpl propertyGroup; public RepositoryImpl repository; public ActivityImpl activity; public BookmarkImpl bookmark; public ConversionImpl conversion; public DashboardImpl dashboard; public ImportImpl quickImport; public NoteImpl note; public NotificationImpl notification; public PropertyImpl property; public ShardImpl shard; public RecordImpl record; public NodeImpl node; public PdfImpl pdf; public PluginImpl plugin; public ReportImpl report; public UserConfigImpl userConfig; public StampImpl stamp; public TaskImpl task; public RelationImpl relation; public AutomationImpl automation; public OcrImpl ocr; public SearchImpl search; public DocumentImpl document; public MailImpl mail; public WorkflowImpl workflow; public WizardImpl wizard; } ``` Access any service directly via the field, for example: ```java ws.document.getProperties(uuid); ws.folder.getChildren(parentUuid); ws.auth.getGrantedUsers(nodeUuid); ``` --- ## Service Areas The following table lists all service areas exposed by the SDK: | Field | Interface | Description | |-------|-----------|-------------| | `ws.activity` | `BaseActivity` | Activity log management | | `ws.auth` | `BaseAuth` | Security, users, roles, grants | | `ws.automation` | `BaseAutomation` | Automation rules management | | `ws.bookmark` | `BaseBookmark` | User bookmarks | | `ws.conversion` | `BaseConversion` | Document format conversion | | `ws.cronTab` | `BaseCronTab` | Scheduled tasks (cron) | | `ws.dashboard` | `BaseDashboard` | Dashboard data retrieval | | `ws.document` | `BaseDocument` | Document CRUD, locking, versioning | | `ws.folder` | `BaseFolder` | Folder management | | `ws.quickImport` | `BaseImport` | Bulk import capabilities | | `ws.mail` | `BaseMail` | Email/mail node management | | `ws.node` | `BaseNode` | Generic node operations | | `ws.note` | `BaseNote` | Notes attached to nodes | | `ws.notification` | `BaseNotification` | Subscription and notification management | | `ws.ocr` | `BaseOcr` | OCR processing | | `ws.pdf` | `BasePdf` | PDF operations | | `ws.plugin` | `BasePlugin` | Plugin management | | `ws.property` | `BaseProperty` | Categories and keywords | | `ws.propertyGroup` | `BasePropertyGroup` | Metadata / property groups | | `ws.record` | `BaseRecord` | Records management | | `ws.relation` | `BaseRelation` | Node relationships | | `ws.report` | `BaseReport` | Report execution | | `ws.repository` | `BaseRepository` | Repository-level properties and info | | `ws.search` | `BaseSearch` | Full-text and parametric search | | `ws.shard` | `BaseShard` | Shard management | | `ws.stamp` | `BaseStamp` | Stamp management | | `ws.task` | `BaseTask` | Task creation and tracking | | `ws.userConfig` | `BaseUserConfig` | User configuration | | `ws.wizard` | `BaseWizard` | Wizard actions | | `ws.workflow` | `BaseWorkflow` | Workflow process execution | --- ## Quick Start Example ```java package com.openkm; import com.openkm.sdk4j.OKMWebservicesFactory; import com.openkm.sdk4j.bean.Folder; import com.openkm.sdk4j.impl.OKMWebservices; public class QuickStart { public static void main(String[] args) { String host = "http://localhost:8080/openkm"; String user = "okmAdmin"; String password = "admin"; OKMWebservices ws = OKMWebservicesFactory.getInstance(host); try { ws.login(user, password); // List children folders of a node by UUID for (Folder fld : ws.folder.getChildren("4f873d10-654e-4d99-a94f-15466e30a0f6")) { System.out.println("Folder -> " + fld.getPath()); } // Get repository version info System.out.println(ws.repository.getAppVersion()); } catch (Exception e) { e.printStackTrace(); } } } ``` --- --- ## ws.activity — Activity Log Activity logs track system events across documents, folders, mail items, and records. ### findActivityLog ```java ActivityList findActivityLog(int page, int length, Calendar beginDate, Calendar endDate, String user, String action, String item) ``` Retrieves activity log entries filtered by date range, user, action type, and node. | Parameter | Type | Description | |-----------|------|-------------| | `page` | int | Page number for pagination (0-based) | | `length` | int | Number of records per page | | `beginDate` | Calendar | Start date filter | | `endDate` | Calendar | End date filter | | `user` | String | Username filter | | `action` | String | Action type filter; empty string returns all actions | | `item` | String | UUID of the node (document, folder, mail, or record) | **Returns:** `ActivityList` — iterable via `getActivities()` ```java ws.login(user, password); Calendar beginDate = Calendar.getInstance(); beginDate.add(Calendar.MONTH, -1); Calendar endDate = Calendar.getInstance(); String item = "f84a2e1f-a858-4e53-9c09-36519d903782"; ActivityList results = ws.activity.findActivityLog(0, 20, beginDate, endDate, user, "", item); for (Activity activity : results.getActivities()) { System.out.println(activity); } ``` ### getActivityActions ```java List getActivityActions() ``` Returns all valid activity action type identifiers supported by the system. ```java for (String action : ws.activity.getActivityActions()) { System.out.println(action); } ``` --- ## ws.auth — Authentication & Authorization ### Permission constants `com.openkm.sdk4j.bean.Permission` provides permission bit constants. Combine with `+` or `|`: ```java int permission = Permission.READ + Permission.WRITE; // Check: if ((permission | Permission.WRITE) == Permission.WRITE) { /* has WRITE */ } ``` ### Authentication | Method | Description | |--------|-------------| | `ws.login(user, password)` | Login; token valid 24 h by default | | `ws.login(user, password, expiration, restrictIP)` | Login with custom expiration (days) and optional IP lock | | `ws.setAuthorizationToken(token)` | Reuse a previously obtained token | | `ws.logout()` | Server-side logout; resets internal token | | `ws.getRefreshToken()` | Refresh current token before expiration | | `ws.auth.setUserTenant(tenantId)` | Switch tenant in multi-tenant environments | | `ws.auth.getToken()` | Generate a fresh token (resets 24 h expiration) | > **Note:** The first successful `login()` creates the user's folder structure (`/okm:trash/userId`). Without it, path-based calls will throw `PathNotExistsException`. ### User management ```java // List all users (including disabled) List users = ws.auth.getUsers(true); // Get a single user CommonUser u = ws.auth.getUser("okmAdmin"); // Users by role List admins = ws.auth.getUsersByRole("ROLE_ADMIN"); // Create user ws.auth.createUser("test", "password.2016", "test@mail.com", "User test", true); // Update user ws.auth.updateUser("test", "newpassword", "test@mail.com", "Test", false); // Delete user ws.auth.deleteUser("test"); // Get token for a specific user String token = ws.auth.getUserToken("okmAdmin"); ``` ### Role management ```java // List all roles List roles = ws.auth.getRoles(true); // Roles assigned to a user List userRoles = ws.auth.getRolesByUser("okmAdmin"); // Create / update / delete role ws.auth.createRole("ROLE_TEST", true); ws.auth.updateRole("ROLE_TEST", true); ws.auth.deleteRole("ROLE_TEST"); // Assign / remove role from user ws.auth.assignRole("test", "ROLE_USER"); ws.auth.removeRole("test", "ROLE_USER"); ``` ### Node permissions (grants) ```java // Read grants on a node GrantedUsersAndRolesItem grants = ws.auth.getGrantedUsersAndRoles(uuid); Map userGrants = ws.auth.getGrantedUsers(uuid); Map roleGrants = ws.auth.getGrantedRoles(uuid); // Grant permissions ws.auth.grantUser(uuid, "sochoa", Permission.ALL_GRANTS, false); ws.auth.grantRole(uuid, "ROLE_USER", Permission.ALL_GRANTS, true); // recursive // Revoke permissions ws.auth.revokeUser(uuid, "sochoa", Permission.ALL_GRANTS, false); ws.auth.revokeRole(uuid, "ROLE_USER", Permission.ALL_GRANTS, false); // Set (overwrite) permissions for a user or role on a node ws.auth.setUserPermissions(uuid, "sochoa", Permission.READ + Permission.WRITE, false); ws.auth.setRolePermissions(uuid, "ROLE_USER", Permission.READ + Permission.WRITE, false); ``` > The `recursive` flag only applies to folders and record nodes. ### Batch security changes **`changeSecurity`** — adds grants (does not remove existing ones): ```java List guList = new ArrayList<>(); GrantedUser gu = new GrantedUser(); gu.setUser("sochoa"); gu.setPermissions(Permission.ALL_GRANTS); guList.add(gu); List grList = new ArrayList<>(); GrantedRole gr = new GrantedRole(); gr.setRole("ROLE_TEST"); gr.setPermissions(Permission.READ | Permission.WRITE); grList.add(gr); ChangeSecurity cs = new ChangeSecurity(); cs.setGrantedUsersList(guList); cs.setGrantedRolesList(grList); cs.setRecursive(true); ws.auth.changeSecurity(uuid, cs); ``` **`overwriteSecurity`** — replaces all grants completely: ```java ChangeSecurity cs = new ChangeSecurity(); cs.setGrantedUsersList(guList); cs.setRecursive(true); ws.auth.overwriteSecurity(uuid, cs); ``` ### Profiles ```java // List active profiles for (Profile p : ws.auth.getProfiles(true)) { System.out.println(p.getName()); } // Get profile of a user Profile p = ws.auth.getUserProfile("okmAdmin"); // Assign profile to user ws.auth.setUserProfile("test", profile.getId()); ``` ### Tenants ```java List tenants = ws.auth.getUserTenants(); ws.auth.setUserTenant(tenantId); ``` ### Personal Access Tokens ```java // Create long-lived token with expiration date Calendar expiration = Calendar.getInstance(); expiration.set(Calendar.DAY_OF_MONTH, 10); String pat = ws.auth.createPersonalAccessToken("openkm", expiration); ``` ### Misc ```java String sessionId = ws.auth.getSessionId(); boolean isLowercase = ws.auth.isLoginLowercase(); ``` --- ## ws.bookmark — Bookmarks Bookmarks allow users to save references to nodes (documents, folders, mails, records) for quick access. ### getUserBookmarks ```java List getUserBookmarks() ``` Returns all bookmarks of the currently authenticated user. ```java for (Bookmark bookmark : ws.bookmark.getUserBookmarks()) { System.out.println(bookmark); } ``` ### create ```java void create(String uuid, String name) ``` Creates a bookmark for the node identified by `uuid`. ```java ws.bookmark.create("a37f570f-5e7f-4a03-8d04-9b3689be82f1", "test bookmark"); ``` ### rename ```java void rename(int bookmarkId, String name) ``` Renames an existing bookmark. ```java ws.bookmark.rename(1, "set bookmark"); ``` ### delete ```java void delete(int bookmarkId) ``` Removes a bookmark by its ID. ```java ws.bookmark.delete(1); ``` --- --- ## ws.conversion — Document & Image Conversion All conversion methods stream content in and out via `InputStream`. Use `IOUtils.copy()` (Apache Commons IO) for stream handling and always close streams after use. > **Server requirements:** `doc2pdf` requires OpenOffice/LibreOffice enabled. `imageConvert` requires ImageMagick. `html2pdf` requires an HTML-to-PDF tool. `img2txt` requires an OCR engine. `barcode2txt` requires barcode tools. ### doc2pdf ```java InputStream doc2pdf(InputStream is, String fileName) ``` Converts a document to PDF. The `fileName` is used to detect the MIME type by extension (e.g. `"report.docx"`). Returns an `InputStream` with the PDF content. ```java InputStream pdfStream = ws.conversion.doc2pdf(new FileInputStream("report.docx"), "report.docx"); IOUtils.copy(pdfStream, new FileOutputStream("report.pdf")); pdfStream.close(); ``` ### imageConvert ```java InputStream imageConvert(InputStream is, String fileName, String params, String dstMimeType) ``` Applies an ImageMagick transformation to an image. | Parameter | Description | |-----------|-------------| | `is` | Source image stream | | `fileName` | Original filename (for MIME detection) | | `params` | ImageMagick parameters — **must end with `${fileIn} ${fileOut}`**, use single spaces | | `dstMimeType` | Output MIME type (e.g. `"image/png"`) | ```java InputStream result = ws.conversion.imageConvert( new FileInputStream("photo.jpg"), "photo.jpg", "-resize 800x600 ${fileIn} ${fileOut}", "image/jpeg"); ``` > Tip: test your ImageMagick `params` locally before integrating. ### html2pdf ```java InputStream html2pdf(String url) ``` Converts a public URL to a PDF document. Returns the PDF as an `InputStream`. ```java InputStream pdf = ws.conversion.html2pdf("https://www.example.com"); IOUtils.copy(pdf, new FileOutputStream("page.pdf")); pdf.close(); ``` ### doc2txt ```java String doc2txt(InputStream is, String fileName) ``` Extracts plain text from a document. Requires an appropriate text extractor configured for the document's MIME type. ```java String text = ws.conversion.doc2txt(new FileInputStream("report.pdf"), "report.pdf"); System.out.println(text); ``` ### img2txt ```java String img2txt(InputStream is, String fileName) ``` Performs OCR on an image and returns the recognised text. ```java String text = ws.conversion.img2txt(new FileInputStream("scan.png"), "scan.png"); ``` ### barcode2txt ```java String barcode2txt(InputStream is, String fileName) ``` Decodes a barcode from an image and returns the decoded value as a String. ```java String barcode = ws.conversion.barcode2txt(new FileInputStream("barcode.png"), "barcode.png"); ``` --- ## ws.dashboard — Dashboard All dashboard methods are paginated via `offset` and `limit` parameters and return a `DashboardResultSet` (except `getUserSearches` and `findUserSearches`). **Pagination pattern:** - `offset=0, limit=10` → first 10 results - `offset=10, limit=10` → results 11–20 **Return types:** - `DashboardResultSet` — contains total count + list of `DashboardResult` objects - `List` — list of saved search parameter objects - `List` — list of node results from a saved search ### Method reference | Method | Description | |--------|-------------| | `getUserCheckedOutDocuments(offset, limit)` | Documents currently checked out by the user | | `getUserLockedDocuments(offset, limit)` | Documents locked by the user | | `getUserLastModifiedDocuments(offset, limit)` | Documents most recently modified by the user | | `getUserLastDownloadedDocuments(offset, limit)` | Documents most recently downloaded | | `getUserLastCreatedDocuments(offset, limit)` | Documents most recently created | | `getUserLastDocumentsNotesCreated(offset, limit)` | Notes recently added to documents | | `getUserSubscribedDocuments(offset, limit)` | Documents the user is subscribed to | | `getUserLockedFolders(offset, limit)` | Folders locked by the user | | `getUserSubscribedFolders(offset, limit)` | Folders the user is subscribed to | | `getUserLastCreatedFolders(offset, limit)` | Folders recently created by the user | | `getUserLastFoldersNotesCreated(offset, limit)` | Notes recently added to folders | | `getUserLockedRecords(offset, limit)` | Records locked by the user | | `getUserSubscribedRecords(offset, limit)` | Records the user is subscribed to | | `getUserLastCreatedRecords(offset, limit)` | Records recently created | | `getUserLastRecordsNotesCreated(offset, limit)` | Notes recently added to records | | `getUserLockedMails(offset, limit)` | Mails locked by the user | | `getUserLastImportedMails(offset, limit)` | Recently imported mail messages | | `getUserLastMailsNotesCreated(offset, limit)` | Notes recently added to mails | | `getUserLastImportedMailAttachments(offset, limit)` | Mails with attachments recently imported | | `getUserSearches()` | Returns `List` of saved user searches | | `findUserSearches(long qpId)` | Executes a saved search by ID; returns `List` | ```java // Example: list the last 10 created documents DashboardResultSet result = ws.dashboard.getUserLastCreatedDocuments(0, 10); System.out.println("Total: " + result.getTotal()); for (DashboardResult item : result.getResults()) { System.out.println(item); } // Example: run a saved search List searches = ws.dashboard.getUserSearches(); for (QueryParams qp : searches) { List nodes = ws.dashboard.findUserSearches(qp.getId()); nodes.forEach(System.out::println); } ``` --- ## ws.document — Documents The most comprehensive service in the SDK. Covers the full lifecycle of a document: creation, content management, versioning, metadata, security, PDF operations, OCR, and more. ### Creation ```java Document create(String uuid, File file) Document create(String uuid, String name, InputStream is) Document create(String uuid, String name, InputStream is, long nodeClass) ``` - `uuid` — UUID of the parent folder or record node - `nodeClass` — business type (series) ID; `0` = none - Returns a `Document` object with all properties of the created document ```java // From a File Document doc = ws.document.create(folderUuid, new File("/tmp/report.pdf")); // From an InputStream with a name Document doc = ws.document.create(folderUuid, "report.pdf", new FileInputStream("/tmp/report.pdf")); ``` ### Reading content & properties ```java Document getProperties(String uuid) InputStream getContent(String uuid) InputStream getContentByVersion(String uuid, String versionName) String getExtractedText(String uuid) InputStream getThumbnail(String uuid, ThumbnailType type) List getChildren(String uuid) // children of a folder/record List getCheckedOut() // all docs checked out by current user int getNumberOfPages(String uuid) String getPageAsImage(String uuid, int pageNumber, int maxWidth, int maxHeight) // Base64 ``` > Use `getProperties()` to get the content length before streaming; do not rely on `InputStream.available()`. `ThumbnailType` values: `THUMBNAIL_PROPERTIES`, `THUMBNAIL_LIGHTBOX`, `THUMBNAIL_SEARCH`. ### Metadata ```java void setTitle(String uuid, String title) void setDescription(String uuid, String description) void setLanguage(String uuid, String lang) // ISO 639-1 code void setExtractedText(String uuid, InputStream is) void setNodeClass(String uuid, long nodeClass) void setDispositionStage(String uuid, long stage) void setIndexable(String uuid, boolean enabled) List getDetectedLanguages() ``` ### Rename, move, copy, delete ```java Document rename(String uuid, String newName) void move(String uuid, String dstId) Document copy(String uuid, String dstId, String newName) void delete(String uuid) // moves to /okm:trash/userId void purge(String uuid) // permanent deletion — no recovery without backup ``` **`extendedCopy`** — copy with selective metadata transfer (use `ws.node.extendedCopy`): ```java // copies categories, keywords, propertyGroups, notes, and security grants ws.node.extendedCopy(nodeId, dstId, name, true, true, true, true, true); ``` ### Version control ```java void checkout(String uuid) void cancelCheckout(String uuid) // only by the user who checked out void forceCancelCheckout(String uuid) // requires ROLE_ADMIN boolean isCheckedOut(String uuid) Version checkin(String uuid, InputStream is, String comment) Version checkin(String uuid, InputStream is, String comment, int increment) long getVersionHistorySize(String uuid) // bytes used by version history ``` - Only the user who performed `checkout` can `checkin` or `cancelCheckout`. - `increment` ≥ 1 depends on the configured `VersionNumberAdapter`. ### Annotations & differences ```java String getAnnotations(String uuid, String versionName) InputStream getDifferences(String uuid, String versionName1, String versionName2) // PDF ``` ### PDF operations ```java String mergePdf(String dstId, String docName, List uuids) // returns UUID of merged doc InputStream getPdf(String uuid) Document saveAsPdf(String uuid, String newName, boolean propertyGroups, boolean security) boolean isConvertibleToPDF(String uuid) // false if already PDF void webPageImport(String uuid, String url) // saves web page as PDF into repository ``` ### OCR / data capture ```java boolean isOCRDataCaptureSupported(String uuid) OCRRecognise recognize(String uuid) void captureData(String uuid, long templateId) ``` ### Stamps ```java void stamp(String uuid, int type, long stampId) ``` ### Templates ```java WizardNode createWizard(String uuid, String name, long nodeClass, InputStream is) void updateFromTemplate(String uuid, String dstId, Map properties) ``` `createWizard` returns a `WizardNode` describing pending wizard actions (keyword assignment, category selection, metadata entry). Property values in the `properties` map must use ISO 8601 date format for date fields. ### Live Edit ```java void liveEditCheckin(String uuid, String comment, int increment) void liveEditCancelCheckout(String uuid) void liveEditForceCancelCheckout(String uuid) // requires ROLE_ADMIN void liveEditSetContent(String uuid, InputStream is) ``` ### AutoCAD (XRef) ```java List getXrefs(String uuid) void refresh(String uuid) ``` ### Utility / validation ```java boolean isValid(String uuid) // true if node is a document boolean isAttachment(String uuid) // true if node is a mail attachment String getPath(String uuid) // converts UUID to repository path ``` --- --- ## ws.filePlan — File Plan The file plan organises records management through a hierarchy of **sections** and **classes** (node classes / business types). All methods are accessed via `ws.filePlan`. **Key beans:** - `NodeClassSectionDefinition` — an organisational section within the file plan - `NodeClass` — a classification category (business type) for records ### Sections ```java // Root sections List getRootSections() List getRootSectionsFilteredBySecurity(int permissions) List getRootSectionsIdWithChildren() List getAllSectionsOrdered() // Children sections List getChildrenSections(long parentId) List getChildrenSectionsFilteredBySecurity(long parentId, int permissions) List getChildrenSectionsIdByTenantWithChildren(long parentId) // Search & navigation List findSectionFiltered(String code, String name, int permissions) List getBreadcrumb(long sectionId) // path from root to section ``` ### Classes (Node Classes) ```java List getChildrenClasses(long sectionId) List getChildrenClassesFilteredBySecurity(long sectionId, int permissions) NodeClass findNodeClassByPk(long id) List getAllNodeClasses() List findElectronicRecordClasses() List findElectronicRecordClassesFilteredBySecurity(int permissions) List findFilteredByCodeOrNameFilteredBySecurity(String code, String name, int permissions) ``` **Permissions** use constants from `com.openkm.sdk4j.bean.Permission` (`READ`, `WRITE`, `ALL_GRANTS`, etc.), combined with `+` or `|`. ```java // Example: list root sections the current user can read List roots = ws.filePlan.getRootSectionsFilteredBySecurity(Permission.READ); for (NodeClassSectionDefinition s : roots) { System.out.println(s); } // Example: find all electronic record classes List classes = ws.filePlan.findElectronicRecordClasses(); // Example: search sections by code prefix List results = ws.filePlan.findSectionFiltered("1.", "invoice", Permission.ALL_GRANTS); // Example: get breadcrumb path to a section List breadcrumb = ws.filePlan.getBreadcrumb(3L); ``` --- ## ws.folder — Folders ### Creation ```java Folder create(String uuid, String name) Folder create(String uuid, String name, long nodeClass) ``` - `uuid` — UUID of the parent folder or record node - `nodeClass` — business type (series) ID; `0` = none - Returns a `Folder` object with all properties of the created folder ```java Folder folder = ws.folder.create("1be884f4-5758-4985-94d1-f18bfe004db8", "test"); ``` ### Reading ```java Folder getProperties(String uuid) List getChildren(String uuid) ContentInfo getContentInfo(String uuid) // folder count, doc count, record count, mail count, total bytes boolean isValid(String uuid) // true if node is a folder String getPath(String uuid) // UUID → repository path ``` ```java for (Folder fld : ws.folder.getChildren(parentUuid)) { System.out.println(fld.getPath()); } ContentInfo info = ws.folder.getContentInfo(uuid); // info.getFolders(), info.getDocuments(), info.getRecords(), info.getMails(), info.getSize() ``` ### Rename, move, copy, delete ```java Folder rename(String uuid, String newName) void move(String uuid, String dstId) Folder copy(String uuid, String dstId, String newName) // null newName keeps original name Folder extendedCopy(String uuid, String dstId, String newName, boolean categories, boolean keywords, boolean propertyGroups, boolean notes, boolean security) void delete(String uuid) // moves to /okm:trash/userId void purge(String uuid) // permanent — only recoverable from backup ``` > `copy` transfers only security grants. Use `extendedCopy` to also copy categories, keywords, property groups, notes, and/or security. ```java // Simple copy, keep original name ws.folder.copy(srcUuid, dstUuid, null); // Extended copy with full metadata ws.folder.extendedCopy(srcUuid, dstUuid, "new name folder", true, true, true, true, true); ``` ### Metadata ```java void setDescription(String uuid, String description) void createMissingFolders(String fldPath) // creates all missing folders in the given path ``` ```java ws.folder.createMissingFolders("/okm:root/projects/2024/q1"); ``` ### Templates ```java Folder createFromTemplate(String uuid, String dstPath, boolean categories, boolean keywords, boolean notes, Map properties) Folder createFromTemplate(String uuid, String dstPath, boolean categories, boolean keywords, boolean notes, boolean propertyGroups, boolean security, Map properties) ``` Property values must use ISO 8601 for dates and JSON array notation for multi-value fields: ```java Map props = new HashMap<>(); props.put("okp:tpl.name", "sdk name"); props.put("okp:tpl.bird_date", ISO8601.formatBasic(Calendar.getInstance())); props.put("okp:tpl.language", "[ \"java\" ]"); props.put("okp:technology.comment", "sdk comment"); Folder folder = ws.folder.createFromTemplate( templateUuid, "/okm:root/sdk/template", true, true, true, props); ``` --- ## ws.quickImport — Quick Import Optimised for bulk or high-performance scenarios. Executes fewer background steps than the standard `ws.document` and `ws.folder` creation methods. ### importDocument ```java String importDocument(String uuid, File file) ``` Creates a document inside the folder or record identified by `uuid`. Returns the UUID of the created document. ```java File file = new File("/tmp/logo.png"); String docUuid = ws.quickImport.importDocument(folderUuid, file); System.out.println("Created: " + docUuid); ``` ### importFolder ```java String importFolder(String uuid, String name) ``` Creates a folder inside the parent folder or record identified by `uuid`. Returns the UUID of the created folder. ```java String fldUuid = ws.quickImport.importFolder(parentUuid, "folder-name"); System.out.println("Created: " + fldUuid); ``` > Use `ws.quickImport` instead of `ws.document.create` / `ws.folder.create` when doing large batch imports where performance is critical. --- --- ## ws.mail — Mail Mail nodes represent email messages stored in the repository. They can have attachments (as `Document` nodes), be imported from EML/MSG files, and be sent via the server's mail configuration. ### Core operations ```java Mail getProperties(String uuid) List getChildren(String uuid) // mails inside a folder/record boolean isValid(String uuid) String getPath(String uuid) void delete(String uuid) // moves to trash void purge(String uuid) // permanent deletion Mail rename(String uuid, String newName) void move(String uuid, String dstId) Mail copy(String uuid, String dstId, String newName) Mail extendedCopy(String uuid, String dstId, boolean categories, boolean keywords, boolean propertyGroups, boolean notes, boolean security, String newName) ``` ### Metadata ```java void setTitle(String uuid, String title) void setDescription(String uuid, String description) void setNodeClass(String uuid, long nodeClass) void setDispositionStage(String uuid, long stage) String getExtractedText(String uuid) ``` ### Attachments ```java Document createAttachment(String uuid, String docName, InputStream is) void deleteAttachment(String uuid, String docId) List getAttachments(String uuid) ``` ```java // Add attachment InputStream is = new FileInputStream("/tmp/logo.png"); ws.mail.createAttachment(mailUuid, "logo.png", is); is.close(); // List attachments List atts = ws.mail.getAttachments(mailUuid); ``` ### Import (EML / MSG) ```java Mail importEml(String uuid, String title, InputStream is) Mail importMsg(String uuid, String title, InputStream is) ``` ```java InputStream is = new FileInputStream("/tmp/email.eml"); Mail mail = ws.mail.importEml(folderUuid, "Email Title", is); is.close(); ``` ### Export / PDF ```java InputStream getContent(String uuid) // raw EML stream InputStream getThumbnail(String uuid, ThumbnailType type) InputStream getPdf(String uuid) Document saveAsPdf(String uuid, String newName, boolean propertyGroups, boolean security) ``` ### Sending mail ```java // Single recipient void sendMail(String to, String subject, String body) // Multiple recipients void sendMail(List to, String subject, String body) // With explicit sender void sendMail(String from, List to, String subject, String body) // With repository document attachments Mail sendMailWithAttachments(String from, List to, List cc, List bcc, List replyTo, String subject, String body, List docsId, String uuid) ``` ```java List to = Arrays.asList("user@example.com"); List docsId = Arrays.asList("46762f90-82c6-4886-8d21-ad3017dd78a7"); ws.mail.sendMailWithAttachments("sender@example.com", to, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), "Subject", "Body", docsId, folderUuid); ``` ### Forward email ```java void forwardEmail(String uuid, List users, List roles, List mails, String message) ``` ### Paginated listing ```java SimpleNodeBaseResultSet getMailsPaginated(String context, int offset, int limit, MailFilterQuery filter, String orderColumn, boolean orderAsc) ``` `orderColumn` values: `uuid`, `subject`, `from`, `sentDate`, `hasAttachments` ```java MailFilterQuery filter = new MailFilterQuery(); filter.setSubject(""); filter.setAttachments(AttachmentEnum.ALL); SimpleNodeBaseResultSet results = ws.mail.getMailsPaginated("/okm:root", 0, 20, filter, "subject", true); ``` ### Wizard creation ```java WizardNode createWizard(String uuid, String title, InputStream is, String type) // type: Mail.ORIGIN_EML or Mail.ORIGIN_MSG ``` ### Mail account management ```java List getAccounts() void addAccount(MailAccount account) void updateAccount(MailAccount account) void testAccount(MailAccount account) void deleteAccount(long mailAccountId) MailServerMessages getMailMessages(long accountId, long start) void importMessages(long mailAccountId, List messageIds) ``` `MailAccount` protocol constants: `PROTOCOL_IMAPS`, `PROTOCOL_IMAP`, `PROTOCOL_POP3`, `PROTOCOL_POP3S`. ```java MailAccount account = new MailAccount(); account.setMailProtocol(MailAccount.PROTOCOL_IMAPS); account.setMailUser("inbox@gmail.com"); account.setMailPassword("secret"); account.setMailHost("imap.gmail.com"); account.setMailFolder("OpenKM"); account.setActive(true); account.setRecursive(true); ws.mail.addAccount(account); ``` ### Mail filter management ```java void createFilter(long mailAccountId, MailFilter filter) void updateFilter(MailFilter filter) void deleteFilter(long mailFilterId) List getFilterRules(long filterId) void createRule(long filterId, MailFilterRule rule) void updateRule(MailFilterRule rule) void deleteRule(long ruleId) ``` `MailFilterRule` field constants: `FIELD_FROM`, `FIELD_TO`, `FIELD_SUBJECT`, `FIELD_CONTENT`, `FIELD_ATTACHMENT` `MailFilterRule` operation constants: `OPERATION_EQUALS`, `OPERATION_NOT_EQUALS`, `OPERATION_CONTAINS`, `OPERATION_ENDS_WITH`, `OPERATION_STARTS_WITH` ```java MailFilterRule rule = new MailFilterRule(); rule.setField(MailFilterRule.FIELD_FROM); rule.setOperation(MailFilterRule.OPERATION_CONTAINS); rule.setValue("test@domain.com"); rule.setActive(true); ws.mail.createRule(filterId, rule); ``` --- ## ws.node — Generic Node Operations Node operations work across all node types (documents, folders, mails, records). Access via `ws.node`. ### Node retrieval ```java Node getNodeByUuid(String uuid) List getNodesFiltered(List uuids) ``` ### Version management ```java List getVersionHistory(String uuid) void restoreVersion(String uuid, String versionName) void renameVersion(String uuid, String versionName, String newName) void deleteVersion(String uuid, String versionName) void purgeVersionHistory(String uuid) void setComment(String uuid, String versionName, String comment) ``` ### Locking ```java LockInfo lock(String uuid) LockInfo forceLock(String uuid) // requires ROLE_ADMIN void unlock(String uuid) void forceUnlock(String uuid) // requires ROLE_ADMIN boolean isLocked(String uuid) LockInfo getLockInfo(String uuid) boolean hasNodesLockedByOtherUser(String uuid) ``` ### Record promotion ```java PromoteAsRecordEvaluation mayBePromotedAsRecord(String uuid, boolean fullEvaluation) void promoteAsRecord(String uuid) void degradeRecord(String uuid) boolean isElectronicRecordPath(String uuid) Record getElectronicRecordInPath(String uuid) // first Record ancestor in path ``` ### Subscriptions ```java void subscribe(String uuid) void unsubscribe(String uuid) ``` ### Paginated children ```java SimpleNodeBaseList getChildrenNodesPaginated( String uuid, int offset, int limit, String filter, String orderByField, boolean orderAsc, List filteredTypes) SimpleNodeBaseList getChildrenNodesPaginated( String uuid, int offset, int limit, String filter, String orderByField, boolean orderAsc, List filteredTypes, String pluginName) SimpleNodeBaseList getChildrenNodesByCategoryPaginated( String uuid, int offset, int limit, String filter, String orderByField, boolean orderAsc, List filteredTypes) SimpleNodeBaseList getChildrenNodesByCategoryPaginated( String uuid, int offset, int limit, String filter, String orderByField, boolean orderAsc, List filteredTypes, String pluginName) ``` `filteredTypes` uses constants from `SimpleNodeBase`: `TYPE_FOLDER`, `TYPE_DOCUMENT`, `TYPE_MAIL`, `TYPE_RECORD` `orderByField` uses constants from `NodesPaginationInfo`: `ORDER_BY_NAME`, etc. ```java List types = Arrays.asList(SimpleNodeBase.TYPE_FOLDER, SimpleNodeBase.TYPE_DOCUMENT); SimpleNodeBaseList result = ws.node.getChildrenNodesPaginated( parentUuid, 0, 10, "", NodesPaginationInfo.ORDER_BY_NAME, true, types); // result.getNodes(), result.getTotal(), result.getFilteredCount() ``` ### Navigation ```java List getBreadcrumb(String uuid) // path from root to node ``` ### ZIP import / export ```java void importZip(String uuid, InputStream is) void unZip(String uuid, String dstId) InputStream exportZip(List uuids, boolean withPath, boolean background) ZipDownloadEvaluationResult evaluateDownloadZip(List uuids) ``` ```java // Export nodes as ZIP InputStream zip = ws.node.exportZip(Arrays.asList(uuid1, uuid2), true, false); IOUtils.copy(zip, new FileOutputStream("/tmp/export.zip")); ``` ### Download tokens ```java String generateDownloadToken(String uuid, boolean preview, boolean oneTimeUse, Calendar validUntil) ``` - Download URL: `http://host/openkm/download?DTK=` - Preview URL: `http://host/openkm/download?PTK=` ### Restore & misc ```java Node restore(String uuid) // recovers a deleted node from trash ``` --- ## ws.note — Notes Notes are text annotations attached to any node (document, folder, mail, record). A note has its own UUID (returned in `note.getPath()`). ```java Note add(String uuid, String text) // attach note to a node; returns Note Note get(String noteId) // retrieve note by its UUID void delete(String noteId) // remove note by its UUID void set(String noteId, String text) // update note text List list(String uuid) // all notes on a node List getNoteHistories(String uuid) // full edit history of notes on a node ``` ```java // Add a note Note note = ws.note.add(docUuid, "Reviewed and approved."); String noteId = note.getPath(); // UUID used for subsequent operations // List all notes on a node for (Note n : ws.note.list(docUuid)) { System.out.println(n.getText()); } // Update note text ws.note.set(noteId, "Reviewed, approved, and archived."); // Delete note ws.note.delete(noteId); // Retrieve full history for (NoteHistory nh : ws.note.getNoteHistories(docUuid)) { System.out.println(nh); } ``` --- --- ## ws.notification — Notifications Sends email notifications referencing repository nodes to OpenKM users, roles, and/or external email addresses. ```java void notify(List uuids, List users, List roles, List mails, String message, boolean attachment) ``` | Parameter | Description | |-----------|-------------| | `uuids` | UUIDs of nodes (documents, folders, mails, records) to reference | | `users` | OpenKM usernames to notify | | `roles` | OpenKM role names to notify | | `mails` | External email addresses to notify | | `message` | Email body text | | `attachment` | `true` to attach the referenced node(s) to the email | ```java List uuids = Arrays.asList("b153c4b7-3d1c-4589-bd42-0ed0f34fd338"); List users = Arrays.asList("test", "sochoa"); List roles = Arrays.asList("ROLE_TEST"); List mails = new ArrayList<>(); ws.notification.notify(uuids, users, roles, mails, "Body of the message", false); ``` --- ## ws.pdf — PDF Operations All operations work on documents stored in the repository that are PDF or convertible to PDF. Results are saved as new documents in the specified destination folder/record. ### getImage ```java InputStream getImage(String uuid, int page, String size) ``` Converts a PDF page to an image. `page` starts at 1. `size` is the width in pixels (`null` defaults to 150). ```java InputStream is = ws.pdf.getImage(docUuid, 1, null); IOUtils.copy(is, new FileOutputStream("/tmp/page-1.png")); is.close(); ``` ### split ```java List split(String uuid, String dstId, String baseName, List pages) ``` Splits a PDF into multiple documents, one per specified page. Naming pattern: `baseName-001.pdf`, `baseName-003.pdf`, etc. ```java List docs = ws.pdf.split(srcUuid, dstFolderUuid, "split", Arrays.asList(2, 3)); ``` ### extract ```java boolean extract(String uuid, String dstId, String name, List pages) ``` Extracts specified pages into a new PDF document. Returns `true` on success. ```java boolean ok = ws.pdf.extract(srcUuid, dstFolderUuid, "extract.pdf", Arrays.asList(2, 3)); ``` ### remove ```java Document remove(String uuid, String dstId, String name, List pages) ``` Creates a new PDF with the specified pages removed. Returns the new `Document`. ```java Document doc = ws.pdf.remove(srcUuid, dstFolderUuid, "without-pages.pdf", Arrays.asList(2, 3)); ``` ### rotate ```java Document rotate(String uuid, String dstId, String name, Integer angle, List pages) ``` Rotates specified pages and saves as a new PDF. `angle` values: `90`, `180`, `270`. ```java Document doc = ws.pdf.rotate(srcUuid, dstFolderUuid, "rotated.pdf", 180, Arrays.asList(2, 3)); ``` ### insertPages ```java Document insertPages(String uuid, InsertPagesRequest request) ``` Merges pages from one or more source documents into a target PDF at specified positions. Returns the resulting `Document`. ```java InsertPagesRequest request = new InsertPagesRequest(); request.name = "merged.pdf"; request.dstId = dstFolderUuid; PageInsertOperation op = new PageInsertOperation(); op.srcId = sourceDocUuid; op.pages = Arrays.asList(1, 2, 3); op.insertFromPage = 1; // insert before page 1 of target request.insertOperations.add(op); Document result = ws.pdf.insertPages(targetDocUuid, request); ``` ### optimize ```java boolean optimize(String uuid) ``` Compresses and optimises a PDF in place. Returns `true` on success. ```java boolean ok = ws.pdf.optimize(docUuid); ``` --- ## ws.plugin — REST Plugins Executes custom server-side REST plugins registered in OpenKM. Plugins must implement the `RestPlugin` interface and be annotated with `@PluginImplementation`. Identified by their fully qualified class name. ### executePluginPost ```java Object executePluginPost(String className, Map parameters, Class clazz, InputStream is) ``` Invokes a plugin via POST. `clazz` is used to unmarshall the response. `is` can be `null` or a document stream to upload. ```java Map params = new HashMap<>(); params.put("param1", "value1"); String result = (String) ws.plugin.executePluginPost( "com.openkm.plugin.rest.TestRestPlugin", params, String.class, null); ``` ### executePluginPostReturnFile ```java InputStream executePluginPostReturnFile(String className, Map parameters, InputStream is) ``` Invokes a plugin via POST that returns a file stream. ```java Map params = new HashMap<>(); params.put("docId", "/okm:root/invoices/invoice.pdf"); InputStream is = ws.plugin.executePluginPostReturnFile( "com.openkm.plugin.rest.TestGetDocumentRestPlugin", params, null); IOUtils.copy(is, new FileOutputStream("/tmp/invoice.pdf")); is.close(); ``` ### executePluginGet ```java Object executePluginGet(String className, Map parameters, Class clazz) ``` Invokes a plugin via GET. Response is unmarshalled to `clazz`. ```java Map params = new HashMap<>(); params.put("docId", "/okm:root/invoices/invoice.pdf"); String result = (String) ws.plugin.executePluginGet( "com.openkm.plugin.rest.TestRestPlugin", params, String.class); ``` ### executePluginGetReturnFile ```java InputStream executePluginGetReturnFile(String className, Map parameters) ``` Invokes a plugin via GET that returns a file stream. ```java Map params = new HashMap<>(); params.put("docId", "/okm:root/invoices/invoice.pdf"); InputStream is = ws.plugin.executePluginGetReturnFile( "com.openkm.plugin.rest.TestGetDocumentRestPlugin", params); IOUtils.copy(is, new FileOutputStream("/tmp/result.pdf")); is.close(); ``` --- --- ## ws.property — Properties (Categories, Keywords, Encryption, Signatures) Manages node-level metadata: categories, keywords, encryption flags, and digital signature status. Applies to documents, folders, mails, and records. ### Categories ```java void addCategory(String uuid, String catId) void removeCategory(String uuid, String catId) ``` `catId` is the UUID of the category folder node. ```java ws.property.addCategory(docUuid, categoryFolderUuid); ws.property.removeCategory(docUuid, categoryFolderUuid); ``` ### Keywords ```java void addKeyword(String uuid, String keyword) void removeKeyword(String uuid, String keyword) ``` Keywords are case-sensitive. Use lowercase, single words or underscore-separated values (`"invoice"`, `"two_words"`). ```java ws.property.addKeyword(docUuid, "invoice"); ws.property.addKeyword(docUuid, "pending_review"); ws.property.removeKeyword(docUuid, "pending_review"); ``` ### Encryption flag ```java void setEncryption(String uuid, String cipherName) void unsetEncryption(String uuid) ``` These methods mark the document as encrypted/unencrypted **without** performing actual cryptographic operations. They update the metadata flag only. ```java ws.property.setEncryption(docUuid, "AES"); ws.property.unsetEncryption(docUuid); ``` ### Digital signature flag ```java void setSigned(String uuid, boolean signed) boolean isSigned(String uuid) ``` Marks the document as signed or unsigned **without** performing cryptographic verification. Updates the metadata flag only. ```java ws.property.setSigned(docUuid, true); boolean signed = ws.property.isSigned(docUuid); ``` --- --- ## ws.propertyGroup — Metadata Groups (Property Groups) Metadata groups are collections of structured fields (inputs, selects, dates, checkboxes, textareas, suggestboxes) attached to nodes. Group and field names follow the JCR namespace convention: `okg:groupName` for groups and `okp:groupName.fieldName` for fields. **Date handling** — use `com.openkm.sdk4j.util.ISO8601`: ```java // Write properties.put("okp:technology.date", ISO8601.formatBasic(Calendar.getInstance())); // Read back Calendar cal = ISO8601.parseBasic(properties.get("okp:technology.date")); ``` **Multi-value select fields** — use JSON array strings: ```java properties.put("okp:technology.type", "[ \"t1\", \"t2\" ]"); // Or with Gson: properties.put("okp:technology.language", new Gson().toJson(Arrays.asList("java", "python"))); ``` ### Group assignment ```java void addGroup(String uuid, String grpName, Map propertiesMap) void removeGroup(String uuid, String grpName) boolean hasGroup(String uuid, String grpName) ``` > `addGroup` must be called before `setProperties`. Partial property maps are accepted — omitted fields retain their default/existing values. ```java Map props = new HashMap<>(); props.put("okp:technology.comment", "initial comment"); props.put("okp:technology.date", ISO8601.formatBasic(Calendar.getInstance())); props.put("okp:technology.type", "[ \"t1\", \"t2\" ]"); ws.propertyGroup.addGroup(uuid, "okg:technology", props); boolean has = ws.propertyGroup.hasGroup(uuid, "okg:technology"); ws.propertyGroup.removeGroup(uuid, "okg:technology"); ``` ### Reading group data ```java List getGroups(String uuid) // groups assigned to a node List getAllGroups() // all groups in the system List getAllGroups(String uuid) // all groups, filtered by automation events for node PropertyGroup getGroup(String grpName) // group definition by name Map getProperties(String uuid, String grpName) // field values as key→value map ``` ```java Map values = ws.propertyGroup.getProperties(uuid, "okg:technology"); for (Map.Entry e : values.entrySet()) { System.out.println(e.getKey() + " > " + e.getValue()); } ``` ### Updating field values ```java void setProperties(String uuid, String grpName, Map properties) ``` Partial updates allowed — only specified keys are modified. ### Form element definitions Form elements describe the field schema (type, label, options, validators, etc.). ```java List getPropertyGroupFormDefinition(String grpName) // empty form definition List getPropertyGroupFormDefinition(String uuid, String grpName) // definition in node context List getPropertyGroupForm(String uuid, String grpName) // definition + current values ``` ### Version-specific access ```java List getGroupsByVersion(String uuid, String versionName) Map getPropertiesByVersion(String uuid, String grpName, String versionName) List getPropertiesByVersionForm(String uuid, String grpName, String versionName) ``` ```java Map v13 = ws.propertyGroup.getPropertiesByVersion(uuid, "okg:technology", "1.3"); ``` ### Suggestbox helpers ```java List getSuggestions(String uuid, String grpName, String propName) String getSuggestBoxKeyValue(String grpName, String propertyName, String key) Map getSuggestBoxKeyValuesFiltered(String grpName, String propertyName, String filter) ``` ```java // Autocomplete suggestions List opts = ws.propertyGroup.getSuggestions(uuid, "okg:technology", "okp:technology.language"); // Lookup a single key String val = ws.propertyGroup.getSuggestBoxKeyValue("okg:consulting", "okp:consulting.suggestbox", "countryKey"); // Filter key-value pairs Map filtered = ws.propertyGroup.getSuggestBoxKeyValuesFiltered( "okg:consulting", "okp:consulting.suggestbox", "pai"); ``` ### Field validation ```java String validateField(String value, String className, List uuids) ``` Invokes a custom `FieldValidator` plugin (identified by fully qualified class name). Returns an empty string if valid, or an error message. ```java String msg = ws.propertyGroup.validateField( "test", "com.openkm.plugin.form.validator.DuplicateDocumentNumberValidator", Arrays.asList(uuid1, uuid2)); if (!msg.isEmpty()) System.out.println("Validation error: " + msg); ``` ### Definition management (ROLE_ADMIN only) ```java String getRegisteredDefinition() // returns XML definition void registerDefinition(InputStream is, String name) // applies XML definition ``` ```java // Read current definition System.out.println(ws.propertyGroup.getRegisteredDefinition()); // Upload new definition InputStream is = new FileInputStream("/tmp/PropertyGroups.xml"); ws.propertyGroup.registerDefinition(is, "test"); is.close(); ``` **Minimal XML definition structure:** ```xml