Skip to content

Commit 45f9700

Browse files
feat: add MigrationContext and Migration Summary Report (#27)
* wip: add MigrationContext and Migration Summary Report * refactor: use parameter instead of singleton * refactor: minor adjustments after review * feat: added file operations recording to all migrators * feat: resolve pr comments
1 parent a876c9f commit 45f9700

17 files changed

+480
-66
lines changed

migration/src/main/java/com/intershop/customization/migration/Migrator.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@
44
import java.util.Arrays;
55
import java.util.Optional;
66

7+
import com.intershop.customization.migration.common.MigrationContext;
78
import com.intershop.customization.migration.common.MigrationPreparer;
89
import com.intershop.customization.migration.common.MigrationStep;
910
import com.intershop.customization.migration.common.MigrationStepFolder;
1011
import com.intershop.customization.migration.git.GitInitializationException;
1112
import com.intershop.customization.migration.git.GitRepository;
1213

14+
import org.slf4j.Logger;
1315
import org.slf4j.LoggerFactory;
1416

1517
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
1618
public class Migrator
1719
{
18-
public static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(Migrator.class);
20+
public static final Logger LOGGER = LoggerFactory.getLogger(Migrator.class);
1921
private static final int POS_TASK = 0;
2022
private static final int POS_PATH = 1;
2123
private static final int POS_STEPS = 2;
@@ -24,6 +26,7 @@ public class Migrator
2426

2527
private final File migrationStepFolder;
2628
private Optional<GitRepository> gitRepository = Optional.empty();
29+
private MigrationContext context = new MigrationContext();
2730

2831
/**
2932
* Initializes the migrator
@@ -125,8 +128,7 @@ protected void migrateProjects(File rootProject)
125128
for (MigrationStep step: steps.getSteps())
126129
{
127130
MigrationPreparer migrator = step.getMigrator();
128-
129-
migrator.migrateRoot(rootProject.toPath());
131+
migrator.migrateRoot(rootProject.toPath(), context);
130132

131133
File[] files = rootProject.listFiles();
132134
if (files == null)
@@ -137,11 +139,13 @@ protected void migrateProjects(File rootProject)
137139
{
138140
if (cartridgeDir.isDirectory() && !cartridgeDir.getName().startsWith(".") && (new File(cartridgeDir, "build.gradle")).exists())
139141
{
140-
migrator.migrate(cartridgeDir.toPath());
142+
migrator.migrate(cartridgeDir.toPath(), context);
141143
}
142144
}
143145
gitRepository.ifPresent(r -> commitChanges(r, step));
144146
}
147+
148+
LOGGER.info(context.generateSummaryReport());
145149
}
146150

147151
/**
@@ -155,9 +159,11 @@ protected void migrateProject(File projectDir)
155159
{
156160
MigrationPreparer migrator = step.getMigrator();
157161

158-
migrator.migrate(projectDir.toPath());
162+
migrator.migrate(projectDir.toPath(), context);
159163
gitRepository.ifPresent(r -> commitChanges(r, step));
160164
}
165+
166+
LOGGER.info(context.generateSummaryReport());
161167
}
162168

163169
/**
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package com.intershop.customization.migration.common;
2+
3+
import java.nio.file.Path;
4+
import java.util.ArrayList;
5+
import java.util.Collections;
6+
import java.util.EnumMap;
7+
import java.util.HashMap;
8+
import java.util.List;
9+
import java.util.Map;
10+
11+
import org.slf4j.Logger;
12+
import org.slf4j.LoggerFactory;
13+
14+
/**
15+
* Global context for migration operations that tracks file and folder operations. Provides a way for migrators to
16+
* report success, skipped, unknown, and failed operations.
17+
*/
18+
public class MigrationContext
19+
{
20+
private final Logger LOGGER = LoggerFactory.getLogger(getClass());
21+
22+
public enum OperationType
23+
{
24+
MOVE, DELETE, CREATE, MODIFY
25+
}
26+
27+
public enum OperationStatus
28+
{
29+
SUCCESS, SKIPPED, UNKNOWN, FAILED
30+
}
31+
32+
public record Operation(OperationType type, Path source, Path target, OperationStatus status, String message)
33+
{
34+
@Override
35+
public String toString()
36+
{
37+
return String.format("%s %s: %s -> %s (%s)", status, type, source != null ? source : "N/A",
38+
target != null ? target : "N/A", message != null ? message : "");
39+
}
40+
}
41+
42+
// Store operations by cartridge/project
43+
private final Map<String, List<Operation>> operationsByProject = new HashMap<>();
44+
private final Map<String, Map<OperationStatus, Integer>> statisticsByProject = new HashMap<>();
45+
46+
/**
47+
* Record a file/folder operation
48+
*
49+
* @param projectName Project or cartridge name
50+
* @param type Operation type (MOVE, DELETE, etc.)
51+
* @param source Source path (can be null for CREATE operations)
52+
* @param target Target path (can be null for DELETE operations)
53+
* @param status success, skipped, unknown, or failed
54+
* @param message Optional message explaining the operation's status
55+
*/
56+
public void recordOperation(String projectName, OperationType type, Path source, Path target,
57+
OperationStatus status, String message)
58+
{
59+
Operation op = new Operation(type, source, target, status, message);
60+
61+
operationsByProject.computeIfAbsent(projectName, k -> new ArrayList<>()).add(op);
62+
statisticsByProject.computeIfAbsent(projectName, k -> new EnumMap<>(OperationStatus.class))
63+
.merge(status, 1, Integer::sum);
64+
65+
if (status == OperationStatus.FAILED)
66+
{
67+
LOGGER.warn("Failed operation in {}: {} - {}", projectName, op, message);
68+
}
69+
}
70+
71+
/**
72+
* Record a successful operation
73+
*/
74+
public void recordSuccess(String projectName, OperationType type, Path source, Path target)
75+
{
76+
recordOperation(projectName, type, source, target, OperationStatus.SUCCESS, null);
77+
}
78+
79+
/**
80+
* Record a skipped operation
81+
*/
82+
public void recordSkipped(String projectName, OperationType type, Path source, Path target, String reason)
83+
{
84+
recordOperation(projectName, type, source, target, OperationStatus.SKIPPED, reason);
85+
}
86+
87+
/**
88+
* Record an unknown operation
89+
*/
90+
public void recordUnknown(String projectName, OperationType type, Path source, Path target, String reason)
91+
{
92+
recordOperation(projectName, type, source, target, OperationStatus.UNKNOWN, reason);
93+
}
94+
95+
/**
96+
* Record a failed operation
97+
*/
98+
public void recordFailure(String projectName, OperationType type, Path source, Path target, String error)
99+
{
100+
recordOperation(projectName, type, source, target, OperationStatus.FAILED, error);
101+
}
102+
103+
/**
104+
* Generate a summary report of all operations
105+
*/
106+
public String generateSummaryReport()
107+
{
108+
StringBuilder report = new StringBuilder("Migration Summary Report:\n");
109+
110+
for (String project : operationsByProject.keySet())
111+
{
112+
Map<OperationStatus, Integer> stats = statisticsByProject.getOrDefault(project, Collections.emptyMap());
113+
int success = stats.getOrDefault(OperationStatus.SUCCESS, 0);
114+
int skipped = stats.getOrDefault(OperationStatus.SKIPPED, 0);
115+
int unknown = stats.getOrDefault(OperationStatus.UNKNOWN, 0);
116+
int failed = stats.getOrDefault(OperationStatus.FAILED, 0);
117+
int operationsSum = success + skipped + unknown + failed;
118+
119+
report.append(String.format("Project '%s': %d operations (%d successful, %d skipped, %d unknown, %d failed)%n",
120+
project, operationsSum, success, skipped, unknown, failed));
121+
122+
// List unknown operations for quick review
123+
if (unknown > 0)
124+
{
125+
report.append(" Unknown operations:\n");
126+
operationsByProject.get(project)
127+
.stream()
128+
.filter(op -> op.status() == OperationStatus.UNKNOWN)
129+
.forEach(op -> report.append(" - ").append(op).append("\n"));
130+
}
131+
132+
// List failed operations for quick review
133+
if (failed > 0)
134+
{
135+
report.append(" Failed operations:\n");
136+
operationsByProject.get(project)
137+
.stream()
138+
.filter(op -> op.status() == OperationStatus.FAILED)
139+
.forEach(op -> report.append(" - ").append(op).append("\n"));
140+
}
141+
}
142+
143+
return report.toString();
144+
}
145+
}

migration/src/main/java/com/intershop/customization/migration/common/MigrationPreparer.java

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,79 @@
22

33
import java.nio.file.Path;
44

5+
/**
6+
* Interface for implementing migration preparers that handle specific migration tasks.
7+
* </p>
8+
* A migration preparer is responsible for transforming resources according to migration rules. Implementations should
9+
* provide logic for modifying directories, files, and other resources to ensure compatibility with target platform
10+
* versions.
11+
*/
512
public interface MigrationPreparer
613
{
714
/**
8-
* @param resource contains information, what needs to be migrated
15+
* Migrates a resource.
16+
*
17+
* @param resource Path to the resource that needs to be migrated
18+
*
19+
* @deprecated Use {@link #migrate(Path, MigrationContext)} instead.
920
*/
10-
void migrate(Path resource);
21+
@Deprecated(forRemoval = true)
22+
default void migrate(Path resource)
23+
{
24+
}
25+
26+
/**
27+
* Migrates resources at the root level.
28+
* This is typically used for project-wide migrations that need to be applied once.
29+
*
30+
* @param resource Path to the root directory that needs to be processed
31+
*
32+
* @deprecated Use {@link #migrateRoot(Path, MigrationContext)} instead.
33+
*/
34+
@Deprecated(forRemoval = true)
35+
default void migrateRoot(Path resource)
36+
{
37+
}
1138

1239
/**
13-
* @param resource contains information, what needs to be migrated on root directory
40+
* Migrates a resource with context tracking.
41+
* It allows recording success, failures, and other metrics.
42+
*
43+
* @param resource Path to the resource that needs to be migrated
44+
* @param context The migration context for tracking operations and their results
1445
*/
15-
default void migrateRoot(Path resource) {}
46+
default void migrate(Path resource, MigrationContext context)
47+
{
48+
migrate(resource);
49+
}
50+
51+
/**
52+
* Migrates resources at the root level with context tracking.
53+
* It allows recording success, failures, and other metrics.
54+
*
55+
* @param resource Path to the root directory that needs to be processed
56+
* @param context The migration context for tracking operations and their results
57+
*/
58+
default void migrateRoot(Path resource, MigrationContext context)
59+
{
60+
migrateRoot(resource);
61+
}
1662

1763
/**
18-
* Define options for migrator
19-
* @param step assigns a migration step to the preparer
64+
* Configures this preparer with a migration step, which may contain options and other configuration details needed
65+
* for the migration process.
66+
*
67+
* @param step The migration step containing configuration options for this preparer
2068
*/
2169
default void setStep(MigrationStep step)
2270
{
2371
}
2472

2573
/**
26-
* Gets the name of the resource (cartridge).
27-
* @param resource the resource to get the name from
28-
* @return the name of the resource (cartridge)
74+
* Extracts the name of the resource from its path. For cartridge migrations, this is typically the cartridge name.
75+
*
76+
* @param resource The resource path to extract the name from
77+
* @return The name of the resource, or null if the resource path is null
2978
*/
3079
default String getResourceName(Path resource)
3180
{

migration/src/main/java/com/intershop/customization/migration/file/MoveFiles.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package com.intershop.customization.migration.file;
22

3+
import static com.intershop.customization.migration.common.MigrationContext.OperationType.MOVE;
4+
import static com.intershop.customization.migration.file.MoveFilesConstants.PLACEHOLDER_CARTRIDGE_NAME;
5+
6+
import com.intershop.customization.migration.common.MigrationContext;
37
import com.intershop.customization.migration.common.MigrationPreparer;
48
import com.intershop.customization.migration.common.MigrationStep;
59
import org.slf4j.Logger;
@@ -12,8 +16,6 @@
1216
import java.util.Collections;
1317
import java.util.Map;
1418

15-
import static com.intershop.customization.migration.file.MoveFilesConstants.PLACEHOLDER_CARTRIDGE_NAME;
16-
1719
/**
1820
* This migration step is used to migrate the files structure of a cartridge
1921
* by moving files from their old location to the new. The files to
@@ -60,7 +62,8 @@ public void setStep(MigrationStep step)
6062
this.filterConfiguration = step.getOption(YAML_KEY_FILTER_MAP);
6163
}
6264

63-
public void migrate(Path cartridgeDir)
65+
@Override
66+
public void migrate(Path cartridgeDir, MigrationContext context)
6467
{
6568
String cartridgeName = getResourceName(cartridgeDir);
6669
LOGGER.info("Processing cartridge {}.", cartridgeName);
@@ -99,9 +102,12 @@ public void migrate(Path cartridgeDir)
99102
try
100103
{
101104
Files.move(file.toPath(), targetFile);
105+
context.recordSuccess(cartridgeName, MOVE, file.toPath(), targetFile);
102106
}
103107
catch(IOException e)
104108
{
109+
context.recordFailure(cartridgeName, MOVE, file.toPath(), targetFile,
110+
"Can't move file: " + e.getMessage());
105111
throw new RuntimeException(e);
106112
}
107113
}

0 commit comments

Comments
 (0)