@@ -8,8 +8,10 @@ import com.atlan.pkg.cache.ConnectionCache
8
8
import com.atlan.pkg.objectstore.ObjectStorageSyncer
9
9
import com.atlan.pkg.serde.csv.CSVPreprocessor
10
10
import com.atlan.pkg.serde.csv.RowPreprocessor
11
+ import com.atlan.util.AssetBatch.AssetIdentity
11
12
import mu.KLogger
12
13
import java.io.File.separator
14
+ import java.io.IOException
13
15
import java.nio.file.Paths
14
16
import java.time.Instant
15
17
import java.time.ZoneId
@@ -27,6 +29,7 @@ import java.time.format.DateTimeFormatter
27
29
* @param preprocessedDetails details retrieved from pre-processing the input CSV file
28
30
* @param typesToRemove limit the asset types that will be removed to this collection of type names
29
31
* @param logger for logging
32
+ * @param reloadSemantic the type of reload to do for assets (default {@code all} will reload any assets that are not deleted, whether changed or not)
30
33
* @param previousFilePreprocessor responsible for pre-processing the previous CSV file (if provided directly)
31
34
* @param outputDirectory local directory where files can be written and compared
32
35
* @param previousFileProcessedExtension extension to use in the object store for files that have been processed
@@ -40,59 +43,110 @@ class DeltaProcessor(
40
43
val preprocessedDetails : Results ,
41
44
val typesToRemove : Collection <String >,
42
45
private val logger : KLogger ,
46
+ val reloadSemantic : String = " all" ,
43
47
val previousFilePreprocessor : CSVPreprocessor ? = null ,
44
48
val outputDirectory : String = Paths .get(separator, "tmp").toString(),
45
49
private val previousFileProcessedExtension : String = " .processed" ,
46
- ) {
50
+ ) : AutoCloseable {
51
+ private val objectStore = Utils .getBackingStore(outputDirectory)
52
+ private var initialLoad: Boolean = true
53
+ private var delta: FileBasedDelta ? = null
54
+ private var deletedAssets: OffHeapAssetCache ? = null
55
+ private val reloadAll = reloadSemantic == " all"
56
+
47
57
/* *
48
- * Run the delta detection.
49
- * This includes: determining which assets should be deleted, deleting those assets, and updating
50
- * the persistent connection cache by removing any deleted assets and creating / updating any assets
51
- * that were created or modified.
52
- *
53
- * @param modifiedAssets list of assets that were modified by the processing up to the point of this delta detection
58
+ * Calculate any delta from the provided file context.
54
59
*/
55
- fun run (modifiedAssets : OffHeapAssetCache ? = null) {
56
- var deletedAssets: OffHeapAssetCache ? = null
60
+ fun calculate () {
57
61
if (semantic == " full" ) {
58
62
if (qualifiedNamePrefix.isNullOrBlank()) {
59
- logger.warn { " Unable to determine qualifiedName prefix, will not delete any assets ." }
63
+ logger.warn { " Unable to determine qualifiedName prefix, cannot calculate any delta ." }
60
64
} else {
61
65
val purgeAssets = removalType == " purge"
62
66
val assetRootName = preprocessedDetails.assetRootName
63
67
val previousFileLocation = " $previousFilesPrefix /$qualifiedNamePrefix "
64
- val objectStore = Utils .getBackingStore(outputDirectory)
65
68
val previousFile =
66
69
if (previousFilePreprocessor != null && previousFilePreprocessor.filename.isNotBlank()) {
67
70
transformPreviousRaw(assetRootName, previousFilePreprocessor)
68
71
} else {
69
72
objectStore.copyLatestFrom(previousFileLocation, previousFileProcessedExtension, outputDirectory)
70
73
}
71
74
if (previousFile.isNotBlank()) {
72
- // If there was a previous file, calculate the delta to see what we need
73
- // to delete
74
- val assetRemover =
75
- AssetRemover (
75
+ // If there was a previous file, calculate the delta (changes + deletions)
76
+ initialLoad = false
77
+ delta =
78
+ FileBasedDelta (
76
79
ConnectionCache .getIdentityMap(),
77
80
resolver,
78
81
logger,
79
82
typesToRemove.toList(),
80
83
qualifiedNamePrefix,
81
84
purgeAssets,
85
+ ! reloadAll,
82
86
outputDirectory,
83
87
)
84
- assetRemover.calculateDeletions(preprocessedDetails.preprocessedFile, previousFile)
85
- if (assetRemover.hasAnythingToDelete()) {
86
- // Note: this will update the persistent connection cache for both adds and deletes
87
- deletedAssets = assetRemover.deleteAssets()
88
- }
88
+ delta!! .calculateDelta(preprocessedDetails.preprocessedFile, previousFile)
89
89
} else {
90
90
logger.info { " No previous file found, treated it as an initial load." }
91
91
}
92
- // Copy processed files to specified location in object storage for future comparison purposes
93
- uploadToBackingStore(objectStore, preprocessedDetails.preprocessedFile, qualifiedNamePrefix, previousFileProcessedExtension)
94
92
}
95
93
}
94
+ }
95
+
96
+ /* *
97
+ * Resolve the asset represented by a row of values in a CSV to an asset identity.
98
+ *
99
+ * @param values row of values for that asset from the CSV
100
+ * @param header order of column names in the CSV file being processed
101
+ * @return a unique asset identity for that row of the CSV
102
+ */
103
+ @Throws(IOException ::class )
104
+ fun resolveAsset (
105
+ values : List <String >,
106
+ header : List <String >,
107
+ ): AssetIdentity ? {
108
+ return delta?.resolveAsset(values, header)
109
+ }
110
+
111
+ /* *
112
+ * Determine whether the provided asset identity should be processed (true) or skipped (false).
113
+ *
114
+ * @param identity of the asset to check whether reloading should occur
115
+ * @return true if the asset with this identity should be reloaded, otherwise false
116
+ */
117
+ fun reloadAsset (identity : AssetIdentity ): Boolean {
118
+ if (! reloadAll) {
119
+ return delta?.assetsToReload?.containsKey(identity) ? : true
120
+ }
121
+ return true
122
+ }
123
+
124
+ /* *
125
+ * Delete any assets that were detected by the delta to be deleted.
126
+ */
127
+ fun processDeletions () {
128
+ if (! initialLoad && delta!! .hasAnythingToDelete()) {
129
+ // Note: this will update the persistent connection cache for both adds and deletes
130
+ deletedAssets = delta!! .deleteAssets()
131
+ }
132
+ }
133
+
134
+ /* *
135
+ * Upload the latest processed file to the backing store, to persist the state for the next run.
136
+ */
137
+ fun uploadStateToBackingStore () {
138
+ if (! qualifiedNamePrefix.isNullOrBlank()) {
139
+ // Copy processed files to specified location in object storage for future comparison purposes
140
+ uploadToBackingStore(objectStore, preprocessedDetails.preprocessedFile, qualifiedNamePrefix, previousFileProcessedExtension)
141
+ }
142
+ }
143
+
144
+ /* *
145
+ * Update the persistent connection cache with details of any assets that were added or removed.
146
+ *
147
+ * @param modifiedAssets cache of assets that were created or modified (whether by initial processing or reloading)
148
+ */
149
+ fun updateConnectionCache (modifiedAssets : OffHeapAssetCache ? = null) {
96
150
// Update the connection cache with any changes (added and / or removed assets)
97
151
Utils .updateConnectionCache(
98
152
added = modifiedAssets,
@@ -101,6 +155,33 @@ class DeltaProcessor(
101
155
)
102
156
}
103
157
158
+ /* * {@inheritDoc} */
159
+ override fun close () {
160
+ uploadStateToBackingStore()
161
+ var exception: Exception ? = null
162
+ if (delta != null ) {
163
+ try {
164
+ delta!! .close()
165
+ } catch (e: Exception ) {
166
+ exception = e
167
+ }
168
+ }
169
+ if (deletedAssets != null ) {
170
+ try {
171
+ deletedAssets!! .close()
172
+ } catch (e: Exception ) {
173
+ if (exception != null ) {
174
+ exception.addSuppressed(e)
175
+ } else {
176
+ exception = e
177
+ }
178
+ }
179
+ }
180
+ if (exception != null ) {
181
+ throw exception
182
+ }
183
+ }
184
+
104
185
/* *
105
186
* Upload the file used to load the assets to Atlan backing store.
106
187
*
0 commit comments