# OpenKM 8.2 — Core Utility Classes # Full reference for LLM consumption # Version: 8.2.5 **Bean access:** `ContextWrapper.getContext().getBean(ClassName.class)` --- ## ArchiveUtils Utility class for creating and manipulating ZIP and JAR archives. **Bean:** `ContextWrapper.getContext().getBean(ArchiveUtils.class)` ### createZip (single file) ``` createZip(File path, OutputStream os) → void ``` Creates a ZIP archive containing a single file. ```java ArchiveUtils archiveUtils = ContextWrapper.getContext().getBean(ArchiveUtils.class); File file = new File("/home/openkm/test.pdf"); ByteArrayOutputStream baos = new ByteArrayOutputStream(); archiveUtils.createZip(file, baos); ``` ### createZip (directory) ``` createZip(File path, String root, OutputStream os) → void ``` Recursively creates a ZIP archive from a directory. `root` is the root entry name inside the ZIP. ```java ArchiveUtils archiveUtils = ContextWrapper.getContext().getBean(ArchiveUtils.class); FileUtils fileUtils = ContextWrapper.getContext().getBean(FileUtils.class); File tmp = fileUtils.createTempDir(); // ... populate tmp with files ... FileOutputStream os = new FileOutputStream(new File("/home/openkm/output.zip")); archiveUtils.createZip(tmp, "myexport", os); ``` ### deleteFilesFromZipByExtension ``` deleteFilesFromZipByExtension(String zipPath, String fileExtension) → void ``` Deletes all files with a given extension from an existing ZIP archive in place. The extension match is case-insensitive. ```java ArchiveUtils archiveUtils = ContextWrapper.getContext().getBean(ArchiveUtils.class); archiveUtils.deleteFilesFromZipByExtension("/home/openkm/output.zip", ".xml"); ``` ### createJar ``` createJar(File path, String root, OutputStream os) → void ``` Recursively creates a JAR archive from a directory. `root` is the root entry name inside the JAR. ```java ArchiveUtils archiveUtils = ContextWrapper.getContext().getBean(ArchiveUtils.class); PathUtils pathUtils = ContextWrapper.getContext().getBean(PathUtils.class); FileUtils fileUtils = ContextWrapper.getContext().getBean(FileUtils.class); RepositoryExporter repositoryExporter = ContextWrapper.getContext().getBean(RepositoryExporter.class); StringWriter out = new StringWriter(); File dirFile = new File("/home/openkm"); final File tmpFile = File.createTempFile("test", ".jar", dirFile); FileOutputStream os = new FileOutputStream(tmpFile); File tmp = fileUtils.createTempDir(); repositoryExporter.exportFolder(null, "/okm:root/jar", tmp, false, out, new TextInfoDecorator("/okm:root/jar")); archiveUtils.createJar(tmp, pathUtils.getName("/home/openkm"), os); ``` --- ## AutomationUtils `AutomationUtils` is a Spring bean that provides type-safe access to the event context map (`env`) shared between automation events and automation actions/validators. When an event fires (e.g. Document create, Mail move, Property group set), the system populates `env` with variables relevant to that event. Your action reads those values through `AutomationUtils` getters. **Bean:** `ContextWrapper.getContext().getBean(AutomationUtils.class)` — or inject via `@Autowired` in your action class. **Key rule:** Most getters throw `AutomationException` if the variable is not present. Use `has*()` guards or catch the exception. Some getters return null instead (noted below). ### Typical action skeleton ```java @PluginImplementation public class MyCustomAction implements Action { @Autowired private AutomationUtils automationUtils; @Override public void execute(Map env, Object... params) throws Exception { String param0 = automationUtils.getString(0, params); // UI-configured param NodeBase node = automationUtils.getNodeBase(env); // event context String name = automationUtils.getName(env); } } ``` ### Action parameter accessors Read the typed values from the `params` array (UI-configured values for the action). ``` getString(int index, Object... params) → String getInteger(int index, Object... params) → Integer getLong(int index, Object... params) → Long getBoolean(int index, Object... params) → Boolean getList(int index, Object... params) → List ``` ### Node getters | Method | Return | Notes | |---|---|---| | `getNodeBase(env)` | `NodeBase` | Main node involved in the event | | `getNodeToEvaluate(env)` | `NodeBase` | Smart: returns destinationNode on PRE create/move, else nodeBase | | `getNodeBaseList(env)` | `List` | Bulk operations (e.g. ZIP export) | | `getDestinationNode(env)` | `NodeParent` | Target container for create/move events | | `hasDestinationNode(env)` | `boolean` | Guard for getDestinationNode | | `getSourcePath(env)` | `String` | Original path before a move | | `hasSourcePath(env)` | `boolean` | Guard for getSourcePath | | `getName(env)` | `String` | Node name; writable in PRE create/rename | | `hasName(env)` | `boolean` | Guard for getName | | `getMimeType(env)` | `String` | Document MIME type | | `getCategories(env)` | `Set` | Category UUIDs | | `getKeywords(env)` | `Set` | Node keywords | | `getNotes(env)` | `List` | Notes attached to the node | | `getTitle(env)` | `String` | Title metadata field | | `getVersion(env)` | `String` | Document version label (e.g. "1.0") | | `getCreationDate(env)` | `Calendar` | Node creation date | | `getNodeClass(env)` | `Long` | Node class ID; -1 if absent | | `getNewNodeClass(env)` | `Long` | New class ID in set-node-class events; -1 if absent | | `getOldNodeClass(env)` | `Long` | Previous class ID in set-node-class events; -1 if absent | ### Document getters | Method | Return | Notes | |---|---|---| | `getFile(env)` | `File` | Temp local file with document content | | `hasFile(env)` | `boolean` | Guard for getFile | | `getInputStream(env)` | `InputStream` | Document content stream (PRE create/update) | | `getDocumentSize(env)` | `long` | Content size in bytes | | `getTextExtracted(env)` | `String` | Full extracted text (text extraction event) | | `getLanguageDetected(env)` | `String` | ISO 639-1 detected language code | | `getTextExtractor(env)` | `TextExtractor` | Extractor plugin instance; **returns null if absent** | ### Mail getters (mail events only) | Method | Return | |---|---| | `getMailFrom(env)` | `String` | | `getMailReply(env)` | `String[]` | | `getMailTo(env)` | `String[]` | | `getMailCc(env)` | `String[]` | | `getMailBcc(env)` | `String[]` | | `getMailSubject(env)` | `String` | | `getMailContent(env)` | `String` | | `getMailSentDate(env)` | `Calendar` | | `getMailReceivedDate(env)` | `Calendar` | ### Property group getters (metadata events) | Method | Return | Notes | |---|---|---| | `getPropertyGroupName(env)` | `String` | Group name in add/set/remove group events | | `hasPropertyGroupName(env)` | `boolean` | Guard | | `getPropertyGroupProperties(env)` | `Map` | Field name → value map being set | | `hasPropertyGroupProperties(env)` | `boolean` | Guard | | `getPropertyGroups(env)` | `Map` | All groups assigned to the node | | `getAllPropertyGroups(env)` | `List` | All system group definitions | ### Task getters (task events) | Method | Return | |---|---| | `getTaskId(env)` | `long` | | `getTaskManagerTask(env)` | `TaskManagerTask` | ### User / session getters | Method | Return | Notes | |---|---|---| | `getUser(env)` | `DbUser` | User object in user create/login/logout events | | `getTenant(env)` | `long` | Tenant ID in multi-tenant deployments | | `getHttpServletRequest(env)` | `HttpServletRequest` | **Returns null** if not in HTTP context | | `getHttpServletResponse(env)` | `HttpServletResponse` | **Returns null** if not in HTTP context | | `getAuthentication(env)` | `Authentication` | Spring Security auth; **returns null** if absent | | `getNoteText(env)` | `String` | Note text in note events; **returns null** if absent | ### Event type helpers | Method | Return | Notes | |---|---|---| | `getEvent(env)` | `AutomationRule.EnumEvents` | Current event enum value | | `getEventAt(env)` | `String` | `AT_PRE` or `AT_POST` | | `isPreEvent(env)` | `boolean` | True if PRE phase (can still modify outcome) | | `isMoveAction(env)` | `boolean` | True for any move event | | `isCreationAction(env)` | `boolean` | True for any create event | | `isDocumentCreateFromTemplate()` | `boolean` | Stack-trace check; no env needed | | `isFolderCreateFromTemplate()` | `boolean` | Stack-trace check; no env needed | | `isRecordCreateFromTemplate()` | `boolean` | Stack-trace check; no env needed | ```java // Example: skip processing when document comes from template if (automationUtils.isDocumentCreateFromTemplate()) { return; } // Example: check PRE phase and read node info if (automationUtils.isPreEvent(env)) { NodeBase node = automationUtils.getNodeBase(env); String name = automationUtils.getName(env); // name can be modified here by calling automationUtils.setName(env, newName) } // Example: read property group data on metadata event if (automationUtils.hasPropertyGroupProperties(env)) { Map props = automationUtils.getPropertyGroupProperties(env); String invoiceNum = props.get("okp:invoice.number"); } ``` --- ## ConfigSrv `ConfigSrv` is a Spring service that provides typed read and write access to the OpenKM configuration parameter store. Parameters are persisted in the database, identified by a string key, and typed (string, text, boolean, integer, long, html, password, hidden, list, file). **Bean:** `ContextWrapper.getContext().getBean(ConfigSrv.class)` **Key behavior of typed getters:** If a key does not exist, it is automatically created with the supplied default value. This is the standard pattern for registering default config values in plugins. ### CRUD methods ``` save(Config cfg) → void — Create or update a Config entity exists(String key) → boolean — Returns true if the key exists delete(String key) → void — Deletes the parameter by key findByPk(String key) → Config — Returns the full Config entity or null findAll() → List — Returns all parameters findAllFiltered(String txt) → List — Returns parameters whose key contains txt ``` ```java ConfigSrv configSrv = ContextWrapper.getContext().getBean(ConfigSrv.class); // Create/update Config cfg = new Config(); cfg.setKey("plugin.myPlugin.enabled"); cfg.setType(Config.BOOLEAN); cfg.setValue("true"); configSrv.save(cfg); // Check existence boolean exists = configSrv.exists("plugin.myPlugin.enabled"); // Read full entity Config c = configSrv.findByPk("plugin.myPlugin.enabled"); // List filtered for (Config entry : configSrv.findAllFiltered("plugin.myPlugin")) { System.out.println(entry.getKey() + " = " + entry.getValue()); } ``` ### Typed getters All getters auto-create the key with the default value if it does not exist. ``` getHidden(String key, String defaultValue) → String — Not shown in admin UI getString(String key, String defaultValue) → String getText(String key, String defaultValue) → String — Multi-line string getBoolean(String key, boolean defaultValue) → boolean getInteger(String key, int defaultValue) → int getLong(String key, long defaultValue) → long getHtml(String key, String defaultValue) → String — HTML markup getPassword(String key, String defaultValue) → String — DES-encrypted at rest, decrypted on read getFile(String key, String path) → ConfigStoredFile — path is a classpath resource getList(String key, String defaultValue) → List — newline-delimited stored value ``` ```java ConfigSrv configSrv = ContextWrapper.getContext().getBean(ConfigSrv.class); String url = configSrv.getString("plugin.myPlugin.serverUrl", "http://localhost:8080"); boolean enabled = configSrv.getBoolean("plugin.myPlugin.enabled", false); int retries = configSrv.getInteger("plugin.myPlugin.maxRetries", 3); long maxSize = configSrv.getLong("plugin.myPlugin.maxFileSize", 10485760L); String secret = configSrv.getPassword("plugin.myPlugin.apiSecret", "changeme"); List roles = configSrv.getList("plugin.myPlugin.allowedRoles", "ROLE_ADMIN"); ``` --- ## DbSessionManager `DbSessionManager` is a singleton that manages the in-memory map of active user sessions. Each session maps a string token to a Spring Security `Authentication` object. **Access:** `DbSessionManager.getInstance()` — not a Spring bean, do not inject it. ### System token (most common use) ``` getSystemToken() → String — Admin token for the current user's tenant getSystemToken(long tenantId) → String — Admin token for an explicit tenant ID ``` Pass the system token to any API method to execute it as the system super-user, bypassing normal user permissions. Typical use in cron tasks and automation actions. ```java String systemToken = DbSessionManager.getInstance().getSystemToken(); okmDocument.delete(systemToken, "d50133e3-dbfa-4d01-a109-28785cd48f40"); ``` ### Session inspection ``` getAuthentication(String token) → Authentication — Spring Security auth for the token; updates last-access. Returns null if token unknown. getUser(String token) → String — Username for the token. Returns null if unknown. getRoles(String token) → Set — Role names for the token. Returns empty set if unknown. getToken(Authentication auth) → String — Reverse lookup: token for a given Authentication. Returns null if not found. getInfo(String token) → DbSessionInfo — Session object with auth, creation time, and last-access time. getTokens() → List — All active session tokens. ``` ```java DbSessionManager mgr = DbSessionManager.getInstance(); // Who owns a token? String user = mgr.getUser(token); Set roles = mgr.getRoles(token); // Reverse lookup Authentication auth = SecurityContextHolder.getContext().getAuthentication(); String token = mgr.getToken(auth); // List all active sessions for (String t : mgr.getTokens()) { System.out.println(t + " -> " + mgr.getUser(t)); } ``` ### Session lifecycle ``` add(String token, Authentication auth) → void — Register a new session remove(String token) → void — Remove a session ``` ```java String token = UUID.randomUUID().toString(); DbSessionManager.getInstance().add(token, auth); // ... later ... DbSessionManager.getInstance().remove(token); ``` --- ## ExecutionUtils Utility class for executing BeanShell scripts and operating-system commands. All methods are static — no bean access needed. ### runScript (from file) ``` runScript(File script) → Object[] ``` Executes a BeanShell script from a `.bsh` file. The returned `Object[]` has three elements: `[0]` return value (may be null), `[1]` stdout as String, `[2]` stderr as String. ```java File script = new File("/home/openkm/test.bsh"); Object[] result = ExecutionUtils.runScript(script); System.out.println("Return: " + result[0]); System.out.println("StdOut: " + result[1]); System.out.println("StdErr: " + result[2]); ``` ### runScript (from string with system token) ``` runScript(String script, String systemToken) → Object[] ``` Executes a BeanShell script string with the system token pre-injected as the variable `systemToken` inside the script environment. ```java String systemToken = DbSessionManager.getInstance().getSystemToken(); String script = "import com.openkm.api.OKMDocument;\n" + "import com.openkm.util.ContextWrapper;\n" + "OKMDocument doc = ContextWrapper.getContext().getBean(OKMDocument.class);\n" + "System.out.println(doc.getProperties(systemToken, \"/okm:root/test.pdf\"));"; Object[] result = ExecutionUtils.runScript(script, systemToken); System.out.println("StdOut: " + result[1]); System.out.println("StdErr: " + result[2]); ``` ### runScript (from string with custom environment) ``` runScript(String script, Map env) → Object[] ``` Executes a BeanShell script string with all variables from the `env` map pre-injected into the interpreter. Most flexible overload for passing custom context to scripts. ```java Map env = new HashMap<>(); env.put("systemToken", DbSessionManager.getInstance().getSystemToken()); env.put("folderPath", "/okm:root/reports"); String script = "System.out.println(\"Processing folder: \" + folderPath);"; Object[] result = ExecutionUtils.runScript(script, env); System.out.println("StdOut: " + result[1]); ``` ### runCmd (string, default timeout) ``` runCmd(String cmd) → CmdExecutionResult ``` Executes an OS command string using the system default timeout. The command is split on spaces; quoted arguments containing spaces are handled correctly. `CmdExecutionResult` fields: `exitValue` (0 = success), `stdout`, `stderr`. ```java CmdExecutionResult result = ExecutionUtils.runCmd("ls -la /home/openkm"); System.out.println("Exit: " + result.getExitValue()); System.out.println("StdOut: " + result.getStdout()); System.out.println("StdErr: " + result.getStderr()); ``` ### runCmd (string, custom timeout) ``` runCmd(String cmd, long timeout) → CmdExecutionResult ``` Executes an OS command string with a custom timeout in milliseconds. The process is killed if it does not finish within the timeout. ```java long timeout = TimeUnit.SECONDS.toMillis(30); CmdExecutionResult result = ExecutionUtils.runCmd("convert input.pdf output.png", timeout); if (result.getExitValue() != 0) { System.err.println("Command failed: " + result.getStderr()); } ``` ### runCmd (string array) ``` runCmd(String[] cmd) → CmdExecutionResult ``` Executes an OS command provided as a pre-split string array using the system default timeout. Use this overload when arguments already contain spaces or special characters that should not be re-split. ```java String[] cmd = { "cp", "/home/openkm/source.pdf", "/home/openkm/dest dir/copy.pdf" }; CmdExecutionResult result = ExecutionUtils.runCmd(cmd); System.out.println("Exit: " + result.getExitValue()); ``` ### printErrorInfo ``` printErrorInfo(Logger srcLog, CmdExecutionResult er) → void ``` Logs the exit value, stdout, and stderr of a `CmdExecutionResult` at ERROR level using the provided SLF4J logger. Use after a failed command execution to emit diagnostic output. ```java CmdExecutionResult result = ExecutionUtils.runCmd("somecommand --arg"); if (result.getExitValue() != 0) { ExecutionUtils.printErrorInfo(log, result); } ``` --- ## FileLogger Utility class for writing timestamped log entries to date-stamped files. Log files are named `_yyyyMMdd.log` and written to `Config.LOG_DIR`. Two usage modes: **instance** (construct, call methods, then `close()`) and **static** (call static overloads directly, file is opened/closed per call). Message format: supports `%s` (Java `String.format`) and `{0}` (`MessageFormat`) placeholders. ### Instance mode ``` new FileLogger(String baseName) — constructor; opens/appends the log file info(String message, Object... params) → void warn(String message, Object... params) → void error(String message, Object... params) → void close() → void — must be called when done ``` ```java FileLogger logger = new FileLogger("myProcess"); try { logger.info("Starting process for user %s", "admin"); logger.warn("Retrying step %s", "step1"); logger.error("Failed with code %s", "500"); } finally { logger.close(); } ``` ### Static mode ``` info(String baseName, String message, Object... params) → void info(Logger logger, String baseName, String message, Object... params) → void warn(String baseName, String message, Object... params) → void warn(Logger logger, String baseName, String message, Object... params) → void error(String baseName, String message, Object... params) → void error(Logger logger, String baseName, String message, Object... params) → void error(Logger logger, String baseName, String message, Throwable e) → void — appends full stack trace error(Logger logger, String baseName, Throwable e) → void — uses e.getMessage() + stack trace ``` When a `Logger` is passed the same message is also emitted to it at the corresponding SLF4J level. ```java // Without logger FileLogger.info("logName", "Processing file %s", fileName); FileLogger.warn("logName", "Slow response: %s ms", elapsed); FileLogger.error("logName", "Unexpected value: %s", value); // With logger and exception try { // ... } catch (Exception e) { FileLogger.error(log, "logName", "Unexpected error", e); } ``` --- ## FileUtils Utility class with static helpers for common file operations. All methods are static. ### Name and path utilities ``` getFileName(String file) → String — File name without extension getFileExtension(String file) → String — Extension without dot; "" if none getParent(String file) → String — Parent directory path toValidFilename(String filename) → String — Strips Windows-reserved chars (\ / : " * ? < > |) getFileNameNotDuplicatedInDestinationPath(String pathFile, String name, String ext) → String — Returns name.ext, or name(1).ext if already exists generateUniqueName(String basename) → String — Prepends yyyyMMddHHmmss to basename ``` ```java String baseName = FileUtils.getFileName("report.pdf"); // "report" String ext = FileUtils.getFileExtension("report.pdf"); // "pdf" String safe = FileUtils.toValidFilename("Q1/2024: report.pdf"); // "Q12024 report.pdf" String unique = FileUtils.generateUniqueName("report.pdf"); // "20240315143022report.pdf" ``` ### Temporary file and directory creation ``` createTempDir() → File — Unique temp dir in system temp createTempFile() → File — Temp file with .tmp extension createTempFile(String ext) → File — Temp file with given extension createSharedTempFile(String ext) → File — Temp file in Config.REPOSITORY_TEMP_HOME (shared FS) createTempFileFromMime(String mimeType) → File — Temp file; extension resolved from MIME registry; fallback .bin ``` ```java File dir = FileUtils.createTempDir(); File tmp = FileUtils.createTempFile("pdf"); File tmp2 = FileUtils.createTempFileFromMime("application/pdf"); ``` ### Directory creation ``` createDateDir(String parent) → File — Creates yyyy/MM/dd under parent; returns the leaf dir createDateUserDir(String parent) → File — Creates yyyy/MM/dd/ under parent ``` ### Read and write ``` readFileToByteArray(File file) → byte[] — Entire file as bytes readFileToString(File file) → String — Entire file as UTF-8 string readLines(File file) → List — File lines as UTF-8 writeStringToFile(File file, String data) → void — Write UTF-8 string to file (overwrite) ``` ### Copy ``` copy(InputStream input, File output) → void copy(Reader input, File output) → void — UTF-8 copy(File input, OutputStream output) → void copy(File input, File output) → void ``` ### Delete ``` deleteQuietly(File file) → boolean — Delete file/dir silently; true if deleted cleanDirectory(File dir) → void — Delete all contents, keep dir deleteEmpty(File file) → void — Delete dir only if empty secureDelete(File file) → boolean — Overwrite with random bytes then delete ``` ### Existence checks ``` existFile(String file) → boolean existFile(File file) → boolean existDirectory(String dir) → boolean existDirectory(File dir) → boolean ``` ### File listing ``` listFiles(File dir, String[] extensions, boolean recursive) → Collection — extensions: array like {"pdf","docx"}, or null for all countFiles(File dir) → int — recursive count of files + directories ``` ### MIME type detection (Apache Tika) ``` getMimeType(InputStream is) → String — Detects from magic bytes; null for unknown getMimeType(File file) → String — Detects from magic bytes; null for unknown getMediaType(String mimeType) → MediaType — Converts "type/subtype" to Spring MediaType ``` ```java String mime = FileUtils.getMimeType(new File("/home/openkm/doc.pdf")); // "application/pdf" ``` --- ## FormatUtil Utility class with static helpers for formatting, sanitizing, and validating strings. All methods are static. ### Size ``` formatSize(long bytes) → String — Human-readable size: "2.3 MB", "512 B", etc. parseSize(String text) → long — Parse "10MB", "2GB" → bytes ``` ### Dates (format: dd-MM-yyyy HH:mm:ss) ``` formatDate(Calendar cal) → String — null-safe; uses calendar's own timezone formatDate(Date date) → String formatDate(long unixEpoch) → String ``` ### Time intervals ``` formatSeconds(long ms) → String — "HH:MM:SS" formatSecondsSince(long start) → String — elapsed since start millis formatMilliSeconds(long ms) → String — "HH:MM:SS.mmm" formatMilliSecondsSince(long start) → String — elapsed since start millis ``` ```java long start = System.currentTimeMillis(); // ... operation ... System.out.println(FormatUtil.formatMilliSecondsSince(start)); // "00:00:01.234" ``` ### Object/array formatting ``` formatArray(String[] values) → String — Single element returned directly; multi uses ArrayUtils.toString formatObject(Object value) → String — Object[] uses ArrayUtils.toString; otherwise toString() ``` ### HTML escaping and input sanitization ``` escapeHtml(String str) → String — Escapes < > as entities; \n →
sanitizeInput(String string) → String — XSS removal via AntiSamy or legacy regex (config-driven) cleanXSS(String value) → String — Escapes angle brackets, removes eval/javascript/script sanitizeEmailString(String v) → String — Removes non-letter/digit/punctuation/space chars ``` ### Encoding and validation ``` isValidUUID(String str) → boolean — true if valid UUID format validUTF8(byte[] input) → boolean — true if valid UTF-8 fixUTF8(byte[] input) → byte[] — Replace 0x00 with 0x20 fixUTF8(String input) → String — Replace \u0000 with space, trim trimUnicodeSurrogates(String text) → String — Remove surrogate chars, trim stripNonValidXMLCharacters(String in) → String — Keep only XML 1.0 valid chars ``` --- ## GeneralUtils Spring bean for sending in-application UI notifications to users. **Bean:** `ContextWrapper.getContext().getBean(GeneralUtils.class)` ``` uiNotify(String msg) → void — Notify the currently authenticated user uiNotify(String msg, Set users) → void — Notify a specific set of users uiNotifyAll(String msg) → void — Notify all users in the current tenant ``` ```java GeneralUtils generalUtils = ContextWrapper.getContext().getBean(GeneralUtils.class); // Notify current user generalUtils.uiNotify("Your export is ready."); // Notify specific users Set recipients = new HashSet<>(); recipients.add("jsmith"); generalUtils.uiNotify("Scheduled maintenance in 10 minutes.", recipients); // Notify everyone generalUtils.uiNotifyAll("System will restart in 5 minutes."); ``` --- ## ISO8601 Static utility class for converting between Java time objects and ISO 8601 strings. Two formats: - **Extended:** `YYYY-MM-DDThh:mm:ss.SSSTZD` — e.g. `2024-03-15T14:30:00.000+01:00` - **Basic:** `yyyyMMddHHmmss` — e.g. `20240315143000` (used by OpenKM property group date fields) ``` isExtended(String value) → boolean — true if string matches extended pattern parseExtended(String value) → Calendar — parse extended; null input → null formatExtended(Calendar value) → String — format as extended; null input → null parseBasic(String value) → Calendar — parse yyyyMMddHHmmss; null input → null formatBasic(Calendar value) → String — format as yyyyMMddHHmmss; null input → null formatBasic(Date value) → String — format as yyyyMMddHHmmss printBasic(String value) → String — reformat yyyyMMddHHmmss → yyyy-MM-dd (display only) ``` ```java // Read a date from a property group field and reformat for display String raw = "20240315143000"; Calendar cal = ISO8601.parseBasic(raw); System.out.println(ISO8601.printBasic(raw)); // "2024-03-15" // Write current time to a property group field String fieldValue = ISO8601.formatBasic(Calendar.getInstance()); // "20240315143000" ``` --- ## MailUtils Spring service for sending emails and forwarding stored OpenKM mails. **Bean:** `ContextWrapper.getContext().getBean(MailUtils.class)` ### Sending messages (no attachments) ``` sendMessage(String toAddress, String subject, String content) → void sendMessage(Collection toAddress, String subject, String content) → void sendMessage(String fromAddress, String toAddress, String subject, String content) → void sendMessage(String fromAddress, List toAddress, String subject, String content) → void sendMessage(String from, String to, List replyTo, List cc, List bcc, String subject, String content) → void sendAdminMessage(String subject, String content) → void — Sends to the configured admin user ``` ### Sending documents as attachments `docsPath` / `docPath` are OpenKM repository paths or UUIDs, not local disk paths. ``` sendDocument(String from, List to, String subject, String text, String docPath) → MimeMessage sendDocuments(String from, List to, String subject, String text, List docsPath) → MimeMessage sendDocuments(String from, List replyTo, List to, List cc, List bcc, String subject, String text, List docsPath) → MimeMessage ``` ```java MailUtils mailUtils = ContextWrapper.getContext().getBean(MailUtils.class); mailUtils.sendDocuments("noreply@openkm.com", Arrays.asList("user@example.com"), "Monthly report", "Please find the reports attached.", Arrays.asList("/okm:root/documents/report.pdf")); ``` ### Forwarding and utilities ``` forwardMail(String token, String from, Collection to, String message, String mailId) → MimeMessage — Forwards a stored mail; prepends message; subjects gets "Fwd: " prefix parseMailList(String mails) → List — Parse comma-separated emails; invalid entries dropped ``` ### Static helpers ``` getMailFileName(Mail mail) → String — Returns .eml or .msg getMailMimeType(Mail mail) → String — Returns EML or Outlook MIME type genMessageId() → String — Generates a unique X-Message-Id value ``` --- ## PathUtils Static utility class for working with OpenKM repository paths (e.g. `/okm:root/folder/file.pdf`). ### Path navigation ``` getParent(String path) → String — Parent path; "/" if no parent getName(String path) → String — Last segment (node name) getContext(String path) → String — Root context: "/okm:root/folder/f" → "/okm:root" getDepth(String path) → int — Number of segments getElements(String path) → String[] — All segments as array ``` ### Sanitization ``` escape(String name) → String — Remove Windows-reserved and OpenKM-forbidden chars cleanup(String name) → String — Remove / * ; collapse spaces; trim toValidPathName(String path) → String — Apply cleanup+escape to each segment below root context shortenName(String name, int max) → String — Truncate file name preserving extension shortenPath(String path, int max) → String — Shorten middle of parent path with "..." ``` ### Entity encoding ``` encodeEntities(String path) → String — Encode & < > " ' as HTML entities (idempotent) decodeEntities(String path) → String — Reverse of encodeEntities ``` ### Validation and inspection ``` isPath(String nodeId) → boolean — true if starts with "/" checkPath(String path) → boolean — true if starts with "/okm:" isChild(String parentPath, String childPath) → boolean — true if child is descendant of parent fixContext(String context) → String — "/okm:root" → "okm_root" ``` ```java PathUtils.getParent("/okm:root/folder/file.pdf") // "/okm:root/folder" PathUtils.getName("/okm:root/folder/file.pdf") // "file.pdf" PathUtils.isPath("d50133e3-dbfa-4d01-a109-28785cd48f40") // false PathUtils.checkPath("/okm:root/file.pdf") // true PathUtils.isChild("/okm:root/folder", "/okm:root/folder/sub/file.pdf") // true ``` --- ## PDFUtils Spring bean for PDF operations (merge, encrypt/decrypt, split, extract, rotate, form filling, page rendering). Static overloads work on local files/streams; instance methods work on OpenKM repository documents. **Bean:** `ContextWrapper.getContext().getBean(PDFUtils.class)` ### Merging ``` merge(List inputs, OutputStream output) → void merge(String token, List docIds, String dstPath) → String — returns dstPath ``` ### Form operations ``` fillForm(InputStream input, Map values, OutputStream output) → void — Fills AcroForm fields; supports text, checkbox, date; flattens filled fields listFormFields(String filePath) → List ``` ### Encryption ``` encrypt(InputStream in, String userPwd, String ownerPwd, int perms, OutputStream out) → void encrypt(File in, String userPwd, String ownerPwd, int perms) → File (temp) encrypt(String token, String docId, String userPwd, String ownerPwd, int perms, String dstPath) → void decrypt(InputStream in, String ownerPwd, OutputStream out) → void decrypt(File in, String ownerPwd) → File (temp) isEncrypted(InputStream in) → boolean isEncrypted(File in) → boolean disableEncryption(InputStream in, OutputStream out) → void disableEncryption(File in) → File (temp) ``` ### Page count and rendering ``` getNumberOfPages(File pdf) → int getNumberOfPages(InputStream stream) → int getNumberOfPages(String token, String docId) → int — auto-converts non-PDF to PDF getDocumentInPdf(String token, String docId) → InputStream — caller must close getPageAsImage(String token, String docId, int page, String size) → File (temp PNG) ``` ### Page operations ``` split(File file, String baseName, List pages) → File (ZIP) split(String token, String docId, String dstId, List pages, String baseName) → List extract(File src, File dst, List pages) → void extract(String token, String docId, String dstId, List pages, String docName) → Document rotate(File src, File dst, List pages, Integer angle) → void rotate(String token, String docId, String dstId, List pages, String docName, Integer angle) → Document ``` ### Optimization and conversion ``` optimize(File pdfIn, File pdfOut) → void optimize(String token, String docId) → void — in-place checkout/checkin convertToPdfA(File input) → File (temp) ``` ### Utility ``` generateSample(int paragraphs, OutputStream os) → void — Lorem Ipsum sample PDF createPdfWithText(String text, File out) → void — Single-page PDF with text ``` --- ## PrincipalUtils Static utility class for querying the current user's identity, roles, and tenant from the Spring Security context. **Note:** Methods reading from the security context require a valid authenticated session. In background threads use the token-based overloads or `DbSessionManager`. ``` getUser() → String — Username from security context isUser(String userId) → boolean — true if current user equals userId getUserByToken(String token) → String — Username for a session token; throws AccessDeniedException if not found getTenantId() → long — Tenant ID of current user; fallback to DEFAULT_TENANT_ID getTenantIdByToken(String token) → long — Tenant ID for a session token getRoles() → Set — Role names of current user; empty set if not authenticated hasRole(String role) → boolean — true if current user has the role isRegularUser() → boolean — true if current user lacks admin role isAdminUser() → boolean — true if current user has admin role isSystemUser() → boolean — true if current user is the internal system user isSuperUser() → boolean — true if current user is the default admin user (okmAdmin) hasFullAccess() → boolean — true if system user, super user, or admin role getAuthentication() → Authentication — From SecurityContextHolder or HTTP request; null if not found getAuthenticationByToken(String token) → Authentication — Throws AccessDeniedException if token unknown getRemoteAddress() → String — IP of current user; null if unavailable ``` ```java String user = PrincipalUtils.getUser(); long tenant = PrincipalUtils.getTenantId(); boolean isAdmin = PrincipalUtils.isAdminUser(); boolean hasAccess = PrincipalUtils.hasFullAccess(); ``` --- ## StackTraceUtils Utility class with static helper methods for inspecting the current call stack, formatting exception stack traces, capturing thread dumps, and locating heap dump files. All methods are static. Trace results automatically exclude common framework packages (Tomcat, Spring, Spring Security) so that only application-level frames are returned. ### Call stack inspection #### whoCalledMe() - Returns: `StackTraceElement` - Returns the StackTraceElement of the method that called the current method (two frames up in the stack). Returns null if the stack is too shallow. #### whoseCalledMe() - Returns: `List` - Returns the full list of StackTraceElement entries from the calling frame upward. Returns an empty list if the stack is too shallow. #### isCallingMe(String className) - Returns: `boolean` - Returns true if any frame in the current call stack has a class name that starts with className. #### isCallingMe(String className, String methodName) - Returns: `boolean` - Returns true if any frame in the current call stack matches both the given class name prefix and the exact method name. #### logTrace(Logger log) - Returns: `void` - Logs the current call stack at WARN level using the supplied SLF4J Logger, one frame per line. Framework frames (Tomcat, Spring, etc.) are omitted. ### Stack trace formatting #### getTrace() - Returns: `String` - Returns the current call stack as a formatted string, excluding framework frames. #### getTrace(Throwable t) - Returns: `String` - Returns the stack trace of the given throwable as a formatted string, excluding framework frames. #### getTrace(Throwable t, int row) - Returns: `String` - Returns the stack trace of the given throwable starting from frame index row, excluding framework frames. #### getTraceList(Throwable t) - Returns: `List` - Returns the stack trace of the given throwable as a list of strings, one per frame, excluding framework frames. #### getTraceList(Throwable t, int row) - Returns: `List` - Returns the stack trace of the given throwable as a list of strings starting from frame index row, excluding framework frames. #### toString(Throwable t) - Returns: `String` - Converts the full exception stack trace (including message and all frames) to a plain text string, equivalent to printing it to a PrintWriter. ### Thread and heap diagnostics #### getThreadDump() - Returns: `String` - Returns a full thread dump of all live threads in the JVM, including lock and monitor information. Useful for diagnosing deadlocks or hung threads. #### searchHprofFiles() - Returns: `List` - Searches the Tomcat home directory (catalina.home system property) for heap dump files ending in .hprof. Returns an empty list if catalina.home is not set or is not accessible. Example: ```java package com.openkm; import com.openkm.util.StackTraceUtils; import java.io.File; import java.util.List; public class Test { public static void main(String[] args) { boolean calledFromAction = StackTraceUtils.isCallingMe("com.openkm.automation.action"); System.out.println("Called from action: " + calledFromAction); try { List keys = null; for (String key : keys) {} } catch (Exception e) { System.out.println(StackTraceUtils.toString(e)); System.out.println(StackTraceUtils.getTrace(e)); } System.out.println(StackTraceUtils.getThreadDump()); List hprofs = StackTraceUtils.searchHprofFiles(); hprofs.forEach(f -> System.out.println("Heap dump: " + f.getAbsolutePath())); } } ``` ## TemplateUtils Utility class with static helper methods for processing FreeMarker templates and performing simple variable substitution. Templates are loaded from the OpenKM home directory (Config.HOME_DIR). All methods are static. ### FreeMarker configuration #### getConfig() - Returns: `Configuration` - Returns the singleton FreeMarker Configuration object, initializing it on first call. The configuration loads templates from Config.HOME_DIR. ### Template existence check #### templateExists(String name) - Returns: `boolean` - Returns true if a FreeMarker template with the given name exists in Config.HOME_DIR. ### Template variable replacement #### replace(String name, String template, Map model) - Returns: `String` - Processes a FreeMarker template string using the given model and returns the rendered result. name is used only as an identifier for error messages. #### replace(String name, InputStream input, Map model, OutputStream out) - Returns: `void` - Reads a FreeMarker template from input, processes it with the given model, and writes the rendered result to out. #### replace(String template, Map model) - Returns: `String` - Replaces all occurrences of ${key} in the template string with the corresponding value from model. This is a simple literal string substitution (no FreeMarker). Values are trimmed before substitution. ### Variable helpers #### toVar(String id) - Returns: `String` - Wraps the given identifier in ${...} to produce a template variable reference. For example, toVar("user") returns "${user}". ### Built-in templates #### useMailTemplate(String body) - Returns: `String` - Wraps the given HTML body in the built-in OpenKM mail template (/tpl/mail.ftlh) and returns the fully rendered HTML. Throws IOException on template processing errors. #### useNotesTemplate(String docName, List notes) - Returns: `String` - Renders the built-in OpenKM notes template (/tpl/notes.ftlh) with the given document name and list of notes. Returns the rendered HTML. NodeNote has fields: author, date, text. Example: ```java package com.openkm; import com.openkm.core.Config; import com.openkm.util.TemplateUtils; import com.openkm.util.TemplateUtils.NodeNote; import java.util.*; public class Test { public static void main(String[] args) { try { // FreeMarker replace Map model = new HashMap<>(); model.put("fileIn", "/tmp/input.xls"); String cmd = TemplateUtils.replace("SYSTEM_XLS2CSV", Config.SYSTEM_CATDOC_XLS2CSV, model); System.out.println("Command: " + cmd); // Simple string substitution Map vars = new HashMap<>(); vars.put("user", "jsmith"); String msg = TemplateUtils.replace("Hello ${user}!", vars); System.out.println(msg); // "Hello jsmith!" // Built-in templates String html = TemplateUtils.useMailTemplate("

