DAM Migrator

Easily migrate from one DAM to another, either one-time or continuously (if the DAM generates events).

The underlying technology is Apache Camel, a fantastic integration framework with all sorts of EIP.

Every supported DAM is implemented as a Camel-Endpoint, providing a consumer (fetch asset from a DAM) and/or a producer (upsert assets to a DAM).

This is developer-ware, as every migration will require some custom mapping code.

A basic camel route will look like this:

public class MyRoutes extends RouteBuilder {
  @Override
  public void configure() {
    from("source-dam://old.place.com?token=xxxxx&query=some-condition")
      .transform().body(CommonAsset.class, asset -> {/* custom mapping code*/})
      .to("target-dam://new.place.com?token=xxxxx&previous-ID=some-field");
    }
}

In practice however we would advise to use a queue for buffering and rate-limiting:

from("source-dam://old.place.com?token=xxxxx&query=some-condition")
  .transform().body(CommonAsset.class, asset -> {/* custom mapping code*/})
  .to("vm:my-queue?blockWhenFull=true&size=50");

from("vm:my-queue?concurrentConsumers=5")
  .throttle(250).timePeriodMillis(60000) // in case you are rate-limited
  .to("target-dam://new.place.com?token=xxxxx&previous-ID=some-field");

This is where all the pre-built camel stuff comes in handy!

dam-common

As we're trying to map from one DAM to another, and every DAM has different ideas when it comes to metadata, folders, relations, etc., we map everything to and from dam-common. Overview:

  • Identifiable (unique ID)
    • Nameable (name of the thing)
    • CommonEntity (metadata and relations)
      • CommonAsset (file or url)
      • CommonNode (structure object)

... note that metadata and relations themselves use Identifiable/CommonEntity themselves, allowing relations between arbitrary DAM objects (e.g. the folder structure an asset is assigned to is simply a relation to a CommonNode).

Bynder

Usage: bynder://your-instance.bynder.com?token=xxxxx, based on their API.

Parameters

token

type: String, required: yes, default: -

The API token used to authenticate against Bynder's API. To create one, head over to "Advanced Settings > Portal Settings > Permanent Tokens"

previousIdField

type: String, required: no (but recommended), default: -

The metaproperty text-field (name or UUID) where the asset ID of the previous DAM is stored. This enables deduplication and later updates to the asset.

locale

type: locale, required: no, default: English

When encountering a Nameable that needs to be resolved to a string, which locale to prefer. This should probably be set to the default locale of the source.

dryRun

type: boolean, required: no, default: false

Handy for development, does the mapping, but without the final call to the API.

skipExisting

type: boolean, required: no, default: false

Whether to update assets found via the previousIdField, even though they've already been uploaded before.

mediaQuery

type: String, required: no, default: -

When in consumer mode, what asset query to run against Bynder ("and"-joined), e.g.

property_foo = bar and dateCreatedFrom = 2020-01-01T00:00:00 and tag = baz

pageSize

type: int, required: no, default: 50

How many assets to fetch per page, max is 1000

collectionId

type: String, required: no, default: -

When looking for a previousIdField, only look in this collection (yeah, special case)

CELUM Remote

Usage: celum-remote://some.place.com/remoteapi?clientId=xxxx&clientSecret=xxxx&serverSecret=xxxxx. Requires their Remote-SDK (which only works up to version 6.20).

Note that only the consumer side has been implemented - use CELUM REST if you need a producer.

clientId

type: String, required: yes, default: -

The client ID for the Remote-SDK (see appserver/conf/remote.yml)

clientSecret

type: String, required: yes, default: -

The client secret for the Remote-SDK (see appserver/conf/remote.yml)

serverSecret

type: String, required: yes, default: -

The server secret for the Remote-SDK (see appserver/conf/remote.yml)

assetQuery

type: String, required: yes (until listeners are implemented), default: -

A SearchUtil2 query that define the scope of the assets to be transferred, e.g. assetType=123 and info_456=true. Note that due to the poor quality of the Remote-SDK, not every query parameter can be used (most notably everything involving a NOT, except emptiness-checks).

downloadFormat

type: Long, required: no, default: 1

The download format ID that should be used, defaults to OriginalFormat (1 on most servers, 6 on older ones - see CMA).

preferredDownloadFormat

type: Long, required: no, default: -

Under certain circumstances you may prefer an alternative download format for certain assets. When enabled, the endpoint will check if the preferred format is available for a given asset - otherwise downloadFormat will be used.

assetIdsFile

type: path, required: no, default: -

An alternative to the assetQuery, expects one asset ID per line, which is then fetched as if found via a query. Useful for migrating a known set of assets.

