Repository navigation
Documentum Repository Connector
Module: oc-documentum-repository-connector
Connector Class: org.opencrawling.documentum.DocumentumRepositoryConnector
Client Class: org.opencrawling.documentum.DocumentumClient
DQL Builder: org.opencrawling.documentum.DocumentumDql
Document Builder: org.opencrawling.documentum.DocumentumDocumentBuilder
Security Mapper: org.opencrawling.documentum.DocumentumSecurityMapper
Properties Class: org.opencrawling.documentum.DocumentumProperties
Crawl State: org.opencrawling.documentum.DocumentumCrawlState
Spring Bean: @Component (activated when spring.opencrawling.connector.type=documentum; the Docker stack sets SPRING_OPENCRAWLING_CONNECTOR_TYPE=documentum)
The OpenText Documentum Repository Connector (oc-documentum-repository-connector) crawls a repository (docbase) of an OpenText Documentum Content Server through Documentum REST Services (/dctm-rest) and streams its documents into the OpenCrawling pipeline as Open Ingestion Standard (OIS) documents—with their attributes (including the single and repeating attributes of custom types), primary content or a preferred rendition, folder paths, versions and fail-closed Zero-Trust permissions.
Important
Licence & Prerequisites: The connector requires a licensed, running OpenText Documentum Content Server with Documentum REST Services deployed and reachable from OpenCrawling. OpenCrawling does not include OpenText Documentum or any OpenText licence. OpenCrawling connects only to a Documentum deployment you already operate and license, and using Documentum this way (automated reading and indexing into a search or AI system) must be allowed by that licence. The connector is strictly read-only: it sends GET requests only and never creates, modifies or deletes anything in Documentum.
Note
Verification status: no free Documentum system exists (the Developer Edition ended with 6.x, and the official container images need an OpenText support account), so the connector has not yet been run against a real Content Server. It is verified against a Documentum REST Services mock that conforms to OpenText's published OpenAPI document for the REST API and is driven by OpenText's own REST client, with a DQL engine and Documentum's permission evaluation (see Testing & Verification). The points that could not be confirmed are checked when the connector connects, handled both ways, or resolved so that a wrong guess hides documents rather than exposing them (see Known Limitations below).
graph TD
subgraph Documentum["OpenText Documentum"]
REST["Documentum REST Services (/dctm-rest)"]
Repo["Repository (docbase)"]
Docs["dm_document & Custom Subtypes (CURRENT / Latest Major / All Versions)"]
Content["Primary Content & Renditions (LOCAL enclosure)"]
Folders["Cabinets & Folders (FOLDER ... DESCEND)"]
ACLs["Permission Sets (dm_acl), dm_user, dm_group"]
Audit["dm_audittrail (dm_destroy / dm_prune, optional)"]
Repo --> REST
Docs & Content & Folders & ACLs & Audit --> REST
end
subgraph OpenCrawling["OpenCrawling Crawler (Java 25)"]
Client["DocumentumClient (GET only, Retries, Same-Host Downloads)"]
Dql["DocumentumDql (Keyset Cursors, Server-Time-Zone Date Literals)"]
Connector["DocumentumRepositoryConnector (DQL / Folder Crawl Modes)"]
CrawlState["DocumentumCrawlState (Watermark, vstamps, ACL Stamps, Deletion Tracking)"]
VThreads["Virtual Threads (StructuredTaskScope + ObservabilityTask, bounded parallelism)"]
DocBld["DocumentumDocumentBuilder (OIS Metadata & Custom Attributes)"]
SecMap["DocumentumSecurityMapper (Permits, Restrictions as Denies, Required Groups Excluded)"]
TempStream["Temporary-File Content Stream (Size-Capped, Auto-Deleted)"]
REST <-->|"GET ?dql=... (application/vnd.emc.documentum+json)"| Client
REST -->|"GET objects/{id}, contents/content, LOCAL enclosure"| Client
Dql --> Connector
Client --> Connector
Connector <--> CrawlState
Connector --> VThreads
VThreads --> DocBld
VThreads --> SecMap
VThreads --> TempStream
OIS["OIS Document (UPSERT / DELETE Tombstone)"]
DocBld --> OIS
SecMap --> OIS
TempStream --> OIS
end
OIS --> ClaimCheck["Claim-Check Store (Local / Apache Ozone)"]
ClaimCheck --> Kafka["Kafka (opencrawling-documents -> Tika + chunking -> opencrawling-chunks -> embedding -> opencrawling-embedded)"]
Kafka --> Sinks[("Vector Stores: PGVector / Milvus / Qdrant / Solr / Vespa / OpenSearch / Luxir / OpenArch")]
sequenceDiagram
participant C as Connector
participant R as Documentum REST Services
participant S as Crawl State (JSON)
participant P as Pipeline (Kafka)
Note over C,R: Connect: refuse to crawl rather than guess
C->>R: GET /repositories/{repo}
C->>R: DQL: root folder exists (folder mode)
C->>R: DQL page of batch-size rows (the server may refuse the size)
C->>R: DQL: one cabinet again by its exact r_modify_date (date literal check)
C->>R: GET currentuser (audit tracking: View Audit privilege)
C->>S: load watermark, offsets, permission set stamps, known documents
loop keyset pages: r_modify_date >= watermark
C->>R: DQL listing (ORDER BY r_modify_date, r_object_id)
C->>R: DQL: permission sets and users of the page (cached)
par per document, bounded parallelism
alt object, i_vstamp and mapped permissions unchanged
C->>C: skip
else changed or new
C->>R: GET objects/{id}
C->>R: GET contents/content?media-url-policy=local
C->>R: GET LOCAL enclosure (content bytes)
C->>P: OIS UPSERT
end
end
end
C->>R: DQL: folders changed below the root (folder mode), listed again
C->>R: DQL: i_vstamp of the permission sets in use, documents of edited ones listed again
C->>R: DQL: id sweep (reconcile) or dm_audittrail (audit)
C->>P: OIS DELETE tombstones
C->>S: save: advance the watermarks only if nothing failed or was cut short
-
A licensed OpenText Documentum Content Server with Documentum REST Services deployed (
https://<host>/dctm-rest) and reachable from the OpenCrawling runtime. -
The repository name (docbase), as listed at
https://<host>/dctm-rest/repositories. -
An account that can read the documents to crawl. Through DQL a non-superuser only sees the objects it has at least BROWSE on, and can only download content it has READ on, so a superuser (or an account with READ on everything in scope) is the usual choice.
-
auth-type: basic(default): user name and password; a Documentum login ticket (DM_TICKET=...) works as the password. -
auth-type: oauth2: client-credentials grant against OpenText Directory Services (OTDS) or another identity provider in front of REST Services, or a ready access token (bearer). - Kerberos (SPNEGO) is not supported.
-
-
For
deletion-tracking: auditonly: a superuser or an account with the View Audit extended privilege (checked when the connector connects), anddm_destroy/dm_pruneaudited for the crawled types. The defaultreconciletracking needs neither.
-
Documentum REST Services, as OpenText's own REST clients use them: DQL through the repository's
dqllink, objects, content metadata withmedia-url-policy=local, and content from theLOCALenclosure on the REST server itself. GET requests only, so no CSRF token is needed. -
DQL and folder crawl modes: per object type (subtypes included), an optional DQL condition or a full DQL statement; in folder mode a cabinet or folder tree (
FOLDER('/path', DESCEND)) or a single folder. -
Version policies: CURRENT version, latest major version (
n.0), or every version. -
Content & renditions: primary content, or a preferred rendition (e.g.
pdf) when present; size-capped temporary files. -
Custom types: single and repeating attributes of custom subtypes as
documentum_attr_*metadata. -
Fail-closed Zero-Trust ACL mapping of permission sets: access permits, access restrictions as denies,
dm_owner/dm_group/dm_world, required groups and required group sets left out. -
Incremental crawls with keyset paging on
r_modify_date, r_object_id, detection of edited permission sets, and deletion tracking by reconciliation or the audit trail. - Date-literal check on connect: DQL reads date literals in the Content Server's time zone; the connector proves its literals match before crawling, because a wrong time zone would skip documents silently.
-
Bounded concurrency on virtual threads, retries with back-off for 429 and 502–504 (honouring
Retry-After), one OAuth2 token renewal on 401, credentials that never leave the REST server (content links naming a proxy's host are followed on the configured URL; ACS and BOCS hosts are refused). -
Hardened for real servers: the page size is checked against the server's maximum on connect,
INlists stay under the servlet container's request line limit, daylight saving changes between runs skip nothing, and a document the account may browse but not read is indexed by metadata only instead of stalling every later run. - Safe incremental state: a failed or cut-short run advances nothing and forgets nothing, so no permission set edit and no deletion is ever lost.
Spring properties live under spring.opencrawling.connector.documentum.* (kebab-case); a connector configuration in the Admin UI or connectors.json uses the camelCase key.
| Property | Key | Default | Description |
|---|---|---|---|
url |
url |
http://localhost:8080/dctm-rest |
The REST Services context |
repository-name |
repositoryName |
— | The repository (docbase) name (required) |
auth-type |
authType |
basic |
basic, oauth2 or bearer; kerberos is refused |
username, password
|
username, password
|
— | For basic
|
oauth2.token-url, oauth2.client-id, oauth2.client-secret, oauth2.scope
|
oauth2TokenUrl, … |
— | Client-credentials grant for oauth2
|
oauth2.access-token |
oauth2AccessToken |
— | A ready token, for bearer (or oauth2) |
crawl-mode |
crawlMode |
dql |
dql or folder
|
object-types |
objectTypes |
dm_document |
Types queried one after the other; subtypes included |
where |
where |
— | An extra DQL condition |
dql-query |
dqlQuery |
— | A full SELECT ... FROM <type> [WHERE ...]: its type and condition are used |
root-cabinet-path |
rootCabinetPath |
/ |
Cabinet or folder of folder mode |
include-subfolders |
includeSubfolders |
true |
FOLDER(..., DESCEND), or the folder itself only |
version-mode |
versionMode |
current_only |
current_only, latest_major or all_versions
|
include-content-stream |
includeContentStream |
true |
Download content |
max-content-size-bytes |
maxContentSizeBytes |
52428800 |
Larger content is indexed by metadata only; 0 = no limit |
prefer-rendition |
preferRendition |
— | A format indexed instead of the primary content when present, e.g. pdf
|
include-acls |
includeAcls |
true |
Map permission sets; false makes every document public |
world-identity |
worldIdentity |
dm_world |
Group identity for dm_world; empty grants nothing |
world-public |
worldPublic |
false |
Map dm_world to public instead |
principal-format |
principalFormat |
name |
name (user_name) or login (user_login_name) |
required-group-documents |
requiredGroupDocuments |
exclude |
exclude, or fallback (fallback principals only) |
fallback-principals |
fallbackPrincipals |
— | Readers of documents nobody can read through their permission set |
include-custom-attributes |
includeCustomAttributes |
true |
Attributes beyond dm_sysobject's |
include-system-attributes |
includeSystemAttributes |
false |
The remaining dm_sysobject attributes too |
include-folder-paths |
includeFolderPaths |
true |
Resolve i_folder_id to folder paths |
attribute-prefix |
attributePrefix |
documentum_attr_ |
Prefix of attribute metadata keys |
deletion-tracking |
deletionTracking |
reconcile |
reconcile, audit or none
|
audit-deletion-enabled |
auditDeletionEnabled |
false |
Shorthand for deletion-tracking: audit
|
audit-events |
auditEvents |
dm_destroy,dm_prune |
Audit events that mean a version is gone |
incremental-enabled |
incrementalEnabled |
true |
Watermark and skip-unchanged |
modified-after |
modifiedAfter |
— | Only objects modified since this date or instant |
server-time-zone |
serverTimeZone |
auto |
Time zone of DQL date literals; auto uses each date's offset from REST; a region such as Europe/Berlin follows daylight saving; an invalid value is an error |
clock-skew-seconds |
clockSkewSeconds |
300 |
Margin subtracted from the watermark |
batch-size |
batchSize |
100 |
Rows per DQL page for every query; at most 1000 and at most the server's rest.paging.max.size (checked on connect) |
parallelism |
parallelism |
4 |
Documents processed at once |
timeout-seconds |
timeoutSeconds |
30 |
HTTP timeout |
max-retries |
maxRetries |
3 |
Retries of throttled or failed requests |
max-documents |
maxDocuments |
0 |
Index at most this many changed documents per run (0 = no limit); unchanged ones do not count, so successive incremental runs work through a large repository |
state-directory |
stateDirectory |
data/documentum-state |
Incremental crawl state |
spring:
opencrawling:
connector:
type: documentum
documentum:
url: "https://dctm.example.com/dctm-rest"
repository-name: "corp_repo"
auth-type: "basic"
username: "dctm_crawler"
password: "${DOCUMENTUM_PASSWORD}"
crawl-mode: "folder"
root-cabinet-path: "/Regulatory/Submissions"
object-types: "my_submission"
where: "a_status = 'Approved'"
version-mode: "current_only"
prefer-rendition: "pdf"
deletion-tracking: "reconcile"
batch-size: 100| Setting | Choose | When |
|---|---|---|
crawl-mode |
dql (default) |
The scope is a set of types and conditions anywhere in the repository (object-types, where, or dql-query). |
folder |
The scope is a cabinet or folder tree (root-cabinet-path). Incremental runs then also notice folders moved into the tree. |
|
version-mode |
current_only (default) |
Search and AI over what users see today: one OIS document per document, the CURRENT version. |
latest_major |
Minor versions are drafts: one OIS document per document, its highest n.0 version in scope. |
|
all_versions |
Records, audit or eDiscovery use: one OIS document per version, with its own id. | |
deletion-tracking |
reconcile (default) |
Sees every way a document leaves: destroyed, pruned, moved out of scope, its CURRENT version destroyed. One id sweep per run (one DQL page per batch-size objects in scope). |
audit |
Very large repositories where a full id sweep per run is too expensive: reads dm_audittrail since the last run instead. Needs auditing of dm_destroy / dm_prune and the View Audit privilege; does not see documents moved out of scope. |
|
none |
Append-only indexing; deletions need a full run. | |
principal-format |
name (default) |
The search side identifies users by their Documentum user_name. |
login |
It identifies them by user_login_name (e.g. a directory account name). |
|
world-identity / world-public
|
dm_world group (default) |
Documents readable by dm_world are readable by members of that group in the index. |
world-public: true |
Anyone who can query the index may read them (only when the index is not shared beyond the repository's users). |
Every Documentum version is its own object (r_object_id); i_chronicle_id (the first version's id) ties a version tree together. Ids are stable per chronicle, so a new version replaces the previous one downstream:
| Case | OIS id |
|---|---|
current_only, latest_major
|
documentum://<repository>/documents/<i_chronicle_id> |
all_versions |
documentum://<repository>/documents/<i_chronicle_id>/versions/<r_object_id> |
| Key | Value |
|---|---|
name, title, mimeType
|
object_name, title (or the name), the indexed content's MIME type |
documentum.repository, documentum.objectId, documentum.chronicleId
|
Repository, the version's r_object_id, i_chronicle_id
|
documentum.objectName, documentum.title, documentum.subject, documentum.type, documentum.status, documentum.authors, documentum.keywords
|
Document attributes (documentum.status is a_status, e.g. Approved) |
documentum.creator, documentum.created, documentum.modifier, documentum.modified, documentum.owner, documentum.group, documentum.lockOwner
|
People and dates |
documentum.versionLabels, documentum.versionLabel, documentum.isCurrentVersion
|
Version labels (implicit first, then symbolic) |
documentum.contentFormat, documentum.contentSize, documentum.indexedFormat, documentum.renditionFormat
|
Primary format and size; the format indexed; the rendition, when one was indexed |
documentum.aclDomain, documentum.aclName
|
The permission set |
documentum.folderPath |
Every folder the object is linked into |
documentum_attr_<name> |
Attributes of custom types; repeating attributes as several values, dates as ISO instants |
documentum_identity_users, documentum_identity_groups
|
The readers |
A DELETE tombstone carries documentum.status: DELETED and documentum.id, the key of the chronicle (or version) in the crawl state.
The connector's real output for a document of the DCTM REST mock: an oc_submission (custom type with single and repeating attributes) in version 2.1, linked into two folders, with the permission set of the worked example below:
{
"id": "documentum://corp/documents/090277b680000169",
"uri": "documentum://corp/documents/090277b680000169",
"lastModified": "2026-01-06T08:39:13Z",
"action": "UPSERT",
"metadata": {
"name": ["Submission 047"],
"title": ["Submission 047 title"],
"mimeType": ["text/plain"],
"documentum.repository": ["corp"],
"documentum.objectId": ["090277b68000016c"],
"documentum.chronicleId": ["090277b680000169"],
"documentum.objectName": ["Submission 047"],
"documentum.title": ["Submission 047 title"],
"documentum.subject": ["Subject of 47"],
"documentum.type": ["oc_submission"],
"documentum.creator": ["Rui Costa"],
"documentum.created": ["2026-01-06T08:39:13Z"],
"documentum.modifier": ["Rui Costa"],
"documentum.modified": ["2026-01-06T08:39:13Z"],
"documentum.owner": ["Rui Costa"],
"documentum.group": ["regulatory"],
"documentum.aclDomain": ["dmadmin"],
"documentum.aclName": ["oc_finance_restricted"],
"documentum.versionLabels": ["2.1", "CURRENT"],
"documentum.versionLabel": ["2.1"],
"documentum.isCurrentVersion": ["true"],
"documentum.authors": ["Rui Costa"],
"documentum.keywords": ["oc", "k2"],
"documentum.contentFormat": ["crtext"],
"documentum.contentSize": ["121"],
"documentum.indexedFormat": ["crtext"],
"documentum.folderPath": ["/Corporate/Contracts", "/Regulatory/Submissions"],
"documentum_identity_users": ["rui costa"],
"documentum_identity_groups": ["finance", "emea"],
"documentum_attr_submission_id": ["SUB-0047"],
"documentum_attr_agency": ["FDA"],
"documentum_attr_product_codes": ["P47", "P48"],
"documentum_attr_approved": ["false"],
"documentum_attr_page_estimate": ["57"],
"documentum_attr_review_dates": ["2026-02-08T00:00:00Z"]
},
"acl": "rui costa,finance,emea",
"security": {
"inheritanceEnabled": false,
"permissions": [
{ "identity": "rui costa", "identityType": "user", "displayName": "rui costa", "access": "read" },
{ "identity": "finance", "identityType": "group", "displayName": "finance", "access": "read" },
{ "identity": "emea", "identityType": "group", "displayName": "emea", "access": "read" },
{ "identity": "mallory grey", "identityType": "user", "displayName": "mallory grey", "access": "deny" }
]
}
}When the document is destroyed (or leaves the scope), the next run emits a tombstone with the same id; the writer purges its chunks, and no content, text or embedding is produced for it:
{
"id": "documentum://corp/documents/090277b680000169",
"uri": "documentum://corp/documents/090277b680000169",
"action": "DELETE",
"metadata": {
"documentum.repository": ["corp"],
"documentum.id": ["090277b680000169"],
"documentum.status": ["DELETED"]
}
}A document's permission set (acl_domain, acl_name) is read with DQL from dm_acl (cached, and re-read when its i_vstamp changes). Documentum gives a user the most permissive basic permit among the entries that match them, lowers it by any matching access restriction, and takes it away when they are outside a required group or required group set.
Permission set entry (r_permit_type) |
OIS rule |
|---|---|
| Access permit (0) of READ (3) or more |
read for the accessor |
| Access permit below READ (NONE, BROWSE) | nothing |
| Access restriction (3) of READ or less (takes READ away) |
deny for the accessor (wins over every grant) |
dm_owner, dm_group
|
the document's owner_name (user, or group when the owner is one), group_name
|
dm_world |
the world-identity group, or public with world-public; a restriction on dm_world leaves only the fallback principals |
| Required group (6), required group set (7) | the document is left out (exclude, default) or indexed for the fallback principals only (fallback) |
Extended (1, 4) and application (2, 5) permits and restrictions, alias accessors (%name) |
nothing |
A restriction whose accessor has no identity (login format: an unknown user, or a login several login domains share) |
the fallback principals only: dropping it would grant that user |
| No reader |
fallback-principals, or nobody. Never public
|
Identities are lower-cased user and group names (principal-format: login uses user_login_name; a login name that users of several user_login_domains share identifies nobody and is never used). Where the mapping differs from Documentum it only restricts: superusers, dm_read_all / dm_browse_all members and any implicit owner rights are not represented.
The document of the sample above, owned by Rui Costa, with the permission set dmadmin / oc_finance_restricted:
| Accessor | r_permit_type |
r_accessor_permit |
Documentum | OIS rule |
|---|---|---|---|---|
dm_world |
0 access permit | 1 NONE | grants nothing | — |
dm_owner |
0 access permit | 7 DELETE | Rui Costa (the owner) may read |
rui costa user read
|
finance (group) |
0 access permit | 3 READ | members may read |
finance group read
|
emea (group) |
0 access permit | 3 READ | members may read |
emea group read
|
Mallory Grey |
3 access restriction | 3 READ | READ taken away, although Mallory is in emea
|
mallory grey user deny
|
In the index, members of finance or emea and Rui Costa find the document; Mallory Grey does not, because a deny wins over her group's grant; everyone else finds nothing. Had the permission set also had a required group (r_permit_type 6), the document would have been left out of the index altogether (required-group-documents: exclude), since "member of every required group" cannot be written as OIS rules.
The tests check this for every document and every user of the mock: the documents the OIS rules let a user read are exactly those Documentum's own evaluation lets them READ.
-
Keyset paging: listings select single-valued attributes, ordered by
r_modify_date, r_object_id, and continue withr_modify_date > t OR (r_modify_date = t AND r_object_id > id)until a page comes back empty. REST page numbers re-run the query and shift when objects change;r_modify_datehas one-second precision. -
Date literals: written in the offset the REST server gave each date (
server-time-zone: auto) or the configured zone, and checked on connect against a cabinet's exactr_modify_date. Withautothe state records the offset of the watermark, so a daylight saving change between runs skips nothing. -
Watermark:
r_modify_date >= <watermark>; documents whose object,i_vstampand mapped permissions are unchanged are skipped. The watermark advances only after a run without failures. -
Edited permission sets change no document: each run compares the
i_vstampof the permission sets in use and lists the documents of a changed one again. -
Moved folders: moving a folder saves only the folder, so in
foldermode each run lists again the folders below the root saved since the watermark. - Content the account may browse but not read (HTTP 403), or only offered by another server, is indexed by metadata only with a warning, so it never holds the watermark back.
-
Failed and cut-short runs advance nothing and forget nothing: when a document cannot be crawled, or
max-documentsstops the run, the watermark, the audit watermark and the permission set stamps stay as they were, and every document the state knows stays known. The next run retries, so no permission set edit and no deletion is lost. -
max-documentscounts the documents fetched and indexed, not the unchanged ones, so successive incremental runs work through a large repository in steps. -
Deletions: the first run tombstones everything previously known that it did not find. Later runs use
reconcile(object ids in scope against the state) oraudit(dm_audittrailevents since the last run, read a day back). A document whose indexed object is gone is listed again by chronicle: another version in scope is re-indexed (destroying the CURRENT version makes its antecedent CURRENT without modifying it), otherwise it is tombstoned.
| Request | Use |
|---|---|
GET <url>/repositories/<repository> |
Connection check |
GET <url>/repositories/<repository>?dql=...&page=1&items-per-page=<batch> |
Listings, permission sets, users, folder paths, id sweeps, audit trail, date-literal check |
GET .../currentuser |
Privileges, for audit tracking |
GET .../objects/<id> |
Every attribute of a changed document |
GET .../objects/<id>/contents/content?page=0&media-url-policy=local[&format=<rendition>] |
Content metadata and its LOCAL enclosure |
GET <enclosure> |
The content bytes, from the REST server |
Admin UI: OpenText Documentum (REST Services) in the repository connector list, with every setting above and a Test Connection button. It runs the same checks as a crawl's start: the repository, the root folder (folder mode), the page size and the date-literal time zone, and reports the server's time zone offset.
CLI:
oc connector check --name dctm-corp --type repositoryOpenText Documentum has no free container image, so the tests run against the DCTM REST mock (oc-documentum-repository-connector/documentum-mock): a Documentum REST Services emulator (Python standard library only) with a DQL engine over typed object tables, in the response shapes of a REST 16.7 server and OpenText's REST clients. It also implements Documentum's permission evaluation, and the security tests compare the connector's mapping with it user by user. Its README lists every assumption.
mvn test -pl oc-documentum-repository-connector -amDocumentumMappingTest (settings, DQL statements and date literals, every permission rule), DocumentumClientTest (MockWebServer, responses in src/test/resources/documentum-mock-responses) and, with DCTM_MOCK_URL set, DocumentumMockServerTest: full crawls reconciled with the mock's ground truth; for every user, the documents the OIS rules let them read are exactly those Documentum lets them READ; incremental runs after check-ins, edited permission sets, destroyed documents and versions, folder moves; failed and cut-short runs; and what a real server may do differently (see below). The scenarios also pass against a repository of 3,000 documents in a daylight saving time zone.
./scripts/test-documentum-repository-connector.shChecks the mock against OpenText's published OpenAPI document, runs the module tests against it as a server at +02:00, then again at -05:00 with every DQL page silently capped at 5 rows.
With no Documentum system available, the mock is held to everything OpenText publishes:
-
OpenAPI document: OpenText publishes the Documentum REST API as an OpenAPI 3 document on developer.opentext.com ("Documentum D2", the D2 REST Services, which embed the core REST resources).
documentum-mock/conformance/check_spec.pydownloads it (it is not redistributed), crawls the mock the way a client does and checks every exchange: documented path and method, query parameters, status, media type and JSON schema. No deviation, as a superuser and as an ordinary user; the places where the document does not describe the core server are reported as warnings with the reason. -
OpenText's own client: OpenText's Python REST client navigates the mock unmodified (home, repositories, DQL and its
nextlinks, objects, contents and downloads, cabinets → folders → documents, users, groups, types, formats, versions). - Constants and real responses: the DFC 26.4 javadoc confirms its permit and privilege constants, and real REST 16.7 responses its shapes.
-
Independent audits: they found what the mock got wrong and what a real server may do that it did not show. Each of those is now a mock setting (
POST /mock/config,/mock/folders/move) with a connector test that fails without the connector's handling: a smaller page maximum, an 8 KB request line limit, links naming a proxy's host, content redirects, daylight saving, login names shared across login domains, folder moves, content the account may browse but not read.
./scripts/test-documentum-decoupled.sh
# LOCAL_BUILD=true builds from docker/local (BuildKit Maven cache); MCP_PORT and OC_IMAGE_TAG move the MCP port and tag the imagesCrawl → Kafka → Tika → embeddings → pgvector → MCP secure search, then an incremental crawl after a new version, a destroyed document, a new document and an edited permission set.
First run checklist, as the connector has not met a real Content Server yet:
- Use a dedicated read-only account (a superuser is simplest: it reads everything, and the mapping never grants more than the permission sets do).
- Run Test Connection in the Admin UI first: it proves the credentials, the repository, the page size and the date-literal time zone.
- Start with
include-content-stream: falseandmax-documents: 100, and compare a few documents' metadata and permission rules with Documentum Administrator. - Then enable content, remove the limit (or raise it for a phased first load), and keep
deletion-tracking: reconcileuntil the audit trail's setup is confirmed. - Watch the log for the warnings listed under Troubleshooting.
Set DOCUMENTUM_URL, DOCUMENTUM_REPOSITORY, DOCUMENTUM_USERNAME, DOCUMENTUM_PASSWORD (optionally DOCUMENTUM_AUTH_TYPE, DOCUMENTUM_OBJECT_TYPES, DOCUMENTUM_WHERE, DOCUMENTUM_CRAWL_MODE, DOCUMENTUM_ROOT_CABINET_PATH, DOCUMENTUM_VERSION_MODE, DOCUMENTUM_PREFER_RENDITION, DOCUMENTUM_MAX_DOCUMENTS, DOCUMENTUM_FALLBACK_PRINCIPALS, DOCUMENTUM_SERVER_TIME_ZONE) before docker compose -f oc-documentum-repository-connector/docker/docker-compose-decoupled-with-documentum.yml up. Start with DOCUMENTUM_MAX_DOCUMENTS on a large repository.
| Work | Requests |
|---|---|
| Listing | One DQL page per batch-size objects in scope (since the watermark on incremental runs) |
| Permission sets and users | One DQL query per page of listed rows, for those not cached yet in the run |
| Folder paths | One DQL query per changed document whose folders are not cached yet in the run |
| An unchanged document | None beyond its listing row |
| A changed or new document |
GET objects/{id}, GET contents/content (twice when a preferred rendition is missing), GET of the content |
reconcile |
One DQL page per batch-size objects in scope (the id sweep) |
audit |
One DQL page per batch-size audit events since the last run (read a day back) |
-
First load of a large repository: set
max-documents(e.g. 50000) and run the job repeatedly; each run indexes the next documents and the watermark is only set once a run completes. Then remove the limit. -
parallelism(default 4) bounds the documents fetched at once; raise it when the REST server's latency, not its load, limits the crawl. Every request is a separate authenticated HTTP call; there is no session to keep. -
batch-size(default 100, at most the server'srest.paging.max.size, 1000 by default): larger pages mean fewer DQL round trips for listings and sweeps. - Memory: the crawl state (one entry per document) and the reconcile id sweep are held in memory, a few hundred bytes per document; content is streamed through size-capped temporary files.
- The mock-backed scenario tests pass against a repository of 3,000 documents in a daylight saving time zone.
| Message (log or Test Connection) | Cause | What to do |
|---|---|---|
Documentum reads DQL date literals in another time zone than ...: cabinet ... is not found as DATE(...) |
DQL reads date literals in the Content Server's time zone, which differs from the one the connector writes | Set server-time-zone to the Content Server's zone, preferably a region such as Europe/Berlin
|
Documentum date literals could not be verified (no cabinet visible) (warning) |
The account cannot see any cabinet, so the check could not run | Give the account BROWSE on a cabinet, or set server-time-zone explicitly |
Documentum listing of ... made no progress ...; is server-time-zone right? |
A date literal is read in another zone | As above |
Documentum refused a DQL page of N items (...). Lower batch-size ... |
batch-size is above the REST server's rest.paging.max.size
|
Lower batch-size
|
Documentum REST repository answered HTTP 401 E_BAD_CREDENTIALS |
Wrong user name, password or expired login ticket | Fix the credentials; with OAuth2, check the client and the token URL |
OAuth2 token endpoint ... answered HTTP ... |
The identity provider refused the client credentials | Check oauth2.client-id, oauth2.client-secret, oauth2.scope
|
Documentum folder '...' was not found, or the account cannot see it. |
Wrong root-cabinet-path, or no BROWSE on it |
Fix the path or the account's permissions |
deletion-tracking 'audit' needs an account that can read dm_audittrail ... |
The account is neither a superuser nor has View Audit | Grant View Audit, or use deletion-tracking: reconcile
|
Documentum object ...: the crawl account may browse it but not read its content (HTTP 403); indexed by metadata only. (warning) |
The account has BROWSE but not READ on the document | Grant READ (or use a superuser). The content is indexed the next time the document changes, or on a full run (delete the state file) |
Refusing to send Documentum credentials to <host>: the content link is not on the REST server. (warning) |
The server offers the content only through an ACS or BOCS host | Indexed by metadata only; make REST Services serve LOCAL content |
Documentum login name '...' of '...' belongs to users of several login domains ... (warning) |
principal-format: login and a login name shared across user_login_domains |
Use principal-format: name, or make the login names unique |
Documentum permission set .../... was not found; its documents get no readers. (warning) |
The permission set cannot be read | Its documents fall back to fallback-principals or nobody; check the account can read dm_acl
|
auth-type 'kerberos' is not supported ... |
SPNEGO cannot be negotiated by the connector | Use basic (a login ticket works as the password) or oauth2
|
server-time-zone '...' is not a time zone ... |
An abbreviation such as CEST, or a typo |
Use auto, an offset such as +02:00, or a region such as Europe/Berlin
|
-
Never run against a real Documentum system. Unverified points are checked on connect (the time zone of DQL date literals, the page size), handled both ways (oversized
items-per-pagerefused or capped), kept clear of (INlists of at most 100 ids, under the servlet container's request line limit), or treated as opaque (REST error codes: only HTTP statuses are relied on). - Kerberos (SPNEGO) is not supported.
- Required groups and required group sets cannot be expressed as OIS rules: such documents are left out by default.
- Multi-page content and virtual documents: only page 0 is indexed; virtual document children are indexed on their own.
-
Audit tracking sees destroyed and pruned versions only, and needs
dm_destroyregistered for auditing (not checked); reconcile also sees documents leaving the scope. -
The repeated hour when clocks go back: a date literal in it is ambiguous to DQL, so a listing crossing it then can skip documents saved in the earlier hour (once a year; a full run or
reconcilerecovers them). - Large repositories: the crawl state and the reconcile sweep are held in memory, a few hundred bytes per document.
- Folders, cabinets and non-sysobject types are not crawled.
- Home
- User & Installation Guide
- Quick Start
- VM & Bare-Metal Distribution Installation
- Local Development & Fast Docker Builds
- User Guide
- Architecture
- Zero-Trust Security & ACL Management
- Document Lifecycle Actions & Tombstones
- Apache Tika 4 Text Extraction
- Configuration Guide
- Connectors Reference
- Alfresco Content Services (ACS) Connector
- Alfresco Process Services (APS) Connector
- Flowable Repository Connector
- Camunda Repository Connector
- OASIS CMIS Repository Connector
- Doxis Repository Connector
- M-Files Repository Connector
- SharePoint Online Repository Connector
- IBM FileNet P8 Repository Connector
- OpenText Documentum Repository Connector
- Hyland OpenArch Integration (Repository, Output & MCP)
- Relational Database (JDBC) Connector
- Apache StormCrawler Repository Connector
- gRPC Push Repository Connector
- Apache Solr Output Connector
- Luxir Output Connector
- Apache SeaTunnel Output Connector
- Apache Ozone Output Connector (Migration Mode)
- AWS S3 Output Connector (Migration Mode)
- Alfresco Content Services Output Connector (Migration Mode)
- Connector Execution Modes
- Model Context Protocol
- Apache Ozone Claim Check Store
- Lakehouse Audit Reporting & Ledger
- AI-Powered Observability (AIOps)
- Auto-Narrativization Copilot
- Maven Connector Archetypes
- Java Client SDK
- OpenCrawling CLI
- OpenAPI Specification & Swagger UI
- gRPC Internal Transport
- Migration Guide (ManifoldCF)
- Integration Test — Decoupled Pipeline
- M-Files Integration Test
- SharePoint Online Integration Test
- IBM FileNet Integration Test
- OpenText Documentum Integration Test
- Hyland OpenArch Integration Test
- Local Docker Build Test
- Secure MCP Server Integration Test
- Narrativization Integration Test
- Reporting Integration Test
- AWS S3 Migration Integration Test
- Alfresco Migration Integration Test