Document approved.

"); System.out.println(html); NodeNote n = new NodeNote(); n.setAuthor("jsmith"); n.setDate("2024-03-15"); n.setText("Reviewed and approved."); String notesHtml = TemplateUtils.useNotesTemplate("report.pdf", List.of(n)); System.out.println(notesHtml); } catch (Exception e) { e.printStackTrace(); } } } ``` ## WorkflowUtils Utility class with static helper methods for querying workflow process instances and their log entries. All methods are static and may throw WorkflowException if the workflow engine is unavailable. ### Process instance queries #### findProcessInstancesByNode(String uuid) - Returns: `List` - Returns all process instances associated with the repository node identified by the given UUID. The UUID can belong to a document, folder, mail, or record. Returns an empty list if the UUID is null or no instances are found. #### findLogsByProcessInstance(long processInstanceId) - Returns: `List` - Returns the log entries associated with the given process instance ID, sorted by date. Each entry contains: processDefinitionId, processDefinitionName, processInstanceId, token, date, type, info. #### getRunningWorkflowNodes() - Returns: `Set` - Returns the set of repository node UUIDs that have at least one running (not yet ended) workflow process instance. Returns an empty set if no workflows are currently running or if the workflow engine is unavailable. ### Process instance conversion #### copy(ProcessInstanceRef piRef) - Returns: `ProcessInstance` - Converts a jBPM-specific ProcessInstanceRef adapter object to an OpenKM ProcessInstance bean. Used internally by the workflow adapter layer. ### Inner classes **ProcessInstanceLogEntry** — data class with fields: processInstanceId (long), processDefinitionId (long), processDefinitionName (String), token (String), date (Date), type (String), info (String). **DiagramInfo** — holds layout info for a workflow diagram: width (int), height (int), nodeMap (Map). Method getNodes() returns an unmodifiable list of DiagramNodeInfo. **DiagramNodeInfo** — layout info for a single diagram node: name (String), x (int), y (int), width (int), height (int). Example: ```java package com.openkm; import com.openkm.bean.workflow.ProcessInstance; import com.openkm.util.WorkflowUtils; import com.openkm.util.WorkflowUtils.ProcessInstanceLogEntry; import java.util.Set; public class Test { public static void main(String[] args) { try { String uuid = "d50133e3-dbfa-4d01-a109-28785cd48f40"; for (ProcessInstance pi : WorkflowUtils.findProcessInstancesByNode(uuid)) { System.out.println("Process instance: " + pi.getId()); for (ProcessInstanceLogEntry entry : WorkflowUtils.findLogsByProcessInstance(pi.getId())) { System.out.println(" [" + entry.getDate() + "] " + entry.getType() + ": " + entry.getInfo()); } } Set running = WorkflowUtils.getRunningWorkflowNodes(); System.out.println("Nodes with active workflows: " + running.size()); } catch (Exception e) { e.printStackTrace(); } } } ``` ## ShardUtils ShardUtils is a Spring service that provides methods for managing the OpenKM shard configuration. Shards allow the document repository to be distributed across multiple physical disks or directories. Access via ContextWrapper.getContext().getBean(ShardUtils.class) or @Autowired. Use getShardIds() to list available shard IDs before calling methods that require a shardId parameter. ### Shard configuration #### setCurrentShard(Long shardId) - Returns: `void` - Sets the active shard to the given shard ID. Updates the configuration in the database and reloads it across the cluster if clustering is enabled. Throws RepositoryException if the shard ID is not registered. #### createShardDirs(Long shardId) - Returns: `void` - Creates the required storage directories for the given shard ID across all tenants. Should be called after registering a new shard in the database. ### Moving nodes between shards #### changeNodeShard(String uuid, Long newShardId) - Returns: `void` - Moves the physical content of the given repository node to the target shard. Behaviour varies by node type: documents move all version files and extraction; mails move content, extraction, and child attachments recursively; folders/records apply recursively. Throws RepositoryException if the shard is not registered, IOException if a file move fails. ### Querying shards #### getShardIds() - Returns: `List` - Returns the list of all registered shard IDs. #### getShardSize(Long shardId) - Returns: `Long` - Returns the total size in bytes of all content stored in the given shard across all tenants. #### getShardPaths(Long shardId) - Returns: `List` - Returns the list of absolute filesystem paths for the given shard, one per tenant. #### getShards() - Returns: `List` - Returns the list of all registered Shard entities from the database. #### getShardFolders() - Returns: `List` - Returns the list of shard directories found under Config.REPOSITORY_HOME, identified by the configured shard prefix, sorted alphabetically. Example: ```java package com.openkm; import com.openkm.db.bean.Shard; import com.openkm.util.ContextWrapper; import com.openkm.util.ShardUtils; import java.io.File; import java.util.List; public class Test { public static void main(String[] args) { try { ShardUtils shardUtils = ContextWrapper.getContext().getBean(ShardUtils.class); for (long id : shardUtils.getShardIds()) { System.out.println("Shard " + id + " size: " + shardUtils.getShardSize(id) + " bytes"); System.out.println("Shard " + id + " paths: " + shardUtils.getShardPaths(id)); } shardUtils.getShards().forEach(System.out::println); shardUtils.changeNodeShard("e2391b01-ce10-42c7-94a9-b0d6b394048e", 2L); } catch (Exception e) { e.printStackTrace(); } } } ``` ## LegacySrv LegacySrv is a Spring service that provides direct database access methods for executing raw SQL and HQL (Hibernate Query Language) queries. Package: com.openkm.db.service. Access via ContextWrapper.getContext().getBean(LegacySrv.class) or @Autowired. WARNING: Direct SQL/HQL execution bypasses OpenKM security and permission checks. Only a single statement per call is supported. Admin privileges required. ### SQL execution #### executeSQL(Reader rd) - Returns: `void` - Reads and executes an SQL script from the given Reader, one statement per line. Lines starting with -- and blank lines are skipped. Errors per line are logged but do not abort the script. #### executeSQL(String query) - Returns: `List>` - Executes a single SQL statement. Returns results as a list of rows (each row is a list of column values as strings). BLOB columns are represented as "BLOB". For non-SELECT statements, returns the affected row count as a single-row result. #### executeSQL(String query, Object... params) - Returns: `List>` - Parameterized variant using ? placeholders. Supported types: String, Integer, Long, java.sql.Date, java.sql.Time, java.sql.Timestamp, Boolean. #### executeSqlAsMap(String query) - Returns: `List>` - Executes a SQL SELECT statement. Returns results as a list of maps (column name → typed Java object). Type mapping: BIGINT→Long, INTEGER→Integer, TIMESTAMP→Timestamp, BLOB→"BLOB", others→String. #### executeSqlAsMap(String query, Object... params) - Returns: `List>` - Parameterized variant of executeSqlAsMap. ### HQL execution #### executeHQL(String hql) - Returns: `List` - Executes a single HQL statement. For SELECT/FROM queries, returns matching entities or Object[] projections. For UPDATE/DELETE/INSERT, returns a single Integer with the affected row count. Example: ```java package com.openkm; import java.util.List; import java.util.Map; import com.openkm.db.service.LegacySrv; import com.openkm.util.ContextWrapper; public class Test { public static void main(String[] args) { try { LegacySrv legacySrv = ContextWrapper.getContext().getBean(LegacySrv.class); // SQL query List> rows = legacySrv.executeSQL( "SELECT NBS_UUID, NBS_AUTHOR FROM OKM_NODE_BASE WHERE NBS_AUTHOR = ? LIMIT 5;", "jsmith"); rows.forEach(r -> System.out.println("uuid=" + r.get(0) + " author=" + r.get(1))); // SQL as map List> maps = legacySrv.executeSqlAsMap( "SELECT NBS_UUID, NBS_SIZE FROM OKM_NODE_BASE LIMIT 5;"); maps.forEach(r -> System.out.println("uuid=" + r.get("NBS_UUID") + " size=" + r.get("NBS_SIZE"))); // HQL query List results = legacySrv.executeHQL( "SELECT nb.uuid, nb.author FROM NodeBase nb WHERE nb.name = 'okm:root'"); for (Object obj : results) { if (obj instanceof Object[]) { Object[] cols = (Object[]) obj; System.out.println("uuid=" + cols[0] + " author=" + cols[1]); } } } catch (Exception e) { e.printStackTrace(); } } } ```