CELUM REST

Usage: celum-rest://instance.celum.hosting?clientId=foo&apiKey=bar. Requires the new REST API >= 6.20.

clientId

type: String, required: yes, default: -

The REST API client ID to use, the middle bit of restClient.<ClientIdentifier>.enabled, see their docs. E.g. mobileapp.

apiKey

type: String, required: yes, default: -

The REST API key to use, as set in CMA > Authentication > Profiles > REST

assetQuery

type: String, required: no, default: -

When in consumer mode, what asset query to run against CELUM. The syntax is like SearchUtil2, e.g. nodeRecursive=123 & modified >= "2020-01-01"

downloadFormat

type: int, required: no, default: 1

When in consumer mode, what download format to request. Usually the original file format.

previousIdField

type: long, required: no (but recommended), default: -

The information field where the asset ID of the previous DAM is stored. This enables deduplication and later updates to already-uploaded asset.

skipExisting

type: boolean, required: no, default: false

Whether to update assets found via the previousIdField, even though they've already been uploaded before.

fallbackUploadNode

type: long, required: no, default: -

When in producer mode, and you have not provided a relation to indicate which node the asset should be uploaded to, fall back to this node. This probably indicates a problem in your mapping code, but at least it got uploaded (and could still be moved later on).

defaultLocale

type: String, required: no, default: fetched from the instance

The default locale of this CELUM instance. When not provided, it will be queried via the API.

validationMode

type: String, required: no, default: tollerant

When creating new folders, which validation mode to create them with (strict or tollerant).

permissionDefiningNodeTypes

type: String, required: no, default: -

When in producer mode, which permission defining node types to copy, when an asset it assigned to it. E.g. folder:101,public:105

nonPermissionDefiningNodeTypes

type: String, required: no, default: -

When in producer mode, which non-permission defining node types to copy, when an asset it assigned to it. E.g. collection:103

trashbin

type: long, required: no, default: -

Node-ID of the trashbin -> ignore assets that are assigned to it.

duplicateStrategy

type: bean name, required: no, default: NoopDuplicateStrategy

When uploading new assets, which strategy to use to detect duplicates. Existing strageties: FilenameDuplicateStrategy, ChecksumDuplicateStrategy.

searchForDuplicatesGlobally

type: boolean, required: no, default: false

Search for duplicates globally across the entire system instead of only within the target node.

type: boolean, required: no, default: false

Links globally found duplicates to the target node (only works with searchForDuplicatesGlobally).

preheatNodeFinder

type: boolean, required: no, default: false

When in producer mode, fetch all node referencing keywords ahead of time, which reduces search calls at runtime.

skipMetadataUpdate

type: boolean, required: no, default: false

Whether to skip metadata updates when encountering an asset matching through previousIdField.

skipNodeAssignmentUpdate

type: boolean, required: no, default: false

Whether to skip node assignment updates when encountering an asset matching through previousIdField

Cloudinary

Usage: cloudinary://cloud-name?apiKey=x&apiSecret=y. Cloud name refers to the name of the instance on Cloudinary, e.g. test01-some-customer.

Note that only the producer side has been implemented so far.

apiKey

type: String, required: yes, default: -

The API key as delivered by Cloudinary.

apiSecret

type: String, required: yes, default: -

The API key as delivered by Cloudinary.

previousId

type: String, required: no, default: -

The metadata field where the asset ID of the previous DAM is stored. This enables deduplication and later updates to already-uploaded asset.

previousIdAsPublicId

type: boolean, required: no, default: false

Instead of using a previousId, put the previous DAM ID into their public_id.

skipExisting

type: boolean, required: no, default: false

Whether to skip existing files (requires previousId or previousIdAsPublicId to be set)

resultFile

type: path, required: no, default: -

When defined, log Cloudinary upload results in a JSONL file.

defaultLocale

type: String, required: no, default: EN

The default locale used for paths and multilingual fields

fallbackPath

type: String, required: no, default: ""

The fallback asset_path that an asset is uploaded to, when none has been sent. Default is empty string -> root level

forceOverwrite

type: , required: no, default: false

Whether to overwrite existing files (requires previousId or previousIdAsPublicId to be set). Only use this if you're sending pre-filtered assets deemed relevant to overwrite.

pixx.io

Usage: pixxio://customer.px.media?apiKey=x.

Note that only the producer side has been implemented so far.

apiKey

type: String, required: yes, default: -

The API key as set in pixx.io (Settings > API > API-Key).

previousIdField

type: long, required: no (but recommended), default: -

