diff --git a/migration-examples/layout/index.html b/migration-examples/layout/index.html index 3af7fc91..4708347b 100644 --- a/migration-examples/layout/index.html +++ b/migration-examples/layout/index.html @@ -421,6 +421,7 @@
+
@@ -449,12 +450,13 @@ * @typedef {Object} LayoutData * @property {PageEntry[]} pages * @property {TemplateEntry[]} templates + * @property {({pageIndex: number, areaIndex: number}|null)[]} [rowMap] */ /** * @typedef {Object} LayoutAnalysis * @property {Similarity} similarity - * @property {ProximityGroup[][]} pageProximityGroups + * @property {BaseTemplateAreaDraft[][]} pageBaseTemplateAreaDrafts * @property {number} proximityGap */ @@ -512,9 +514,12 @@ */ /** - * @typedef {Object} ProximityGroup + * A candidate base template area: a cluster of nearby areas on a page (grouped by proximity) + * that would become a single area/interactiveFlowName in a base template. + * @typedef {Object} BaseTemplateAreaDraft * @property {number[]} areaIndices * @property {Position} position + * @property {string} name */ /** @@ -669,12 +674,11 @@ /** * Computes containment for each area: the index of its smallest enclosing area, or null. - * Mirrors the findContainment algorithm in LayoutExport.groovy. * @param {{x: number, y: number, w: number, h: number}[]} areas * @returns {(number|null)[]} */ function computeContainment(areas) { - const TOL = 0.7; + const tolerance = 0.7; const containment = /** @type {(number|null)[]} */ (new Array(areas.length).fill(null)); for (let i = 0; i < areas.length; i++) { const inner = areas[i]; @@ -684,9 +688,9 @@ const outer = areas[j]; const outerSize = outer.w * outer.h; if (outerSize <= innerSize) continue; - if (outer.x - TOL <= inner.x && outer.y - TOL <= inner.y - && (inner.x + inner.w) <= (outer.x + outer.w) + TOL - && (inner.y + inner.h) <= (outer.y + outer.h) + TOL) { + if (outer.x - tolerance <= inner.x && outer.y - tolerance <= inner.y + && (inner.x + inner.w) <= (outer.x + outer.w) + tolerance + && (inner.y + inner.h) <= (outer.y + outer.h) + tolerance) { const cur = containment[i]; const curSize = cur != null ? areas[cur].w * areas[cur].h : Infinity; if (curSize > outerSize) containment[i] = j; @@ -697,14 +701,14 @@ } /** - * Parses the areas CSV produced by AreasExport.groovy into a LayoutData object. + * Parses the layout CSV produced by LayoutExport.groovy into a LayoutData object. * Templates and templatePageIndex are derived from row order; containedIn is computed from geometry. * @param {string} text * @returns {LayoutData} */ 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 +719,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 +746,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 +764,37 @@ tmplPageCounter.set(meta.templateId, n + 1); } - // Build PageEntry objects /** @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 +808,45 @@ })); } - 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 = "layout.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]); + const typeIdx = currentCsvHeaders.map(h => normalizeHeaderName(h)).indexOf("type"); + currentCsvRows = lines.slice(1).map(parseCsvLine) + .filter(row => typeIdx < 0 || (row[typeIdx] ?? "").trim().toLowerCase() !== "base"); + } else { + currentCsvHeaders = null; + currentCsvRows = null; + } + currentFileName = file.name || "layout.csv"; + saveCsvBtn.disabled = !currentCsvHeaders; + + const layoutData = parseCsvToLayoutData(text); + currentRowMap = layoutData.rowMap ?? []; + render(layoutData); } catch (err) { statusEl.textContent = "Error: " + /** @type {Error} */ (err).message; } @@ -814,6 +857,211 @@ 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"; +} + +/** + * Derives a human-friendly interactive flow name from an area's content preview, + * e.g. "docRef: address" -> "address". Returns null if no usable name can be derived. + * @param {string} contentPreview + * @returns {string|null} + */ +function parseFlowNameFromPreview(contentPreview) { + if (!contentPreview) return null; + const first = contentPreview.split(";")[0]?.trim(); + if (!first) return null; + const colonIdx = first.indexOf(": "); + const value = colonIdx === -1 ? first : first.slice(colonIdx + 2).trim(); + return value || null; +} + +/** + * Builds the "Base" CSV rows describing candidate base templates/pages/areas derived from the + * current template/page grouping, plus per-page interactiveFlowName/targetId overrides for the + * "Standard" rows that reference them. + * + * @param {LayoutData} data + * @param {ViewModel} viewModel + * @param {LayoutAnalysis} analysis + * @returns {{baseRows: string[][], overridesByPageIndex: Map}} + */ +function computeBaseTemplateData(data, viewModel, analysis) { + /** @type {string[][]} */ + const baseRows = []; + /** @type {Map} */ + const overridesByPageIndex = new Map(); + + viewModel.templateGroups.forEach((templateGroup, templateGroupIndex) => { + const baseTemplateId = `bt-${templateGroupIndex + 1}`; + const baseTemplateName = `${templateGroup.templateNames.join(" / ")}BaseTemplate`; + + templateGroup.pageGroups.forEach((pageGroup, pageGroupIndex) => { + const basePageId = `page-${pageGroupIndex + 1}`; + const basePageName = `Page ${pageGroupIndex + 1}`; + + const representativePageIndex = pageGroup.pageIndices[0]; + const representativePage = data.pages[representativePageIndex]; + const representativeDrafts = analysis.pageBaseTemplateAreaDrafts[representativePageIndex] ?? []; + const pageWidthMm = representativePage.pageSize ? `${representativePage.pageSize.w}mm` : ""; + const pageHeightMm = representativePage.pageSize ? `${representativePage.pageSize.h}mm` : ""; + + /** @type {string[]} */ + const flowNamesByAreaGroupIndex = []; + const usedFlowNames = new Set(); + + representativeDrafts.forEach((draft, areaGroupIndex) => { + const flowToNextPage = draft.areaIndices.some(ai => representativePage.areas[ai]?.flowToNextPage); + let flowName = draft.name; + if (usedFlowNames.has(flowName)) { + flowName = `${flowName} (Area ${areaGroupIndex + 1})`; + } + usedFlowNames.add(flowName); + flowNamesByAreaGroupIndex.push(flowName); + + baseRows.push([ + baseTemplateId, baseTemplateName, basePageId, basePageName, + pageWidthMm, pageHeightMm, + flowName, String(flowToNextPage), + `${draft.position.x}mm`, `${draft.position.y}mm`, `${draft.position.w}mm`, `${draft.position.h}mm`, + "Base", "", "", + ]); + }); + + pageGroup.pageIndices.forEach(pageIndex => { + const page = data.pages[pageIndex]; + const ownDrafts = pageIndex === representativePageIndex + ? representativeDrafts + : (analysis.pageBaseTemplateAreaDrafts[pageIndex] ?? []); + + /** @type {(string|null)[]} */ + const flowNames = new Array(page.areas.length).fill(null); + ownDrafts.forEach((draft, ordinal) => { + if (ordinal >= flowNamesByAreaGroupIndex.length) return; + const flowName = flowNamesByAreaGroupIndex[ordinal]; + draft.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(); }); + document.body.addEventListener("dragover", e => e.preventDefault()); document.body.addEventListener("drop", e => { e.preventDefault(); @@ -933,10 +1181,10 @@ /** * @param {PageEntry} page - * @param {ProximityGroup[]} proximityGroups + * @param {BaseTemplateAreaDraft[]} baseTemplateAreaDrafts * @returns {SVGElement|null} */ -function buildPageSvg(page, proximityGroups) { +function buildPageSvg(page, baseTemplateAreaDrafts) { const areas = page.areas; if (!areas.length) return null; @@ -945,39 +1193,37 @@ const scale = (settings.canvasW - 2 * PAD) / Math.max(maxX2, 210); const svgH = Math.ceil(maxY2 * scale + 2 * PAD); const svg = svgEl("svg", {width: settings.canvasW, height: svgH, style: "background:#fff;cursor:default"}); - svg.appendChild(Object.assign(document.createElementNS("http://www.w3.org/2000/svg", "title"), { - textContent: pageCardTooltip(page) - })); - const proximityGroupByArea = new Map(); - proximityGroups.forEach((group, groupIndex) => { - group.areaIndices.forEach(areaIndex => proximityGroupByArea.set(areaIndex, groupIndex)); + svg.appendChild(svgTitle(pageCardTooltip(page))); + const draftIndexByArea = new Map(); + baseTemplateAreaDrafts.forEach((draft, draftIndex) => { + draft.areaIndices.forEach(areaIndex => draftIndexByArea.set(areaIndex, draftIndex)); }); - proximityGroups.forEach((group, i) => { - const p = group.position; - const tip = `G${i + 1}\nw ${p.w}mm · h ${p.h}mm · x ${p.x}mm · y ${p.y}mm`; + baseTemplateAreaDrafts.forEach(draft => { + const p = draft.position; + const tip = `${draft.name}\nw ${p.w}mm · h ${p.h}mm · x ${p.x}mm · y ${p.y}mm`; + const gx = svgPos(p.x, scale), gy = svgPos(p.y, scale), + gw = svgDim(p.w, scale), gh = svgDim(p.h, scale); const bg = svgEl("rect", { - x: svgPos(p.x, scale), y: svgPos(p.y, scale), - width: svgDim(p.w, scale), height: svgDim(p.h, scale), + x: gx, y: gy, width: gw, height: gh, fill: "transparent", stroke: "none", "pointer-events": "all" }); - bg.appendChild(Object.assign(document.createElementNS("http://www.w3.org/2000/svg", "title"), {textContent: tip})); + bg.appendChild(svgTitle(tip)); svg.appendChild(bg); const border = svgEl("rect", { - x: svgPos(p.x, scale), y: svgPos(p.y, scale), - width: svgDim(p.w, scale), height: svgDim(p.h, scale), + x: gx, y: gy, width: gw, height: gh, fill: "none", stroke: "#e00", "stroke-width": 1.5, "stroke-dasharray": "5,3", "pointer-events": "none" }); svg.appendChild(border); const label = svgEl("text", { - x: svgPos(p.x, scale) + 2, y: svgPos(p.y, scale) - 2, + x: gx + 2, y: gy - 2, "font-size": 7, fill: "#e00", "font-weight": "bold", "pointer-events": "none" }); - label.textContent = `G${i + 1}`; + label.textContent = draft.name.length > 24 ? draft.name.slice(0, 24) : draft.name; svg.appendChild(label); }); @@ -992,12 +1238,12 @@ .filter(({o, oi}) => oi !== i && o.x < a.x + a.w && o.x + o.w > a.x && o.y < a.y + a.h && o.y + o.h > a.y) - .sort((a, b) => (a.o.w * a.o.h) - (b.o.w * b.o.h)); + .sort((m, n) => (m.o.w * m.o.h) - (n.o.w * n.o.h)); - const proximityGroupIndex = proximityGroupByArea.get(i); - const pg = proximityGroupIndex != null ? proximityGroups[proximityGroupIndex] : null; - const pgLine = pg ? `G${proximityGroupIndex + 1}\nw ${pg.position.w}mm · h ${pg.position.h}mm · x ${pg.position.x}mm · y ${pg.position.y}mm\n\n` : ""; - const tip = pgLine + [areaTip(a, i), ...overlapping.map(({o, oi}) => areaTip(o, oi))].join("\n\n"); + const draftIndex = draftIndexByArea.get(i); + const draft = draftIndex != null ? baseTemplateAreaDrafts[draftIndex] : null; + const draftLine = draft ? `${draft.name}\nw ${draft.position.w}mm · h ${draft.position.h}mm · x ${draft.position.x}mm · y ${draft.position.y}mm\n\n` : ""; + const tip = draftLine + [areaTip(a, i), ...overlapping.map(({o, oi}) => areaTip(o, oi))].join("\n\n"); const rect = svgEl("rect", { x: rx, y: ry, width: rw, height: rh, @@ -1005,7 +1251,7 @@ "stroke-width": isRoot ? "1.5" : "1", "stroke-dasharray": isRoot ? "none" : "3,2" }); - rect.appendChild(Object.assign(document.createElementNS("http://www.w3.org/2000/svg", "title"), {textContent: tip})); + rect.appendChild(svgTitle(tip)); svg.appendChild(rect); if (rw > 12 && rh > 8) { @@ -1268,7 +1514,7 @@ }); wrap.appendChild(title); - const svg = buildPageSvg(currentData.pages[pageIndex], currentAnalysis.pageProximityGroups[pageIndex] ?? []); + const svg = buildPageSvg(currentData.pages[pageIndex], currentAnalysis.pageBaseTemplateAreaDrafts[pageIndex] ?? []); if (svg) wrap.appendChild(svg); return wrap; } @@ -1541,12 +1787,12 @@ const similarity = sameData ? previousAnalysis.similarity : buildSimilarity(data); - const pageProximityGroups = sameData && previousAnalysis.proximityGap === activeSettings.proximityGap - ? previousAnalysis.pageProximityGroups - : buildPageProximityGroups(data.pages, activeSettings.proximityGap); + const pageBaseTemplateAreaDrafts = sameData && previousAnalysis.proximityGap === activeSettings.proximityGap + ? previousAnalysis.pageBaseTemplateAreaDrafts + : buildBaseTemplateAreaDrafts(data.pages, activeSettings.proximityGap); return { similarity, - pageProximityGroups, + pageBaseTemplateAreaDrafts, proximityGap: activeSettings.proximityGap, }; } @@ -1634,9 +1880,9 @@ /** * @param {PageEntry[]} pages * @param {number} groupingGap - * @returns {ProximityGroup[][]} + * @returns {BaseTemplateAreaDraft[][]} */ -function buildPageProximityGroups(pages, groupingGap) { +function buildBaseTemplateAreaDrafts(pages, groupingGap) { return pages.map(page => groupByProximity(page.areas, groupingGap)); } @@ -1727,10 +1973,10 @@ /** * Vertical sweep grouping: areas whose Y ranges overlap or are within - * the proximity gap of each other are placed in the same group. + * the proximity gap of each other are placed in the same base template area draft. * @param {AreaEntry[]} areas * @param {number} groupingGap - * @returns {ProximityGroup[]} + * @returns {BaseTemplateAreaDraft[]} */ function groupByProximity(areas, groupingGap) { if (!areas.length) return []; @@ -1752,12 +1998,13 @@ }); groupedIndices.push(current); - return groupedIndices.map(areaIndices => { + return groupedIndices.map((areaIndices, draftIndex) => { const subset = areaIndices.map(index => areas[index]); const x = Math.min(...subset.map(area => area.x)); const y = Math.min(...subset.map(area => area.y)); const x2 = Math.max(...subset.map(area => area.x + area.w)); const y2 = Math.max(...subset.map(area => area.y + area.h)); + const previewSource = areaIndices.map(idx => areas[idx].contentPreview).find(Boolean); return { areaIndices, position: { @@ -1766,6 +2013,7 @@ w: roundTo(x2 - x, 2), h: roundTo(y2 - y, 2), }, + name: parseFlowNameFromPreview(previewSource) || `Area ${draftIndex + 1}`, }; }); } @@ -1955,6 +2203,16 @@ return el; } +/** + * @param {string} text + * @returns {SVGTitleElement} + */ +function svgTitle(text) { + const title = /** @type {SVGTitleElement} */ (document.createElementNS("http://www.w3.org/2000/svg", "title")); + title.textContent = text; + return title; +} + /** * @param {number} mm * @param {number} scale @@ -1979,8 +2237,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/AreasImport.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasImport.groovy deleted file mode 100644 index 0da1cbd4..00000000 --- a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasImport.groovy +++ /dev/null @@ -1,79 +0,0 @@ -//! --- -//! displayName: Import Areas -//! category: Mapping -//! description: Import areas with modified interactive flow names to their respective pages and templates -//! target: gradle -//! --- -package com.quadient.migration.example.common.mapping - -import com.quadient.migration.api.Migration -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 groovy.transform.Field -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -import java.nio.file.Path - -import static com.quadient.migration.example.common.util.InitMigration.initMigration - -@Field static Logger log = LoggerFactory.getLogger(this.class.name) - -def migration = initMigration(this.binding) -def areasFile = Mapping.csvPath(binding, migration.projectConfig.name, "areas") - -run(migration, areasFile) - -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() - DocumentObject currentDocumentObject = null - MappingItem.Area mapping = null - int areaIndex = 0 - for (line in fileLines) { - def values = Csv.getCells(line, columnNames) - - 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 - } - - def documentObjectModel = migration.documentObjectRepository.find(documentObjectId) - if (!documentObjectModel) { - throw new IllegalStateException("Document object '${documentObjectId}' not found.") - } - - mapping = migration.mappingRepository.getAreaMapping(documentObjectId) - currentDocumentObject = documentObjectModel - areaIndex = 0 - } - - def interactiveFlowName = Csv.deserialize(values.get("interactiveFlowName"), String.class) - mapping.areas[areaIndex] = interactiveFlowName - - def flowToNextPage = Csv.deserialize(values.get("flowToNextPage"), Boolean.class) - mapping.flowToNextPage[areaIndex] = flowToNextPage ?: false - - areaIndex++ - } - - if (currentDocumentObject != null) { - mappings[currentDocumentObject.id] = mapping - } - - 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()) - } - migration.mappingRepository.applyAllAreaMappings() -} - 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/AreasExport.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/LayoutExport.groovy similarity index 71% rename from migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasExport.groovy rename to migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/LayoutExport.groovy index 00063705..801e4743 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/LayoutExport.groovy @@ -1,5 +1,5 @@ //! --- -//! displayName: Export Areas +//! displayName: Export Layout //! category: Mapping //! description: Export areas assigned to the appropriate pages and templates. Allows user to modify interactive flow name //! target: gradle @@ -12,6 +12,8 @@ import com.quadient.migration.api.dto.migrationmodel.builder.DocumentObjectFilte import com.quadient.migration.api.repository.DocumentObjectRepository 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.DocumentObjectType import groovy.transform.Field @@ -21,7 +23,7 @@ import static com.quadient.migration.example.common.util.InitMigration.initMigra @Field Migration migration = initMigration(this.binding) -def areasFile = Mapping.csvPath(binding, migration.projectConfig.name, "areas") +def areasFile = Mapping.csvPath(binding, migration.projectConfig.name, "layout") run(migration, areasFile) @@ -50,7 +52,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 -> @@ -82,6 +86,16 @@ static void run(Migration migration, Path path) { areas.eachWithIndex { area, idx -> writer.writeLine(buildArea(migration, idx, area, page, null)) } } } + + def baseTemplates = migration.baseTemplateRepository.listAll() ?: [] + baseTemplates.each { baseTemplate -> + baseTemplate.pages.eachWithIndex { page, pageIdx -> + def pageId = "page-${pageIdx + 1}" + page.areas.each { area -> + writer.writeLine(buildBaseTemplateArea(baseTemplate, pageId, page, area)) + } + } + } } } @@ -102,7 +116,33 @@ 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() } + +static String buildBaseTemplateArea(BaseTemplate baseTemplate, String pageId, BaseTemplatePage page, BaseTemplateArea area) { + def builder = new StringBuilder() + builder.append(Csv.serialize(baseTemplate.id) + ",") + builder.append(Csv.serialize(baseTemplate.name) + ",") + builder.append(Csv.serialize(pageId) + ",") + builder.append(Csv.serialize(page.name) + ",") + builder.append(Csv.serialize(page.pageWidth) + ",") + builder.append(Csv.serialize(page.pageHeight) + ",") + builder.append(Csv.serialize(area.interactiveFlowName) + ",") + builder.append(Csv.serialize(area.flowToNextPage) + ",") + builder.append(Csv.serialize(area.position?.x) + ",") + builder.append(Csv.serialize(area.position?.y) + ",") + builder.append(Csv.serialize(area.position?.width) + ",") + builder.append(Csv.serialize(area.position?.height) + ",") + + builder.append("Base,") + builder.append(Csv.serialize(null) + ",") + + builder.append(Csv.serialize(null)) + + return builder.toString() +} diff --git a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/LayoutImport.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/LayoutImport.groovy new file mode 100644 index 00000000..c75d3ceb --- /dev/null +++ b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/LayoutImport.groovy @@ -0,0 +1,186 @@ +//! --- +//! displayName: Import Layout +//! category: Mapping +//! description: Imports areas with modified interactive flow names to their respective pages and templates. The import is interactive, prompting the user to select the layout CSV to import if more than one matching file is found. +//! target: gradle +//! stdin: true +//! --- +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.DocumentObject +import com.quadient.migration.api.dto.migrationmodel.builder.BaseTemplateBuilder +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.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 + +import java.nio.file.Path + +import static com.quadient.migration.example.common.util.InitMigration.initMigration + +@Field static Logger log = LoggerFactory.getLogger(this.class.name) + +def migration = initMigration(this.binding) +def areasFile = new Mapping().getLayoutMappingPath(migration.projectConfig.name) + +run(migration, areasFile) + +static void run(Migration migration, Path path) { + def fileLines = path.toFile().readLines() + def columnNames = Csv.parseColumnNames(fileLines.removeFirst()).collect { Mapping.normalizeHeader(it) } + + def areaMappings = new HashMap() + def docObjectsToTargetIds = new LinkedHashMap() + def baseTemplateDrafts = new LinkedHashMap() + + DocumentObject currentDocumentObject = 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) { + areaMappings[currentDocumentObject.id] = areaMapping + } + + def documentObjectModel = migration.documentObjectRepository.find(documentObjectId) + if (!documentObjectModel) { + throw new IllegalStateException("Document object '${documentObjectId}' not found.") + } + + areaMapping = migration.mappingRepository.getAreaMapping(documentObjectId) + currentDocumentObject = documentObjectModel + areaIndex = 0 + } + + def interactiveFlowName = Csv.deserialize(values.get("interactiveFlowName"), String.class) + areaMapping.areas[areaIndex] = interactiveFlowName + + def flowToNextPage = Csv.deserialize(values.get("flowToNextPage"), Boolean.class) + 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) { + areaMappings[currentDocumentObject.id] = areaMapping + } + + 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 BaseTemplateBuilder.Page() + .name(Csv.deserialize(values.get("pageName"), String.class)) + .pageWidth(Csv.deserialize(values.get("pageWidth"), Size.class)) + .pageHeight(Csv.deserialize(values.get("pageHeight"), Size.class)) + } + + 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.addArea(interactiveFlowName) + .position(position) + .flowToNextPage(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 BaseTemplateBuilder(baseTemplateId).build()) + } + + def mapping = migration.mappingRepository.getBaseTemplateMapping(baseTemplateId) + mapping.name = draft.name ?: existing?.name + mapping.targetFolder = existing?.targetFolder + mapping.pages = draft.pages.values().collect { it.build() } as List + + 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/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..6e361c9c 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 @@ -24,11 +28,11 @@ Path getVariablesMappingPath(String[] args, String projectName) { System.exit(1) } - def selectedFile = null + File selectedFile = null def argUserInput = (getValueOfArg("--variable-structure-id", args as List)).orElseGet { null } if (argUserInput) { def fileName = variableStructureFileNameFromId(argUserInput, projectName) - def csvFile = csvFiles.find { it.name.equalsIgnoreCase(fileName) } + File csvFile = csvFiles.find { (it as File).name.equalsIgnoreCase(fileName) } as File if (csvFile) { selectedFile = csvFile println "Selected file: ${selectedFile.name}" @@ -36,28 +40,62 @@ Path getVariablesMappingPath(String[] args, String projectName) { println "CSV file '${fileName}' not found in mapping. Please provide a valid file name." System.exit(1) } + } else if (csvFiles.size() == 1) { + selectedFile = csvFiles.first() as File + println "Selected file: ${selectedFile.name}" } else { - println "Available CSV files for import:" - csvFiles.eachWithIndex { file, i -> println "${i + 1}) ${file.name}" } - println "Select a number of the CSV file to import:" - - while (true) { - def userInput = System.in.newReader().readLine().trim() - if (userInput.isInteger()) { - def idx = userInput.toInteger() - 1 - if (idx >= 0 && idx < csvFiles.size()) { - selectedFile = csvFiles[idx] - println "Selected file: ${selectedFile.name}" - break - } - } - println "Invalid selection. Please enter a valid number:" - } + selectedFile = promptForFileSelection(csvFiles) + } + + return selectedFile.toPath() +} + +Path getLayoutMappingPath(String projectName) { + def mappingDir = Paths.get("mapping").toFile() + def pattern = layoutFileNamePattern(projectName) + def csvFiles = mappingDir.listFiles()?.findAll { + it.name.toLowerCase().contains(pattern) && it.name.toLowerCase().endsWith(".csv") + } ?: [] + + if (csvFiles.isEmpty()) { + println "No CSV files found in mapping with matching pattern '*${pattern}*.csv'." + System.exit(1) + } + + File selectedFile + if (csvFiles.size() == 1) { + selectedFile = csvFiles.first() as File + println "Selected file: ${selectedFile.name}" + } else { + selectedFile = promptForFileSelection(csvFiles) } return selectedFile.toPath() } +static String layoutFileNamePattern(String projectName) { + return "${projectName}-layout".toLowerCase() +} + +private static File promptForFileSelection(List csvFiles) { + println "Available CSV files for import:" + csvFiles.eachWithIndex { file, i -> println "${i + 1}) ${file.name}" } + println "Select a number of the CSV file to import:" + + while (true) { + def userInput = System.in.newReader().readLine().trim() + if (userInput.isInteger()) { + def idx = userInput.toInteger() - 1 + if (idx >= 0 && idx < csvFiles.size()) { + def selectedFile = csvFiles[idx] + println "Selected file: ${selectedFile.name}" + return selectedFile + } + } + println "Invalid selection. Please enter a valid number:" + } +} + static String variableStructureFileNamePrefix(String projectName) { return "${projectName}-variable-structure-" } @@ -91,3 +129,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/AreasImportTest.groovy b/migration-examples/src/test/groovy/AreasImportTest.groovy deleted file mode 100644 index 2a494468..00000000 --- a/migration-examples/src/test/groovy/AreasImportTest.groovy +++ /dev/null @@ -1,112 +0,0 @@ -import com.quadient.migration.api.Migration -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.DocumentObjectType -import com.quadient.migration.shared.Position -import com.quadient.migration.shared.Size -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.io.TempDir - -import java.nio.file.Path -import java.nio.file.Paths - -import static org.mockito.Mockito.verify -import static org.mockito.Mockito.when - -class AreasImportTest { - @TempDir - File dir - - Migration migration - - @BeforeEach - void setup() { - migration = Utils.mockMigration() - } - - @Test - void importTest() { - Path mappingFile = Paths.get(dir.path, "testProject.csv") - - when(migration.mappingRepository.getAreaMapping("page1")).thenReturn(new MappingItem.Area(null, [:], [:])) - when(migration.mappingRepository.getAreaMapping("page2")).thenReturn(new MappingItem.Area(null, [:], [:])) - when(migration.mappingRepository.getAreaMapping("page3")).thenReturn(new MappingItem.Area(null, [:], [:])) - - givenPageExists("page1", ["flow1", "flow2", "flow3"], [false, false, false]) - givenPageExists("page2", ["flowA", "flowB"], [false, false]) - givenPageExists("page3", [null, "beta", null, "delta"]) - - def input = """\ - templateId,templateName,pageId,pageName,interactiveFlowName,flowToNextPage,x,y,width,height,contentPreview - ,,page1,,flow1,false,0.0mm,0.0mm,0.0mm,0.0mm, - ,,page1,,new flow2,false,0.0mm,0.0mm,0.0mm,0.0mm, - ,,page1,,flow3,true,0.0mm,0.0mm,0.0mm,0.0mm, - tmpl2,,page2,,flowA,true,0.0mm,0.0mm,0.0mm,0.0mm, - tmpl2,,page2,,modified flowB,false,0.0mm,0.0mm,0.0mm,0.0mm, - tmpl3,,page3,,new alpha,false,0.0mm,0.0mm,0.0mm,0.0mm, - tmpl3,,page3,,beta,true,0.0mm,0.0mm,0.0mm,0.0mm, - tmpl3,,page3,,new gamma,false,0.0mm,0.0mm,0.0mm,0.0mm, - tmpl3,,page3,,modified delta,true,0.0mm,0.0mm,0.0mm,0.0mm, - """.stripIndent() - mappingFile.toFile().write(input) - - AreasImport.run(migration, mappingFile) - - verify(migration.mappingRepository).upsertBatch([ - "page1": new MappingItem.Area(null, [0: "flow1", 1: "new flow2", 2: "flow3"], [0: false, 1: false, 2: true]), - "page2": new MappingItem.Area(null, [0: "flowA", 1: "modified flowB"], [0: true, 1: false]), - "page3": new MappingItem.Area(null, [0: "new alpha", 1: "beta", 2: "new gamma", 3: "modified delta"], [0: false, 1: true, 2: false, 3: true]) - ]) - verify(migration.mappingRepository).applyAllAreaMappings() - } - - @Test - void importTemplateDirectAreas() { - Path mappingFile = Paths.get(dir.path, "testProject.csv") - - when(migration.mappingRepository.getAreaMapping("tmpl1")).thenReturn(new MappingItem.Area(null, [:], [:])) - - when(migration.documentObjectRepository.find("tmpl1")).thenReturn( - new DocumentObjectBuilder("tmpl1", DocumentObjectType.Template) - .content([createArea("Address Content", false), createArea(null, false), createArea("Footer", false)]) - .build() - ) - - def input = """\ - templateId,templateName,pageId,pageName,interactiveFlowName,flowToNextPage,x,y,width,height,contentPreview - tmpl1,,,,Updated Address,true,0.0mm,0.0mm,0.0mm,0.0mm, - tmpl1,,,,New Header,false,0.0mm,0.0mm,0.0mm,0.0mm, - tmpl1,,,,Footer,true,0.0mm,0.0mm,0.0mm,0.0mm, - """.stripIndent() - mappingFile.toFile().write(input) - - AreasImport.run(migration, mappingFile) - - verify(migration.mappingRepository).upsertBatch([ - "tmpl1": new MappingItem.Area(null, [0: "Updated Address", 1: "New Header", 2: "Footer"], [0: true, 1: false, 2: true]) - ]) - verify(migration.mappingRepository).applyAllAreaMappings() - } - - 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))) - .flowToNextPage(flowToNextPage) - - if (flowName != null) { - areaBuilder.interactiveFlowName(flowName) - } - - return areaBuilder.build() - } - - void givenPageExists(String pageId, List flowNames, List flowToNextPageValues = null) { - def values = flowToNextPageValues ?: flowNames.collect { false } - def content = [flowNames, values].transpose() - .collect { String flowName, Boolean flowToNextPage -> createArea(flowName, flowToNextPage) } - when(migration.documentObjectRepository.find(pageId)).thenReturn(new DocumentObjectBuilder(pageId, DocumentObjectType.Page).content(content).build()) - } -} 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/AreasExportTest.groovy b/migration-examples/src/test/groovy/LayoutExportTest.groovy similarity index 58% rename from migration-examples/src/test/groovy/AreasExportTest.groovy rename to migration-examples/src/test/groovy/LayoutExportTest.groovy index 69f2d736..27571d0d 100644 --- a/migration-examples/src/test/groovy/AreasExportTest.groovy +++ b/migration-examples/src/test/groovy/LayoutExportTest.groovy @@ -4,7 +4,9 @@ import com.quadient.migration.api.dto.migrationmodel.builder.DocumentObjectBuild import com.quadient.migration.api.dto.migrationmodel.builder.ImageBuilder import com.quadient.migration.api.dto.migrationmodel.builder.documentcontent.AreaBuilder import com.quadient.migration.api.repository.DocumentObjectRepository -import com.quadient.migration.example.common.mapping.AreasExport +import com.quadient.migration.example.common.mapping.LayoutExport +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 @@ -19,7 +21,7 @@ import java.nio.file.Paths import static org.mockito.ArgumentMatchers.any import static org.mockito.Mockito.when -class AreasExportTest { +class LayoutExportTest { @TempDir java.io.File dir @@ -43,15 +45,15 @@ class AreasExportTest { .build(), ]) - AreasExport.run(migration, mappingFile) + LayoutExport.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")) } @@ -66,13 +68,65 @@ class AreasExportTest { .build(), ]) - AreasExport.run(migration, mappingFile) + LayoutExport.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(), + ]) + + LayoutExport.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")) + } + + @Test + void exportIncludesBaseTemplatesImportedViaLayoutImport() { + 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([]) + + def baseTemplate = new BaseTemplate("bt-1", "Base template 1", [], new CustomFieldMap(new HashMap()), null, [ + new BaseTemplatePage("Page 1", Size.ofMillimeters(210), Size.ofMillimeters(297), [ + new BaseTemplateArea("address", new Position(Size.ofCentimeters(1), Size.ofCentimeters(1), Size.ofMillimeters(190), Size.ofMillimeters(20)), false), + new BaseTemplateArea("Area 2", new Position(Size.ofCentimeters(1), Size.ofMillimeters(30), Size.ofMillimeters(190), Size.ofMillimeters(50)), true), + ]), + new BaseTemplatePage("Page 2", Size.ofMillimeters(210), Size.ofMillimeters(99), [ + new BaseTemplateArea("Area 1", new Position(Size.ofMillimeters(0), Size.ofMillimeters(0), Size.ofMillimeters(210), Size.ofMillimeters(99)), false), + ]), + ], null, null) + when(migration.baseTemplateRepository.listAll()).thenReturn([baseTemplate]) + + LayoutExport.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) + bt-1,Base template 1,page-1,Page 1,210mm,297mm,address,false,1cm,1cm,190mm,20mm,Base,, + bt-1,Base template 1,page-1,Page 1,210mm,297mm,Area 2,true,1cm,30mm,190mm,50mm,Base,, + bt-1,Base template 1,page-2,Page 2,210mm,99mm,Area 1,false,0mm,0mm,210mm,99mm,Base,, """.stripIndent() Assertions.assertEquals(expected, mappingFile.toFile().text.replaceAll("\\r\\n|\\r", "\n")) } @@ -97,11 +151,11 @@ class AreasExportTest { when(migration.imageRepository.find("img-1")).thenReturn(new ImageBuilder("img-1").name("Image One").build()) when(migration.imageRepository.find("img-2")).thenReturn(new ImageBuilder("img-2").name("Image Two").build()) - AreasExport.run(migration, mappingFile) + LayoutExport.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/LayoutImportTest.groovy b/migration-examples/src/test/groovy/LayoutImportTest.groovy new file mode 100644 index 00000000..804038f3 --- /dev/null +++ b/migration-examples/src/test/groovy/LayoutImportTest.groovy @@ -0,0 +1,210 @@ +import com.quadient.migration.api.Migration +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.LayoutImport +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 + +import static org.mockito.Mockito.verify +import static org.mockito.Mockito.when + +class LayoutImportTest { + @TempDir + File dir + + Migration migration + + @BeforeEach + void setup() { + migration = Utils.mockMigration() + } + + @Test + void importTest() { + Path mappingFile = Paths.get(dir.path, "testProject.csv") + + when(migration.mappingRepository.getAreaMapping("page1")).thenReturn(new MappingItem.Area(null, [:], [:])) + when(migration.mappingRepository.getAreaMapping("page2")).thenReturn(new MappingItem.Area(null, [:], [:])) + when(migration.mappingRepository.getAreaMapping("page3")).thenReturn(new MappingItem.Area(null, [:], [:])) + + givenPageExists("page1", ["flow1", "flow2", "flow3"], [false, false, false]) + givenPageExists("page2", ["flowA", "flowB"], [false, false]) + givenPageExists("page3", [null, "beta", null, "delta"]) + + def input = """\ + templateId,templateName,pageId,pageName,interactiveFlowName,flowToNextPage,x,y,width,height,contentPreview + ,,page1,,flow1,false,0.0mm,0.0mm,0.0mm,0.0mm, + ,,page1,,new flow2,false,0.0mm,0.0mm,0.0mm,0.0mm, + ,,page1,,flow3,true,0.0mm,0.0mm,0.0mm,0.0mm, + tmpl2,,page2,,flowA,true,0.0mm,0.0mm,0.0mm,0.0mm, + tmpl2,,page2,,modified flowB,false,0.0mm,0.0mm,0.0mm,0.0mm, + tmpl3,,page3,,new alpha,false,0.0mm,0.0mm,0.0mm,0.0mm, + tmpl3,,page3,,beta,true,0.0mm,0.0mm,0.0mm,0.0mm, + tmpl3,,page3,,new gamma,false,0.0mm,0.0mm,0.0mm,0.0mm, + tmpl3,,page3,,modified delta,true,0.0mm,0.0mm,0.0mm,0.0mm, + """.stripIndent() + mappingFile.toFile().write(input) + + LayoutImport.run(migration, mappingFile) + + verify(migration.mappingRepository).upsertBatch([ + "page1": new MappingItem.Area(null, [0: "flow1", 1: "new flow2", 2: "flow3"], [0: false, 1: false, 2: true]), + "page2": new MappingItem.Area(null, [0: "flowA", 1: "modified flowB"], [0: true, 1: false]), + "page3": new MappingItem.Area(null, [0: "new alpha", 1: "beta", 2: "new gamma", 3: "modified delta"], [0: false, 1: true, 2: false, 3: true]) + ]) + verify(migration.mappingRepository).applyAllAreaMappings() + } + + @Test + void importTemplateDirectAreas() { + Path mappingFile = Paths.get(dir.path, "testProject.csv") + + when(migration.mappingRepository.getAreaMapping("tmpl1")).thenReturn(new MappingItem.Area(null, [:], [:])) + + when(migration.documentObjectRepository.find("tmpl1")).thenReturn( + new DocumentObjectBuilder("tmpl1", DocumentObjectType.Template) + .content([createArea("Address Content", false), createArea(null, false), createArea("Footer", false)]) + .build() + ) + + def input = """\ + templateId,templateName,pageId,pageName,interactiveFlowName,flowToNextPage,x,y,width,height,contentPreview + tmpl1,,,,Updated Address,true,0.0mm,0.0mm,0.0mm,0.0mm, + tmpl1,,,,New Header,false,0.0mm,0.0mm,0.0mm,0.0mm, + tmpl1,,,,Footer,true,0.0mm,0.0mm,0.0mm,0.0mm, + """.stripIndent() + mappingFile.toFile().write(input) + + LayoutImport.run(migration, mappingFile) + + verify(migration.mappingRepository).upsertBatch([ + "tmpl1": new MappingItem.Area(null, [0: "Updated Address", 1: "New Header", 2: "Footer"], [0: true, 1: false, 2: true]) + ]) + 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) + + LayoutImport.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) + + LayoutImport.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) + + LayoutImport.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))) + .flowToNextPage(flowToNextPage) + + if (flowName != null) { + areaBuilder.interactiveFlowName(flowName) + } + + return areaBuilder.build() + } + + void givenPageExists(String pageId, List flowNames, List flowToNextPageValues = null) { + def values = flowToNextPageValues ?: flowNames.collect { false } + def content = [flowNames, values].transpose() + .collect { String flowName, Boolean flowToNextPage -> createArea(flowName, flowToNextPage) } + when(migration.documentObjectRepository.find(pageId)).thenReturn(new DocumentObjectBuilder(pageId, DocumentObjectType.Page).content(content).build()) + } +} 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 fef190f6..40841f58 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 @@ -97,6 +97,7 @@ class Migration(val config: MigConfig, val projectConfig: ProjectConfig) { single() single() single() + single() single() single() single() @@ -130,6 +131,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() } @@ -166,6 +168,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/BaseTemplateBuilder.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/BaseTemplateBuilder.kt new file mode 100644 index 00000000..91fcbe6d --- /dev/null +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/BaseTemplateBuilder.kt @@ -0,0 +1,124 @@ +package com.quadient.migration.api.dto.migrationmodel.builder + +import com.quadient.migration.api.dto.migrationmodel.BaseTemplate +import com.quadient.migration.api.dto.migrationmodel.builder.components.HasPosition +import com.quadient.migration.api.dto.migrationmodel.builder.components.HasTargetFolder +import com.quadient.migration.shared.BaseTemplateArea +import com.quadient.migration.shared.BaseTemplatePage +import com.quadient.migration.shared.Position +import com.quadient.migration.shared.Size + +@DslMarker +annotation class BaseTemplateBuilderDsl + +@BaseTemplateBuilderDsl +class BaseTemplateBuilder(id: String) : DtoBuilderBase(id), + HasTargetFolder { + override var targetFolder: String? = null + private val pages = mutableListOf() + + /** Creates a new [Page], appends it, and returns it for further configuration. */ + fun addPage() = Page().also { pages.add(it) } + + /** + * Creates a new [Page], configures it via [init], appends it, and returns this builder. + * @return This builder instance for method chaining. + */ + fun addPage(init: Page.() -> Unit): BaseTemplateBuilder = apply { pages.add(Page().apply(init)) } + + /** + * Appends a pre-configured [page] to this base template. + * @return This builder instance for method chaining. + */ + fun addPage(page: Page) = apply { pages.add(page) } + + /** + * Appends multiple pre-configured [pages] to this base template. + * @return This builder instance for method chaining. + */ + fun addPages(pages: List) = apply { this.pages.addAll(pages) } + + override fun build(): BaseTemplate { + return BaseTemplate( + id = id, + name = name, + originLocations = originLocations, + customFields = customFields, + targetFolder = targetFolder, + pages = pages.map(Page::build), + ) + } + + @BaseTemplateBuilderDsl + class Page { + private var name: String? = null + private var pageWidth: Size? = null + private var pageHeight: Size? = null + private val areas = mutableListOf() + + fun name(name: String?) = apply { this.name = name } + fun pageWidth(pageWidth: Size?) = apply { this.pageWidth = pageWidth } + fun pageHeight(pageHeight: Size?) = apply { this.pageHeight = pageHeight } + + /** + * Sets both the width and height of the page. + * @return This builder instance for method chaining. + */ + fun pageSize(width: Size?, height: Size?) = apply { + this.pageWidth = width + this.pageHeight = height + } + + /** Creates a new [Area], appends it, and returns it for further configuration. */ + fun addArea(interactiveFlowName: String) = Area(interactiveFlowName).also { areas.add(it) } + + /** + * Creates a new [Area], configures it via [init], appends it, and returns this builder. + * @return This builder instance for method chaining. + */ + fun addArea(interactiveFlowName: String, init: Area.() -> Unit): Page = + apply { areas.add(Area(interactiveFlowName).apply(init)) } + + /** + * Appends a pre-configured [area] to this page. + * @return This builder instance for method chaining. + */ + fun addArea(area: Area) = apply { areas.add(area) } + + /** + * Appends multiple pre-configured [areas] to this page. + * @return This builder instance for method chaining. + */ + fun addAreas(areas: List) = apply { this.areas.addAll(areas) } + + fun build(): BaseTemplatePage { + return BaseTemplatePage( + name = name, + pageWidth = pageWidth, + pageHeight = pageHeight, + areas = areas.map(Area::build), + ) + } + } + + @BaseTemplateBuilderDsl + class Area(private val interactiveFlowName: String) : HasPosition { + override var position: Position? = null + private var flowToNextPage: Boolean = false + + /** + * Set whether the flow area should flow to the next page. + * @param flowToNextPage Whether the flow area should flow to the next page. Default is false. + * @return The [Area] instance for method chaining. + */ + fun flowToNextPage(flowToNextPage: Boolean) = apply { this.flowToNextPage = flowToNextPage } + + fun build(): BaseTemplateArea { + return BaseTemplateArea( + interactiveFlowName = interactiveFlowName, + position = position, + flowToNextPage = flowToNextPage, + ) + } + } +} 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 d809ffb8..a94434b4 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 9c57e211..05517875 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..84f17300 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,14 @@ 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.BaseTemplate +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,16 +63,31 @@ 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?, + resourcePathProvider: ResourcePathProvider, + findBaseTemplate: (String) -> BaseTemplate, +): IcmPath { + val literalPath = when (documentObjectBaseTemplate) { + is LiteralBaseTemplatePath -> documentObjectBaseTemplate.path + is BaseTemplateRef -> { + val baseTemplate = findBaseTemplate(documentObjectBaseTemplate.id) + val baseTemplatePath = resourcePathProvider.getBaseTemplatePath(baseTemplate) + logger.info( + "Base template '$baseTemplatePath' will not be used because referencing base templates by id is not yet supported during deployment. The project config default base template will be used instead." + ) + config.baseTemplatePath + } + + null -> config.baseTemplatePath + } + + val path = literalPath.toIcmPath() if (path.isAbsolute()) return path - return "icm://Interactive".toIcmPath() - .join(config.interactiveTenant) - .join("BaseTemplates") - .join(path) + return resourcePathProvider.getBaseTemplatePath(literalPath) } fun DisplayRule.resolveTarget(findRule: (String) -> DisplayRule): DisplayRule { 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/ResourcePathProvider.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/ResourcePathProvider.kt index f1369f8f..32346f26 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/ResourcePathProvider.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/ResourcePathProvider.kt @@ -2,6 +2,7 @@ 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.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 @@ -45,6 +46,9 @@ interface ResourcePathProvider { fun getDisplayRulePath(rule: DisplayRule): IcmPath + fun getBaseTemplatePath(literalPath: String): IcmPath + fun getBaseTemplatePath(baseTemplate: BaseTemplate): IcmPath + fun getStyleDefinitionPath(): IcmPath fun getFontRootFolder(): IcmPath @@ -112,6 +116,14 @@ class DesignerResourcePathProvider(private val projectConfig: ProjectConfig) : R error("External display rules are not supported and should not be used for Designer output. Report this as a bug.") } + override fun getBaseTemplatePath(literalPath: String): IcmPath { + error("Referencing base templates is not supported for Designer output. Report this as a bug.") + } + + override fun getBaseTemplatePath(baseTemplate: BaseTemplate): IcmPath { + error("Referencing base templates by id is not supported for Designer output. Report this as a bug.") + } + override fun getFontRootFolder(): IcmPath { val fontConfigPath = projectConfig.paths.fonts @@ -201,6 +213,26 @@ open class InteractiveResourcePathProvider(private val projectConfig: ProjectCon .join(resolveTargetDir(projectConfig.defaultTargetFolder, targetFolder)).join(attachmentName) } + override fun getBaseTemplatePath(literalPath: String): IcmPath { + return IcmPath.root() + .join("Interactive") + .join(projectConfig.interactiveTenant) + .join("BaseTemplates") + .join(literalPath) + } + + override fun getBaseTemplatePath(baseTemplate: BaseTemplate): IcmPath { + val fileName = "${baseTemplate.name ?: baseTemplate.id}.wfd" + + val targetFolder = baseTemplate.targetFolder?.let { IcmPath.from(it) } + if (targetFolder?.isAbsolute() == true) { + return targetFolder.join(fileName) + } + + val relativePath = resolveTargetDir(projectConfig.defaultTargetFolder, targetFolder)?.join(fileName) ?: IcmPath.from(fileName) + return getBaseTemplatePath(relativePath.toString()) + } + override fun getStyleDefinitionPath(): IcmPath { val styleDefConfigPath = projectConfig.styleDefinitionPath 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 7a0a63b0..72a55127 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 @@ -18,6 +19,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 @@ -75,6 +77,7 @@ sealed class DeployClient( protected val displayRuleRepository: DisplayRuleRepository, protected val variableRepository: VariableRepository, protected val variableStructureRepository: VariableStructureRepository, + protected val baseTemplateRepository: BaseTemplateRepository, protected val documentObjectBuilder: InspireDocumentObjectBuilder, protected val ipsService: IpsService, protected val storage: Storage, @@ -351,7 +354,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 -> { @@ -371,7 +374,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/DesignerDeployClient.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/DesignerDeployClient.kt index b95f84fd..7d2f9268 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/DesignerDeployClient.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/DesignerDeployClient.kt @@ -7,6 +7,7 @@ import com.quadient.migration.api.repository.StatusTrackingRepository import com.quadient.migration.api.dto.migrationmodel.DocumentObject import com.quadient.migration.api.dto.migrationmodel.Image 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 @@ -57,6 +58,7 @@ class DesignerDeployClient( displayRuleRepository: DisplayRuleRepository, variableRepository: VariableRepository, variableStructureRepository: VariableStructureRepository, + baseTemplateRepository: BaseTemplateRepository, documentObjectBuilder: InspireDocumentObjectBuilder, ipsService: IpsService, storage: Storage, @@ -77,6 +79,7 @@ class DesignerDeployClient( displayRuleRepository, variableRepository, variableStructureRepository, + baseTemplateRepository, documentObjectBuilder, ipsService, storage diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/EvolveDeployClient.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/EvolveDeployClient.kt index d8faf73e..85025091 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/EvolveDeployClient.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/EvolveDeployClient.kt @@ -7,6 +7,7 @@ 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.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 @@ -53,6 +54,7 @@ class EvolveDeployClient( displayRuleRepository: DisplayRuleRepository, variableRepository: VariableRepository, variableStructureRepository: VariableStructureRepository, + baseTemplateRepository: BaseTemplateRepository, documentObjectBuilder: InspireDocumentObjectBuilder, ipsService: IpsService, storage: Storage, @@ -73,6 +75,7 @@ class EvolveDeployClient( displayRuleRepository, variableRepository, variableStructureRepository, + baseTemplateRepository, documentObjectBuilder, ipsService, storage, @@ -95,7 +98,9 @@ class EvolveDeployClient( val ipsMemLocation = "memory://${UUID.randomUUID()}" try { val runCommandType = obj.type.toRunCommandType() - val baseTemplatePath = getBaseTemplateFullPath(projectConfig, obj.baseTemplate) + val baseTemplatePath = getBaseTemplateFullPath( + projectConfig, obj.baseTemplate, resourcePathProvider + ) { baseTemplateRepository.findOrFail(it) } val deployResult = ipsService.deployJld( baseTemplate = baseTemplatePath, type = runCommandType, @@ -215,7 +220,9 @@ class EvolveDeployClient( } val resolvedFolder = resolveTargetDir(projectConfig.defaultTargetFolder, rule.targetFolder?.toIcmPath()) - val baseTemplatePath = getBaseTemplateFullPath(projectConfig, rule.baseTemplate) + val baseTemplatePath = getBaseTemplateFullPath( + projectConfig, rule.baseTemplate, resourcePathProvider + ) { baseTemplateRepository.findOrFail(it) } val result = caClient.createRuleDraft(rule.nameOrId(), resolvedFolder, baseTemplatePath, data) if (result !is HttpResult.Success) return result.toOperationResult() 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 4f1addab..28f4805a 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 @@ -17,6 +18,7 @@ import com.quadient.migration.api.dto.migrationmodel.VariableRef 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 @@ -60,7 +62,7 @@ import kotlin.uuid.Uuid open class InteractiveDeployClient( private val projectConfig: ProjectConfig, - private val resourcePathProvider: ResourcePathProvider, + protected val resourcePathProvider: ResourcePathProvider, metadataValidator: MetadataValidatorImpl, postProcess: PostProcessImpl, conflictDetector: ConflictDetectorImpl, @@ -75,6 +77,7 @@ open class InteractiveDeployClient( displayRuleRepository: DisplayRuleRepository, variableRepository: VariableRepository, variableStructureRepository: VariableStructureRepository, + baseTemplateRepository: BaseTemplateRepository, documentObjectBuilder: InspireDocumentObjectBuilder, ipsService: IpsService, storage: Storage, @@ -95,6 +98,7 @@ open class InteractiveDeployClient( displayRuleRepository, variableRepository, variableStructureRepository, + baseTemplateRepository, documentObjectBuilder, ipsService, storage, @@ -155,7 +159,9 @@ open class InteractiveDeployClient( override fun uploadDocumentObject(obj: DocumentObject, targetPath: IcmPath, wfdXml: String): OperationResult { val runCommandType = obj.type.toRunCommandType() return ipsService.deployJld( - baseTemplate = getBaseTemplateFullPath(projectConfig, obj.baseTemplate), + baseTemplate = getBaseTemplateFullPath( + projectConfig, obj.baseTemplate, resourcePathProvider + ) { baseTemplateRepository.findOrFail(it) }, type = runCommandType, moduleName = "DocumentLayout", xmlContent = wfdXml, @@ -279,7 +285,11 @@ open class InteractiveDeployClient( continue } - val jrd = Jrd.fromDisplayRule(rule, projectConfig, variableStructure, findVar) + val baseTemplatePath = getBaseTemplateFullPath( + projectConfig, rule.baseTemplate, resourcePathProvider + ) { baseTemplateRepository.findOrFail(it) }.toMapInteractive(projectConfig.interactiveTenant) + + val jrd = Jrd.fromDisplayRule(rule, baseTemplatePath, variableStructure, findVar) val uploadResult = deployDisplayRule(rule, targetPath, jrd.toByteArray()) if (uploadResult is OperationResult.Failure) { @@ -458,7 +468,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/DeployOrder.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/utility/DeployOrder.kt index d58295e0..2a1c1eec 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/utility/DeployOrder.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/utility/DeployOrder.kt @@ -1,6 +1,7 @@ package com.quadient.migration.service.deploy.utility 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.DocumentObject import com.quadient.migration.api.dto.migrationmodel.DocumentObjectRef @@ -99,6 +100,7 @@ class DeployOrderImpl(private val documentObjectRepository: DocumentObjectReposi 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/service/inspirebuilder/DesignerDocumentObjectBuilder.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/DesignerDocumentObjectBuilder.kt index 7f5eeb75..cb85e8c1 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/DesignerDocumentObjectBuilder.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/DesignerDocumentObjectBuilder.kt @@ -4,6 +4,7 @@ import com.quadient.migration.api.InspireOutput import com.quadient.migration.api.ProjectConfig import com.quadient.migration.api.dto.migrationmodel.* 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 @@ -58,6 +59,7 @@ class DesignerDocumentObjectBuilder( resourcePathProvider: ResourcePathProvider, projectConfig: ProjectConfig, icmDataCache: IcmDataCache, + baseTemplateRepository: BaseTemplateRepository, ) : InspireDocumentObjectBuilder( documentObjectRepository, textStyleRepository, @@ -71,6 +73,7 @@ class DesignerDocumentObjectBuilder( resourcePathProvider, projectConfig.inspireOutput, icmDataCache, + baseTemplateRepository, ) { private val resolvedStyleDefinitionPath: IcmPath? by lazy { val path = resourcePathProvider.getStyleDefinitionPath() diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt index 30b80404..6a37bd84 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt @@ -111,6 +111,7 @@ import com.quadient.migration.api.dto.migrationmodel.Shape import com.quadient.migration.api.dto.migrationmodel.TextStyle import com.quadient.migration.api.dto.migrationmodel.builder.ParagraphBuilder 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.ImageRepository import com.quadient.migration.api.repository.VariableRepository @@ -141,6 +142,7 @@ abstract class InspireDocumentObjectBuilder( protected val resourcePathProvider: ResourcePathProvider, protected val output: InspireOutput, protected val icmDataCache: IcmDataCache, + protected val baseTemplateRepository: BaseTemplateRepository, ) { protected val logger by logger() diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilder.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilder.kt index 3cd21f2d..8ba119c4 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilder.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilder.kt @@ -8,6 +8,7 @@ import com.quadient.migration.api.dto.migrationmodel.DocumentObject import com.quadient.migration.api.dto.migrationmodel.DocumentObjectRef import com.quadient.migration.api.dto.migrationmodel.VariableStructure 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 @@ -40,6 +41,7 @@ class InteractiveDocumentObjectBuilder( projectConfig: ProjectConfig, resourcePathProvider: ResourcePathProvider, icmDataCache: IcmDataCache, + baseTemplateRepository: BaseTemplateRepository, ) : InspireDocumentObjectBuilder( documentObjectRepository, textStyleRepository, @@ -53,6 +55,7 @@ class InteractiveDocumentObjectBuilder( resourcePathProvider, projectConfig.inspireOutput, icmDataCache, + baseTemplateRepository, ) { private val mainFlowId = "Def.MainFlow" private val snippetBuilder = InteractiveSnippetBuilder( @@ -93,7 +96,9 @@ class InteractiveDocumentObjectBuilder( } - val baseTemplatePath = getBaseTemplateFullPath(projectConfig, documentObject.baseTemplate) + val baseTemplatePath = getBaseTemplateFullPath( + projectConfig, documentObject.baseTemplate, resourcePathProvider + ) { baseTemplateRepository.findOrFail(it) } val currentBaseTemplateData = icmDataCache.getOrLoadBaseTemplateData(baseTemplatePath) ?: error("Unable to deploy document object ${documentObject.id}. Base template '$baseTemplatePath' does not exist.") 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 26934891..787a0462 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 @@ -27,11 +27,11 @@ class Jrd(@field:JsonProperty("InteractivePlusJsonDefinition") val interactivePl companion object { fun fromDisplayRule( rule: DisplayRule, - projectConfig: ProjectConfig, + baseTemplate: String, variableStructure: VariableStructure, - findVar: (String) -> Variable + findVar: (String) -> Variable, ): String { - val result = Jrd(JrdDefinition.fromDisplayRule(rule, projectConfig, variableStructure, findVar)) + val result = Jrd(JrdDefinition.fromDisplayRule(rule, baseTemplate, variableStructure, findVar)) return JsonMapper.builder().disable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY).build() .writerWithDefaultPrettyPrinter().writeValueAsString(result) } @@ -50,9 +50,9 @@ data class JrdDefinition( companion object { fun fromDisplayRule( rule: DisplayRule, - projectConfig: ProjectConfig, + baseTemplate: String, variableStructure: VariableStructure, - findVar: (String) -> Variable + findVar: (String) -> Variable, ): JrdDefinition { val nodes = mutableListOf(null) @@ -83,9 +83,6 @@ data class JrdDefinition( variable } - val baseTemplate = IcmPath.from(rule.baseTemplate ?: projectConfig.baseTemplatePath) - .toMapInteractive(projectConfig.interactiveTenant) - val ruleDef = requireNotNull(rule.definition) { "Display rule '${rule.id}' cannot be deployed because it has missing definition" } val value = JrdDefinition( type = "Rule", subject = rule.subject ?: "", dataSet = DataSet(type = "Template", master = baseTemplate), 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..e30d2458 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,10 @@ 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.BaseTemplate +import com.quadient.migration.api.dto.migrationmodel.BaseTemplateRef +import com.quadient.migration.api.dto.migrationmodel.CustomFieldMap +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 @@ -10,10 +14,12 @@ class DeployPhaseUtilsTest { val projectConfig = aProjectConfig( "vcs:\\\\Interactive\\StandardPackage\\BaseTemplates\\BaseTemplate.wfd", interactiveTenant = "StandardPackage" ) + val resourcePathProvider = InteractiveResourcePathProvider(projectConfig) + val findBaseTemplate: (String) -> BaseTemplate = { error("Unexpected base template lookup for '$it'") } @Test fun `project config base template is used and normalized`() { - val result = getBaseTemplateFullPath(projectConfig, null).toString() + val result = getBaseTemplateFullPath(projectConfig, null, resourcePathProvider, findBaseTemplate).toString() result.shouldBeEqualTo("icm://Interactive/StandardPackage/BaseTemplates/BaseTemplate.wfd") } @@ -21,25 +27,59 @@ 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), resourcePathProvider, findBaseTemplate + ).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"), resourcePathProvider, findBaseTemplate + ).toString() result.shouldBeEqualTo("icm://Interactive/${projectConfig.interactiveTenant}/BaseTemplates/projectA/AddressBT.wfd") } @Test fun `only base template name in project config is correctly translated to full path`() { - val result = getBaseTemplateFullPath(aProjectConfig("myBT.wfd", interactiveTenant = "StandardPackage"), null).toString() + val config = aProjectConfig("myBT.wfd", interactiveTenant = "StandardPackage") + val result = getBaseTemplateFullPath( + config, null, InteractiveResourcePathProvider(config), findBaseTemplate + ).toString() result.shouldBeEqualTo("icm://Interactive/StandardPackage/BaseTemplates/myBT.wfd") } + @Test + fun `base template referenced by id is looked up and resolved via resource path provider, but project config default is used instead`() { + val baseTemplate = BaseTemplate( + id = "bt-1", + name = "AddressBaseTemplate", + customFields = CustomFieldMap(), + ) + + val result = getBaseTemplateFullPath( + projectConfig, BaseTemplateRef(baseTemplate.id), resourcePathProvider + ) { id -> if (id == baseTemplate.id) baseTemplate else error("Unexpected id '$id'") }.toString() + + result.shouldBeEqualTo("icm://Interactive/StandardPackage/BaseTemplates/BaseTemplate.wfd") + } + + @Test + fun `base template referenced by id fails if it cannot be found`() { + try { + getBaseTemplateFullPath( + projectConfig, BaseTemplateRef("missing"), resourcePathProvider + ) { error("Record 'missing' not found") } + error("Expected an exception to be thrown") + } catch (e: IllegalStateException) { + e.message.shouldBeEqualTo("Record 'missing' not found") + } + } + @Test fun `extractExtensionFromPath handles various path formats correctly`() { // Valid extensions 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/DeployClientTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/service/deploy/DeployClientTest.kt index 15a69b31..ad1450fe 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/service/deploy/DeployClientTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/service/deploy/DeployClientTest.kt @@ -14,6 +14,7 @@ import com.quadient.migration.api.dto.migrationmodel.TextStyleRef import com.quadient.migration.api.dto.migrationmodel.builder.ParagraphStyleBuilder import com.quadient.migration.api.dto.migrationmodel.builder.TextStyleBuilder 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 @@ -78,6 +79,7 @@ class DeployClientTest { val displayRuleRepository = mockk() val variableRepository = mockk() val variableStructureRepository = mockk() + val baseTemplateRepository = mockk() val ipsService = mockk() val storage = mockk() val projectConfig = aProjectConfig(output = InspireOutput.Designer) @@ -104,6 +106,7 @@ class DeployClientTest { displayRuleRepository, variableRepository, variableStructureRepository, + baseTemplateRepository, documentObjectBuilder, ipsService, storage diff --git a/migration-library/src/test/kotlin/com/quadient/migration/service/deploy/DesignerDeployClientTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/service/deploy/DesignerDeployClientTest.kt index 219ca2bc..bde382fc 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/service/deploy/DesignerDeployClientTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/service/deploy/DesignerDeployClientTest.kt @@ -17,6 +17,7 @@ import com.quadient.migration.api.dto.migrationmodel.builder.AttachmentBuilder import com.quadient.migration.api.dto.migrationmodel.builder.DocumentObjectBuilder import com.quadient.migration.api.dto.migrationmodel.builder.ImageBuilder 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 @@ -95,6 +96,7 @@ class DesignerDeployClientTest { val displayRuleRepository = mockk() val variableRepository = mockk() val variableStructureRepository = mockk() + val baseTemplateRepository = mockk() val statusTrackingRepository = mockk() val documentObjectBuilder = mockk() val ipsService = mockk() @@ -122,6 +124,7 @@ class DesignerDeployClientTest { displayRuleRepository, variableRepository, variableStructureRepository, + baseTemplateRepository, documentObjectBuilder, ipsService, storage 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 f480dd9c..d2acb39c 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 @@ -8,6 +8,7 @@ import com.quadient.migration.api.dto.migrationmodel.builder.DisplayRuleBuilder import com.quadient.migration.api.dto.migrationmodel.builder.DocumentObjectBuilder import com.quadient.migration.api.dto.migrationmodel.builder.ImageBuilder 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 @@ class EvolveDeployClientTest { val displayRuleRepository = mockk() val variableRepository = mockk() val variableStructureRepository = mockk() + val baseTemplateRepository = mockk() val statusTrackingRepository = mockk() val documentObjectBuilder = mockk() val ipsService = mockk() @@ -102,6 +104,7 @@ class EvolveDeployClientTest { displayRuleRepository, variableRepository, variableStructureRepository, + baseTemplateRepository, documentObjectBuilder, ipsService, storage, @@ -335,7 +338,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/service/deploy/InteractiveDeployClientTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/service/deploy/InteractiveDeployClientTest.kt index 38426c16..546f2e96 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/service/deploy/InteractiveDeployClientTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/service/deploy/InteractiveDeployClientTest.kt @@ -16,6 +16,7 @@ import com.quadient.migration.api.dto.migrationmodel.builder.DisplayRuleBuilder import com.quadient.migration.api.dto.migrationmodel.builder.DocumentObjectBuilder import com.quadient.migration.api.dto.migrationmodel.builder.ImageBuilder 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 @@ -99,6 +100,7 @@ class InteractiveDeployClientTest { val displayRuleRepository = mockk() val variableRepository = mockk() val variableStructureRepository = mockk() + val baseTemplateRepository = mockk() val statusTrackingRepository = mockk() val documentObjectBuilder = mockk() val ipsService = mockk() @@ -131,6 +133,7 @@ class InteractiveDeployClientTest { displayRuleRepository, variableRepository, variableStructureRepository, + baseTemplateRepository, documentObjectBuilder, ipsService, storage, diff --git a/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/DesignerDocumentObjectBuilderTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/DesignerDocumentObjectBuilderTest.kt index d9564d6e..487b54b6 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/DesignerDocumentObjectBuilderTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/DesignerDocumentObjectBuilderTest.kt @@ -23,6 +23,7 @@ import com.quadient.migration.api.dto.migrationmodel.builder.EmailObjectBuilder import com.quadient.migration.api.dto.migrationmodel.builder.SmsObjectBuilder import com.quadient.migration.api.dto.migrationmodel.builder.documentcontent.ShapeBuilder 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 @@ -89,6 +90,7 @@ class DesignerDocumentObjectBuilderTest { val displayRuleRepository = mockk() val imageRepository = mockk() val attachmentRepository = mockk() + val baseTemplateRepository = mockk() val ipsService = mockk() val config = aProjectConfig(targetDefaultFolder = "defaultFolder", output = InspireOutput.Designer) val resourcePathProvider = DesignerResourcePathProvider(config) @@ -1283,6 +1285,7 @@ class DesignerDocumentObjectBuilderTest { resourcePathProvider, config, icmDataCache, + baseTemplateRepository, ) @Test diff --git a/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilderTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilderTest.kt index 37b6adcf..06c81179 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilderTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilderTest.kt @@ -25,6 +25,7 @@ import com.quadient.migration.api.dto.migrationmodel.builder.TextStyleBuilder import com.quadient.migration.api.dto.migrationmodel.builder.VariableBuilder import com.quadient.migration.api.dto.migrationmodel.builder.VariableStructureBuilder 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 @@ -82,6 +83,7 @@ class InspireDocumentObjectBuilderTest { private val displayRuleRepository = mockk() private val imageRepository = mockk() private val attachmentRepository = mockk() + private val baseTemplateRepository = mockk() private val ipsService = mockk() private val resourcePathProvider = DesignerResourcePathProvider(config) private val icmDataCache = DesignerIcmDataCache(ipsService, resourcePathProvider) @@ -100,6 +102,7 @@ class InspireDocumentObjectBuilderTest { resourcePathProvider, config, icmDataCache, + baseTemplateRepository, ) @BeforeEach @@ -1522,5 +1525,6 @@ class InspireDocumentObjectBuilderTest { resourcePathProvider, config, icmDataCache, + baseTemplateRepository, ) } diff --git a/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilderTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilderTest.kt index 2c50b5d9..b5c6da64 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilderTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilderTest.kt @@ -28,6 +28,7 @@ import com.quadient.migration.api.dto.migrationmodel.builder.VariableBuilder import com.quadient.migration.api.dto.migrationmodel.builder.VariableStructureBuilder import com.quadient.migration.api.dto.migrationmodel.builder.documentcontent.AreaBuilder 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 @@ -96,6 +97,7 @@ class InteractiveDocumentObjectBuilderTest { val paragraphStyleRepository = mockk() val variableRepository = mockk() val variableStructureRepository = mockk() + val baseTemplateRepository = mockk() val displayRuleRepository = mockk() val imageRepository = mockk() val attachmentRepository = mockk() @@ -935,7 +937,7 @@ class InteractiveDocumentObjectBuilderTest { } every { - ipsService.wfd2xml(getBaseTemplateFullPath(config, null)) + ipsService.wfd2xml(getBaseTemplateFullPath(config, null, resourcePathProvider) { baseTemplateRepository.findOrFail(it) }) } returns """ @@ -1657,7 +1659,7 @@ class InteractiveDocumentObjectBuilderTest { val emailDoc = EmailObjectBuilder("E_1").string("Email content").build().mock() val template = DocumentObjectBuilder("T_1", Template).documentObjectRef(emailDoc).build() - every { ipsService.wfd2xml(getBaseTemplateFullPath(config, null)) } returns """ + every { ipsService.wfd2xml(getBaseTemplateFullPath(config, null, resourcePathProvider) { baseTemplateRepository.findOrFail(it) }) } returns """ @@ -1716,7 +1718,7 @@ class InteractiveDocumentObjectBuilderTest { val smsDoc = SmsObjectBuilder("S_1").string("SMS content").build().mock() val template = DocumentObjectBuilder("T_1", Template).documentObjectRef(smsDoc).build() - every { ipsService.wfd2xml(getBaseTemplateFullPath(config, null)) } returns """ + every { ipsService.wfd2xml(getBaseTemplateFullPath(config, null, resourcePathProvider) { baseTemplateRepository.findOrFail(it) }) } returns """ @@ -1772,7 +1774,7 @@ class InteractiveDocumentObjectBuilderTest { .build().mock() val template = DocumentObjectBuilder("T_1", Template).documentObjectRef(emailDoc).build() - every { ipsService.wfd2xml(getBaseTemplateFullPath(config, null)) } returns """ + every { ipsService.wfd2xml(getBaseTemplateFullPath(config, null, resourcePathProvider) { baseTemplateRepository.findOrFail(it) }) } returns """ @@ -1848,7 +1850,7 @@ class InteractiveDocumentObjectBuilderTest { .build().mock() val template = DocumentObjectBuilder("T_1", Template).documentObjectRef(smsDoc).build() - every { ipsService.wfd2xml(getBaseTemplateFullPath(config, null)) } returns """ + every { ipsService.wfd2xml(getBaseTemplateFullPath(config, null, resourcePathProvider) { baseTemplateRepository.findOrFail(it) }) } returns """ @@ -1950,5 +1952,6 @@ class InteractiveDocumentObjectBuilderTest { config, resourcePathProvider, icmDataCache, + baseTemplateRepository, ) -} \ No newline at end of file +} diff --git a/migration-library/src/test/kotlin/com/quadient/migration/shared/JrdDefinitionTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/shared/JrdDefinitionTest.kt index ef166c66..a95911fc 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/shared/JrdDefinitionTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/shared/JrdDefinitionTest.kt @@ -35,8 +35,8 @@ class JrdDefinitionTest { .definition(DisplayRuleDefinition(group = Group(items = groupItems, operator = GroupOp.Or, false))) .build() - val result = JrdDefinition.fromDisplayRule(rule, projectConfig, variableStructure, { name -> aVariable(name) }) - val json = Jrd.fromDisplayRule(rule, projectConfig, variableStructure, { name -> aVariable(name) }) + val result = JrdDefinition.fromDisplayRule(rule, "map://interactive/BaseTemplates/templ.wfd", variableStructure, { name -> aVariable(name) }) + val json = Jrd.fromDisplayRule(rule, "map://interactive/BaseTemplates/templ.wfd", variableStructure, { name -> aVariable(name) }) result.subject.shouldBeEqualTo("Test Subject") result.dataSet.type.shouldBeEqualTo("Template") @@ -128,10 +128,7 @@ class JrdDefinitionTest { .definition(DisplayRuleDefinition(group = Group(items = groupItems, operator = GroupOp.And, false))) .build() - val result = Jrd.fromDisplayRule(rule, aProjectConfig( - baseTemplatePath = "icm://Interactive/StandardPackage/BaseTemplates/templ.wfd", - interactiveTenant = "StandardPackage", - ), variableStructure, { name -> aVariable(name) }) + val result = Jrd.fromDisplayRule(rule, "map://interactive/BaseTemplates/templ.wfd", variableStructure, { name -> aVariable(name) }) result.replace("\r\n", "\n").shouldBeEqualTo($$""" { @@ -197,10 +194,7 @@ class JrdDefinitionTest { comparison { value("a").notEndsWithCaseInsensitive().value("a") } }.build() - val result = Jrd.fromDisplayRule(allBinOpsRule, aProjectConfig( - baseTemplatePath = "icm://Interactive/StandardPackage/BaseTemplates/templ.wfd", - interactiveTenant = "StandardPackage", - ), variableStructure, { name -> aVariable(name) }) + val result = Jrd.fromDisplayRule(allBinOpsRule, "map://interactive/BaseTemplates/templ.wfd", variableStructure, { name -> aVariable(name) }) result.replace("\r\n", "\n").shouldBeEqualTo($$""" 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),