1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.cling.invoker.mvnup.goals;
20
21 import java.nio.file.Path;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.Map;
25 import java.util.Set;
26
27 import eu.maveniverse.domtrip.Document;
28 import eu.maveniverse.domtrip.Editor;
29 import eu.maveniverse.domtrip.Element;
30 import eu.maveniverse.domtrip.maven.MavenPomElements;
31 import org.apache.maven.api.Lifecycle;
32 import org.apache.maven.api.cli.mvnup.UpgradeOptions;
33 import org.apache.maven.api.di.Named;
34 import org.apache.maven.api.di.Priority;
35 import org.apache.maven.api.di.Singleton;
36 import org.apache.maven.cling.invoker.mvnup.UpgradeContext;
37
38 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.BUILD;
39 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.EXECUTIONS;
40 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.MODEL_VERSION;
41 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.MODULE;
42 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.MODULES;
43 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PLUGIN;
44 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PLUGINS;
45 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PLUGIN_MANAGEMENT;
46 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PROFILE;
47 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PROFILES;
48 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.SUBPROJECT;
49 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.SUBPROJECTS;
50 import static eu.maveniverse.domtrip.maven.MavenPomElements.ModelVersions.MODEL_VERSION_4_0_0;
51 import static eu.maveniverse.domtrip.maven.MavenPomElements.ModelVersions.MODEL_VERSION_4_1_0;
52 import static eu.maveniverse.domtrip.maven.MavenPomElements.Namespaces.MAVEN_4_0_0_NAMESPACE;
53 import static eu.maveniverse.domtrip.maven.MavenPomElements.Namespaces.MAVEN_4_1_0_NAMESPACE;
54 import static org.apache.maven.cling.invoker.mvnup.goals.ModelVersionUtils.getSchemaLocationForModelVersion;
55
56
57
58
59
60 @Named
61 @Singleton
62 @Priority(40)
63 public class ModelUpgradeStrategy extends AbstractUpgradeStrategy {
64
65 public ModelUpgradeStrategy() {
66
67 }
68
69 @Override
70 public boolean isApplicable(UpgradeContext context) {
71 UpgradeOptions options = getOptions(context);
72
73
74 if (options.all().orElse(false)) {
75 return true;
76 }
77
78 String targetModel = determineTargetModelVersion(context);
79
80 return !MODEL_VERSION_4_0_0.equals(targetModel);
81 }
82
83 @Override
84 public String getDescription() {
85 return "Upgrading POM model version";
86 }
87
88 @Override
89 public UpgradeResult doApply(UpgradeContext context, Map<Path, Document> pomMap) {
90 String targetModelVersion = determineTargetModelVersion(context);
91
92 Set<Path> processedPoms = new HashSet<>();
93 Set<Path> modifiedPoms = new HashSet<>();
94 Set<Path> errorPoms = new HashSet<>();
95
96 for (Map.Entry<Path, Document> entry : pomMap.entrySet()) {
97 Path pomPath = entry.getKey();
98 Document pomDocument = entry.getValue();
99 processedPoms.add(pomPath);
100
101 String currentVersion = ModelVersionUtils.detectModelVersion(pomDocument);
102 context.info(pomPath + " (current: " + currentVersion + ")");
103 context.indent();
104
105 try {
106 if (currentVersion.equals(targetModelVersion)) {
107 context.success("Already at target version " + targetModelVersion);
108 } else if (ModelVersionUtils.canUpgrade(currentVersion, targetModelVersion)) {
109 context.action("Upgrading from " + currentVersion + " to " + targetModelVersion);
110
111
112 context.indent();
113 try {
114 Document upgradedDocument =
115 performModelUpgrade(pomDocument, context, currentVersion, targetModelVersion);
116
117 pomMap.put(pomPath, upgradedDocument);
118 } finally {
119 context.unindent();
120 }
121 context.success("Model upgrade completed");
122 modifiedPoms.add(pomPath);
123 } else {
124
125 context.failure("Cannot upgrade from " + currentVersion + " to " + targetModelVersion);
126 errorPoms.add(pomPath);
127 }
128 } catch (Exception e) {
129 context.failure("Model upgrade failed: " + e.getMessage());
130 errorPoms.add(pomPath);
131 } finally {
132 context.unindent();
133 }
134 }
135
136 return new UpgradeResult(processedPoms, modifiedPoms, errorPoms);
137 }
138
139
140
141
142
143
144 private Document performModelUpgrade(
145 Document pomDocument, UpgradeContext context, String currentVersion, String targetModelVersion) {
146
147 Editor editor = new Editor(pomDocument);
148
149
150 Element root = editor.root();
151 Element modelVersionElement = root.child(MODEL_VERSION).orElse(null);
152 if (modelVersionElement != null) {
153 editor.setTextContent(modelVersionElement, targetModelVersion);
154 context.detail("Updated modelVersion to " + targetModelVersion);
155 } else {
156
157 DomUtils.insertContentElement(root, MODEL_VERSION, targetModelVersion);
158 context.detail("Added modelVersion " + targetModelVersion);
159 }
160
161
162 upgradeNamespaceAndSchemaLocation(editor, context, targetModelVersion);
163
164
165 if (ModelVersionUtils.isVersionGreaterOrEqual(targetModelVersion, MODEL_VERSION_4_1_0)) {
166 convertModulesToSubprojects(editor, context);
167 upgradeDeprecatedPhases(editor, context);
168 }
169
170
171 return editor.document();
172 }
173
174
175
176
177 private void upgradeNamespaceAndSchemaLocation(Editor editor, UpgradeContext context, String targetModelVersion) {
178 Element root = editor.root();
179 if (root == null) {
180 return;
181 }
182
183
184 String targetNamespace = getNamespaceForModelVersion(targetModelVersion);
185
186
187
188 root.attribute("xmlns", targetNamespace);
189 context.detail("Updated namespace to " + targetNamespace);
190
191
192 String currentSchemaLocation = root.attribute("xsi:schemaLocation");
193 if (currentSchemaLocation != null) {
194 String newSchemaLocation = getSchemaLocationForModelVersion(targetModelVersion);
195 root.attribute("xsi:schemaLocation", newSchemaLocation);
196 context.detail("Updated xsi:schemaLocation");
197 }
198 }
199
200
201
202
203 private void convertModulesToSubprojects(Editor editor, UpgradeContext context) {
204 Element root = editor.root();
205 if (root == null) {
206 return;
207 }
208
209
210 Element modulesElement = root.child(MODULES).orElse(null);
211 if (modulesElement != null) {
212
213
214 modulesElement.name(SUBPROJECTS);
215 context.detail("Converted <modules> to <subprojects>");
216
217
218 var moduleElements = modulesElement.children(MODULE).toList();
219 for (Element moduleElement : moduleElements) {
220 moduleElement.name(SUBPROJECT);
221 }
222
223 if (!moduleElements.isEmpty()) {
224 context.detail("Converted " + moduleElements.size() + " <module> elements to <subproject>");
225 }
226 }
227
228
229 Element profilesElement = root.child(PROFILES).orElse(null);
230 if (profilesElement != null) {
231 var profileElements = profilesElement.children(PROFILE).toList();
232 for (Element profileElement : profileElements) {
233 Element profileModulesElement = profileElement.child(MODULES).orElse(null);
234 if (profileModulesElement != null) {
235 profileModulesElement.name(SUBPROJECTS);
236
237 var profileModuleElements =
238 profileModulesElement.children(MODULE).toList();
239 for (Element moduleElement : profileModuleElements) {
240 moduleElement.name(SUBPROJECT);
241 }
242
243 if (!profileModuleElements.isEmpty()) {
244 context.detail("Converted " + profileModuleElements.size()
245 + " <module> elements to <subproject> in profiles");
246 }
247 }
248 }
249 }
250 }
251
252
253
254
255 private String determineTargetModelVersion(UpgradeContext context) {
256 UpgradeOptions options = getOptions(context);
257
258 if (options.modelVersion().isPresent()) {
259 return options.modelVersion().get();
260 } else if (options.all().orElse(false)) {
261 return MODEL_VERSION_4_1_0;
262 } else {
263 return MODEL_VERSION_4_0_0;
264 }
265 }
266
267
268
269
270 private String getNamespaceForModelVersion(String modelVersion) {
271 if (MavenPomElements.ModelVersions.MODEL_VERSION_4_2_0.equals(modelVersion)) {
272 return MavenPomElements.Namespaces.MAVEN_4_2_0_NAMESPACE;
273 } else if (MODEL_VERSION_4_1_0.equals(modelVersion)) {
274 return MAVEN_4_1_0_NAMESPACE;
275 } else {
276 return MAVEN_4_0_0_NAMESPACE;
277 }
278 }
279
280
281
282
283
284 private void upgradeDeprecatedPhases(Editor editor, UpgradeContext context) {
285
286 Map<String, String> phaseUpgrades = createPhaseUpgradeMap();
287
288 Element root = editor.root();
289 if (root == null) {
290 return;
291 }
292
293 int totalUpgrades = 0;
294
295
296 Element buildElement = root.child(BUILD).orElse(null);
297 if (buildElement != null) {
298 totalUpgrades += upgradePhaseElements(buildElement, phaseUpgrades, context);
299 }
300
301
302 Element profilesElement = root.child(PROFILES).orElse(null);
303 if (profilesElement != null) {
304 var profileElements = profilesElement.children(PROFILE).toList();
305 for (Element profileElement : profileElements) {
306 Element profileBuildElement = profileElement.child(BUILD).orElse(null);
307 if (profileBuildElement != null) {
308 totalUpgrades += upgradePhaseElements(profileBuildElement, phaseUpgrades, context);
309 }
310 }
311 }
312
313 if (totalUpgrades > 0) {
314 context.detail("Upgraded " + totalUpgrades + " deprecated phase name(s) to Maven 4 equivalents");
315 }
316 }
317
318
319
320
321
322 private Map<String, String> createPhaseUpgradeMap() {
323 Map<String, String> phaseUpgrades = new HashMap<>();
324
325
326 phaseUpgrades.put("pre-clean", Lifecycle.BEFORE + Lifecycle.Phase.CLEAN);
327 phaseUpgrades.put("post-clean", Lifecycle.AFTER + Lifecycle.Phase.CLEAN);
328
329
330 phaseUpgrades.put("pre-integration-test", Lifecycle.BEFORE + Lifecycle.Phase.INTEGRATION_TEST);
331 phaseUpgrades.put("post-integration-test", Lifecycle.AFTER + Lifecycle.Phase.INTEGRATION_TEST);
332
333
334 phaseUpgrades.put("pre-site", Lifecycle.BEFORE + Lifecycle.SITE);
335 phaseUpgrades.put("post-site", Lifecycle.AFTER + Lifecycle.SITE);
336
337 return phaseUpgrades;
338 }
339
340
341
342
343 private int upgradePhaseElements(Element buildElement, Map<String, String> phaseUpgrades, UpgradeContext context) {
344 if (buildElement == null) {
345 return 0;
346 }
347
348 int upgrades = 0;
349
350
351 Element pluginsElement = buildElement.child(PLUGINS).orElse(null);
352 if (pluginsElement != null) {
353 upgrades += upgradePhaseElementsInPlugins(pluginsElement, phaseUpgrades, context);
354 }
355
356
357 Element pluginManagementElement = buildElement.child(PLUGIN_MANAGEMENT).orElse(null);
358 if (pluginManagementElement != null) {
359 Element managedPluginsElement =
360 pluginManagementElement.child(PLUGINS).orElse(null);
361 if (managedPluginsElement != null) {
362 upgrades += upgradePhaseElementsInPlugins(managedPluginsElement, phaseUpgrades, context);
363 }
364 }
365
366 return upgrades;
367 }
368
369
370
371
372 private int upgradePhaseElementsInPlugins(
373 Element pluginsElement, Map<String, String> phaseUpgrades, UpgradeContext context) {
374 int upgrades = 0;
375
376 var pluginElements = pluginsElement.children(PLUGIN).toList();
377 for (Element pluginElement : pluginElements) {
378 Element executionsElement = pluginElement.child(EXECUTIONS).orElse(null);
379 if (executionsElement != null) {
380 var executionElements = executionsElement
381 .children(MavenPomElements.Elements.EXECUTION)
382 .toList();
383 for (Element executionElement : executionElements) {
384 Element phaseElement = executionElement
385 .child(MavenPomElements.Elements.PHASE)
386 .orElse(null);
387 if (phaseElement != null) {
388 String currentPhase = phaseElement.textContent().trim();
389 String newPhase = phaseUpgrades.get(currentPhase);
390 if (newPhase != null) {
391 phaseElement.textContent(newPhase);
392 context.detail("Upgraded phase: " + currentPhase + " → " + newPhase);
393 upgrades++;
394 }
395 }
396 }
397 }
398 }
399
400 return upgrades;
401 }
402 }