The meta-property field where the asset ID of the previous DAM is stored. This enables deduplication and later updates to already-uploaded asset.

skipExisting

type: boolean, required: no, default: false

Whether to skip existing files (requires previousIdField to be set).

deleteExternalLocalFile

type: boolean, required: no, default: false

When uploading a local file, delete it after a sucessfull upload (files downloaded from a URL will always be deleted again). Relevance depends on the source.

connectTimeoutMs

type: long, required: no, default: 10k

Connect timeout in milliseconds when downloading assets.

downloadTimeoutMs

type: long, required: no, default: 300k

Read timeout in milliseconds when downloading assets.

uploadTimeout

type: long, required: no, default: 300k

Timeout in milliseconds when uploading assets.

Sharedien

Usage: sharedien://api.demo.sharedien.com?entityType=Asset&token=x

Note that only the producer side has been implemented so far.

token

type: String, required: yes, default: -

The Sharedien API token to use (Admin > Security > Access Token).

entityType

type: String, required: no, default: Asset

The entity type to transfer, possible values: 'Asset', 'Category', any custom entity type identifier.

skipCreation

type: boolean, required: no, default: false

Whether to completely skip creation of entities which doesn't exist yet.

skipExisting

type: boolean, required: no, default: false

Whether to completely skip existing entities instead of trying to update them.

skipMetadataUpdate

type: boolean, required: no, default: false

Whether to skip metadata updates on existing entities.

skipRelationUpdate

type: boolean, required: no, default: false

Whether to skip relation updates on existing entities.

throwExceptionOnFailure

type: boolean, required: no, default: false

Whether to throw an Exception after failing to process the message

assetSearchRequest

type: String, required: no, default: {}

The full search request JSON. Refer to api.{customer}.sharedien.com and look at /assets/search for details.

languages

type: String, required: no, default: -

Specifies the desired translation languages as comma separated values, eg: en,de,fr.

browserIdentifier

type: String, required: no, default: -

The identifier of a browser to combine this search with (think of this as a named filter).

derivationTypeIdentifier

type: String , required: no, default: Thumbnail

The derivation type for downloads, such as "Original", "Thumbnail", etc.

withParentCategory

type: boolean, required: no, default: true

Whether to load and include the parent category.

withLinkedCategories

type: boolean, required: no, default: true

Whether to load and include all linked categories.

previousIdProperty

type: String, required: no, default: -

Property containing the ID of the previous system (for dedupe).

uploadChunkSize

type: int, required: no, default: -

Upload chunk size in bytes

forceHttp11

type: boolean, required: no, default: false

Whether to force HTTP/1.1 as protocol

bulkRelationCreateLimit

type: int, required: no, default: -

Limit the number of relations created in bulk (a value <= 0 is considered as unlimited)

versionByDateProperty

type: String, required: no, default: -

Date property containing the last binary modification date of the asset. Should an asset found via previousIdProperty have an older date, a new version is uploaded.

setRelationsOnDuplicateAsset

type: boolean, required: no, default: false

Whether to set relations on the original asset when a duplicate was detected. This is only relevant for Asset entities

versionReplacement

type: boolean, required: no, default: false

Whether to replace the version on existing assets with the presented binary (requires a previousIdProperty match)

removeRelationTypeId

type: String, required: no, default: -

When updating asset relations on an existing asset, remove all existing relations of the given type first

Uptempo (formerly Brandmaker)

Usage: uptempo://dam.customer.com?username=x&password=y

Note that only the consumer side has been implemented so far.

username

type: String, required: yes, default: -

The username for REST.

password

type: String, required: yes, default: -

The password for REST.

defaultLocale

type: String, required: no, default: EN

The default locale to use for translatable fields and names.

assetQuery

type: , required: no, default: -

An asset query, based on SearchFiltersWrapperRestDto, in JSON format, e.g. "{"filters": [{"type": "UPLOAD_DATE","value": [{"search": "1970-01-01"},{"search": null}]}]}";. See their docs for details.

assetIds

type: String, required: no, default: -

Only sync these specific asset IDs (comma separated).

renditionId

type: int, required: no, default: -

Which rendition (download format) to use when fetching an asset.

maxMessagesPerPoll

type: int , required: no, default: -

The maximum number of assets that a consumer generates per query.

tempDirectory

type: , required: no, default: System.getProperty("java.io.tmpdir")

Temporary directory to store downloaded binaries in (they don't have stable download URLs).

skipAssetDownload

type: boolean, required: no, default: false

Whether the consumer should download asset binaries at all.

fetchDownloadStatistics

type: boolean, required: no, default: false

Whether the consumer should query the download statistics of an asset.