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