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