diff --git a/migration-examples/layout/index.html b/migration-examples/layout/index.html index 3af7fc91..00ca1c2f 100644 --- a/migration-examples/layout/index.html +++ b/migration-examples/layout/index.html @@ -421,6 +421,7 @@
+
@@ -449,6 +450,7 @@ * @typedef {Object} LayoutData * @property {PageEntry[]} pages * @property {TemplateEntry[]} templates + * @property {({pageIndex: number, areaIndex: number}|null)[]} [rowMap] */ /** @@ -704,7 +706,7 @@ */ function parseCsvToLayoutData(text) { const lines = text.split(/\r?\n/); - if (!lines.length) return { pages: [], templates: [] }; + if (!lines.length) return { pages: [], templates: [], rowMap: [] }; // Parse headers, stripping the " (read-only)" suffix added by Mapping.displayHeader const headers = parseCsvLine(lines[0]).map(h => { @@ -715,12 +717,13 @@ /** @param {string[]} row @param {string} name @returns {string} */ const col = (row, name) => { const idx = headers.indexOf(name); return idx >= 0 ? (row[idx] ?? '') : ''; }; - // Group rows into pages; each unique page in the CSV keeps its declaration order - /** @type {Map} */ + /** @type {Map} */ const pageMap = new Map(); for (let i = 1; i < lines.length; i++) { if (!lines[i].trim()) continue; const row = parseCsvLine(lines[i]); + if (col(row, 'type').trim().toLowerCase() === 'base') continue; + const pageId = col(row, 'pageId') || null; const templateId = col(row, 'templateId') || null; const pageKey = pageId && templateId @@ -741,12 +744,13 @@ pageHeight: col(row, 'pageHeight'), }, rows: [], + lineIndices: [], }); } pageMap.get(pageKey).rows.push(row); + pageMap.get(pageKey).lineIndices.push(i); } - // Assign templatePageIndex as the ordinal of each real page within its template (CSV order) /** @type {Map} */ const pageIndexMap = new Map(); /** @type {Map} */ @@ -758,29 +762,38 @@ tmplPageCounter.set(meta.templateId, n + 1); } - // Build PageEntry objects + // TODO d.svitak - review start /** @type {PageEntry[]} */ const pages = []; - for (const [pageKey, { meta, rows }] of pageMap) { - const rawAreas = /** @type {{x:number,y:number,w:number,h:number,flowToNextPage:boolean,interactiveFlowName:string,contentPreview:string}[]} */ ( - rows.map(row => { - const x = parseSizeMm(col(row, 'x')); - const y = parseSizeMm(col(row, 'y')); - const w = parseSizeMm(col(row, 'width')); - const h = parseSizeMm(col(row, 'height')); - if (x == null || y == null || w == null || h == null) return null; - return { + /** @type {({pageIndex: number, areaIndex: number}|null)[]} */ + const rowMap = new Array(lines.length).fill(null); + for (const [pageKey, { meta, rows, lineIndices }] of pageMap) { + const rawAreasWithLine = rows.map((row, idx) => { + const x = parseSizeMm(col(row, 'x')); + const y = parseSizeMm(col(row, 'y')); + const w = parseSizeMm(col(row, 'width')); + const h = parseSizeMm(col(row, 'height')); + if (x == null || y == null || w == null || h == null) return null; + return { + area: { x, y, w, h, flowToNextPage: col(row, 'flowToNextPage') === 'true', interactiveFlowName: col(row, 'interactiveFlowName') || '', contentPreview: col(row, 'contentPreview') || '', - }; - }).filter(a => a != null) + }, + lineIndex: lineIndices[idx], + }; + }).filter(a => a != null); + const rawAreas = /** @type {{x:number,y:number,w:number,h:number,flowToNextPage:boolean,interactiveFlowName:string,contentPreview:string}[]} */ ( + rawAreasWithLine.map(a => a.area) ); const containment = computeContainment(rawAreas); const areas = /** @type {AreaEntry[]} */ (rawAreas.map((a, idx) => ({ ...a, containedIn: containment[idx] }))); + const pageIndex = pages.length; + rawAreasWithLine.forEach((a, areaIndex) => { rowMap[a.lineIndex] = { pageIndex, areaIndex }; }); + const pw = parseSizeMm(meta.pageWidth); const ph = parseSizeMm(meta.pageHeight); pages.push(/** @type {PageEntry} */ ({ @@ -794,16 +807,43 @@ })); } - return { pages, templates: deriveTemplates(pages) }; + return { pages, templates: deriveTemplates(pages), rowMap }; } +// Raw CSV state kept alongside the parsed LayoutData so the file can be re-serialized +// losslessly (including columns/rows the viewer doesn't otherwise use) for saving back to disk. +/** @type {string[]|null} */ +let currentCsvHeaders = null; +/** @type {string[][]|null} */ +let currentCsvRows = null; +let currentFileName = "areas.csv"; +/** @type {({pageIndex: number, areaIndex: number}|null)[]} */ +let currentRowMap = []; + +const saveCsvBtn = /** @type {HTMLButtonElement} */ (document.getElementById("save-csv")); + /** * @param {File} file */ async function loadFile(file) { try { statusEl.textContent = ""; - render(parseCsvToLayoutData(await file.text())); + const text = await file.text(); + + const lines = text.split(/\r?\n/); + if (lines.length && lines[0].trim() !== "") { + currentCsvHeaders = parseCsvLine(lines[0]); + currentCsvRows = lines.slice(1).map(parseCsvLine); + } else { + currentCsvHeaders = null; + currentCsvRows = null; + } + currentFileName = file.name || "areas.csv"; + saveCsvBtn.disabled = !currentCsvHeaders; + + const layoutData = parseCsvToLayoutData(text); + currentRowMap = layoutData.rowMap ?? []; + render(layoutData); } catch (err) { statusEl.textContent = "Error: " + /** @type {Error} */ (err).message; } @@ -814,6 +854,181 @@ if (file) loadFile(file); }); +function normalizeHeaderName(header) { + const s = (header ?? "").trim(); + return s.endsWith(" (read-only)") ? s.slice(0, -" (read-only)".length) : s; +} + +function ensureCsvColumn(headers, rows, logicalName) { + const idx = headers.findIndex(h => normalizeHeaderName(h) === logicalName); + if (idx >= 0) return idx; + const insertIdx = headers.findIndex(h => normalizeHeaderName(h) === "contentPreview"); + const at = insertIdx >= 0 ? insertIdx : headers.length; + headers.splice(at, 0, logicalName); + rows.forEach(row => { row.splice(at, 0, ""); }); + return at; +} + +function csvEscapeField(field) { + const s = field ?? ""; + if (/[",\r\n]/.test(s)) { + return '"' + s.replace(/"/g, '""') + '"'; + } + return s; +} + +function serializeCsv(headers, rows) { + const lines = [headers.map(csvEscapeField).join(",")]; + for (const row of rows) { + lines.push(row.map(csvEscapeField).join(",")); + } + return lines.join("\r\n") + "\r\n"; +} + +function computeBaseTemplateData(data, viewModel, analysis) { + /** @type {string[][]} */ + const baseRows = []; + /** @type {Map} */ + const overridesByPageIndex = new Map(); + + viewModel.templateGroups.forEach((templateGroup, templateGroupIndex) => { + const baseTemplateId = `G${templateGroupIndex + 1}`; + const baseTemplateName = `Base template of ${templateGroup.templateNames.join(" / ")}`; + + templateGroup.pageGroups.forEach((pageGroup, pageGroupIndex) => { + const basePageId = `${baseTemplateId}-P${pageGroupIndex + 1}`; + const basePageName = `Page group ${pageGroupIndex + 1}`; + + const representativePageIndex = pageGroup.pageIndices[0]; + const representativePage = data.pages[representativePageIndex]; + const representativeGroups = analysis.pageProximityGroups[representativePageIndex] ?? []; + const pageWidthMm = representativePage.pageSize ? `${representativePage.pageSize.w}mm` : ""; + const pageHeightMm = representativePage.pageSize ? `${representativePage.pageSize.h}mm` : ""; + + representativeGroups.forEach((pg, areaGroupIndex) => { + const flowName = `${basePageId}.Area${areaGroupIndex + 1}`; + const flowToNextPage = pg.areaIndices.some(ai => representativePage.areas[ai]?.flowToNextPage); + const contentPreview = pg.areaIndices + .map(ai => representativePage.areas[ai]?.contentPreview) + .filter(Boolean) + .join(";"); + baseRows.push([ + baseTemplateId, baseTemplateName, basePageId, basePageName, + pageWidthMm, pageHeightMm, + flowName, String(flowToNextPage), + `${pg.position.x}mm`, `${pg.position.y}mm`, `${pg.position.w}mm`, `${pg.position.h}mm`, + "Base", "", contentPreview, + ]); + }); + + pageGroup.pageIndices.forEach(pageIndex => { + const page = data.pages[pageIndex]; + const ownGroups = pageIndex === representativePageIndex + ? representativeGroups + : (analysis.pageProximityGroups[pageIndex] ?? []); + + /** @type {(string|null)[]} */ + const flowNames = new Array(page.areas.length).fill(null); + ownGroups.forEach((pg, ordinal) => { + if (ordinal >= representativeGroups.length) return; + const flowName = `${basePageId}.Area${ordinal + 1}`; + pg.areaIndices.forEach(ai => { flowNames[ai] = flowName; }); + }); + overridesByPageIndex.set(pageIndex, { flowNames, targetId: `$${baseTemplateId}` }); + }); + }); + }); + + return { baseRows, overridesByPageIndex }; +} + +async function saveCsv() { + if (!currentCsvHeaders || !currentCsvRows) { + statusEl.textContent = "No CSV loaded to save."; + return; + } + if (!currentData || !currentViewModel || !currentAnalysis) { + statusEl.textContent = "No layout data loaded to save."; + return; + } + + const headers = [...currentCsvHeaders]; + const rows = currentCsvRows.map(row => [...row]); + + const typeIdx = ensureCsvColumn(headers, rows, "type"); + const targetIdIdx = ensureCsvColumn(headers, rows, "targetId"); + const flowNameIdx = ensureCsvColumn(headers, rows, "interactiveFlowName"); + + const { baseRows, overridesByPageIndex } = computeBaseTemplateData(currentData, currentViewModel, currentAnalysis); + + currentRowMap.forEach((entry, lineIndex) => { + if (!entry) return; + const rowIdx = lineIndex - 1; + const row = rows[rowIdx]; + if (!row) return; + + const override = overridesByPageIndex.get(entry.pageIndex); + if (!override) return; + + row[targetIdIdx] = override.targetId; + const flowName = override.flowNames[entry.areaIndex]; + if (flowName != null) row[flowNameIdx] = flowName; + }); + + for (let i = rows.length - 1; i >= 0; i--) { + if (rows[i].every(cell => cell === "")) { + rows.splice(i, 1); + } else if (!rows[i][typeIdx]) { + rows[i][typeIdx] = "Standard"; + } + } + + baseRows.forEach(baseRow => { + const byLogicalName = { + templateId: baseRow[0], templateName: baseRow[1], pageId: baseRow[2], pageName: baseRow[3], + pageWidth: baseRow[4], pageHeight: baseRow[5], interactiveFlowName: baseRow[6], flowToNextPage: baseRow[7], + x: baseRow[8], y: baseRow[9], width: baseRow[10], height: baseRow[11], + type: baseRow[12], targetId: baseRow[13], contentPreview: baseRow[14], + }; + const row = headers.map(h => byLogicalName[normalizeHeaderName(h)] ?? ""); + rows.push(row); + }); + + const text = serializeCsv(headers, rows); + + if (typeof window.showSaveFilePicker === "function") { + try { + const handle = await window.showSaveFilePicker({ + suggestedName: currentFileName, + types: [{ description: "CSV file", accept: { "text/csv": [".csv"] } }], + }); + const writable = await handle.createWritable(); + await writable.write(text); + await writable.close(); + statusEl.textContent = `Saved ${handle.name}`; + return; + } catch (err) { + if (/** @type {Error} */ (err).name === "AbortError") return; + statusEl.textContent = "Error saving file: " + /** @type {Error} */ (err).message; + return; + } + } + + const blob = new Blob([text], { type: "text/csv" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = currentFileName; + document.body.appendChild(a); + a.click(); + a.remove(); + URL.revokeObjectURL(url); + statusEl.textContent = `Downloaded ${currentFileName}`; +} + +saveCsvBtn.addEventListener("click", () => { saveCsv(); }); +// TODO d.svitak - review end + document.body.addEventListener("dragover", e => e.preventDefault()); document.body.addEventListener("drop", e => { e.preventDefault(); @@ -1979,8 +2194,8 @@ */ function areaLabel(preview) { if (!preview) return ""; - const m = preview.match(/\(([^)]+)\)$/); - return m ? m[1] : preview.slice(0, 40); + const firstChunk = preview.split(";")[0]; + return firstChunk.slice(0, 40); } /** diff --git a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasExport.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasExport.groovy index 00063705..722444da 100644 --- a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasExport.groovy +++ b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasExport.groovy @@ -50,7 +50,9 @@ static void run(Migration migration, Path path) { Mapping.displayHeader("y", true), Mapping.displayHeader("width", true), Mapping.displayHeader("height", true), - Mapping.displayHeader("contentPreview", true) + Mapping.displayHeader("type", false), + Mapping.displayHeader("targetId", false), + Mapping.displayHeader("contentPreview", true), ] writer.writeLine(headers.join(",")) templates.each { template -> @@ -102,6 +104,9 @@ static String buildArea(Migration migration, Number idx, Area area, DocumentObje builder.append(Csv.serialize(area.position.width) + ",") builder.append(Csv.serialize(area.position.height) + ",") + builder.append("Standard,") + builder.append(Csv.serialize(page?.baseTemplate ?: template?.baseTemplate) + ",") + builder.append(Csv.serialize(migration.previewProvider.buildDocumentContentListPreview(area.content))) return builder.toString() diff --git a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasImport.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasImport.groovy index 0da1cbd4..ce43785b 100644 --- a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasImport.groovy +++ b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasImport.groovy @@ -7,10 +7,17 @@ package com.quadient.migration.example.common.mapping import com.quadient.migration.api.Migration +import com.quadient.migration.api.dto.migrationmodel.BaseTemplate +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateLocation +import com.quadient.migration.api.dto.migrationmodel.CustomFieldMap import com.quadient.migration.api.dto.migrationmodel.DocumentObject import com.quadient.migration.api.dto.migrationmodel.MappingItem import com.quadient.migration.example.common.util.Csv import com.quadient.migration.example.common.util.Mapping +import com.quadient.migration.shared.BaseTemplateArea +import com.quadient.migration.shared.BaseTemplatePage +import com.quadient.migration.shared.Position +import com.quadient.migration.shared.Size import groovy.transform.Field import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -30,20 +37,29 @@ static void run(Migration migration, Path path) { def fileLines = path.toFile().readLines() def columnNames = Csv.parseColumnNames(fileLines.removeFirst()).collect { Mapping.normalizeHeader(it) } - def mappings = new HashMap() + def areaMappings = new HashMap() + def docObjectsToTargetIds = new LinkedHashMap() + def baseTemplateDrafts = new LinkedHashMap() + DocumentObject currentDocumentObject = null - MappingItem.Area mapping = null + MappingItem.Area areaMapping = null int areaIndex = 0 for (line in fileLines) { def values = Csv.getCells(line, columnNames) + def type = Csv.deserialize(values.get("type"), String.class) ?: "Standard" + if (type.equalsIgnoreCase("Base")) { + assignAreaToBaseTemplateDraft(baseTemplateDrafts, values) + continue + } + def pageId = Csv.deserialize(values.get("pageId"), String.class) def templateId = Csv.deserialize(values.get("templateId"), String.class) def documentObjectId = pageId ?: templateId if (currentDocumentObject?.id != documentObjectId) { if (currentDocumentObject != null) { - mappings[currentDocumentObject.id] = mapping + areaMappings[currentDocumentObject.id] = areaMapping } def documentObjectModel = migration.documentObjectRepository.find(documentObjectId) @@ -51,29 +67,119 @@ static void run(Migration migration, Path path) { throw new IllegalStateException("Document object '${documentObjectId}' not found.") } - mapping = migration.mappingRepository.getAreaMapping(documentObjectId) + areaMapping = migration.mappingRepository.getAreaMapping(documentObjectId) currentDocumentObject = documentObjectModel areaIndex = 0 } def interactiveFlowName = Csv.deserialize(values.get("interactiveFlowName"), String.class) - mapping.areas[areaIndex] = interactiveFlowName + areaMapping.areas[areaIndex] = interactiveFlowName def flowToNextPage = Csv.deserialize(values.get("flowToNextPage"), Boolean.class) - mapping.flowToNextPage[areaIndex] = flowToNextPage ?: false + areaMapping.flowToNextPage[areaIndex] = flowToNextPage ?: false + + def targetId = Csv.deserialize(values.get("targetId"), String.class) + if (targetId) { + if (pageId && !docObjectsToTargetIds.containsKey(pageId)) { + docObjectsToTargetIds[pageId] = targetId + } + if (templateId && !docObjectsToTargetIds.containsKey(templateId)) { + docObjectsToTargetIds[templateId] = targetId + } + } areaIndex++ } if (currentDocumentObject != null) { - mappings[currentDocumentObject.id] = mapping + areaMappings[currentDocumentObject.id] = areaMapping } - def batches = mappings.entrySet().collate(1000) - for (int i = 0; i < batches.size(); i++) { - log.info "Upserting mappings batch ${i + 1}/${batches.size()} (${batches[i].size()} items)" - migration.mappingRepository.upsertBatch(batches[i].collectEntries()) - } + Mapping.upsertBatched(migration.mappingRepository, areaMappings, "area mappings", log) migration.mappingRepository.applyAllAreaMappings() + + applyDocumentObjectTargetIdMappings(migration, docObjectsToTargetIds) + applyBaseTemplateDraftMappings(migration, baseTemplateDrafts) } +private static void assignAreaToBaseTemplateDraft(Map baseTemplateDrafts, Map values) { + def baseTemplateId = Csv.deserialize(values.get("templateId"), String.class) + if (!baseTemplateId) { + throw new IllegalStateException("Rows of type 'Base' must specify a templateId identifying the base template.") + } + def pageGroupId = Csv.deserialize(values.get("pageId"), String.class) ?: baseTemplateId + + def baseTemplateDraft = baseTemplateDrafts.computeIfAbsent(baseTemplateId) { new BaseTemplateDraft() } + if (baseTemplateDraft.name == null) { + baseTemplateDraft.name = Csv.deserialize(values.get("templateName"), String.class) + } + + def page = baseTemplateDraft.pages.computeIfAbsent(pageGroupId) { + new BaseTemplatePage(Csv.deserialize(values.get("pageName"), String.class), + Csv.deserialize(values.get("pageWidth"), Size.class), + Csv.deserialize(values.get("pageHeight"), Size.class), + new ArrayList()) + } + + def interactiveFlowName = Csv.deserialize(values.get("interactiveFlowName"), String.class) + if (!interactiveFlowName) { + throw new IllegalStateException("Rows of type 'Base' must specify an interactiveFlowName for the consolidated area.") + } + def x = Csv.deserialize(values.get("x"), Size.class) + def y = Csv.deserialize(values.get("y"), Size.class) + def width = Csv.deserialize(values.get("width"), Size.class) + def height = Csv.deserialize(values.get("height"), Size.class) + def position = (x != null && y != null && width != null && height != null) ? new Position(x, y, width, height) : null + def flowToNextPage = Csv.deserialize(values.get("flowToNextPage"), Boolean.class) ?: false + + page.areas.add(new BaseTemplateArea(interactiveFlowName, position, flowToNextPage)) +} + +private static void applyDocumentObjectTargetIdMappings(Migration migration, Map docObjectsToTargetIds) { + if (docObjectsToTargetIds.isEmpty()) return + + def mappings = new HashMap() + docObjectsToTargetIds.each { documentObjectId, targetId -> + def mapping = migration.mappingRepository.getDocumentObjectMapping(documentObjectId) + def existingObject = migration.documentObjectRepository.find(documentObjectId) + + if (mapping.name == null) mapping.name = existingObject?.name + if (mapping.internal == null) mapping.internal = existingObject?.internal + if (mapping.targetFolder == null) mapping.targetFolder = existingObject?.targetFolder + if (mapping.variableStructureRef == null) mapping.variableStructureRef = existingObject?.variableStructureRef?.id + if (mapping.skip == null) mapping.skip = existingObject?.skip + + mapping.baseTemplate = Csv.deserialize(targetId, BaseTemplateLocation.class) + mappings[documentObjectId] = mapping + } + + Mapping.upsertBatched(migration.mappingRepository, mappings, "document object base template ref mappings", log) + migration.mappingRepository.applyAllDocumentObjectMappings() +} + +private static void applyBaseTemplateDraftMappings(Migration migration, Map baseTemplateDrafts) { + if (baseTemplateDrafts.isEmpty()) return + + def mappings = new HashMap() + baseTemplateDrafts.each { baseTemplateId, draft -> + def existing = migration.baseTemplateRepository.find(baseTemplateId) + if (existing == null) { + migration.baseTemplateRepository.upsert(new BaseTemplate(baseTemplateId, null, [], new CustomFieldMap(new HashMap()), null, [], null, null)) + } + + def mapping = migration.mappingRepository.getBaseTemplateMapping(baseTemplateId) + mapping.name = draft.name ?: existing?.name + mapping.targetFolder = existing?.targetFolder + mapping.pages = new ArrayList(draft.pages.values()) + + mappings[baseTemplateId] = mapping + } + + Mapping.upsertBatched(migration.mappingRepository, mappings, "base template mappings", log) + migration.mappingRepository.applyAllBaseTemplateMappings() +} + +class BaseTemplateDraft { + String name + Map pages = new LinkedHashMap<>() +} diff --git a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AttachmentsImport.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AttachmentsImport.groovy index c6f95b2f..6576cfe3 100644 --- a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AttachmentsImport.groovy +++ b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AttachmentsImport.groovy @@ -89,10 +89,6 @@ static void run(Migration migration, Path attachmentsFilePath) { } } - def batches = mappings.entrySet().collate(1000) - for (int i = 0; i < batches.size(); i++) { - log.info "Upserting mappings batch ${i + 1}/${batches.size()} (${batches[i].size()} items)" - migration.mappingRepository.upsertBatch(batches[i].collectEntries()) - } + Mapping.upsertBatched(migration.mappingRepository, mappings, "attachment mappings", log) migration.mappingRepository.applyAllAttachmentMappings() } diff --git a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/DisplayRulesImport.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/DisplayRulesImport.groovy index 62630cf8..43f321eb 100644 --- a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/DisplayRulesImport.groovy +++ b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/DisplayRulesImport.groovy @@ -7,6 +7,7 @@ package com.quadient.migration.example.common.mapping import com.quadient.migration.api.Migration +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateLocation import com.quadient.migration.api.dto.migrationmodel.MappingItem import com.quadient.migration.example.common.util.Csv import com.quadient.migration.example.common.util.Mapping @@ -56,7 +57,7 @@ static void run(Migration migration, File file) { def newInternal = Csv.deserialize(values.get("internal"), boolean) existingMapping.internal = newInternal - def newBaseTemplate = Csv.deserialize(values.get("baseTemplate"), String.class) + def newBaseTemplate = Csv.deserialize(values.get("baseTemplate"), BaseTemplateLocation.class) existingMapping.baseTemplate = newBaseTemplate def newTargetFolder = Csv.deserialize(values.get("targetFolder"), String.class) @@ -83,10 +84,6 @@ static void run(Migration migration, File file) { } } - def batches = mappings.entrySet().collate(1000) - for (int i = 0; i < batches.size(); i++) { - log.info "Upserting mappings batch ${i + 1}/${batches.size()} (${batches[i].size()} items)" - migration.mappingRepository.upsertBatch(batches[i].collectEntries()) - } + Mapping.upsertBatched(migration.mappingRepository, mappings, "display rule mappings", log) migration.mappingRepository.applyAllDisplayRuleMappings() } diff --git a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/DocumentObjectsImport.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/DocumentObjectsImport.groovy index 37e016ba..3c30cd55 100644 --- a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/DocumentObjectsImport.groovy +++ b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/DocumentObjectsImport.groovy @@ -7,6 +7,7 @@ package com.quadient.migration.example.common.mapping import com.quadient.migration.api.Migration +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateLocation import com.quadient.migration.api.dto.migrationmodel.MappingItem import com.quadient.migration.example.common.util.Csv import com.quadient.migration.example.common.util.Mapping @@ -59,8 +60,8 @@ static void run(Migration migration, Path documentObjFilePath) { def newInternal = Csv.deserialize(values.get("internal"), boolean) Mapping.mapProp(existingMapping, existingDocObject, "internal", newInternal) - def newBaseTemplate = Csv.deserialize(values.get("baseTemplate"), String.class) - Mapping.mapProp(existingMapping, existingDocObject, "baseTemplate", newBaseTemplate) + def newBaseTemplate = Csv.deserialize(values.get("baseTemplate"), BaseTemplateLocation.class) + existingMapping.baseTemplate = newBaseTemplate def newTargetFolder = Csv.deserialize(values.get("targetFolder"), String.class) Mapping.mapProp(existingMapping, existingDocObject, "targetFolder", newTargetFolder) @@ -93,10 +94,6 @@ static void run(Migration migration, Path documentObjFilePath) { } } - def batches = mappings.entrySet().collate(1000) - for (int i = 0; i < batches.size(); i++) { - log.info "Upserting mappings batch ${i + 1}/${batches.size()} (${batches[i].size()} items)" - migration.mappingRepository.upsertBatch(batches[i].collectEntries()) - } + Mapping.upsertBatched(migration.mappingRepository, mappings, "document object mappings", log) migration.mappingRepository.applyAllDocumentObjectMappings() } \ No newline at end of file diff --git a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/ImagesImport.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/ImagesImport.groovy index fb041b6e..5375ac27 100644 --- a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/ImagesImport.groovy +++ b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/ImagesImport.groovy @@ -92,10 +92,6 @@ static void run(Migration migration, Path imagesFilePath) { } } - def batches = mappings.entrySet().collate(1000) - for (int i = 0; i < batches.size(); i++) { - log.info "Upserting mappings batch ${i + 1}/${batches.size()} (${batches[i].size()} items)" - migration.mappingRepository.upsertBatch(batches[i].collectEntries()) - } + Mapping.upsertBatched(migration.mappingRepository, mappings, "image mappings", log) migration.mappingRepository.applyAllImageMappings() } \ No newline at end of file diff --git a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/ParagraphStylesImport.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/ParagraphStylesImport.groovy index ab34b00d..55c94395 100644 --- a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/ParagraphStylesImport.groovy +++ b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/ParagraphStylesImport.groovy @@ -58,11 +58,7 @@ static void run(Migration migration, Path path) { } } - def batches = mappings.entrySet().collate(1000) - for (int i = 0; i < batches.size(); i++) { - log.info "Upserting mappings batch ${i + 1}/${batches.size()} (${batches[i].size()} items)" - migration.mappingRepository.upsertBatch(batches[i].collectEntries()) - } + Mapping.upsertBatched(migration.mappingRepository, mappings, "paragraph style mappings", log) migration.mappingRepository.applyAllParagraphStyleMappings() } diff --git a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/TablesImport.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/TablesImport.groovy index a8e5a758..9d3fabed 100644 --- a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/TablesImport.groovy +++ b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/TablesImport.groovy @@ -83,16 +83,11 @@ static void run(Migration migration, Path path) { tablesByDocObjId.computeIfAbsent(docObjId) { [] }.add(entry) } - def mappings = tablesByDocObjId.collectEntries { docObjId, tables -> + Map mappings = tablesByDocObjId.collectEntries { docObjId, tables -> [docObjId, new MappingItem.Table(null, tables)] } - def batches = mappings.entrySet().collate(1000) - for (int i = 0; i < batches.size(); i++) { - log.info("Upserting table mappings batch ${i + 1}/${batches.size()} (${batches[i].size()} items)") - migration.mappingRepository.upsertBatch(batches[i].collectEntries()) - } - + Mapping.upsertBatched(migration.mappingRepository, mappings, "table mappings", log) migration.mappingRepository.applyAllTableMappings { hasErrors = true } if (hasErrors) { diff --git a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/TextStylesImport.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/TextStylesImport.groovy index 97cc09a6..bedcd828 100644 --- a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/TextStylesImport.groovy +++ b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/TextStylesImport.groovy @@ -56,11 +56,7 @@ static void run(Migration migration, Path path) { } } - def batches = mappings.entrySet().collate(1000) - for (int i = 0; i < batches.size(); i++) { - log.info "Upserting mappings batch ${i + 1}/${batches.size()} (${batches[i].size()} items)" - migration.mappingRepository.upsertBatch(batches[i].collectEntries()) - } + Mapping.upsertBatched(migration.mappingRepository, mappings, "text style mappings", log) migration.mappingRepository.applyAllTextStyleMappings() } diff --git a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/VariablesImport.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/VariablesImport.groovy index fd9ca30e..94a9a0f4 100644 --- a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/VariablesImport.groovy +++ b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/VariablesImport.groovy @@ -10,6 +10,7 @@ package com.quadient.migration.example.common.mapping import com.quadient.migration.api.Migration import com.quadient.migration.api.dto.migrationmodel.MappingItem import com.quadient.migration.api.dto.migrationmodel.VariableRef +import com.quadient.migration.api.dto.migrationmodel.builder.VariableStructureBuilder import com.quadient.migration.example.common.util.Csv import com.quadient.migration.example.common.util.Mapping import com.quadient.migration.shared.DataType @@ -39,6 +40,11 @@ static void run(Migration migration, Path path) { def structureId = Mapping.variableStructureIdFromFileName(path.fileName.toString(), migration.projectConfig.name) def structureMapping = migration.mappingRepository.getVariableStructureMapping(structureId) + def existingStructure = migration.variableStructureRepository.find(structureId) + if (existingStructure == null) { + migration.variableStructureRepository.upsert(new VariableStructureBuilder(structureId).build()) + } + def languageVariableFound = false def mappings = new HashMap() for (line in lines) { @@ -74,11 +80,7 @@ static void run(Migration migration, Path path) { } } - def batches = mappings.entrySet().collate(1000) - for (int i = 0; i < batches.size(); i++) { - log.info "Upserting mappings batch ${i + 1}/${batches.size()} (${batches[i].size()} items)" - migration.mappingRepository.upsertBatch(batches[i].collectEntries()) - } + Mapping.upsertBatched(migration.mappingRepository, mappings, "variable mappings", log) migration.mappingRepository.applyAllVariableMappings() if (!languageVariableFound) { diff --git a/migration-examples/src/main/groovy/com/quadient/migration/example/common/util/Csv.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/util/Csv.groovy index cd665cc3..fb138349 100644 --- a/migration-examples/src/main/groovy/com/quadient/migration/example/common/util/Csv.groovy +++ b/migration-examples/src/main/groovy/com/quadient/migration/example/common/util/Csv.groovy @@ -1,5 +1,8 @@ package com.quadient.migration.example.common.util +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateLocation +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateRef +import com.quadient.migration.api.dto.migrationmodel.LiteralBaseTemplatePath import com.quadient.migration.api.dto.migrationmodel.Tab import com.quadient.migration.api.dto.migrationmodel.Tabs import com.quadient.migration.shared.Color @@ -102,6 +105,8 @@ static String serialize(Object obj, Size.Unit unitOverride) { case Long: return obj.toString() case Color: return obj.toHex() case IcmPath: return obj.toString() + case BaseTemplateRef: return "\$${obj.id}" + case LiteralBaseTemplatePath: return obj.path case Size: return unitOverride != null ? obj.toString(unitOverride) : obj.toString() case Tabs: return """{ "tabs": ${serialize(obj.tabs)}; "useOutsideTabs": "${serialize(obj.useOutsideTabs)}" }""" case Tab: return """{ "position": ${serialize(obj.position)}; "type": "${serialize(obj.type)}" }""" @@ -142,6 +147,10 @@ static T deserialize(String value, Class cls) { case IcmPath: return IcmPath.from(value) as T case Color: return Color.fromHex(value) as T case Size: return Size.fromString(value) as T + case BaseTemplateLocation: { + if (value.startsWith("@") || value.startsWith("\$")) return new BaseTemplateRef(value.substring(1)) as T + return new LiteralBaseTemplatePath(value) as T + } case Boolean: case boolean: if (value.toLowerCase() == "true") { diff --git a/migration-examples/src/main/groovy/com/quadient/migration/example/common/util/Mapping.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/util/Mapping.groovy index 899df365..fe9921cd 100644 --- a/migration-examples/src/main/groovy/com/quadient/migration/example/common/util/Mapping.groovy +++ b/migration-examples/src/main/groovy/com/quadient/migration/example/common/util/Mapping.groovy @@ -1,5 +1,9 @@ package com.quadient.migration.example.common.util +import com.quadient.migration.api.dto.migrationmodel.MappingItem +import com.quadient.migration.api.repository.MappingRepository +import org.slf4j.Logger + import java.nio.file.Path import java.nio.file.Paths @@ -91,3 +95,11 @@ static String normalizeHeader(String displayName) { } return s.trim() } + +static void upsertBatched(MappingRepository mappingRepository, Map mappings, String label, Logger log) { + def batches = mappings.entrySet().collate(1000) + for (int i = 0; i < batches.size(); i++) { + log.info "Upserting ${label} batch ${i + 1}/${batches.size()} (${batches[i].size()} items)" + mappingRepository.upsertBatch(batches[i].collectEntries()) + } +} diff --git a/migration-examples/src/test/groovy/AreasExportTest.groovy b/migration-examples/src/test/groovy/AreasExportTest.groovy index 69f2d736..f589766c 100644 --- a/migration-examples/src/test/groovy/AreasExportTest.groovy +++ b/migration-examples/src/test/groovy/AreasExportTest.groovy @@ -46,12 +46,12 @@ class AreasExportTest { AreasExport.run(migration, mappingFile) def expected = """\ - templateId,templateName (read-only),pageId,pageName (read-only),pageWidth (read-only),pageHeight (read-only),interactiveFlowName,flowToNextPage,x (read-only),y (read-only),width (read-only),height (read-only),contentPreview (read-only) - full tmpl,,full page,,,,test flow2,false,0mm,0mm,0mm,0mm, - full tmpl,,full page,,,,test flow3,true,0mm,0mm,0mm,0mm, - full tmpl,,full page,,,,,false,0mm,0mm,0mm,0mm, - full tmpl,,full page,,,,test flow5,false,0mm,0mm,0mm,0mm, - ,,unreferenced page,,,,test flow,true,0mm,0mm,0mm,0mm, + templateId,templateName (read-only),pageId,pageName (read-only),pageWidth (read-only),pageHeight (read-only),interactiveFlowName,flowToNextPage,x (read-only),y (read-only),width (read-only),height (read-only),type,targetId,contentPreview (read-only) + full tmpl,,full page,,,,test flow2,false,0mm,0mm,0mm,0mm,Standard,, + full tmpl,,full page,,,,test flow3,true,0mm,0mm,0mm,0mm,Standard,, + full tmpl,,full page,,,,,false,0mm,0mm,0mm,0mm,Standard,, + full tmpl,,full page,,,,test flow5,false,0mm,0mm,0mm,0mm,Standard,, + ,,unreferenced page,,,,test flow,true,0mm,0mm,0mm,0mm,Standard,, """.stripIndent() Assertions.assertEquals(expected, mappingFile.toFile().text.replaceAll("\\r\\n|\\r", "\n")) } @@ -69,10 +69,34 @@ class AreasExportTest { AreasExport.run(migration, mappingFile) def expected = """\ - templateId,templateName (read-only),pageId,pageName (read-only),pageWidth (read-only),pageHeight (read-only),interactiveFlowName,flowToNextPage,x (read-only),y (read-only),width (read-only),height (read-only),contentPreview (read-only) - tmpl with areas,,,,,,Address Content,false,0mm,0mm,0mm,0mm, - tmpl with areas,,,,,,,true,0mm,0mm,0mm,0mm, - tmpl with areas,,,,,,Footer,false,0mm,0mm,0mm,0mm, + templateId,templateName (read-only),pageId,pageName (read-only),pageWidth (read-only),pageHeight (read-only),interactiveFlowName,flowToNextPage,x (read-only),y (read-only),width (read-only),height (read-only),type,targetId,contentPreview (read-only) + tmpl with areas,,,,,,Address Content,false,0mm,0mm,0mm,0mm,Standard,, + tmpl with areas,,,,,,,true,0mm,0mm,0mm,0mm,Standard,, + tmpl with areas,,,,,,Footer,false,0mm,0mm,0mm,0mm,Standard,, + """.stripIndent() + Assertions.assertEquals(expected, mappingFile.toFile().text.replaceAll("\\r\\n|\\r", "\n")) + } + + @Test + void exportUsesPageOrTemplateBaseTemplateAsTargetId() { + Path mappingFile = Paths.get(dir.path, "testProject.csv") + when(migration.mappingRepository.getAreaMapping(any())).thenReturn(new MappingItem.Area(null, [:], [:])) + when((migration.documentObjectRepository as DocumentObjectRepository).list(any())).thenReturn([ + new DocumentObjectBuilder("tmpl with base", DocumentObjectType.Template) + .baseTemplateRef("G2") + .documentObjectRef("page with own base") + .build(), + new DocumentObjectBuilder("page with own base", DocumentObjectType.Page) + .baseTemplateRef("G1") + .content([createArea("test flow")]) + .build(), + ]) + + AreasExport.run(migration, mappingFile) + + def expected = """\ + templateId,templateName (read-only),pageId,pageName (read-only),pageWidth (read-only),pageHeight (read-only),interactiveFlowName,flowToNextPage,x (read-only),y (read-only),width (read-only),height (read-only),type,targetId,contentPreview (read-only) + tmpl with base,,page with own base,,,,test flow,false,0mm,0mm,0mm,0mm,Standard,\$G1, """.stripIndent() Assertions.assertEquals(expected, mappingFile.toFile().text.replaceAll("\\r\\n|\\r", "\n")) } @@ -100,8 +124,8 @@ class AreasExportTest { AreasExport.run(migration, mappingFile) def expected = """\ - templateId,templateName (read-only),pageId,pageName (read-only),pageWidth (read-only),pageHeight (read-only),interactiveFlowName,flowToNextPage,x (read-only),y (read-only),width (read-only),height (read-only),contentPreview (read-only) - ,,page with preview,,,,test flow,false,0mm,0mm,0mm,0mm,docRef: Block One;imageRef: Image One;docRef: Block Two;(+2 more) + templateId,templateName (read-only),pageId,pageName (read-only),pageWidth (read-only),pageHeight (read-only),interactiveFlowName,flowToNextPage,x (read-only),y (read-only),width (read-only),height (read-only),type,targetId,contentPreview (read-only) + ,,page with preview,,,,test flow,false,0mm,0mm,0mm,0mm,Standard,,docRef: Block One;imageRef: Image One;docRef: Block Two;(+2 more) """.stripIndent() Assertions.assertEquals(expected, mappingFile.toFile().text.replaceAll("\\r\\n|\\r", "\n")) } diff --git a/migration-examples/src/test/groovy/AreasImportTest.groovy b/migration-examples/src/test/groovy/AreasImportTest.groovy index 2a494468..fc721eb2 100644 --- a/migration-examples/src/test/groovy/AreasImportTest.groovy +++ b/migration-examples/src/test/groovy/AreasImportTest.groovy @@ -3,12 +3,17 @@ import com.quadient.migration.api.dto.migrationmodel.* import com.quadient.migration.api.dto.migrationmodel.builder.DocumentObjectBuilder import com.quadient.migration.api.dto.migrationmodel.builder.documentcontent.AreaBuilder import com.quadient.migration.example.common.mapping.AreasImport +import com.quadient.migration.shared.BaseTemplateArea +import com.quadient.migration.shared.BaseTemplatePage import com.quadient.migration.shared.DocumentObjectType import com.quadient.migration.shared.Position import com.quadient.migration.shared.Size +import com.quadient.migration.shared.SkipOptions +import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir +import org.mockito.ArgumentCaptor import java.nio.file.Path import java.nio.file.Paths @@ -91,6 +96,99 @@ class AreasImportTest { verify(migration.mappingRepository).applyAllAreaMappings() } + @Test + void importSetsPageBaseTemplateFromTargetIdOnStandardRows() { + Path mappingFile = Paths.get(dir.path, "testProject.csv") + + when(migration.mappingRepository.getAreaMapping("page1")).thenReturn(new MappingItem.Area(null, [:], [:])) + when(migration.mappingRepository.getDocumentObjectMapping("page1")).thenReturn( + new MappingItem.DocumentObject(null, null, null, null, null, null, null) + ) + when(migration.mappingRepository.getDocumentObjectMapping("tmpl1")).thenReturn( + new MappingItem.DocumentObject(null, null, null, null, null, null, null) + ) + givenPageExists("page1", ["flow1", "flow2"], [false, false]) + + def input = """\ + templateId,templateName,pageId,pageName,interactiveFlowName,flowToNextPage,x,y,width,height,type,targetId,contentPreview + tmpl1,,page1,,flow1,false,0.0mm,0.0mm,0.0mm,0.0mm,Standard,\$G1, + tmpl1,,page1,,flow2,false,0.0mm,0.0mm,0.0mm,0.0mm,Standard,\$G1, + """.stripIndent() + mappingFile.toFile().write(input) + + AreasImport.run(migration, mappingFile) + + verify(migration.mappingRepository).upsertBatch([ + "page1": new MappingItem.DocumentObject(null, false, new BaseTemplateRef("G1"), null, null, null, new SkipOptions(false, null, null)), + "tmpl1": new MappingItem.DocumentObject(null, null, new BaseTemplateRef("G1"), null, null, null, null) + ]) + verify(migration.mappingRepository).applyAllDocumentObjectMappings() + } + + @Test + void importCreatesNewBaseTemplateFromBaseRows() { + Path mappingFile = Paths.get(dir.path, "testProject.csv") + + when(migration.mappingRepository.getAreaMapping("page1")).thenReturn(new MappingItem.Area(null, [:], [:])) + when(migration.mappingRepository.getDocumentObjectMapping("page1")).thenReturn( + new MappingItem.DocumentObject(null, null, null, null, null, null, null) + ) + when(migration.mappingRepository.getDocumentObjectMapping("tmpl1")).thenReturn( + new MappingItem.DocumentObject(null, null, null, null, null, null, null) + ) + givenPageExists("page1", ["flow1"], [false]) + when(migration.baseTemplateRepository.find("G1")).thenReturn(null) + when(migration.mappingRepository.getBaseTemplateMapping("G1")).thenReturn(new MappingItem.BaseTemplate(null, null, [])) + + def input = """\ + templateId,templateName,pageId,pageName,pageWidth,pageHeight,interactiveFlowName,flowToNextPage,x,y,width,height,type,targetId,contentPreview + tmpl1,,page1,,,,flow1,false,0.0mm,0.0mm,0.0mm,0.0mm,Standard,\$G1, + G1,Base template 1,G1-P1,Page group 1,210mm,297mm,G1-P1.Area1,false,1cm,1cm,190mm,20mm,Base,, + G1,Base template 1,G1-P1,Page group 1,210mm,297mm,G1-P1.Area2,true,1cm,30mm,190mm,50mm,Base,, + """.stripIndent() + mappingFile.toFile().write(input) + + AreasImport.run(migration, mappingFile) + + def baseTemplateCaptor = ArgumentCaptor.forClass(BaseTemplate.class) + verify(migration.baseTemplateRepository).upsert(baseTemplateCaptor.capture()) + Assertions.assertEquals("G1", baseTemplateCaptor.value.id) + + def created = new BaseTemplatePage("Page group 1", Size.ofMillimeters(210), Size.ofMillimeters(297), [ + new BaseTemplateArea("G1-P1.Area1", new Position(Size.ofCentimeters(1), Size.ofCentimeters(1), Size.ofMillimeters(190), Size.ofMillimeters(20)), false), + new BaseTemplateArea("G1-P1.Area2", new Position(Size.ofCentimeters(1), Size.ofMillimeters(30), Size.ofMillimeters(190), Size.ofMillimeters(50)), true), + ]) + verify(migration.mappingRepository).upsertBatch([ + "G1": new MappingItem.BaseTemplate("Base template 1", null, [created]) + ]) + verify(migration.mappingRepository).applyAllBaseTemplateMappings() + } + + @Test + void importUpdatesExistingBaseTemplateMappingButKeepsItsOtherFields() { + Path mappingFile = Paths.get(dir.path, "testProject.csv") + + def existing = new BaseTemplate("G1", "Old name", ["origin.wfd"], new CustomFieldMap(new HashMap()), "target/folder", [], null, null) + when(migration.baseTemplateRepository.find("G1")).thenReturn(existing) + when(migration.mappingRepository.getBaseTemplateMapping("G1")).thenReturn(new MappingItem.BaseTemplate(null, null, [])) + + def input = """\ + templateId,templateName,pageId,pageName,pageWidth,pageHeight,interactiveFlowName,flowToNextPage,x,y,width,height,type,targetId,contentPreview + G1,,G1-P1,Page group 1,210mm,297mm,G1-P1.Area1,false,1cm,1cm,190mm,20mm,Base,, + """.stripIndent() + mappingFile.toFile().write(input) + + AreasImport.run(migration, mappingFile) + + def created = new BaseTemplatePage("Page group 1", Size.ofMillimeters(210), Size.ofMillimeters(297), [ + new BaseTemplateArea("G1-P1.Area1", new Position(Size.ofCentimeters(1), Size.ofCentimeters(1), Size.ofMillimeters(190), Size.ofMillimeters(20)), false), + ]) + verify(migration.mappingRepository).upsertBatch([ + "G1": new MappingItem.BaseTemplate("Old name", "target/folder", [created]) + ]) + verify(migration.mappingRepository).applyAllBaseTemplateMappings() + } + static Area createArea(String flowName, boolean flowToNextPage) { def areaBuilder = new AreaBuilder() .position(new Position(Size.ofMillimeters(0), Size.ofMillimeters(0), Size.ofMillimeters(0), Size.ofMillimeters(0))) diff --git a/migration-examples/src/test/groovy/DisplayRulesMappingExportTest.groovy b/migration-examples/src/test/groovy/DisplayRulesMappingExportTest.groovy index 2489b755..09a82f0b 100644 --- a/migration-examples/src/test/groovy/DisplayRulesMappingExportTest.groovy +++ b/migration-examples/src/test/groovy/DisplayRulesMappingExportTest.groovy @@ -1,6 +1,8 @@ +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateRef import com.quadient.migration.api.dto.migrationmodel.CustomFieldMap import com.quadient.migration.api.dto.migrationmodel.DisplayRule import com.quadient.migration.api.dto.migrationmodel.DisplayRuleRef +import com.quadient.migration.api.dto.migrationmodel.LiteralBaseTemplatePath import com.quadient.migration.api.dto.migrationmodel.VariableStructureRef import com.quadient.migration.data.Active import com.quadient.migration.example.common.mapping.DisplayRulesExport @@ -25,10 +27,10 @@ class DisplayRulesMappingExportTest { when(migration.displayRuleRepository.listAll()).thenReturn([ new DisplayRule("empty", null, [], new CustomFieldMap([:]), null, null, null, null, true, [], null, null, null, null), - new DisplayRule("full", "full", ["foo", "bar"], new CustomFieldMap([:]), null, null, null, new DisplayRuleRef("targetId1"), false, [], null, "targetFolder1", "baseTemplate1", new VariableStructureRef("varStruct1")), + new DisplayRule("full", "full", ["foo", "bar"], new CustomFieldMap([:]), null, null, null, new DisplayRuleRef("targetId1"), false, [], null, "targetFolder1", new LiteralBaseTemplatePath("baseTemplate1"), new VariableStructureRef("varStruct1")), new DisplayRule("with-variable-structure", "with-var-struct", [], new CustomFieldMap([:]), null, null, null, null, true, [], null, null, null, new VariableStructureRef("varStruct2")), new DisplayRule("overridden empty", null, [], new CustomFieldMap([:]), null, null, null, null, true, [], null, null, null, null), - new DisplayRule("overridden full", "full", ["foo", "bar"], new CustomFieldMap(["originalName": "originalFull"]), null, null, null, new DisplayRuleRef("targetId2"), false, [], null, "targetFolder2", "baseTemplate2", null), + new DisplayRule("overridden full", "full", ["foo", "bar"], new CustomFieldMap(["originalName": "originalFull"]), null, null, null, new DisplayRuleRef("targetId2"), false, [], null, "targetFolder2", new BaseTemplateRef("baseTemplate2"), null), ]) when(migration.statusTrackingRepository.findLastEventRelevantToOutput(any(), any(), any())).thenReturn(new Active()) @@ -41,7 +43,7 @@ class DisplayRulesMappingExportTest { full,full,false,baseTemplate1,targetFolder1,targetId1,varStruct1,Active,,[foo; bar], with-variable-structure,with-var-struct,true,,,,varStruct2,Active,,[], overridden empty,,true,,,,,Active,,[], - overridden full,full,false,baseTemplate2,targetFolder2,targetId2,,Active,originalFull,[foo; bar], + overridden full,full,false,\$baseTemplate2,targetFolder2,targetId2,,Active,originalFull,[foo; bar], """.stripIndent() Assertions.assertEquals(expected, mappingFile.toFile().text.replaceAll("\\r\\n|\\r", "\n")) } diff --git a/migration-examples/src/test/groovy/DisplayRulesMappingImportTest.groovy b/migration-examples/src/test/groovy/DisplayRulesMappingImportTest.groovy index bb763dc4..7f6bc8f6 100644 --- a/migration-examples/src/test/groovy/DisplayRulesMappingImportTest.groovy +++ b/migration-examples/src/test/groovy/DisplayRulesMappingImportTest.groovy @@ -1,5 +1,7 @@ import com.quadient.migration.api.InspireOutput import com.quadient.migration.api.Migration +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateRef +import com.quadient.migration.api.dto.migrationmodel.LiteralBaseTemplatePath import com.quadient.migration.api.dto.migrationmodel.MappingItem import com.quadient.migration.api.dto.migrationmodel.builder.DisplayRuleBuilder import com.quadient.migration.data.Active @@ -22,169 +24,49 @@ class DisplayRulesMappingImportTest { File dir @Test - void overridesDisplayRuleName() { + void overridesAllMappableFields() { def migration = Utils.mockMigration() Path mappingFile = Paths.get(dir.path, "testProject.csv") def input = """\ id,name,internal,baseTemplate,targetFolder,targetId,variableStructureRef,status,originalName (read-only),originLocations (read-only) unchanged,,true,,,,,Active,,[] - kept,keptName,true,,,,,Active,,[] - overridden,someName,true,,,,,Active,,[] + overridden,someName,false,overriddenTemplate,overriddenFolder,overriddenId,overriddenVarStruct,Active,,[] """.stripIndent() mappingFile.toFile().write(input) givenExistingDisplayRule(migration, "unchanged", null, true, null, null, null, null) givenExistingDisplayRuleMapping(migration, "unchanged", null, null, null, null, null, null) - givenExistingDisplayRule(migration, "kept", "someName", true, null, null, null, null) - givenExistingDisplayRuleMapping(migration, "kept", "keptName", null, null, null, null, null) - givenExistingDisplayRule(migration, "overridden", "previousName", true, null, null, null, null) - givenExistingDisplayRuleMapping(migration, "overridden", "previousName", null, null, null, null, null) + givenExistingDisplayRule(migration, "overridden", "previousName", true, "previousId", "previousTemplate", "previousFolder", "previousVarStruct") + givenExistingDisplayRuleMapping(migration, "overridden", "previousName", true, "previousId", "previousTemplate", "previousFolder", "previousVarStruct") DisplayRulesImport.run(migration, mappingFile.toFile()) verify(migration.mappingRepository, times(1)).upsertBatch([ "unchanged" : new MappingItem.DisplayRule(null, null, null, null, null, true), - "kept" : new MappingItem.DisplayRule("keptName", null, null, null, null, true), - "overridden": new MappingItem.DisplayRule("someName", null, null, null, null, true) + "overridden": new MappingItem.DisplayRule("someName", "overriddenFolder", "overriddenId", new LiteralBaseTemplatePath("overriddenTemplate"), "overriddenVarStruct", false) ]) verify(migration.mappingRepository, times(1)).applyAllDisplayRuleMappings() } @Test - void overridesDisplayRuleInternal() { + void overridesDisplayRuleBaseTemplateRef() { def migration = Utils.mockMigration() Path mappingFile = Paths.get(dir.path, "testProject.csv") def input = """\ id,name,internal,baseTemplate,targetFolder,targetId,variableStructureRef,status,originalName (read-only),originLocations (read-only) - unchanged,,false,,,,,Active,,[] - kept,,true,,,,,Active,,[] - overridden,,true,,,,,Active,,[] + atPrefixed,,true,@someBaseTemplateId,,,,Active,,[] + dollarPrefixed,,true,\$anotherBaseTemplateId,,,,Active,,[] """.stripIndent() mappingFile.toFile().write(input) - givenExistingDisplayRule(migration, "unchanged", null, false, null, null, null, null) - givenExistingDisplayRuleMapping(migration, "unchanged", null, null, null, null, null, null) - givenExistingDisplayRule(migration, "kept", null, true, null, null, null, null) - givenExistingDisplayRuleMapping(migration, "kept", null, true, null, null, null, null) - givenExistingDisplayRule(migration, "overridden", null, false, null, null, null, null) - givenExistingDisplayRuleMapping(migration, "overridden", null, false, null, null, null, null) - - DisplayRulesImport.run(migration, mappingFile.toFile()) - - verify(migration.mappingRepository, times(1)).upsertBatch([ - "unchanged" : new MappingItem.DisplayRule(null, null, null, null, null, false), - "kept" : new MappingItem.DisplayRule(null, null, null, null, null, true), - "overridden": new MappingItem.DisplayRule(null, null, null, null, null, true) - ]) - verify(migration.mappingRepository, times(1)).applyAllDisplayRuleMappings() - } - - @Test - void overridesDisplayRuleBaseTemplate() { - def migration = Utils.mockMigration() - Path mappingFile = Paths.get(dir.path, "testProject.csv") - def input = """\ - id,name,internal,baseTemplate,targetFolder,targetId,variableStructureRef,status,originalName (read-only),originLocations (read-only) - unchanged,,true,,,,,Active,,[] - kept,,true,keptTemplate,,,,Active,,[] - overridden,,true,overriddenTemplate,,,,Active,,[] - """.stripIndent() - mappingFile.toFile().write(input) - givenExistingDisplayRule(migration, "unchanged", null, true, null, null, null, null) - givenExistingDisplayRuleMapping(migration, "unchanged", null, null, null, null, null, null) - givenExistingDisplayRule(migration, "kept", null, true, null, "keptTemplate", null, null) - givenExistingDisplayRuleMapping(migration, "kept", null, null, null, "keptTemplate", null, null) - givenExistingDisplayRule(migration, "overridden", null, true, null, "previousTemplate", null, null) - givenExistingDisplayRuleMapping(migration, "overridden", null, null, null, "previousTemplate", null, null) + givenExistingDisplayRule(migration, "atPrefixed", null, true, null, null, null, null) + givenExistingDisplayRuleMapping(migration, "atPrefixed", null, null, null, null, null, null) + givenExistingDisplayRule(migration, "dollarPrefixed", null, true, null, null, null, null) + givenExistingDisplayRuleMapping(migration, "dollarPrefixed", null, null, null, null, null, null) DisplayRulesImport.run(migration, mappingFile.toFile()) verify(migration.mappingRepository, times(1)).upsertBatch([ - "unchanged" : new MappingItem.DisplayRule(null, null, null, null, null, true), - "kept" : new MappingItem.DisplayRule(null, null, null, "keptTemplate", null, true), - "overridden": new MappingItem.DisplayRule(null, null, null, "overriddenTemplate", null, true) - ]) - verify(migration.mappingRepository, times(1)).applyAllDisplayRuleMappings() - } - - @Test - void overridesDisplayRuleTargetFolder() { - def migration = Utils.mockMigration() - Path mappingFile = Paths.get(dir.path, "testProject.csv") - def input = """\ - id,name,internal,baseTemplate,targetFolder,targetId,variableStructureRef,status,originalName (read-only),originLocations (read-only) - unchanged,,true,,,,,Active,,[] - kept,,true,,keptFolder,,,Active,,[] - overridden,,true,,overriddenFolder,,,Active,,[] - """.stripIndent() - mappingFile.toFile().write(input) - givenExistingDisplayRule(migration, "unchanged", null, true, null, null, null, null) - givenExistingDisplayRuleMapping(migration, "unchanged", null, null, null, null, null, null) - givenExistingDisplayRule(migration, "kept", null, true, null, null, "keptFolder", null) - givenExistingDisplayRuleMapping(migration, "kept", null, null, null, null, "keptFolder", null) - givenExistingDisplayRule(migration, "overridden", null, true, null, null, "previousFolder", null) - givenExistingDisplayRuleMapping(migration, "overridden", null, null, null, null, "previousFolder", null) - - DisplayRulesImport.run(migration, mappingFile.toFile()) - - verify(migration.mappingRepository, times(1)).upsertBatch([ - "unchanged" : new MappingItem.DisplayRule(null, null, null, null, null, true), - "kept" : new MappingItem.DisplayRule(null, "keptFolder", null, null, null, true), - "overridden": new MappingItem.DisplayRule(null, "overriddenFolder", null, null, null, true) - ]) - verify(migration.mappingRepository, times(1)).applyAllDisplayRuleMappings() - } - - @Test - void overridesDisplayRuleTargetId() { - def migration = Utils.mockMigration() - Path mappingFile = Paths.get(dir.path, "testProject.csv") - def input = """\ - id,name,internal,baseTemplate,targetFolder,targetId,variableStructureRef,status,originalName (read-only),originLocations (read-only) - unchanged,,true,,,,,Active,,[] - kept,,true,,,keptId,,Active,,[] - overridden,,true,,,overriddenId,,Active,,[] - """.stripIndent() - mappingFile.toFile().write(input) - givenExistingDisplayRule(migration, "unchanged", null, true, null, null, null, null) - givenExistingDisplayRuleMapping(migration, "unchanged", null, null, null, null, null, null) - givenExistingDisplayRule(migration, "kept", null, true, "keptId", null, null, null) - givenExistingDisplayRuleMapping(migration, "kept", null, null, "keptId", null, null, null) - givenExistingDisplayRule(migration, "overridden", null, true, "previousId", null, null, null) - givenExistingDisplayRuleMapping(migration, "overridden", null, null, "previousId", null, null, null) - - DisplayRulesImport.run(migration, mappingFile.toFile()) - - verify(migration.mappingRepository, times(1)).upsertBatch([ - "unchanged" : new MappingItem.DisplayRule(null, null, null, null, null, true), - "kept" : new MappingItem.DisplayRule(null, null, "keptId", null, null, true), - "overridden": new MappingItem.DisplayRule(null, null, "overriddenId", null, null, true) - ]) - verify(migration.mappingRepository, times(1)).applyAllDisplayRuleMappings() - } - - @Test - void overridesDisplayRuleVariableStructureRef() { - def migration = Utils.mockMigration() - Path mappingFile = Paths.get(dir.path, "testProject.csv") - def input = """\ - id,name,internal,baseTemplate,targetFolder,targetId,variableStructureRef,status,originalName (read-only),originLocations (read-only) - unchanged,,true,,,,,Active,,[] - kept,,true,,,,keptVarStruct,Active,,[] - overridden,,true,,,,overriddenVarStruct,Active,,[] - """.stripIndent() - mappingFile.toFile().write(input) - givenExistingDisplayRule(migration, "unchanged", null, true, null, null, null, null) - givenExistingDisplayRuleMapping(migration, "unchanged", null, null, null, null, null, null) - givenExistingDisplayRule(migration, "kept", null, true, null, null, null, "keptVarStruct") - givenExistingDisplayRuleMapping(migration, "kept", null, null, null, null, null, "keptVarStruct") - givenExistingDisplayRule(migration, "overridden", null, true, null, null, null, "previousVarStruct") - givenExistingDisplayRuleMapping(migration, "overridden", null, null, null, null, null, "previousVarStruct") - - DisplayRulesImport.run(migration, mappingFile.toFile()) - - verify(migration.mappingRepository, times(1)).upsertBatch([ - "unchanged" : new MappingItem.DisplayRule(null, null, null, null, null, true), - "kept" : new MappingItem.DisplayRule(null, null, null, null, "keptVarStruct", true), - "overridden": new MappingItem.DisplayRule(null, null, null, null, "overriddenVarStruct", true) + "atPrefixed" : new MappingItem.DisplayRule(null, null, null, new BaseTemplateRef("someBaseTemplateId"), null, true), + "dollarPrefixed": new MappingItem.DisplayRule(null, null, null, new BaseTemplateRef("anotherBaseTemplateId"), null, true) ]) verify(migration.mappingRepository, times(1)).applyAllDisplayRuleMappings() } @@ -233,7 +115,7 @@ class DisplayRulesMappingImportTest { DisplayRulesImport.run(migration, mappingFile.toFile()) verify(migration.mappingRepository, times(1)).upsertBatch([ - "rule1": new MappingItem.DisplayRule("myName", "myFolder", "myId", "myTemplate", "myVarStruct", false) + "rule1": new MappingItem.DisplayRule("myName", "myFolder", "myId", new LiteralBaseTemplatePath("myTemplate"), "myVarStruct", false) ]) verify(migration.mappingRepository, times(1)).applyAllDisplayRuleMappings() } @@ -264,7 +146,8 @@ class DisplayRulesMappingImportTest { String baseTemplate, String targetFolder, String variableStructureRef) { + def baseTemplateLocation = baseTemplate ? new LiteralBaseTemplatePath(baseTemplate) : null when(mig.mappingRepository.getDisplayRuleMapping(id)) - .thenReturn(new MappingItem.DisplayRule(name, targetFolder, targetId, baseTemplate, variableStructureRef, internal)) + .thenReturn(new MappingItem.DisplayRule(name, targetFolder, targetId, baseTemplateLocation, variableStructureRef, internal)) } } diff --git a/migration-examples/src/test/groovy/DocumentObjectsMappingExportTest.groovy b/migration-examples/src/test/groovy/DocumentObjectsMappingExportTest.groovy index 78586dfa..e15ce6a1 100644 --- a/migration-examples/src/test/groovy/DocumentObjectsMappingExportTest.groovy +++ b/migration-examples/src/test/groovy/DocumentObjectsMappingExportTest.groovy @@ -37,7 +37,7 @@ class DocumentObjectsMappingExportTest { .customFields(["originalName": "originalFull"]) .targetFolder("someDir") .variableStructureRef("struct") - .baseTemplate("tmpl.wfd") + .baseTemplateRef("tmplRef") .build(), ]) @@ -50,7 +50,7 @@ class DocumentObjectsMappingExportTest { empty,,Block,false,,,,Active,false,,,,[] full,full,Page,false,tmpl.wfd,someDir,struct,Active,true,placeholder,reason,,[foo; bar] overridden empty,,Block,false,,,,Active,false,,,,[] - overridden full,full,Page,false,tmpl.wfd,someDir,struct,Active,false,,,originalFull,[foo; bar] + overridden full,full,Page,false,\$tmplRef,someDir,struct,Active,false,,,originalFull,[foo; bar] """.stripIndent() Assertions.assertEquals(expected, mappingFile.toFile().text.replaceAll("\\r\\n|\\r", "\n")) } diff --git a/migration-examples/src/test/groovy/DocumentObjectsMappingImportTest.groovy b/migration-examples/src/test/groovy/DocumentObjectsMappingImportTest.groovy index 402d3fd3..c460150d 100644 --- a/migration-examples/src/test/groovy/DocumentObjectsMappingImportTest.groovy +++ b/migration-examples/src/test/groovy/DocumentObjectsMappingImportTest.groovy @@ -1,4 +1,6 @@ import com.quadient.migration.api.Migration +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateRef +import com.quadient.migration.api.dto.migrationmodel.LiteralBaseTemplatePath import com.quadient.migration.api.dto.migrationmodel.MappingItem import com.quadient.migration.api.dto.migrationmodel.builder.DocumentObjectBuilder import com.quadient.migration.example.common.mapping.DocumentObjectsImport @@ -16,197 +18,49 @@ class DocumentObjectsMappingImportTest { File dir @Test - void overridesDocumentObjectName() { + void overridesAllMappableFields() { def migration = Utils.mockMigration() Path mappingFile = Paths.get(dir.path, "testProject-variables.csv") def input = """\ - id,name,type,internal,originLocation,baseTemplate,targetFolder,variableStructureId,status - unchanged,,Block,false,[],,,,Active - kept,keptName,Block,false,[],,,,Active - overridden,someName,Block,false,[],,,,Active - """.stripIndent() - mappingFile.toFile().write(input) - givenExistingDocumentObject(migration, "unchanged", null, false, null, null, null, null) - givenExistingDocumentObjectMapping(migration, "unchanged", null, null, null, null, null, null) - givenExistingDocumentObject(migration, "kept", "someName", false, null, null, null, null) - givenExistingDocumentObjectMapping(migration, "kept", "keptName", null, null, null, null, null) - givenExistingDocumentObject(migration, "overridden", "previousName", false, null, null, null, null) - givenExistingDocumentObjectMapping(migration, "overridden", "previousName", null, null, null, null, null) - - DocumentObjectsImport.run(migration, mappingFile) - - verify(migration.mappingRepository, times(1)).upsertBatch([ - "unchanged" : new MappingItem.DocumentObject(null, false, null, null, DocumentObjectType.Block, null, new SkipOptions(false, null, null)), - "kept" : new MappingItem.DocumentObject("keptName", false, null, null, DocumentObjectType.Block, null, new SkipOptions(false, null, null)), - "overridden": new MappingItem.DocumentObject("someName", false, null, null, DocumentObjectType.Block, null, new SkipOptions(false, null, null)) - ]) - verify(migration.mappingRepository, times(1)).applyAllDocumentObjectMappings() - } - - @Test - void overridesDocumentObjectInternal() { - def migration = Utils.mockMigration() - Path mappingFile = Paths.get(dir.path, "testProject-variables.csv") - def input = """\ - id,name,type,internal,originLocation,baseTemplate,targetFolder,variableStructureId,status - unchanged,,Block,false,[],,,,Active - kept,,Block,true,[],,,,Active - overridden,,Block,true,[],,,,Active - """.stripIndent() - mappingFile.toFile().write(input) - givenExistingDocumentObject(migration, "unchanged", null, false, null, null, null, null) - givenExistingDocumentObjectMapping(migration, "unchanged", null, null, null, null, null, null) - givenExistingDocumentObject(migration, "kept", null, true, null, null, null, null) - givenExistingDocumentObjectMapping(migration, "kept", null, true, null, null, null, null) - givenExistingDocumentObject(migration, "overridden", null, false, null, null, null, null) - givenExistingDocumentObjectMapping(migration, "overridden", null, false, null, null, null, null) - - DocumentObjectsImport.run(migration, mappingFile) - - verify(migration.mappingRepository, times(1)).upsertBatch([ - "unchanged" : new MappingItem.DocumentObject(null, false, null, null, DocumentObjectType.Block, null, new SkipOptions(false, null, null)), - "kept" : new MappingItem.DocumentObject(null, true, null, null, DocumentObjectType.Block, null, new SkipOptions(false, null, null)), - "overridden": new MappingItem.DocumentObject(null, true, null, null, DocumentObjectType.Block, null, new SkipOptions(false, null, null)) - ]) - verify(migration.mappingRepository, times(1)).applyAllDocumentObjectMappings() - } - - @Test - void overridesDocumentObjectBaseTemplate() { - def migration = Utils.mockMigration() - Path mappingFile = Paths.get(dir.path, "testProject-variables.csv") - def input = """\ - id,name,type,internal,originLocation,baseTemplate,targetFolder,variableStructureId,status - unchanged,,Block,false,[],,,,Active - kept,,Block,false,[],keptTemplate,,,Active - overridden,,Block,false,[],overriddenTemplate,,,Active - """.stripIndent() - mappingFile.toFile().write(input) - givenExistingDocumentObject(migration, "unchanged", null, false, null, null, null, null) - givenExistingDocumentObjectMapping(migration, "unchanged", null, null, null, null, null, null) - givenExistingDocumentObject(migration, "kept", null, false, null, null, null, null) - givenExistingDocumentObjectMapping(migration, "kept", null, null, "keptTemplate", null, null, null) - givenExistingDocumentObject(migration, "overridden", null, false, "previousTemplate", null, null, null) - givenExistingDocumentObjectMapping(migration, "overridden", null, null, "previousTemplate", null, null, null) - - DocumentObjectsImport.run(migration, mappingFile) - - verify(migration.mappingRepository, times(1)).upsertBatch([ - "unchanged" : new MappingItem.DocumentObject(null, false, null, null, DocumentObjectType.Block, null, new SkipOptions(false, null, null)), - "kept" : new MappingItem.DocumentObject(null, false, "keptTemplate", null, DocumentObjectType.Block, null, new SkipOptions(false, null, null)), - "overridden": new MappingItem.DocumentObject(null, false, "overriddenTemplate", null, DocumentObjectType.Block, null, new SkipOptions(false, null, null)) - ]) - verify(migration.mappingRepository, times(1)).applyAllDocumentObjectMappings() - } - - @Test - void overridesDocumentObjectTargetFolder() { - def migration = Utils.mockMigration() - Path mappingFile = Paths.get(dir.path, "testProject-variables.csv") - def input = """\ - id,name,type,internal,originLocation,baseTemplate,targetFolder,variableStructureId,status - unchanged,,Block,false,[],,,,Active - kept,,Block,false,[],,keptFolder,,Active - overridden,,Block,false,[],,overriddenFolder,,Active - """.stripIndent() - mappingFile.toFile().write(input) - givenExistingDocumentObject(migration, "unchanged", null, false, null, null, null, null) - givenExistingDocumentObjectMapping(migration, "unchanged", null, null, null, null, null, null) - givenExistingDocumentObject(migration, "kept", null, false, null, null, null, null) - givenExistingDocumentObjectMapping(migration, "kept", null, null, null, "keptFolder", null, null) - givenExistingDocumentObject(migration, "overridden", null, false, null, "previousFolder", null, null) - givenExistingDocumentObjectMapping(migration, "overridden", null, null, null, "previousFolder", null, null) - - DocumentObjectsImport.run(migration, mappingFile) - - verify(migration.mappingRepository, times(1)).upsertBatch([ - "unchanged" : new MappingItem.DocumentObject(null, false, null, null, DocumentObjectType.Block, null, new SkipOptions(false, null, null)), - "kept" : new MappingItem.DocumentObject(null, false, null, "keptFolder", DocumentObjectType.Block, null, new SkipOptions(false, null, null)), - "overridden": new MappingItem.DocumentObject(null, false, null, "overriddenFolder", DocumentObjectType.Block, null, new SkipOptions(false, null, null)) - ]) - verify(migration.mappingRepository, times(1)).applyAllDocumentObjectMappings() - } - - @Test - void overridesVariableStructureRef() { - def migration = Utils.mockMigration() - Path mappingFile = Paths.get(dir.path, "testProject-variables.csv") - def input = """\ - id,name,type,internal,originLocation,baseTemplate,targetFolder,variableStructureId,status + id,name,type,internal,originLocation,baseTemplate,targetFolder,variableStructureId,status,skip,skipPlaceholder,skipReason unchanged,,Block,false,[],,,,Active - kept,,Block,false,[],,,keptVarStructure,Active - overridden,,Block,false,[],,,overriddenVarStructure,Active + overridden,someName,Page,true,[],overriddenTemplate,overriddenFolder,overriddenVarStructure,Active,true,placeholder,reason """.stripIndent() mappingFile.toFile().write(input) givenExistingDocumentObject(migration, "unchanged", null, false, null, null, null, null) givenExistingDocumentObjectMapping(migration, "unchanged", null, null, null, null, null, null) - givenExistingDocumentObject(migration, "kept", null, false, null, null, null, "keptVarStructure") - givenExistingDocumentObjectMapping(migration, "kept", null, null, null, null, null, "keptVarStructure") - givenExistingDocumentObject(migration, "overridden", null, false, null, null, null, "previousVarStructure") - givenExistingDocumentObjectMapping(migration, "overridden", null, null, null, null, null, "overriddenVarStructure") + givenExistingDocumentObject(migration, "overridden", "previousName", false, "previousTemplate", "previousFolder", DocumentObjectType.Template, "previousVarStructure") + givenExistingDocumentObjectMapping(migration, "overridden", "previousName", false, "previousTemplate", "previousFolder", DocumentObjectType.Template, "previousVarStructure") DocumentObjectsImport.run(migration, mappingFile) verify(migration.mappingRepository, times(1)).upsertBatch([ "unchanged" : new MappingItem.DocumentObject(null, false, null, null, DocumentObjectType.Block, null, new SkipOptions(false, null, null)), - "kept" : new MappingItem.DocumentObject(null, false, null, null, DocumentObjectType.Block, "keptVarStructure", new SkipOptions(false, null, null)), - "overridden": new MappingItem.DocumentObject(null, false, null, null, DocumentObjectType.Block, "overriddenVarStructure", new SkipOptions(false, null, null)) + "overridden": new MappingItem.DocumentObject("someName", true, new LiteralBaseTemplatePath("overriddenTemplate"), "overriddenFolder", DocumentObjectType.Page, "overriddenVarStructure", new SkipOptions(true, "placeholder", "reason")) ]) verify(migration.mappingRepository, times(1)).applyAllDocumentObjectMappings() } @Test - void overridesDocumentObjectType() { + void overridesDocumentObjectBaseTemplateRef() { def migration = Utils.mockMigration() Path mappingFile = Paths.get(dir.path, "testProject-variables.csv") def input = """\ id,name,type,internal,originLocation,baseTemplate,targetFolder,variableStructureId,status - unchanged,,Block,false,[],,,,Active - kept,,Template,false,[],,,,Active - overridden,,Page,false,[],,,,Active + atPrefixed,,Block,false,[],@someBaseTemplateId,,,Active + dollarPrefixed,,Block,false,[],\$anotherBaseTemplateId,,,Active """.stripIndent() mappingFile.toFile().write(input) - givenExistingDocumentObject(migration, "unchanged", null, false, null, null, null, null) - givenExistingDocumentObjectMapping(migration, "unchanged", null, null, null, null, null, null) - givenExistingDocumentObject(migration, "kept", null, false, null, null, null, null) - givenExistingDocumentObjectMapping(migration, "kept", null, null, null, null, DocumentObjectType.Template, null) - givenExistingDocumentObject(migration, "overridden", null, false, null, null, DocumentObjectType.Template, null) - givenExistingDocumentObjectMapping(migration, "overridden", null, null, null, null, DocumentObjectType.Section, null) + givenExistingDocumentObject(migration, "atPrefixed", null, false, null, null, null, null) + givenExistingDocumentObjectMapping(migration, "atPrefixed", null, null, null, null, null, null) + givenExistingDocumentObject(migration, "dollarPrefixed", null, false, null, null, null, null) + givenExistingDocumentObjectMapping(migration, "dollarPrefixed", null, null, null, null, null, null) DocumentObjectsImport.run(migration, mappingFile) verify(migration.mappingRepository, times(1)).upsertBatch([ - "unchanged" : new MappingItem.DocumentObject(null, false, null, null, DocumentObjectType.Block, null, new SkipOptions(false, null, null)), - "kept" : new MappingItem.DocumentObject(null, false, null, null, DocumentObjectType.Template, null, new SkipOptions(false, null, null)), - "overridden": new MappingItem.DocumentObject(null, false, null, null, DocumentObjectType.Page, null, new SkipOptions(false, null, null)) - ]) - verify(migration.mappingRepository, times(1)).applyAllDocumentObjectMappings() - } - - @Test - void overridesSkipOptions() { - def migration = Utils.mockMigration() - Path mappingFile = Paths.get(dir.path, "testProject-variables.csv") - def input = """\ - id,name,type,internal,originLocation,baseTemplate,targetFolder,variableStructureId,status,skip,skipPlaceholder,skipReason - unchanged,,Block,false,[],,,,Active - kept,,Template,false,[],,,,Active - overridden,,Page,false,[],,,,Active,true,placeholder,reason - """.stripIndent() - mappingFile.toFile().write(input) - givenExistingDocumentObject(migration, "unchanged", null, false, null, null, null, null) - givenExistingDocumentObjectMapping(migration, "unchanged", null, null, null, null, null, null) - givenExistingDocumentObject(migration, "kept", null, false, null, null, null, null) - givenExistingDocumentObjectMapping(migration, "kept", null, null, null, null, DocumentObjectType.Template, null) - givenExistingDocumentObject(migration, "overridden", null, false, null, null, DocumentObjectType.Template, null) - givenExistingDocumentObjectMapping(migration, "overridden", null, null, null, null, DocumentObjectType.Section, null) - - DocumentObjectsImport.run(migration, mappingFile) - - verify(migration.mappingRepository, times(1)).upsertBatch([ - "unchanged" : new MappingItem.DocumentObject(null, false, null, null, DocumentObjectType.Block, null, new SkipOptions(false, null, null)), - "kept" : new MappingItem.DocumentObject(null, false, null, null, DocumentObjectType.Template, null, new SkipOptions(false, null, null)), - "overridden": new MappingItem.DocumentObject(null, false, null, null, DocumentObjectType.Page, null, new SkipOptions(true, "placeholder", "reason")) + "atPrefixed" : new MappingItem.DocumentObject(null, false, new BaseTemplateRef("someBaseTemplateId"), null, DocumentObjectType.Block, null, new SkipOptions(false, null, null)), + "dollarPrefixed": new MappingItem.DocumentObject(null, false, new BaseTemplateRef("anotherBaseTemplateId"), null, DocumentObjectType.Block, null, new SkipOptions(false, null, null)) ]) verify(migration.mappingRepository, times(1)).applyAllDocumentObjectMappings() } @@ -240,7 +94,8 @@ class DocumentObjectsMappingImportTest { String targetFolder, DocumentObjectType type, String varStructureRef) { + def baseTemplateLocation = baseTemplate ? new LiteralBaseTemplatePath(baseTemplate) : null when(mig.mappingRepository.getDocumentObjectMapping(id)) - .thenReturn(new MappingItem.DocumentObject(name, internal, baseTemplate, targetFolder, type, varStructureRef, null)) + .thenReturn(new MappingItem.DocumentObject(name, internal, baseTemplateLocation, targetFolder, type, varStructureRef, null)) } } diff --git a/migration-examples/src/test/groovy/Utils.groovy b/migration-examples/src/test/groovy/Utils.groovy index aada7c8d..7a0bf2f7 100644 --- a/migration-examples/src/test/groovy/Utils.groovy +++ b/migration-examples/src/test/groovy/Utils.groovy @@ -1,6 +1,7 @@ import com.quadient.migration.api.InspireOutput import com.quadient.migration.api.Migration import com.quadient.migration.api.ProjectConfig +import com.quadient.migration.api.repository.BaseTemplateRepository import com.quadient.migration.api.repository.DisplayRuleRepository import com.quadient.migration.api.repository.DocumentObjectRepository import com.quadient.migration.api.repository.AttachmentRepository @@ -34,6 +35,7 @@ static Migration mockMigration() { def textStyleRepo = mock(TextStyleRepository.class) def paraStyleRepo = mock(ParagraphStyleRepository.class) def displayRuleRepo = mock(DisplayRuleRepository.class) + def baseTemplateRepo = mock(BaseTemplateRepository.class) when(migration.getParagraphStyleRepository()).thenReturn(paraStyleRepo) when(migration.getTextStyleRepository()).thenReturn(textStyleRepo) @@ -45,6 +47,7 @@ static Migration mockMigration() { when(migration.getVariableStructureRepository()).thenReturn(structureRepo) when(migration.getMappingRepository()).thenReturn(mappingRepo) when(migration.getDisplayRuleRepository()).thenReturn(displayRuleRepo) + when(migration.getBaseTemplateRepository()).thenReturn(baseTemplateRepo) def previewProvider = new PreviewProvider(docObjectRepo, imageRepo, attachmentRepo, varRepo) when(migration.getPreviewProvider()).thenReturn(previewProvider) diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/Migration.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/Migration.kt index b24d4141..5ec3b59f 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/Migration.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/Migration.kt @@ -96,6 +96,7 @@ class Migration(val config: MigConfig, val projectConfig: ProjectConfig) { single() single() single() + single() single() single() single() @@ -128,6 +129,7 @@ class Migration(val config: MigConfig, val projectConfig: ProjectConfig) { val paragraphStyleRepository: ParagraphStyleRepository by lazy { koin.get() } val variableStructureRepository: VariableStructureRepository by lazy { koin.get() } val displayRuleRepository: DisplayRuleRepository by lazy { koin.get() } + val baseTemplateRepository: BaseTemplateRepository by lazy { koin.get() } val imageRepository: ImageRepository by lazy { koin.get() } val attachmentRepository: AttachmentRepository by lazy { koin.get() } val statusTrackingRepository: StatusTrackingRepository by lazy { koin.get() } @@ -164,6 +166,7 @@ class Migration(val config: MigConfig, val projectConfig: ProjectConfig) { repositories.add(paragraphStyleRepository) repositories.add(variableStructureRepository) repositories.add(displayRuleRepository) + repositories.add(baseTemplateRepository) repositories.add(imageRepository) repositories.add(attachmentRepository) diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/BaseTemplate.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/BaseTemplate.kt new file mode 100644 index 00000000..e2c9cc29 --- /dev/null +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/BaseTemplate.kt @@ -0,0 +1,15 @@ +package com.quadient.migration.api.dto.migrationmodel + +import com.quadient.migration.shared.BaseTemplatePage +import kotlin.time.Instant + +data class BaseTemplate( + override val id: String, + override var name: String? = null, + override var originLocations: List = emptyList(), + override var customFields: CustomFieldMap, + var targetFolder: String? = null, + var pages: List = emptyList(), + override var created: Instant? = null, + override var lastUpdated: Instant? = null, +) : MigrationObject diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/DisplayRule.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/DisplayRule.kt index 0d830336..41de9e01 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/DisplayRule.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/DisplayRule.kt @@ -17,10 +17,12 @@ data class DisplayRule @JvmOverloads constructor( val metadata: List = emptyList(), val subject: String? = null, val targetFolder: String? = null, - val baseTemplate: String? = null, + val baseTemplate: BaseTemplateLocation? = null, val variableStructureRef: VariableStructureRef? = null, ) : MigrationObject, RefValidatable { override fun collectRefs(): Set { - return (definition?.collectRefs() ?: emptySet()) + setOfNotNull(variableStructureRef, targetId) + val baseTemplateRef = baseTemplate as? BaseTemplateRef + + return (definition?.collectRefs() ?: emptySet()) + setOfNotNull(variableStructureRef, targetId, baseTemplateRef) } } \ No newline at end of file diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/DocumentObject.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/DocumentObject.kt index b30e940c..b824edaa 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/DocumentObject.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/DocumentObject.kt @@ -16,7 +16,7 @@ data class DocumentObject( var targetFolder: String? = null, var displayRuleRef: DisplayRuleRef? = null, var variableStructureRef: VariableStructureRef? = null, - var baseTemplate: String? = null, + var baseTemplate: BaseTemplateLocation? = null, var options: DocumentObjectOptions? = null, var pdfMetadata: PdfMetadata? = null, override var created: Instant? = null, @@ -35,6 +35,8 @@ data class DocumentObject( val pdfMetadataRefs = pdfMetadata?.collectRefs().orEmpty() - return contentRefs + pdfMetadataRefs + setOfNotNull(displayRuleRef, variableStructureRef) + val baseTemplateRef = baseTemplate as? BaseTemplateRef + + return contentRefs + pdfMetadataRefs + setOfNotNull(displayRuleRef, variableStructureRef, baseTemplateRef) } } diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/Mapping.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/Mapping.kt index 59d8692b..f172b530 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/Mapping.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/Mapping.kt @@ -13,7 +13,7 @@ sealed class MappingItem { data class DocumentObject( override var name: String?, var internal: Boolean?, - var baseTemplate: String?, + var baseTemplate: BaseTemplateLocation?, var targetFolder: String?, var type: DocumentObjectType?, var variableStructureRef: String?, @@ -98,11 +98,17 @@ sealed class MappingItem { override var name: String?, var targetFolder: String?, var targetId: String?, - var baseTemplate: String?, + var baseTemplate: BaseTemplateLocation?, var variableStructureRef: String?, var internal: Boolean?, ) : MappingItem() + data class BaseTemplate( + override var name: String?, + var targetFolder: String?, + var pages: List = emptyList(), + ) : MappingItem() + data class Table( override var name: String? = null, val tables: List = emptyList(), @@ -123,7 +129,7 @@ sealed class MappingItem { MappingItemEntity.DocumentObject( name = this.name, internal = this.internal, - baseTemplate = this.baseTemplate, + baseTemplate = this.baseTemplate?.toDb(), targetFolder = this.targetFolder, type = this.type, variableStructureRef = this.variableStructureRef, @@ -135,6 +141,12 @@ sealed class MappingItem { name = this.name, areas = this.areas, flowToNextPage = this.flowToNextPage ) + is MappingItem.BaseTemplate -> MappingItemEntity.BaseTemplate( + name = this.name, + targetFolder = this.targetFolder, + pages = this.pages, + ) + is MappingItem.Image -> { MappingItemEntity.Image( name = this.name, @@ -221,7 +233,7 @@ sealed class MappingItem { internal = this.internal, variableStructureRef = this.variableStructureRef, targetFolder = this.targetFolder, - baseTemplate = this.baseTemplate, + baseTemplate = this.baseTemplate?.toDb(), ) is MappingItem.Table -> MappingItemEntity.Table( diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/Ref.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/Ref.kt index d39e17cc..f2df16fb 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/Ref.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/Ref.kt @@ -1,6 +1,8 @@ package com.quadient.migration.api.dto.migrationmodel import com.quadient.migration.persistence.migrationmodel.ColumnLayoutEntity +import com.quadient.migration.persistence.migrationmodel.BaseTemplateEntityRef +import com.quadient.migration.persistence.migrationmodel.BaseTemplateLocationEntity import com.quadient.migration.persistence.migrationmodel.DisplayRuleEntityRef import com.quadient.migration.persistence.migrationmodel.DocumentObjectEntityRef import com.quadient.migration.persistence.migrationmodel.FirstMatchEntity @@ -8,6 +10,7 @@ import com.quadient.migration.persistence.migrationmodel.HyperlinkEntity import com.quadient.migration.persistence.migrationmodel.ImageEntityRef import com.quadient.migration.persistence.migrationmodel.AttachmentEntityRef import com.quadient.migration.persistence.migrationmodel.BarcodeEntity +import com.quadient.migration.persistence.migrationmodel.LiteralBaseTemplatePathEntity import com.quadient.migration.persistence.migrationmodel.ParagraphStyleEntityRef import com.quadient.migration.persistence.migrationmodel.ResourceEntityRef import com.quadient.migration.persistence.migrationmodel.StringEntity @@ -138,6 +141,36 @@ data class VariableStructureRef(override val id: String) : Ref(id) { fun toDb() = VariableStructureEntityRef(id) } +sealed interface BaseTemplateLocation { + companion object { + fun fromDb(entity: BaseTemplateLocationEntity): BaseTemplateLocation = when (entity) { + is LiteralBaseTemplatePathEntity -> LiteralBaseTemplatePath.fromDb(entity) + is BaseTemplateEntityRef -> BaseTemplateRef.fromDb(entity) + } + } +} + +fun BaseTemplateLocation.toDb(): BaseTemplateLocationEntity = when (this) { + is LiteralBaseTemplatePath -> this.toDb() + is BaseTemplateRef -> this.toDb() +} + +data class LiteralBaseTemplatePath(val path: String) : BaseTemplateLocation { + companion object { + fun fromDb(entity: LiteralBaseTemplatePathEntity) = LiteralBaseTemplatePath(entity.path) + } + + fun toDb() = LiteralBaseTemplatePathEntity(path) +} + +data class BaseTemplateRef(override val id: String) : Ref(id), BaseTemplateLocation { + companion object { + fun fromDb(entity: BaseTemplateEntityRef) = BaseTemplateRef(entity.id) + } + + fun toDb() = BaseTemplateEntityRef(id) +} + data class StringValue(val value: String) : VariableStringContent { override val pathName = "str" override fun toPreview(nameResolver: (DocumentContent) -> String?): String = diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/DisplayRuleBuilder.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/DisplayRuleBuilder.kt index ab35c2b9..e06860ed 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/DisplayRuleBuilder.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/DisplayRuleBuilder.kt @@ -11,6 +11,7 @@ import com.quadient.migration.api.dto.migrationmodel.builder.components.HasMetad import com.quadient.migration.api.dto.migrationmodel.builder.components.HasSubject import com.quadient.migration.api.dto.migrationmodel.builder.components.HasTargetFolder import com.quadient.migration.api.dto.migrationmodel.builder.components.HasVariableStructureRef +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateLocation import com.quadient.migration.shared.BinOp import com.quadient.migration.shared.Binary import com.quadient.migration.shared.BinaryOrGroup @@ -35,7 +36,7 @@ class DisplayRuleBuilder(id: String) : DtoBuilderBase = mutableListOf() override var variableStructureRef: VariableStructureRef? = null - override var baseTemplate: String? = null + override var baseTemplate: BaseTemplateLocation? = null override var targetFolder: String? = null var definition: DisplayRuleDefinition? = null var targetId: String? = null diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/DocumentObjectBuilder.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/DocumentObjectBuilder.kt index d0e06c74..39fd0c9e 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/DocumentObjectBuilder.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/DocumentObjectBuilder.kt @@ -19,6 +19,7 @@ import com.quadient.migration.api.dto.migrationmodel.builder.components.HasSubje import com.quadient.migration.api.dto.migrationmodel.builder.components.HasTargetFolder import com.quadient.migration.api.dto.migrationmodel.builder.components.HasVariableStructureRef import com.quadient.migration.api.dto.migrationmodel.DocumentObjectOptions +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateLocation import com.quadient.migration.shared.DocumentObjectType import com.quadient.migration.api.dto.migrationmodel.EmailOptions import com.quadient.migration.shared.MetadataEntry @@ -47,7 +48,7 @@ class DocumentObjectBuilder(id: String, private val type: DocumentObjectType) : override var variableStructureRef: VariableStructureRef? = null override var internal: Boolean = false override var targetFolder: String? = null - override var baseTemplate: String? = null + override var baseTemplate: BaseTemplateLocation? = null override var subject: String? = null override var options: DocumentObjectOptions? = null override var metadata: MutableList = mutableListOf() diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/SnippetBuilder.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/SnippetBuilder.kt index 088f60a6..dcc064b9 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/SnippetBuilder.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/SnippetBuilder.kt @@ -17,6 +17,7 @@ import com.quadient.migration.api.dto.migrationmodel.builder.components.HasSubje import com.quadient.migration.api.dto.migrationmodel.builder.components.HasTargetFolder import com.quadient.migration.api.dto.migrationmodel.builder.components.HasVariableStructureRef import com.quadient.migration.api.dto.migrationmodel.DocumentObjectOptions +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateLocation import com.quadient.migration.shared.DocumentObjectType import com.quadient.migration.shared.MetadataEntry import com.quadient.migration.shared.SkipOptions @@ -54,7 +55,7 @@ class FirstMatchSnippetBuilder(id: String) : DtoBuilderBase = mutableListOf() @@ -118,7 +119,7 @@ class SimpleSnippetBuilder(id: String) : DtoBuilderBase = mutableListOf() diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/components/HasBaseTemplate.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/components/HasBaseTemplate.kt index 3b6306f9..101ed110 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/components/HasBaseTemplate.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/components/HasBaseTemplate.kt @@ -1,13 +1,52 @@ package com.quadient.migration.api.dto.migrationmodel.builder.components +import com.quadient.migration.api.dto.migrationmodel.BaseTemplate +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateLocation +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateRef +import com.quadient.migration.api.dto.migrationmodel.LiteralBaseTemplatePath + @Suppress("UNCHECKED_CAST") interface HasBaseTemplate { - var baseTemplate: String? + var baseTemplate: BaseTemplateLocation? /** * Override the default base template for this object. - * @param baseTemplate Path to the base template to use for this document object. + * @param baseTemplateLocation Location (literal path or base template reference) of the base template to use for this object. + * @return This builder instance for method chaining. + */ + fun baseTemplate(baseTemplateLocation: BaseTemplateLocation?) = + apply { this.baseTemplate = baseTemplateLocation } as T + + /** + * Overrides the default base template for this object with a literal ICM path. + * @param path Path to the base template to use for this object. + * @return This builder instance for method chaining. + */ + @Deprecated( + message = "Use baseTemplatePath() for a literal path or baseTemplateRef() for a reference to a BaseTemplate migration object instead.", + replaceWith = ReplaceWith("baseTemplatePath(baseTemplate)"), + ) + fun baseTemplate(path: String?) = apply { this.baseTemplate = path?.let { LiteralBaseTemplatePath(it) } } as T + + /** + * Overrides the default base template for this object with a literal ICM path. + * @param path Path to the base template to use for this object. + * @return This builder instance for method chaining. + */ + fun baseTemplatePath(path: String?) = apply { this.baseTemplate = path?.let { LiteralBaseTemplatePath(it) } } as T + + /** + * Overrides the default base template for this object with a reference to a [BaseTemplate] migration object. + * @param id ID of the [BaseTemplate] migration object to use for this object. + * @return This builder instance for method chaining. + */ + fun baseTemplateRef(id: String?) = apply { this.baseTemplate = id?.let { BaseTemplateRef(it) } } as T + + /** + * Overrides the default base template for this object with a reference to a [BaseTemplate] migration object. + * @param baseTemplate The [BaseTemplate] migration object to use for this object. * @return This builder instance for method chaining. */ - fun baseTemplate(baseTemplate: String?) = apply { this.baseTemplate = baseTemplate } as T + fun baseTemplateRef(baseTemplate: BaseTemplate) = + apply { this.baseTemplate = BaseTemplateRef(baseTemplate.id) } as T } diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/repository/BaseTemplateRepository.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/repository/BaseTemplateRepository.kt new file mode 100644 index 00000000..c57ee9aa --- /dev/null +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/repository/BaseTemplateRepository.kt @@ -0,0 +1,96 @@ +package com.quadient.migration.api.repository + +import com.quadient.migration.api.ProjectName +import com.quadient.migration.api.dto.migrationmodel.BaseTemplate +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateRef +import com.quadient.migration.api.dto.migrationmodel.CustomFieldMap +import com.quadient.migration.api.dto.migrationmodel.MigrationObject +import com.quadient.migration.persistence.table.BaseTemplateTable +import com.quadient.migration.persistence.table.DocumentObjectTable +import com.quadient.migration.tools.concat +import kotlin.time.Clock +import kotlin.time.toJavaInstant +import kotlinx.serialization.json.Json +import java.sql.Types +import org.jetbrains.exposed.v1.core.ResultRow +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.upsertReturning + +class BaseTemplateRepository(projectName: ProjectName) : + Repository(BaseTemplateTable, projectName.name) { + + override fun fromDb(row: ResultRow): BaseTemplate { + return BaseTemplate( + id = row[BaseTemplateTable.id].value, + name = row[BaseTemplateTable.name], + customFields = CustomFieldMap(row[BaseTemplateTable.customFields].toMutableMap()), + lastUpdated = row[BaseTemplateTable.lastUpdated], + created = row[BaseTemplateTable.created], + originLocations = row[BaseTemplateTable.originLocations], + targetFolder = row[BaseTemplateTable.targetFolder], + pages = row[BaseTemplateTable.pages], + ) + } + + override fun findUsages(id: String): List { + return transaction { + DocumentObjectTable.selectAll().where { DocumentObjectTable.projectName eq projectName } + .map { DocumentObjectTable.fromResultRow(it) } + .filter { it.collectRefs().any { ref -> ref is BaseTemplateRef && ref.id == id } } + .distinct() + } + } + + override fun upsert(dto: BaseTemplate) { + upsertInternal { + val existingItem = table.selectAll().where(filter(dto.id)).firstOrNull()?.let(::fromDb) + + val now = Clock.System.now() + + table.upsertReturning(table.id, table.projectName) { + it[BaseTemplateTable.id] = dto.id + it[BaseTemplateTable.projectName] = this@BaseTemplateRepository.projectName + it[BaseTemplateTable.name] = dto.name + it[BaseTemplateTable.originLocations] = existingItem?.originLocations.concat(dto.originLocations).distinct() + it[BaseTemplateTable.customFields] = dto.customFields.inner + it[BaseTemplateTable.created] = existingItem?.created ?: now + it[BaseTemplateTable.lastUpdated] = now + it[BaseTemplateTable.targetFolder] = dto.targetFolder + it[BaseTemplateTable.pages] = dto.pages + }.first() + } + } + + override fun upsertBatch(dtos: Collection) { + if (dtos.isEmpty()) return + + val columns = listOf( + "id", "project_name", "name", "origin_locations", "custom_fields", + "created", "last_updated", "target_folder", "pages" + ) + val sql = createSql(columns, dtos.size) + val now = Clock.System.now() + + upsertBatchInternal(dtos) { + val stmt = it.prepareStatement(sql) + var index = 1 + dtos.forEach { dto -> + val existingItem = find(dto.id) + + stmt.setString(index++, dto.id) + stmt.setString(index++, this@BaseTemplateRepository.projectName) + stmt.setString(index++, dto.name) + stmt.setArray(index++, it.createArrayOf("text", existingItem?.originLocations.concat(dto.originLocations).distinct().toTypedArray())) + stmt.setObject(index++, Json.encodeToString(dto.customFields.inner), Types.OTHER) + stmt.setTimestamp(index++, java.sql.Timestamp.from((existingItem?.created ?: now).toJavaInstant())) + stmt.setTimestamp(index++, java.sql.Timestamp.from(now.toJavaInstant())) + stmt.setString(index++, dto.targetFolder) + stmt.setObject(index++, Json.encodeToString(dto.pages), Types.OTHER) + } + + stmt.executeUpdate() + } + } +} diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/repository/DisplayRuleRepository.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/repository/DisplayRuleRepository.kt index 25500358..e3ece305 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/repository/DisplayRuleRepository.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/repository/DisplayRuleRepository.kt @@ -1,11 +1,13 @@ package com.quadient.migration.api.repository import com.quadient.migration.api.ProjectName +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateLocation import com.quadient.migration.api.dto.migrationmodel.CustomFieldMap import com.quadient.migration.api.dto.migrationmodel.DisplayRule import com.quadient.migration.api.dto.migrationmodel.DisplayRuleRef import com.quadient.migration.api.dto.migrationmodel.MigrationObject import com.quadient.migration.api.dto.migrationmodel.VariableStructureRef +import com.quadient.migration.api.dto.migrationmodel.toDb import com.quadient.migration.persistence.table.DisplayRuleTable import com.quadient.migration.persistence.table.DocumentObjectTable import com.quadient.migration.service.deploy.utility.ResourceType @@ -32,7 +34,7 @@ class DisplayRuleRepository(projectName: ProjectName, private val statusTracking lastUpdated = row[DisplayRuleTable.lastUpdated], created = row[DisplayRuleTable.created], definition = row[DisplayRuleTable.definition], - baseTemplate = row[DisplayRuleTable.baseTemplate], + baseTemplate = row[DisplayRuleTable.baseTemplate]?.let { BaseTemplateLocation.fromDb(it) }, metadata = row[DisplayRuleTable.metadata], subject = row[DisplayRuleTable.subject], internal = row[DisplayRuleTable.internal], @@ -85,7 +87,7 @@ class DisplayRuleRepository(projectName: ProjectName, private val statusTracking stmt.setBoolean(index++, dto.internal) stmt.setString(index++, dto.subject) stmt.setString(index++, dto.targetFolder) - stmt.setString(index++, dto.baseTemplate) + stmt.setObject(index++, dto.baseTemplate?.let { Json.encodeToString(it.toDb()) }, Types.OTHER) stmt.setString(index++, dto.variableStructureRef?.id) stmt.setObject(index++, Json.encodeToString(dto.metadata), Types.OTHER) } @@ -117,7 +119,7 @@ class DisplayRuleRepository(projectName: ProjectName, private val statusTracking it[DisplayRuleTable.internal] = dto.internal it[DisplayRuleTable.subject] = dto.subject it[DisplayRuleTable.targetFolder] = dto.targetFolder - it[DisplayRuleTable.baseTemplate] = dto.baseTemplate + it[DisplayRuleTable.baseTemplate] = dto.baseTemplate?.toDb() it[DisplayRuleTable.variableStructureRef] = dto.variableStructureRef?.id it[DisplayRuleTable.metadata] = dto.metadata }.first() diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/repository/DocumentObjectRepository.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/repository/DocumentObjectRepository.kt index bbc3c4ad..7893e441 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/repository/DocumentObjectRepository.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/repository/DocumentObjectRepository.kt @@ -85,7 +85,7 @@ class DocumentObjectRepository(projectName: ProjectName, private val statusTrack stmt.setTimestamp(index++, java.sql.Timestamp.from(now.toJavaInstant())) stmt.setString(index++, dto.displayRuleRef?.id) stmt.setString(index++, dto.variableStructureRef?.id) - stmt.setString(index++, dto.baseTemplate) + stmt.setObject(index++, dto.baseTemplate?.let { Json.encodeToString(it.toDb()) }, Types.OTHER) stmt.setObject(index++, dto.options?.let { Json.encodeToString(it.toDb()) }, Types.OTHER) stmt.setObject(index++, dto.pdfMetadata?.let { Json.encodeToString(it.toDb()) }, Types.OTHER) stmt.setObject(index++, dto.metadata.let { Json.encodeToString(it) }, Types.OTHER) @@ -128,7 +128,7 @@ class DocumentObjectRepository(projectName: ProjectName, private val statusTrack it[DocumentObjectTable.lastUpdated] = now it[DocumentObjectTable.displayRuleRef] = dto.displayRuleRef?.id it[DocumentObjectTable.variableStructureRef] = dto.variableStructureRef?.id - it[DocumentObjectTable.baseTemplate] = dto.baseTemplate + it[DocumentObjectTable.baseTemplate] = dto.baseTemplate?.toDb() it[DocumentObjectTable.documentObjectOptions] = dto.options?.toDb() it[DocumentObjectTable.pdfMetadata] = dto.pdfMetadata?.toDb() it[DocumentObjectTable.metadata] = dto.metadata diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/repository/MappingRepository.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/repository/MappingRepository.kt index 982edcc4..267a24c2 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/repository/MappingRepository.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/repository/MappingRepository.kt @@ -2,13 +2,13 @@ package com.quadient.migration.api.repository import com.quadient.migration.api.ProjectName import com.quadient.migration.api.dto.migrationmodel.Attachment +import com.quadient.migration.api.dto.migrationmodel.BaseTemplate import com.quadient.migration.api.dto.migrationmodel.DisplayRule import com.quadient.migration.api.dto.migrationmodel.DocumentObject import com.quadient.migration.api.dto.migrationmodel.Image import com.quadient.migration.api.dto.migrationmodel.ParagraphStyle import com.quadient.migration.api.dto.migrationmodel.TextStyle import com.quadient.migration.api.dto.migrationmodel.Variable -import com.quadient.migration.api.dto.migrationmodel.CustomFieldMap import com.quadient.migration.api.dto.migrationmodel.Mapping import com.quadient.migration.api.dto.migrationmodel.MappingItem import com.quadient.migration.api.dto.migrationmodel.MigrationObject @@ -16,6 +16,7 @@ import com.quadient.migration.api.dto.migrationmodel.VariableStructure import com.quadient.migration.persistence.migrationmodel.MappingItemEntity import com.quadient.migration.persistence.repository.MappingInternalRepository import com.quadient.migration.persistence.table.AttachmentTable +import com.quadient.migration.persistence.table.BaseTemplateTable import com.quadient.migration.persistence.table.DisplayRuleTable import com.quadient.migration.persistence.table.DocumentObjectTable import com.quadient.migration.persistence.table.ImageTable @@ -38,6 +39,7 @@ class MappingRepository( private val variableRepository: VariableRepository, private val variableStructureRepository: VariableStructureRepository, private val displayRuleRepository: DisplayRuleRepository, + private val baseTemplateRepository: BaseTemplateRepository, ) { private val logger by logger() private val internalRepository = MappingInternalRepository(projectName.name) @@ -59,6 +61,7 @@ class MappingRepository( applyAllVariableStructureMappings() applyAllDisplayRuleMappings() applyAllTableMappings(onError) + applyAllBaseTemplateMappings() } fun upsert(id: String, mapping: MappingItem): Mapping { @@ -276,18 +279,9 @@ class MappingRepository( fun applyVariableStructureMapping(id: String) { val mapping = internalRepository.find(id) - val structure = variableStructureRepository.find(id) ?: VariableStructure( - id = id, - name = null, - originLocations = emptyList(), - customFields = CustomFieldMap(), - created = kotlin.time.Clock.System.now(), - lastUpdated = kotlin.time.Clock.System.now(), - structure = mutableMapOf(), - languageVariable = null, - ) - - if (mapping == null) { + val structure = variableStructureRepository.find(id) + + if (mapping == null || structure == null) { return } @@ -316,6 +310,27 @@ class MappingRepository( displayRuleRepository.upsert(mapping.apply(displayRule)) } + fun getBaseTemplateMapping(id: String): MappingItem.BaseTemplate { + return (internalRepository.find(id) ?: MappingItemEntity.BaseTemplate( + name = null, targetFolder = null, pages = emptyList() + )).toDto() as MappingItem.BaseTemplate + } + + fun applyBaseTemplateMapping(id: String) { + val mapping = internalRepository.find(id) + val baseTemplate = baseTemplateRepository.find(id) + + if (mapping == null || baseTemplate == null) { + return + } + + baseTemplateRepository.upsert(mapping.apply(baseTemplate)) + } + + fun applyAllBaseTemplateMappings() { + applyAllResourceMappings(baseTemplateRepository, BaseTemplateTable) + } + fun deleteAll() { internalRepository.deleteAll() } diff --git a/migration-library/src/main/kotlin/com/quadient/migration/persistence/migrationmodel/MappingEntity.kt b/migration-library/src/main/kotlin/com/quadient/migration/persistence/migrationmodel/MappingEntity.kt index 353934da..59b0b36f 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/persistence/migrationmodel/MappingEntity.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/persistence/migrationmodel/MappingEntity.kt @@ -22,6 +22,7 @@ import com.quadient.migration.api.dto.migrationmodel.VariableStructure as Variab import com.quadient.migration.api.dto.migrationmodel.Area as AreaModel import com.quadient.migration.api.dto.migrationmodel.DisplayRule as DisplayRuleModel import com.quadient.migration.api.dto.migrationmodel.Table as TableModel +import com.quadient.migration.api.dto.migrationmodel.BaseTemplate as BaseTemplateModel class MappingEntity(id: EntityID) : CompositeEntity(id) { companion object : CompositeEntityClass(MappingTable) @@ -54,6 +55,7 @@ sealed class MappingItemEntity { is VariableStructure -> this.apply(entity as VariableStructureModel) is DisplayRule -> this.apply(entity as DisplayRuleModel) is Table -> this.apply(entity as DocumentObjectModel, onError) + is BaseTemplate -> this.apply(entity as BaseTemplateModel) } } @@ -61,7 +63,7 @@ sealed class MappingItemEntity { data class DocumentObject( override val name: String?, val internal: Boolean?, - val baseTemplate: String?, + val baseTemplate: BaseTemplateLocationEntity?, val targetFolder: String?, val variableStructureRef: String?, @SerialName("documentObjectType") val type: DocumentObjectType?, @@ -72,7 +74,7 @@ sealed class MappingItemEntity { return item.copy( name = name, internal = internal ?: false, - baseTemplate = baseTemplate, + baseTemplate = baseTemplate?.let { BaseTemplateLocation.fromDb(it) }, targetFolder = targetFolder, type = type ?: item.type, variableStructureRef = variableStructureRef?.let { VariableStructureRef(it) }, @@ -108,6 +110,21 @@ sealed class MappingItemEntity { } } + @Serializable + data class BaseTemplate( + override val name: String?, + val targetFolder: String?, + val pages: List = emptyList(), + ) : MappingItemEntity() { + fun apply(item: BaseTemplateModel): BaseTemplateModel { + return item.copy( + name = name, + targetFolder = targetFolder, + pages = pages, + ) + } + } + @Serializable data class Image( override val name: String?, @@ -234,7 +251,7 @@ sealed class MappingItemEntity { override var name: String?, val targetFolder: String?, var targetId: String?, - val baseTemplate: String?, + val baseTemplate: BaseTemplateLocationEntity?, val variableStructureRef: String?, var internal: Boolean?, ) : MappingItemEntity() { @@ -242,7 +259,7 @@ sealed class MappingItemEntity { return item.copy( name = name, targetFolder = targetFolder, - baseTemplate = baseTemplate, + baseTemplate = baseTemplate?.let { BaseTemplateLocation.fromDb(it) }, variableStructureRef = variableStructureRef?.let { VariableStructureRef(it) }, targetId = targetId?.let { DisplayRuleRef(it) }, internal = internal ?: true @@ -358,7 +375,7 @@ sealed class MappingItemEntity { MappingItem.DocumentObject( name = this.name, internal = this.internal, - baseTemplate = this.baseTemplate, + baseTemplate = this.baseTemplate?.let { BaseTemplateLocation.fromDb(it) }, targetFolder = this.targetFolder, type = this.type, variableStructureRef = this.variableStructureRef, @@ -368,6 +385,12 @@ sealed class MappingItemEntity { is Area -> MappingItem.Area(name = this.name, areas = this.areas) + is BaseTemplate -> MappingItem.BaseTemplate( + name = this.name, + targetFolder = this.targetFolder, + pages = this.pages, + ) + is Image -> { MappingItem.Image( name = this.name, @@ -455,7 +478,7 @@ sealed class MappingItemEntity { internal = this.internal, variableStructureRef = this.variableStructureRef, targetFolder = this.targetFolder, - baseTemplate = this.baseTemplate, + baseTemplate = this.baseTemplate?.let { BaseTemplateLocation.fromDb(it) }, ) is Table -> MappingItem.Table( diff --git a/migration-library/src/main/kotlin/com/quadient/migration/persistence/migrationmodel/RefEntity.kt b/migration-library/src/main/kotlin/com/quadient/migration/persistence/migrationmodel/RefEntity.kt index 3d5b6fac..72744bac 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/persistence/migrationmodel/RefEntity.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/persistence/migrationmodel/RefEntity.kt @@ -4,6 +4,7 @@ import com.quadient.migration.shared.Color import com.quadient.migration.shared.ShapePath import com.quadient.migration.shared.Position import com.quadient.migration.shared.Size +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable @@ -43,6 +44,17 @@ data class AttachmentEntityRef(val id: String) : ResourceEntityRef @Serializable data class VariableStructureEntityRef(val id: String) : RefEntity +@Serializable +sealed interface BaseTemplateLocationEntity + +@Serializable +@SerialName("BaseTemplatePath") +data class LiteralBaseTemplatePathEntity(val path: String) : BaseTemplateLocationEntity + +@Serializable +@SerialName("BaseTemplateRef") +data class BaseTemplateEntityRef(val id: String) : RefEntity, BaseTemplateLocationEntity + @Serializable data class StringEntity(val value: String) : VariableStringContentEntity diff --git a/migration-library/src/main/kotlin/com/quadient/migration/persistence/table/BaseTemplateTable.kt b/migration-library/src/main/kotlin/com/quadient/migration/persistence/table/BaseTemplateTable.kt new file mode 100644 index 00000000..0be62259 --- /dev/null +++ b/migration-library/src/main/kotlin/com/quadient/migration/persistence/table/BaseTemplateTable.kt @@ -0,0 +1,10 @@ +package com.quadient.migration.persistence.table + +import com.quadient.migration.shared.BaseTemplatePage +import kotlinx.serialization.json.Json +import org.jetbrains.exposed.v1.json.jsonb + +object BaseTemplateTable : MigrationObjectTable("base_template") { + val targetFolder = varchar("target_folder", 255).nullable() + val pages = jsonb>("pages", Json) +} diff --git a/migration-library/src/main/kotlin/com/quadient/migration/persistence/table/DisplayRuleTable.kt b/migration-library/src/main/kotlin/com/quadient/migration/persistence/table/DisplayRuleTable.kt index 8fd8e733..0b01a938 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/persistence/table/DisplayRuleTable.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/persistence/table/DisplayRuleTable.kt @@ -1,5 +1,6 @@ package com.quadient.migration.persistence.table +import com.quadient.migration.persistence.migrationmodel.BaseTemplateLocationEntity import com.quadient.migration.shared.DisplayRuleDefinition import com.quadient.migration.shared.MetadataEntry import kotlinx.serialization.json.Json @@ -11,7 +12,7 @@ object DisplayRuleTable : MigrationObjectTable("display_rule") { val internal = bool("internal") val subject = varchar("subject", 255).nullable() val targetFolder = varchar("target_folder", 255).nullable() - val baseTemplate = varchar("base_template", 255).nullable() + val baseTemplate = jsonb("base_template", Json).nullable() val variableStructureRef = varchar("variable_structure_ref", 255).nullable() val metadata = jsonb>("metadata", Json) } diff --git a/migration-library/src/main/kotlin/com/quadient/migration/persistence/table/DocumentObjectTable.kt b/migration-library/src/main/kotlin/com/quadient/migration/persistence/table/DocumentObjectTable.kt index 3bd78d19..59964a9e 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/persistence/table/DocumentObjectTable.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/persistence/table/DocumentObjectTable.kt @@ -1,5 +1,6 @@ package com.quadient.migration.persistence.table +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateLocation import com.quadient.migration.api.dto.migrationmodel.CustomFieldMap import com.quadient.migration.api.dto.migrationmodel.DisplayRuleRef import com.quadient.migration.api.dto.migrationmodel.DocumentContent @@ -7,6 +8,7 @@ import com.quadient.migration.api.dto.migrationmodel.DocumentObject import com.quadient.migration.api.dto.migrationmodel.DocumentObjectOptions import com.quadient.migration.api.dto.migrationmodel.PdfMetadata import com.quadient.migration.api.dto.migrationmodel.VariableStructureRef +import com.quadient.migration.persistence.migrationmodel.BaseTemplateLocationEntity import com.quadient.migration.persistence.migrationmodel.DocumentContentEntity import com.quadient.migration.persistence.migrationmodel.PdfMetadataEntity import com.quadient.migration.persistence.migrationmodel.DocumentObjectOptionsEntity @@ -24,7 +26,7 @@ object DocumentObjectTable : MigrationObjectTable("document_object") { val targetFolder = varchar("target_folder", 255).nullable() val displayRuleRef = varchar("display_rule_ref", 255).nullable() val variableStructureRef = varchar("variable_structure_ref", 255).nullable() - val baseTemplate = varchar("base_template", 255).nullable() + val baseTemplate = jsonb("base_template", Json).nullable() val documentObjectOptions = jsonb("options", Json).nullable() val pdfMetadata = jsonb("pdf_metadata", Json).nullable() val metadata = jsonb>("metadata", Json) @@ -45,7 +47,7 @@ object DocumentObjectTable : MigrationObjectTable("document_object") { lastUpdated = result[lastUpdated], displayRuleRef = result[displayRuleRef]?.let { DisplayRuleRef(it) }, variableStructureRef = result[variableStructureRef]?.let { VariableStructureRef(it) }, - baseTemplate = result[baseTemplate], + baseTemplate = result[baseTemplate]?.let { BaseTemplateLocation.fromDb(it) }, options = result[documentObjectOptions]?.let(DocumentObjectOptions::fromDb), pdfMetadata = result[pdfMetadata]?.let(PdfMetadata::fromDb), metadata = result[metadata], diff --git a/migration-library/src/main/kotlin/com/quadient/migration/persistence/upgrade/V16__add_base_template.kt b/migration-library/src/main/kotlin/com/quadient/migration/persistence/upgrade/V16__add_base_template.kt new file mode 100644 index 00000000..fab0745e --- /dev/null +++ b/migration-library/src/main/kotlin/com/quadient/migration/persistence/upgrade/V16__add_base_template.kt @@ -0,0 +1,60 @@ +package com.quadient.migration.persistence.upgrade + +import org.flywaydb.core.api.migration.BaseJavaMigration +import org.flywaydb.core.api.migration.Context + +class V16__add_base_template : BaseJavaMigration() { + override fun migrate(context: Context) { + val connection = context.connection + connection.createStatement().use { stmt -> + stmt.execute( + """ + CREATE TABLE IF NOT EXISTS base_template ( + id VARCHAR(255) NOT NULL, + project_name VARCHAR(50) NOT NULL, + name VARCHAR(255), + origin_locations TEXT[] NOT NULL, + custom_fields JSONB NOT NULL, + last_updated TIMESTAMP NOT NULL, + created TIMESTAMP NOT NULL, + target_folder VARCHAR(255), + pages JSONB NOT NULL, + PRIMARY KEY (id, project_name) + ) + """.trimIndent() + ) + + for (table in listOf("document_object", "display_rule")) { + stmt.execute( + """ + ALTER TABLE $table + ALTER COLUMN base_template TYPE JSONB + USING ( + CASE + WHEN base_template IS NULL THEN NULL + ELSE jsonb_build_object('type', 'BaseTemplatePath', 'path', base_template) + END + ) + """.trimIndent() + ) + } + + for (type in listOf("DocumentObject", "DisplayRule")) { + stmt.execute( + """ + UPDATE mapping + SET mappings = jsonb_set( + mappings, + '{baseTemplate}', + jsonb_build_object('type', 'BaseTemplatePath', 'path', mappings->>'baseTemplate'), + false + ) + WHERE type = '$type' + AND mappings ? 'baseTemplate' + AND jsonb_typeof(mappings->'baseTemplate') = 'string' + """.trimIndent() + ) + } + } + } +} diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/DeployPhaseUtils.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/DeployPhaseUtils.kt index 34a3a876..9e6cc7ad 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/DeployPhaseUtils.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/DeployPhaseUtils.kt @@ -3,10 +3,13 @@ package com.quadient.migration.service import com.quadient.migration.api.ProjectConfig import com.quadient.migration.api.dto.migrationmodel.Attachment import com.quadient.migration.api.dto.migrationmodel.AttachmentRef +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateRef +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateLocation import com.quadient.migration.api.dto.migrationmodel.DisplayRule import com.quadient.migration.api.dto.migrationmodel.DocumentContent import com.quadient.migration.api.dto.migrationmodel.Image import com.quadient.migration.api.dto.migrationmodel.ImageRef +import com.quadient.migration.api.dto.migrationmodel.LiteralBaseTemplatePath import com.quadient.migration.api.dto.migrationmodel.ResourceRef import com.quadient.migration.api.repository.Repository import com.quadient.migration.shared.IcmPath @@ -59,9 +62,16 @@ fun resolveTargetDir(defaultTargetFolder: IcmPath? = null, specificTargetFolder: } } -fun getBaseTemplateFullPath(config: ProjectConfig, documentObjectBaseTemplatePath: String?): IcmPath { - val baseTemplatePath = documentObjectBaseTemplatePath ?: config.baseTemplatePath - val path = baseTemplatePath.toIcmPath() +fun getBaseTemplateFullPath(config: ProjectConfig, documentObjectBaseTemplate: BaseTemplateLocation?): IcmPath { + val literalPath = when (documentObjectBaseTemplate) { + is LiteralBaseTemplatePath -> documentObjectBaseTemplate.path + is BaseTemplateRef -> error( + "Referencing base templates by id ('${documentObjectBaseTemplate.id}') is not yet supported during deployment." // TODO d.svitak - handle + ) + + null -> config.baseTemplatePath + } + val path = literalPath.toIcmPath() if (path.isAbsolute()) return path diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/RefCollector.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/RefCollector.kt index c52f0ebd..8195c0b1 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/RefCollector.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/RefCollector.kt @@ -1,6 +1,7 @@ package com.quadient.migration.service import com.quadient.migration.api.dto.migrationmodel.AttachmentRef +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateRef import com.quadient.migration.api.dto.migrationmodel.DisplayRuleRef import com.quadient.migration.api.dto.migrationmodel.DocumentObjectRef import com.quadient.migration.api.dto.migrationmodel.ImageRef @@ -12,6 +13,7 @@ import com.quadient.migration.api.dto.migrationmodel.TextStyleRef import com.quadient.migration.api.dto.migrationmodel.VariableRef import com.quadient.migration.api.dto.migrationmodel.VariableStructureRef import com.quadient.migration.api.repository.AttachmentRepository +import com.quadient.migration.api.repository.BaseTemplateRepository import com.quadient.migration.api.repository.DisplayRuleRepository import com.quadient.migration.api.repository.DocumentObjectRepository import com.quadient.migration.api.repository.ImageRepository @@ -29,6 +31,7 @@ class RefCollector( private val displayRuleRepository: DisplayRuleRepository, private val variableRepository: VariableRepository, private val variableStructureRepository: VariableStructureRepository, + private val baseTemplateRepository: BaseTemplateRepository, ) { fun collectAllRefs(obj: T, breakFn: (MigrationObject) -> Boolean = { true }): Set { return obj.collectAllRefs(breakFn) @@ -61,6 +64,7 @@ class RefCollector( is TextStyleRef -> textStyleRepository.findOrFail(ref.id) is VariableRef -> variableRepository.findOrFail(ref.id) is VariableStructureRef -> variableStructureRepository.findOrFail(ref.id) + is BaseTemplateRef -> baseTemplateRepository.findOrFail(ref.id) } private fun T.traverse( diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/ReferenceValidator.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/ReferenceValidator.kt index 97201b6d..a6bfa11b 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/ReferenceValidator.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/ReferenceValidator.kt @@ -1,6 +1,7 @@ package com.quadient.migration.service import com.quadient.migration.api.dto.migrationmodel.* +import com.quadient.migration.api.repository.BaseTemplateRepository import com.quadient.migration.api.repository.DisplayRuleRepository import com.quadient.migration.api.repository.DocumentObjectRepository import com.quadient.migration.api.repository.AttachmentRepository @@ -19,6 +20,7 @@ class ReferenceValidator( private val displayRuleRepository: DisplayRuleRepository, private val imageRepository: ImageRepository, private val attachmentRepository: AttachmentRepository, + private val baseTemplateRepository: BaseTemplateRepository, ) { /** * Validates all objects in the database. @@ -156,6 +158,17 @@ class ReferenceValidator( missingRefs.add(current) } } + + is BaseTemplateRef -> { + val baseTemplate = baseTemplateRepository.find(current.id) + + if (baseTemplate != null) { + validatedRefs.add(current) + alreadyValidRefs.add(current) + } else { + missingRefs.add(current) + } + } } } diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/StylesValidator.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/StylesValidator.kt index 5c7cc097..8e1ad7d3 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/StylesValidator.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/StylesValidator.kt @@ -4,6 +4,7 @@ import com.quadient.migration.api.dto.migrationmodel.DisplayRuleRef import com.quadient.migration.api.dto.migrationmodel.DocumentObject import com.quadient.migration.api.dto.migrationmodel.DocumentObjectRef import com.quadient.migration.api.dto.migrationmodel.AttachmentRef +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateRef import com.quadient.migration.api.dto.migrationmodel.ImageRef import com.quadient.migration.api.dto.migrationmodel.ParagraphStyle import com.quadient.migration.api.dto.migrationmodel.ParagraphStyleRef @@ -77,6 +78,7 @@ class StylesValidator( is AttachmentRef -> {} is VariableRef -> {} is VariableStructureRef -> {} + is BaseTemplateRef -> {} } } diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/DeployClient.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/DeployClient.kt index c9fb6571..784c68db 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/DeployClient.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/DeployClient.kt @@ -9,6 +9,7 @@ import com.quadient.migration.api.dto.migrationmodel.DocumentObject import com.quadient.migration.api.dto.migrationmodel.DocumentObjectRef import com.quadient.migration.api.dto.migrationmodel.Attachment import com.quadient.migration.api.dto.migrationmodel.AttachmentRef +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateRef import com.quadient.migration.api.dto.migrationmodel.DisplayRule import com.quadient.migration.api.dto.migrationmodel.Image import com.quadient.migration.api.dto.migrationmodel.ImageRef @@ -401,7 +402,7 @@ sealed class DeployClient( val dependencies = mutableListOf() this.collectRefs().forEach { ref -> when (ref) { - is DisplayRuleRef, is TextStyleRef, is ParagraphStyleRef, is VariableRef, is VariableStructureRef -> {} + is DisplayRuleRef, is TextStyleRef, is ParagraphStyleRef, is VariableRef, is VariableStructureRef, is BaseTemplateRef -> {} is ImageRef -> {} is AttachmentRef -> {} is DocumentObjectRef -> { @@ -421,7 +422,7 @@ sealed class DeployClient( this.collectRefs().forEach { ref -> when (ref) { - is DisplayRuleRef, is TextStyleRef, is ParagraphStyleRef, is VariableRef, is VariableStructureRef -> {} + is DisplayRuleRef, is TextStyleRef, is ParagraphStyleRef, is VariableRef, is VariableStructureRef, is BaseTemplateRef -> {} is ResourceRef -> resources.add(ref) is DocumentObjectRef -> { val model = documentObjectRepository.find(ref.id) diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/InteractiveDeployClient.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/InteractiveDeployClient.kt index 37bb072f..15b01c9e 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/InteractiveDeployClient.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/InteractiveDeployClient.kt @@ -5,6 +5,7 @@ import com.quadient.migration.api.repository.StatusTrackingRepository import com.quadient.migration.api.dto.migrationmodel.DocumentObject import com.quadient.migration.api.dto.migrationmodel.Attachment import com.quadient.migration.api.dto.migrationmodel.AttachmentRef +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateRef import com.quadient.migration.api.dto.migrationmodel.CustomFieldMap import com.quadient.migration.api.dto.migrationmodel.DisplayRule import com.quadient.migration.api.dto.migrationmodel.DisplayRuleRef @@ -455,7 +456,7 @@ open class InteractiveDeployClient( resources.addAll(model.getAllExternalDisplayRules()) } - is ParagraphStyleRef, is AttachmentRef, is ImageRef, is TextStyleRef, is VariableRef, is VariableStructureRef -> {} + is ParagraphStyleRef, is AttachmentRef, is ImageRef, is TextStyleRef, is VariableRef, is VariableStructureRef, is BaseTemplateRef -> {} } } diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/utility/ProgressReport.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/utility/ProgressReport.kt index fccbd810..692a2a79 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/utility/ProgressReport.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/utility/ProgressReport.kt @@ -5,6 +5,7 @@ import com.quadient.migration.api.dto.migrationmodel.DocumentObject import com.quadient.migration.api.dto.migrationmodel.Image import com.quadient.migration.api.dto.migrationmodel.Attachment import com.quadient.migration.api.dto.migrationmodel.AttachmentRef +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateRef import com.quadient.migration.api.dto.migrationmodel.DisplayRule import com.quadient.migration.api.dto.migrationmodel.DisplayRuleRef import com.quadient.migration.api.dto.migrationmodel.DocumentObjectRef @@ -247,6 +248,7 @@ class ProgressReporterImpl( is ParagraphStyleRef -> null is VariableRef -> null is VariableStructureRef -> null + is BaseTemplateRef -> null } if (resource != null) { diff --git a/migration-library/src/main/kotlin/com/quadient/migration/shared/BaseTemplatePage.kt b/migration-library/src/main/kotlin/com/quadient/migration/shared/BaseTemplatePage.kt new file mode 100644 index 00000000..11f1edad --- /dev/null +++ b/migration-library/src/main/kotlin/com/quadient/migration/shared/BaseTemplatePage.kt @@ -0,0 +1,18 @@ +package com.quadient.migration.shared + +import kotlinx.serialization.Serializable + +@Serializable +data class BaseTemplateArea( + var interactiveFlowName: String, + var position: Position? = null, + var flowToNextPage: Boolean = false, +) + +@Serializable +data class BaseTemplatePage( + var name: String? = null, + var pageWidth: Size? = null, + var pageHeight: Size? = null, + var areas: List = emptyList(), +) diff --git a/migration-library/src/main/kotlin/com/quadient/migration/shared/JrdDefinition.kt b/migration-library/src/main/kotlin/com/quadient/migration/shared/JrdDefinition.kt index 2b430b44..9dec9916 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/shared/JrdDefinition.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/shared/JrdDefinition.kt @@ -20,6 +20,7 @@ import com.quadient.migration.service.inspirebuilder.removeDataFromVariablePath import com.quadient.migration.service.inspirebuilder.toScript import com.quadient.migration.service.inspirebuilder.resolve import com.quadient.migration.service.inspirebuilder.variableToScript +import com.quadient.migration.service.getBaseTemplateFullPath import com.quadient.migration.shared.BinOp.Equals import com.quadient.migration.shared.BinOp.EqualsCaseInsensitive import com.quadient.migration.shared.BinOp.GreaterOrEqualThan @@ -91,7 +92,7 @@ data class JrdDefinition( variable } - val baseTemplate = IcmPath.from(rule.baseTemplate ?: projectConfig.baseTemplatePath) + val baseTemplate = getBaseTemplateFullPath(projectConfig, rule.baseTemplate) .toMapInteractive(projectConfig.interactiveTenant) val ruleDef = requireNotNull(rule.definition) { "Display rule '${rule.id}' cannot be deployed because it has missing definition" } diff --git a/migration-library/src/test/kotlin/com/quadient/migration/persistence/DisplayRuleRepositoryTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/persistence/DisplayRuleRepositoryTest.kt index 6988d41d..2e1339f1 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/persistence/DisplayRuleRepositoryTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/persistence/DisplayRuleRepositoryTest.kt @@ -21,7 +21,7 @@ class DisplayRuleRepositoryTest { .originLocations(listOf("test1", "test2")) .definition(DisplayRuleDefinition(group = Group(items = emptyList(), operator = GroupOp.Or, false))) .metadata("meta1") { string("val") } - .baseTemplate("icm://Interactive/StandardPackage/BaseTemplates/templ.wfd") + .baseTemplatePath("icm://Interactive/StandardPackage/BaseTemplates/templ.wfd") .internal(false) .targetFolder("targetFolder") .subject("Test Subject") @@ -46,7 +46,7 @@ class DisplayRuleRepositoryTest { .originLocations(listOf("origin1")) .definition(DisplayRuleDefinition(group = Group(items = emptyList(), operator = GroupOp.And, false))) .metadata("meta1") { string("val") } - .baseTemplate("icm://Interactive/StandardPackage/BaseTemplates/templ.wfd") + .baseTemplatePath("icm://Interactive/StandardPackage/BaseTemplates/templ.wfd") .internal(false) .targetFolder("targetFolder") .subject("Test Subject") @@ -60,7 +60,7 @@ class DisplayRuleRepositoryTest { .originLocations(listOf("origin2")) .definition(DisplayRuleDefinition(group = Group(items = emptyList(), operator = GroupOp.Or, true))) .metadata("meta1") { string("val") } - .baseTemplate("icm://Interactive/StandardPackage/BaseTemplates/templ.wfd") + .baseTemplatePath("icm://Interactive/StandardPackage/BaseTemplates/templ.wfd") .internal(false) .targetFolder("targetFolder") .subject("Test Subject") @@ -101,4 +101,4 @@ class DisplayRuleRepositoryTest { updatedBlock1.name.shouldBeEqualTo("Updated Rule 1") updatedBlock2.name.shouldBeEqualTo("Updated Rule 2") } -} \ No newline at end of file +} diff --git a/migration-library/src/test/kotlin/com/quadient/migration/persistence/DocumentObjectRepositoryTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/persistence/DocumentObjectRepositoryTest.kt index 8ffe2eb4..b85e3029 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/persistence/DocumentObjectRepositoryTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/persistence/DocumentObjectRepositoryTest.kt @@ -57,7 +57,7 @@ class DocumentObjectRepositoryTest { .internal(true) .targetFolder("acquired") .displayRuleRef("someruleref") - .baseTemplate("someBaseTemplate") + .baseTemplatePath("someBaseTemplate") .skip("reason", "placeholder") .variableStructureRef("someVarStructRef") .subject("somesubject") @@ -76,7 +76,7 @@ class DocumentObjectRepositoryTest { // given val block1 = DocumentObjectBuilder("block1", DocumentObjectType.Block) .internal(true) - .baseTemplate("baseTemplate1") + .baseTemplatePath("baseTemplate1") .customFields(mutableMapOf("field1" to "value1")) .originLocations(listOf("origin1")) .content(listOf(Paragraph("content1"))) @@ -91,7 +91,7 @@ class DocumentObjectRepositoryTest { val block2 = DocumentObjectBuilder("block2", DocumentObjectType.Block) .internal(false) - .baseTemplate("baseTemplate2") + .baseTemplatePath("baseTemplate2") .customFields(mutableMapOf("field2" to "value2")) .originLocations(listOf("origin2")) .content(listOf(Paragraph("content2"))) diff --git a/migration-library/src/test/kotlin/com/quadient/migration/persistence/MappingRepositoryTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/persistence/MappingRepositoryTest.kt index 82593aab..1acdc6e1 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/persistence/MappingRepositoryTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/persistence/MappingRepositoryTest.kt @@ -7,6 +7,7 @@ import com.quadient.migration.api.dto.migrationmodel.MappingItem import com.quadient.migration.api.dto.migrationmodel.VariableStructure import com.quadient.migration.api.dto.migrationmodel.VariableStructureRef import com.quadient.migration.api.repository.AttachmentRepository +import com.quadient.migration.api.repository.BaseTemplateRepository import com.quadient.migration.api.repository.DisplayRuleRepository import com.quadient.migration.api.repository.DocumentObjectRepository import com.quadient.migration.api.repository.ImageRepository @@ -15,6 +16,11 @@ import com.quadient.migration.api.repository.ParagraphStyleRepository import com.quadient.migration.api.repository.TextStyleRepository import com.quadient.migration.api.repository.VariableRepository import com.quadient.migration.api.repository.VariableStructureRepository +import com.quadient.migration.api.dto.migrationmodel.LiteralBaseTemplatePath +import com.quadient.migration.shared.BaseTemplateArea +import com.quadient.migration.shared.BaseTemplatePage +import com.quadient.migration.shared.Position +import com.quadient.migration.shared.Size import com.quadient.migration.shared.VariablePathData import com.quadient.migration.tools.aProjectConfig import com.quadient.migration.tools.aVariable @@ -38,6 +44,7 @@ class MappingRepositoryTest { val variableRepository = mockk() val variableStructureRepository = mockk() val displayRuleRepository = mockk() + val baseTemplateRepository = mockk() private val repo = MappingRepository( ProjectName(projectConfig.name), @@ -49,6 +56,7 @@ class MappingRepositoryTest { variableRepository, variableStructureRepository, displayRuleRepository, + baseTemplateRepository, ) @Test @@ -118,7 +126,7 @@ class MappingRepositoryTest { name = "new name", targetFolder = "/some/folder", targetId = "target-123", - baseTemplate = "template-base", + baseTemplate = LiteralBaseTemplatePath("template-base"), variableStructureRef = "ref", internal = true, ) @@ -133,11 +141,40 @@ class MappingRepositoryTest { it.name == "new name" && it.targetFolder == "/some/folder" && it.targetId?.id == "target-123" && - it.baseTemplate == "template-base" && + it.baseTemplate == LiteralBaseTemplatePath("template-base") && it.internal && it.variableStructureRef == VariableStructureRef("ref") } ) } } + + @Test + fun `apply base template mapping does nothing when base template does not exist`() { + every { baseTemplateRepository.find("baseTemplateId") } returns null + every { baseTemplateRepository.upsert(any()) } just runs + repo.upsert( + "baseTemplateId", MappingItem.BaseTemplate( + name = "New Base Template", + targetFolder = "icm://Interactive/Tenant/BaseTemplates/some.wfd", + pages = listOf( + BaseTemplatePage( + name = "Page1", + pageWidth = Size.ofMillimeters(210.0), + pageHeight = Size.ofMillimeters(297.0), + areas = listOf( + BaseTemplateArea( + interactiveFlowName = "Flow1", + position = Position(Size.ofMillimeters(1.0), Size.ofMillimeters(2.0), Size.ofMillimeters(3.0), Size.ofMillimeters(4.0)), + ) + ), + ) + ), + ) + ) + + repo.applyBaseTemplateMapping("baseTemplateId") + + verify(exactly = 0) { baseTemplateRepository.upsert(any()) } + } } \ No newline at end of file diff --git a/migration-library/src/test/kotlin/com/quadient/migration/persistence/migrationmodel/MappingEntityTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/persistence/migrationmodel/MappingEntityTest.kt index 6c37f950..5f4dc414 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/persistence/migrationmodel/MappingEntityTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/persistence/migrationmodel/MappingEntityTest.kt @@ -9,6 +9,7 @@ import com.quadient.migration.api.dto.migrationmodel.builder.ParagraphStyleBuild import com.quadient.migration.api.dto.migrationmodel.builder.TableBuilder import com.quadient.migration.api.dto.migrationmodel.builder.TextStyleBuilder import com.quadient.migration.api.dto.migrationmodel.builder.documentcontent.AreaBuilder +import com.quadient.migration.api.dto.migrationmodel.LiteralBaseTemplatePath import com.quadient.migration.shared.Alignment import com.quadient.migration.shared.Color import com.quadient.migration.shared.DataType @@ -69,7 +70,7 @@ class MappingEntityTest { val mapping = MappingItemEntity.DocumentObject( name = "new name", internal = false, - baseTemplate = "new base", + baseTemplate = LiteralBaseTemplatePathEntity("new base"), targetFolder = "new folder", type = Block, variableStructureRef = "new structure", @@ -89,7 +90,7 @@ class MappingEntityTest { assertEquals(result.name, "new name") assertEquals(result.internal, false) - assertEquals(result.baseTemplate, "new base") + assertEquals(result.baseTemplate, LiteralBaseTemplatePath("new base")) assertEquals(result.targetFolder, "new folder") assertEquals(result.type, Block) assertEquals(result.variableStructureRef?.id, "new structure") diff --git a/migration-library/src/test/kotlin/com/quadient/migration/persistence/upgrade/V16MigrationTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/persistence/upgrade/V16MigrationTest.kt new file mode 100644 index 00000000..3cfdc913 --- /dev/null +++ b/migration-library/src/test/kotlin/com/quadient/migration/persistence/upgrade/V16MigrationTest.kt @@ -0,0 +1,167 @@ +package com.quadient.migration.persistence.upgrade + +import com.quadient.migration.Postgres.Companion.POSTGRES_CONTAINER +import kotlinx.serialization.json.Json +import org.flywaydb.core.Flyway +import org.jetbrains.exposed.v1.jdbc.Database +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test +import org.testcontainers.postgresql.PostgreSQLContainer +import java.sql.Connection +import java.sql.DriverManager + +/** + * Integration test for V16 migration that introduces the base_template table and converts the + * document_object/display_rule base_template columns (and their mapping counterparts) from a plain + * path string into a discriminated jsonb reference (path or id). + */ +class V16MigrationTest { + + @Test + fun `V16 migration converts base template references to jsonb`() { + PostgreSQLContainer(POSTGRES_CONTAINER).use { postgres -> + postgres.start() + + Database.connect( + url = postgres.jdbcUrl, + driver = "org.postgresql.Driver", + user = postgres.username, + password = postgres.password + ) + + Flyway.configure() + .dataSource(postgres.jdbcUrl, postgres.username, postgres.password) + .locations("classpath:com/quadient/migration/persistence/upgrade") + .target("15") + .load() + .migrate() + + connection(postgres).use { conn -> + insertDocumentObject(conn, "doc-with-base-template", "templates/base.xml") + insertDocumentObject(conn, "doc-without-base-template", null) + insertDisplayRule(conn, "rule-with-base-template", "templates/base-rule.xml") + insertDisplayRule(conn, "rule-without-base-template", null) + + insertMapping( + conn, "map-doc", "DocumentObject", + """{"name":"mapped","baseTemplate":"templates/mapped.xml"}""" + ) + insertMapping( + conn, "map-doc-null", "DocumentObject", + """{"name":"mapped","baseTemplate":null}""" + ) + insertMapping( + conn, "map-rule", "DisplayRule", + """{"name":"mapped","baseTemplate":"templates/mapped-rule.xml"}""" + ) + insertMapping( + conn, "map-area", "Area", + """{"name":"mapped","areas":{}}""" + ) + } + + Flyway.configure() + .dataSource(postgres.jdbcUrl, postgres.username, postgres.password) + .locations("classpath:com/quadient/migration/persistence/upgrade") + .target("16") + .load() + .migrate() + + connection(postgres).use { conn -> + assertJsonEquals( + """{"type":"BaseTemplatePath","path":"templates/base.xml"}""", + queryColumn(conn, "document_object", "base_template", "doc-with-base-template")!! + ) + assertNull(queryColumn(conn, "document_object", "base_template", "doc-without-base-template")) + + assertJsonEquals( + """{"type":"BaseTemplatePath","path":"templates/base-rule.xml"}""", + queryColumn(conn, "display_rule", "base_template", "rule-with-base-template")!! + ) + assertNull(queryColumn(conn, "display_rule", "base_template", "rule-without-base-template")) + + assertJsonEquals( + """{"name":"mapped","baseTemplate":{"type":"BaseTemplatePath","path":"templates/mapped.xml"}}""", + queryMapping(conn, "map-doc", "DocumentObject") + ) + assertJsonEquals( + """{"name":"mapped","baseTemplate":null}""", + queryMapping(conn, "map-doc-null", "DocumentObject") + ) + assertJsonEquals( + """{"name":"mapped","baseTemplate":{"type":"BaseTemplatePath","path":"templates/mapped-rule.xml"}}""", + queryMapping(conn, "map-rule", "DisplayRule") + ) + assertJsonEquals( + """{"name":"mapped","areas":{}}""", + queryMapping(conn, "map-area", "Area") + ) + } + } + } + + private fun insertDocumentObject(conn: Connection, id: String, baseTemplate: String?) { + conn.prepareStatement( + """ + INSERT INTO document_object (id, project_name, name, origin_locations, custom_fields, type, internal, metadata, skip, base_template, last_updated, created) + VALUES (?, 'test', ?, '{}', '{}', 'Block', true, '[]', '{"skipped":false,"reason":null,"placeholder":null}'::jsonb, ?, NOW(), NOW()) + """.trimIndent() + ).apply { + setString(1, id) + setString(2, id) + setString(3, baseTemplate) + }.executeUpdate() + } + + private fun insertDisplayRule(conn: Connection, id: String, baseTemplate: String?) { + conn.prepareStatement( + """ + INSERT INTO display_rule (id, project_name, name, origin_locations, custom_fields, internal, metadata, base_template, last_updated, created) + VALUES (?, 'test', ?, '{}', '{}', true, '{}', ?, NOW(), NOW()) + """.trimIndent() + ).apply { + setString(1, id) + setString(2, id) + setString(3, baseTemplate) + }.executeUpdate() + } + + private fun insertMapping(conn: Connection, id: String, type: String, mappings: String) { + conn.prepareStatement( + """ + INSERT INTO mapping (id, type, project_name, mappings) + VALUES (?, ?, 'test', ?::jsonb) + """.trimIndent() + ).apply { + setString(1, id) + setString(2, type) + setString(3, mappings) + }.executeUpdate() + } + + private fun queryColumn(conn: Connection, table: String, column: String, id: String): String? { + val rs = conn.prepareStatement("SELECT $column::text AS value FROM $table WHERE id = ?") + .apply { setString(1, id) } + .executeQuery() + assert(rs.next()) { "Expected row '$id' in '$table' to exist" } + return rs.getString("value") + } + + private fun queryMapping(conn: Connection, id: String, type: String): String { + val rs = conn.prepareStatement("SELECT mappings::text FROM mapping WHERE id = ? AND type = ?") + .apply { + setString(1, id) + setString(2, type) + } + .executeQuery() + assert(rs.next()) { "Expected mapping row '$id' of type '$type' to exist" } + return rs.getString("mappings") + } + + private fun connection(postgres: PostgreSQLContainer): Connection = + DriverManager.getConnection(postgres.jdbcUrl, postgres.username, postgres.password) + + private fun assertJsonEquals(expected: String, actual: String) = + assertEquals(Json.parseToJsonElement(expected), Json.parseToJsonElement(actual)) +} diff --git a/migration-library/src/test/kotlin/com/quadient/migration/service/DeployPhaseUtilsTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/service/DeployPhaseUtilsTest.kt index cbd4f99a..f949e8ca 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/service/DeployPhaseUtilsTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/service/DeployPhaseUtilsTest.kt @@ -2,6 +2,7 @@ package com.quadient.migration.service import com.quadient.migration.service.inspirebuilder.appendExtensionIfMissing import com.quadient.migration.service.inspirebuilder.extractExtensionFromPath +import com.quadient.migration.api.dto.migrationmodel.LiteralBaseTemplatePath import com.quadient.migration.tools.aProjectConfig import com.quadient.migration.tools.shouldBeEqualTo import org.junit.jupiter.api.Test @@ -21,14 +22,14 @@ class DeployPhaseUtilsTest { @Test fun `specific base template path is preferred over the project config one`() { val baseTemplatePath = "icm://Interactive/Vital/BaseTemplates/MyBaseTemplate.wfd" - val result = getBaseTemplateFullPath(projectConfig, baseTemplatePath).toString() + val result = getBaseTemplateFullPath(projectConfig, LiteralBaseTemplatePath(baseTemplatePath)).toString() result.shouldBeEqualTo(baseTemplatePath) } @Test fun `path not starting with icm is handled as relative`() { - val result = getBaseTemplateFullPath(projectConfig, "/projectA/AddressBT.wfd").toString() + val result = getBaseTemplateFullPath(projectConfig, LiteralBaseTemplatePath("/projectA/AddressBT.wfd")).toString() result.shouldBeEqualTo("icm://Interactive/${projectConfig.interactiveTenant}/BaseTemplates/projectA/AddressBT.wfd") } diff --git a/migration-library/src/test/kotlin/com/quadient/migration/service/ReferenceValidatorTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/service/ReferenceValidatorTest.kt index f05df432..7003c2b3 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/service/ReferenceValidatorTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/service/ReferenceValidatorTest.kt @@ -6,6 +6,7 @@ import com.quadient.migration.api.dto.migrationmodel.ParagraphStyleRef import com.quadient.migration.api.dto.migrationmodel.TextStyleRef import com.quadient.migration.shared.DocumentObjectType import com.quadient.migration.tools.aBlockDto +import com.quadient.migration.tools.aBaseTemplateRepository import com.quadient.migration.tools.aDisplayRuleRepository import com.quadient.migration.tools.aDocumentObjectRepository import com.quadient.migration.tools.aAttachmentRepository @@ -32,6 +33,7 @@ class ReferenceValidatorTest { val displayRuleRepository = aDisplayRuleRepository() val imageRepository = aImageRepository() val attachmentRepository = aAttachmentRepository() + val baseTemplateRepository = aBaseTemplateRepository() val docRepo = aDocumentObjectRepository() val paraStyleRepo = aParaStyleRepository() @@ -46,6 +48,7 @@ class ReferenceValidatorTest { displayRuleRepository, imageRepository, attachmentRepository, + baseTemplateRepository, ) @Test diff --git a/migration-library/src/test/kotlin/com/quadient/migration/service/deploy/EvolveDeployClientTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/service/deploy/EvolveDeployClientTest.kt index 194ee278..85809271 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/service/deploy/EvolveDeployClientTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/service/deploy/EvolveDeployClientTest.kt @@ -332,7 +332,7 @@ class EvolveDeployClientTest { @Test fun `uploadDocumentObject for Block uses custom baseTemplate when set on document object`() { val customBaseTemplate = "icm://Interactive/tenant/BaseTemplates/custom.wfd" - val block = DocumentObjectBuilder("B1", DocumentObjectType.Block).baseTemplate(customBaseTemplate).build() + val block = DocumentObjectBuilder("B1", DocumentObjectType.Block).baseTemplatePath(customBaseTemplate).build() val targetPath = "icm://Interactive/tenant/B1.jld".toIcmPath() val draftResult = DraftJsonIpsResult( draft = CreateDraftResult(guid = draftGuid, url = "http://example.com"), diff --git a/migration-library/src/test/kotlin/com/quadient/migration/tools/TestObjectBuilders.kt b/migration-library/src/test/kotlin/com/quadient/migration/tools/TestObjectBuilders.kt index 360122d7..bba9108f 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/tools/TestObjectBuilders.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/tools/TestObjectBuilders.kt @@ -29,6 +29,7 @@ import com.quadient.migration.api.dto.migrationmodel.TextStyleRef import com.quadient.migration.api.dto.migrationmodel.Variable import com.quadient.migration.api.dto.migrationmodel.VariableStructureRef import com.quadient.migration.api.repository.AttachmentRepository +import com.quadient.migration.api.repository.BaseTemplateRepository import com.quadient.migration.api.repository.DisplayRuleRepository import com.quadient.migration.api.repository.DocumentObjectRepository import com.quadient.migration.api.repository.ImageRepository @@ -52,6 +53,7 @@ import com.quadient.migration.shared.DocumentObjectType import com.quadient.migration.shared.IcmPath import com.quadient.migration.shared.ImageType import com.quadient.migration.shared.LineSpacing +import com.quadient.migration.api.dto.migrationmodel.LiteralBaseTemplatePath import com.quadient.migration.shared.ParagraphPdfTaggingRule import com.quadient.migration.shared.Size import com.quadient.migration.shared.SkipOptions @@ -92,7 +94,7 @@ fun aBlockDto( metadata = emptyList(), skip = SkipOptions(false, null, null), subject = null, - baseTemplate = baseTemplate, + baseTemplate = baseTemplate?.let { LiteralBaseTemplatePath(it) }, variableStructureRef = variableStructureRef, ) } @@ -147,7 +149,7 @@ fun aBlockModel( created = Clock.System.now(), lastUpdated = Clock.System.now(), displayRuleRef = null, - baseTemplate = baseTemplate, + baseTemplate = baseTemplate?.let { LiteralBaseTemplatePath(it) }, options = options, metadata = emptyList(), skip = SkipOptions(false, null, null), @@ -528,3 +530,4 @@ fun aTextStyleRepository() = TextStyleRepository(ProjectName(aProjectConfig().na fun aDisplayRuleRepository() = DisplayRuleRepository(ProjectName(aProjectConfig().name), statusRepo) fun aImageRepository() = ImageRepository(ProjectName(aProjectConfig().name), statusRepo) fun aAttachmentRepository() = AttachmentRepository(ProjectName(aProjectConfig().name), statusRepo) +fun aBaseTemplateRepository() = BaseTemplateRepository(ProjectName(aProjectConfig().name)) diff --git a/migration-library/src/test/kotlin/com/quadient/migration/tools/model/TestModelObjectBuilders.kt b/migration-library/src/test/kotlin/com/quadient/migration/tools/model/TestModelObjectBuilders.kt index a5be8190..47c5dcbd 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/tools/model/TestModelObjectBuilders.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/tools/model/TestModelObjectBuilders.kt @@ -37,6 +37,7 @@ import com.quadient.migration.shared.GroupOp import com.quadient.migration.shared.IcmPath import com.quadient.migration.shared.ImageOptions import com.quadient.migration.shared.ImageType +import com.quadient.migration.api.dto.migrationmodel.LiteralBaseTemplatePath import com.quadient.migration.shared.LiteralOrFunctionCall import com.quadient.migration.shared.MetadataEntry import com.quadient.migration.shared.Position @@ -71,7 +72,7 @@ fun aDocObj( created = null, lastUpdated = null, displayRuleRef = displayRuleRef?.let { DisplayRuleRef(it) }, - baseTemplate = baseTemplate, + baseTemplate = baseTemplate?.let { LiteralBaseTemplatePath(it) }, variableStructureRef = VariableStructureRef?.let { VariableStructureRef(it) }, options = options, metadata = metadata, @@ -108,7 +109,7 @@ fun aBlock( created = created, lastUpdated = lastUpdated, displayRuleRef = displayRuleRef, - baseTemplate = baseTemplate, + baseTemplate = baseTemplate?.let { LiteralBaseTemplatePath(it) }, options = null, metadata = metadata, skip = skip, @@ -140,7 +141,7 @@ fun aTemplate( customFields = CustomFieldMap(), created = null, lastUpdated = null, - baseTemplate = baseTemplate, + baseTemplate = baseTemplate?.let { LiteralBaseTemplatePath(it) }, options = null, metadata = emptyList(), skip = SkipOptions(false, null, null),