1 package com.atlassian.maven.plugins.amps;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.StringReader;
6 import java.util.List;
7
8 import com.atlassian.plugins.codegen.AmpsSystemPropertyVariable;
9 import com.atlassian.plugins.codegen.ArtifactDependency;
10 import com.atlassian.plugins.codegen.ArtifactId;
11 import com.atlassian.plugins.codegen.BundleInstruction;
12 import com.atlassian.plugins.codegen.MavenPlugin;
13 import com.atlassian.plugins.codegen.PluginProjectChangeset;
14 import com.atlassian.plugins.codegen.ProjectRewriter;
15 import com.atlassian.plugins.codegen.VersionId;
16
17 import com.google.common.base.Predicate;
18 import com.google.common.collect.ImmutableList;
19 import com.google.common.collect.ImmutableSet;
20 import com.google.common.collect.Iterables;
21 import com.google.common.collect.Ordering;
22
23 import org.apache.commons.io.IOUtils;
24 import org.apache.commons.io.output.XmlStreamWriter;
25 import org.apache.maven.model.Dependency;
26 import org.apache.maven.model.Model;
27 import org.apache.maven.model.Plugin;
28 import org.apache.maven.model.PluginExecution;
29 import org.apache.maven.plugin.logging.Log;
30 import org.apache.maven.plugin.logging.SystemStreamLog;
31 import org.apache.maven.plugins.shade.pom.PomWriter;
32 import org.apache.maven.project.MavenProject;
33 import org.codehaus.plexus.util.xml.Xpp3Dom;
34 import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
35 import org.dom4j.Document;
36 import org.dom4j.DocumentHelper;
37 import org.dom4j.Element;
38 import org.dom4j.Node;
39
40 import static com.atlassian.fugue.Option.none;
41 import static com.atlassian.fugue.Option.option;
42 import static com.atlassian.fugue.Option.some;
43 import static com.google.common.base.Preconditions.checkNotNull;
44 import static com.google.common.collect.Iterables.any;
45 import static com.google.common.collect.Iterables.concat;
46
47
48
49
50
51
52 public class MavenProjectRewriter implements ProjectRewriter
53 {
54 private static final ImmutableSet<String> AMPS_PLUGIN_IDS =
55 ImmutableSet.of("maven-amps-plugin",
56 "maven-bamboo-plugin",
57 "maven-confluence-plugin",
58 "maven-crowd-plugin",
59 "maven-fecru-plugin",
60 "maven-jira-plugin",
61 "maven-refapp-plugin");
62
63 private final Model model;
64 private final File pom;
65 private final Log log;
66
67 public MavenProjectRewriter(MavenProject project, Log log)
68 {
69 this.model = checkNotNull(project, "project").getModel();
70 this.pom = project.getFile();
71 this.log = checkNotNull(log, "log");
72 }
73
74 public MavenProjectRewriter(Model model, File pom)
75 {
76 this.model = checkNotNull(model, "model");
77 this.pom = checkNotNull(pom, "pom");
78 this.log = new SystemStreamLog();
79 }
80
81 @Override
82 public void applyChanges(PluginProjectChangeset changes) throws Exception
83 {
84 boolean modifyPom = false;
85
86 modifyPom |= applyDependencyChanges(changes.getItems(ArtifactDependency.class));
87 modifyPom |= applyMavenPluginChanges(changes.getItems(MavenPlugin.class));
88 modifyPom |= applyBundleInstructionChanges(changes.getItems(BundleInstruction.class));
89 modifyPom |= applyPluginArtifactChanges(changes.getItems(com.atlassian.plugins.codegen.PluginArtifact.class));
90 modifyPom |= applyAmpsSystemPropertyChanges(changes.getItems(AmpsSystemPropertyVariable.class));
91
92 if (modifyPom)
93 {
94 XmlStreamWriter writer = null;
95 try
96 {
97 writer = new XmlStreamWriter(pom);
98 PomWriter.write(writer, model, true);
99 }
100 catch (IOException e)
101 {
102 log.warn("Unable to write plugin-module dependencies to pom.xml", e);
103 }
104 finally
105 {
106 if (writer != null)
107 {
108 IOUtils.closeQuietly(writer);
109 }
110 }
111 }
112 }
113
114 @SuppressWarnings("unchecked")
115 private boolean applyDependencyChanges(Iterable<ArtifactDependency> dependencies)
116 {
117 boolean modified = false;
118 List<Dependency> originalDependencies = model.getDependencies();
119 for (ArtifactDependency descriptor : dependencies)
120 {
121 boolean alreadyExists = any(originalDependencies, dependencyArtifactId(descriptor.getGroupAndArtifactId()));
122 if (!alreadyExists)
123 {
124 modified = true;
125
126 Dependency newDependency = new Dependency();
127 newDependency.setGroupId(descriptor.getGroupAndArtifactId().getGroupId().get());
128 newDependency.setArtifactId(descriptor.getGroupAndArtifactId().getArtifactId());
129 newDependency.setVersion(descriptor.getVersionId().getVersionOrPropertyPlaceholder().get());
130 createVersionPropertyIfNecessary(descriptor.getVersionId());
131 newDependency.setScope(descriptor.getScope().name().toLowerCase());
132
133 model.addDependency(newDependency);
134 }
135 }
136 return modified;
137 }
138
139 private void createVersionPropertyIfNecessary(VersionId versionId)
140 {
141 for (String p : versionId.getPropertyName())
142 {
143 if (!model.getProperties().containsKey(p))
144 {
145 model.addProperty(p, versionId.getVersion().getOrElse(""));
146 }
147 }
148 }
149
150 private boolean applyMavenPluginChanges(Iterable<MavenPlugin> mavenPlugins) throws Exception
151 {
152 boolean modified = false;
153 @SuppressWarnings("unchecked")
154 List<Plugin> originalPlugins = model.getBuild().getPlugins();
155 for (MavenPlugin descriptor : mavenPlugins)
156 {
157 Document fragDoc = DocumentHelper.parseText("<root>" + descriptor.getXmlContent() + "</root>");
158 Predicate<Plugin> match = pluginArtifactId(descriptor.getGroupAndArtifactId());
159 if (Iterables.any(originalPlugins, match))
160 {
161 modified |= mergeMavenPluginConfig(Iterables.find(originalPlugins, match), fragDoc.getRootElement());
162 }
163 else
164 {
165 originalPlugins.add(toMavenPlugin(descriptor, fragDoc.getRootElement()));
166 modified = true;
167 }
168 }
169 return modified;
170 }
171
172 private static boolean mergeMavenPluginConfig(Plugin plugin, Element paramsDesc)
173 {
174 boolean modified = false;
175 for (Object node : paramsDesc.selectNodes("executions/execution"))
176 {
177 Element executionDesc = (Element) node;
178 if (!plugin.getExecutionsAsMap().containsKey(executionDesc.elementTextTrim("id")))
179 {
180 plugin.addExecution(toMavenPluginExecution(executionDesc));
181 modified = true;
182 }
183 }
184 return modified;
185 }
186
187 private Plugin toMavenPlugin(MavenPlugin descriptor, Element paramsDesc)
188 {
189 Plugin p = new Plugin();
190 p.setGroupId(descriptor.getGroupAndArtifactId().getGroupId().getOrElse((String)null));
191 p.setArtifactId(descriptor.getGroupAndArtifactId().getArtifactId());
192 if (descriptor.getVersionId().isDefined())
193 {
194 p.setVersion(descriptor.getVersionId().getVersionOrPropertyPlaceholder().get());
195 createVersionPropertyIfNecessary(descriptor.getVersionId());
196 }
197 p.setExtensions("true".equals(paramsDesc.elementText("extensions")));
198 for (Object configNode : paramsDesc.selectNodes("configuration"))
199 {
200 p.setConfiguration(toXpp3Dom((Element)configNode));
201 }
202 for (Object execNode : paramsDesc.selectNodes("executions/execution"))
203 {
204 p.addExecution(toMavenPluginExecution((Element)execNode));
205 }
206 return p;
207 }
208
209 private static PluginExecution toMavenPluginExecution(Element executionDesc)
210 {
211 PluginExecution pe = new PluginExecution();
212 pe.setId(executionDesc.elementTextTrim("id"));
213 pe.setPhase(executionDesc.elementTextTrim("phase"));
214 for (Object goalNode : executionDesc.selectNodes("goals/goal"))
215 {
216 pe.addGoal(((Node)goalNode).getText());
217 }
218 for (Object configNode : executionDesc.selectNodes("configuration"))
219 {
220 pe.setConfiguration(toXpp3Dom((Element)configNode));
221 }
222 return pe;
223 }
224
225 private boolean applyBundleInstructionChanges(Iterable<BundleInstruction> instructions)
226 {
227 Xpp3Dom configRoot = getAmpsPluginConfiguration();
228 boolean modified = false;
229 Xpp3Dom instructionsRoot = getOrCreateElement(configRoot, "instructions");
230 for (BundleInstruction instruction : instructions)
231 {
232 String categoryName = instruction.getCategory().getElementName();
233 Xpp3Dom categoryElement = getOrCreateElement(instructionsRoot, categoryName);
234 String body = categoryElement.getValue();
235 Iterable<BundleInstruction> instructionList = parseInstructions(instruction.getCategory(), (body == null) ? "" : body);
236 if (any(instructionList, bundleInstructionPackageName(instruction.getPackageName())))
237 {
238 continue;
239 }
240 Iterable<BundleInstruction> newList = new InstructionPackageOrdering().sortedCopy(
241 concat(instructionList, ImmutableList.of(instruction)));
242 categoryElement.setValue(writeInstructions(newList));
243 modified = true;
244 }
245 return modified;
246 }
247
248 private static Iterable<BundleInstruction> parseInstructions(BundleInstruction.Category category, String body)
249 {
250 ImmutableList.Builder<BundleInstruction> ret = ImmutableList.builder();
251 for (String instructionLine : body.split(","))
252 {
253 String[] instructionParts = instructionLine.trim().split(";");
254 if (instructionParts.length == 1)
255 {
256 ret.add(new BundleInstruction(category, instructionParts[0], none(String.class)));
257 }
258 else if (instructionParts.length == 2 && instructionParts[1].startsWith("version=\"") && instructionParts[1].endsWith("\""))
259 {
260 String version = instructionParts[1].substring(9, instructionParts[1].length() - 1);
261 ret.add(new BundleInstruction(category, instructionParts[0], some(version)));
262 }
263 }
264 return ret.build();
265 }
266
267 private static String writeInstructions(Iterable<BundleInstruction> instructions)
268 {
269 StringBuilder ret = new StringBuilder("\n");
270 for (BundleInstruction instruction : instructions)
271 {
272 if (ret.length() > 1)
273 {
274 ret.append(",\n");
275 }
276 ret.append(instruction.getPackageName());
277 for (String version : instruction.getVersion())
278 {
279 ret.append(";version=\"").append(version).append("\"");
280 }
281 }
282 return ret.append("\n").toString();
283 }
284
285 private boolean applyPluginArtifactChanges(Iterable<com.atlassian.plugins.codegen.PluginArtifact> pluginArtifacts)
286 {
287 Xpp3Dom configRoot = getAmpsPluginConfiguration();
288 boolean modified = false;
289 for (com.atlassian.plugins.codegen.PluginArtifact p : pluginArtifacts)
290 {
291 Xpp3Dom artifactsRoot = getOrCreateElement(configRoot, p.getType().getElementName() + "s");
292 List<Xpp3Dom> existingItems = ImmutableList.copyOf(artifactsRoot.getChildren(p.getType().getElementName()));
293 if (!any(existingItems, artifactElement(p.getGroupAndArtifactId())))
294 {
295 artifactsRoot.addChild(toArtifactElement(p));
296 modified = true;
297 }
298 }
299 return modified;
300 }
301
302 private boolean applyAmpsSystemPropertyChanges(Iterable<AmpsSystemPropertyVariable> propertyVariables)
303 {
304 Xpp3Dom configRoot = getAmpsPluginConfiguration();
305 boolean modified = false;
306 for (AmpsSystemPropertyVariable propertyVariable : propertyVariables)
307 {
308 Xpp3Dom variablesRoot = getOrCreateElement(configRoot, "systemPropertyVariables");
309 if (variablesRoot.getChild(propertyVariable.getName()) == null)
310 {
311 Xpp3Dom variableElement = new Xpp3Dom(propertyVariable.getName());
312 variableElement.setValue(propertyVariable.getValue());
313 variablesRoot.addChild(variableElement);
314 modified = true;
315 }
316 }
317 return modified;
318 }
319
320 private Xpp3Dom toArtifactElement(com.atlassian.plugins.codegen.PluginArtifact pluginArtifact)
321 {
322 Xpp3Dom ret = new Xpp3Dom(pluginArtifact.getType().getElementName());
323 for (String groupId : pluginArtifact.getGroupAndArtifactId().getGroupId())
324 {
325 Xpp3Dom ge = new Xpp3Dom("groupId");
326 ge.setValue(groupId);
327 ret.addChild(ge);
328 }
329 Xpp3Dom ae = new Xpp3Dom("artifactId");
330 ae.setValue(pluginArtifact.getGroupAndArtifactId().getArtifactId());
331 ret.addChild(ae);
332 if (pluginArtifact.getVersionId().isDefined())
333 {
334 Xpp3Dom ve = new Xpp3Dom("version");
335 ve.setValue(pluginArtifact.getVersionId().getVersionOrPropertyPlaceholder().get());
336 createVersionPropertyIfNecessary(pluginArtifact.getVersionId());
337 ret.addChild(ve);
338 }
339 return ret;
340 }
341
342 @SuppressWarnings("unchecked")
343 private Plugin findAmpsPlugin()
344 {
345 for (Plugin p : (List<Plugin>) model.getBuild().getPlugins())
346 {
347 if (p.getGroupId().equals("com.atlassian.maven.plugins")
348 && AMPS_PLUGIN_IDS.contains(p.getArtifactId()))
349 {
350 return p;
351 }
352 }
353 throw new IllegalStateException("Could not find AMPS plugin element in POM");
354 }
355
356 private Xpp3Dom getAmpsPluginConfiguration()
357 {
358 Plugin ampsPlugin = findAmpsPlugin();
359 Xpp3Dom configRoot = (Xpp3Dom) ampsPlugin.getConfiguration();
360 if (configRoot == null)
361 {
362 configRoot = new Xpp3Dom("configuration");
363 ampsPlugin.setConfiguration(configRoot);
364 }
365 return configRoot;
366 }
367
368 private static Xpp3Dom getOrCreateElement(Xpp3Dom container, String name)
369 {
370 Xpp3Dom ret = container.getChild(name);
371 if (ret == null)
372 {
373 ret = new Xpp3Dom(name);
374 container.addChild(ret);
375 }
376 return ret;
377 }
378
379 private static Xpp3Dom toXpp3Dom(Element dom4JElement)
380 {
381 try
382 {
383 return Xpp3DomBuilder.build(new StringReader(dom4JElement.asXML()));
384 }
385 catch (Exception e)
386 {
387
388 throw new IllegalStateException();
389 }
390 }
391
392 private static Predicate<Dependency> dependencyArtifactId(final ArtifactId artifactId)
393 {
394 return new Predicate<Dependency>()
395 {
396 public boolean apply(Dependency d)
397 {
398 return (artifactId.getGroupId().equals(option(d.getGroupId()))
399 && artifactId.getArtifactId().equals(d.getArtifactId()));
400 }
401 };
402 }
403
404 private static Predicate<Plugin> pluginArtifactId(final ArtifactId artifactId)
405 {
406 return new Predicate<Plugin>()
407 {
408 public boolean apply(Plugin p)
409 {
410 return artifactId.getArtifactId().equals(p.getArtifactId())
411 && (artifactId.getGroupId().equals(option(p.getGroupId()))
412 || (!artifactId.getGroupId().isDefined() && "org.apache.maven.plugins".equals(p.getGroupId())));
413 }
414 };
415 }
416
417 private static Predicate<Xpp3Dom> artifactElement(final ArtifactId artifactId)
418 {
419 return new Predicate<Xpp3Dom>()
420 {
421 public boolean apply(Xpp3Dom e)
422 {
423 return (e.getChild("artifactId") != null)
424 && e.getChild("artifactId").getValue().equals(artifactId.getArtifactId())
425 && (((e.getChild("groupId") == null) && !artifactId.getGroupId().isDefined())
426 || (e.getChild("groupId") != null) && artifactId.getGroupId().equals(some(e.getChild("groupId").getValue())));
427 }
428 };
429 }
430
431 private static Predicate<BundleInstruction> bundleInstructionPackageName(final String packageName)
432 {
433 return new Predicate<BundleInstruction>()
434 {
435 public boolean apply(BundleInstruction i)
436 {
437 return i.getPackageName().equals(packageName);
438 }
439 };
440 }
441
442 private static class InstructionPackageOrdering extends Ordering<BundleInstruction>
443 {
444 @Override
445 public int compare(BundleInstruction first, BundleInstruction second)
446 {
447 return first.getPackageName().compareTo(second.getPackageName());
448 }
449 }
450 }