/plugin-module-codegen-engine/src/main/java/com/atlassian/plugins/codegen/MavenProjectRewriter.java

https://bitbucket.org/mmeinhold/amps · Java · 591 lines · 523 code · 57 blank · 11 comment · 80 complexity · dfca95068dcb68131df25b90a2ac75db MD5 · raw file

  1. package com.atlassian.plugins.codegen;
  2. import java.io.File;
  3. import java.io.FileInputStream;
  4. import java.io.FileOutputStream;
  5. import java.io.IOException;
  6. import java.util.List;
  7. import java.util.regex.Matcher;
  8. import java.util.regex.Pattern;
  9. import com.atlassian.fugue.Option;
  10. import com.atlassian.plugins.codegen.AmpsSystemPropertyVariable;
  11. import com.atlassian.plugins.codegen.ArtifactDependency;
  12. import com.atlassian.plugins.codegen.BundleInstruction;
  13. import com.atlassian.plugins.codegen.MavenPlugin;
  14. import com.atlassian.plugins.codegen.PluginProjectChangeset;
  15. import com.atlassian.plugins.codegen.ProjectRewriter;
  16. import com.atlassian.plugins.codegen.VersionId;
  17. import com.google.common.base.Predicate;
  18. import com.google.common.base.Predicates;
  19. import com.google.common.collect.ImmutableList;
  20. import com.google.common.collect.ImmutableSet;
  21. import com.google.common.collect.Iterables;
  22. import org.apache.commons.lang.StringUtils;
  23. import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
  24. import org.dom4j.Document;
  25. import org.dom4j.DocumentException;
  26. import org.dom4j.DocumentHelper;
  27. import org.dom4j.Element;
  28. import org.dom4j.QName;
  29. import org.dom4j.io.OutputFormat;
  30. import org.dom4j.io.SAXReader;
  31. import org.dom4j.io.XMLWriter;
  32. import static com.google.common.base.Preconditions.checkNotNull;
  33. import static com.google.common.base.Predicates.and;
  34. import static com.google.common.collect.Iterables.any;
  35. import static org.apache.commons.io.IOUtils.closeQuietly;
  36. /**
  37. * Applies any changes from a {@link PluginProjectChangeset} that affect the POM of a Maven project.
  38. * These include dependencies, bundle instructions and bundled artifacts in the AMPS configuration,
  39. * and arbitrary build plugin configurations.
  40. */
  41. public class MavenProjectRewriter implements ProjectRewriter
  42. {
  43. private static final int POM_INDENTATION = 4;
  44. private final File pomFile;
  45. private final Document document;
  46. private final Element root;
  47. private static final ImmutableSet<String> AMPS_PLUGIN_IDS =
  48. ImmutableSet.of("maven-amps-plugin",
  49. "maven-bamboo-plugin",
  50. "maven-confluence-plugin",
  51. "maven-crowd-plugin",
  52. "maven-fecru-plugin",
  53. "maven-stash-plugin",
  54. "maven-jira-plugin",
  55. "maven-refapp-plugin");
  56. public MavenProjectRewriter(File pom) throws DocumentException, IOException
  57. {
  58. this.pomFile = checkNotNull(pom, "pom");
  59. document = readPom(pom);
  60. root = document.getRootElement();
  61. }
  62. @Override
  63. public void applyChanges(PluginProjectChangeset changes) throws Exception
  64. {
  65. boolean modifyPom = false;
  66. modifyPom |= applyDependencyChanges(changes.getItems(ArtifactDependency.class));
  67. modifyPom |= applyMavenPluginChanges(changes.getItems(MavenPlugin.class));
  68. modifyPom |= applyBundleInstructionChanges(changes.getItems(BundleInstruction.class));
  69. modifyPom |= applyPluginArtifactChanges(changes.getItems(com.atlassian.plugins.codegen.PluginArtifact.class));
  70. modifyPom |= applyAmpsSystemPropertyChanges(changes.getItems(AmpsSystemPropertyVariable.class));
  71. modifyPom |= applyAmpsVersionUpdate(changes.getItems(AmpsVersionUpdate.class));
  72. if (modifyPom)
  73. {
  74. writePom(document, pomFile);
  75. }
  76. }
  77. @SuppressWarnings("unchecked")
  78. private boolean applyDependencyChanges(Iterable<ArtifactDependency> dependencies)
  79. {
  80. boolean modified = false;
  81. Element eDependencies = getOrCreateElement(root, "dependencies");
  82. for (ArtifactDependency descriptor : dependencies)
  83. {
  84. boolean alreadyExists = any(eDependencies.elements("dependency"),
  85. and(childElementValue("groupId", descriptor.getGroupAndArtifactId().getGroupId().getOrElse("")),
  86. childElementValue("artifactId", descriptor.getGroupAndArtifactId().getArtifactId())));
  87. if (!alreadyExists)
  88. {
  89. modified = true;
  90. Element eNewDep = eDependencies.addElement("dependency");
  91. eNewDep.addElement("groupId").setText(descriptor.getGroupAndArtifactId().getGroupId().get());
  92. eNewDep.addElement("artifactId").setText(descriptor.getGroupAndArtifactId().getArtifactId());
  93. eNewDep.addElement("version").setText(descriptor.getVersionId().getVersionOrPropertyPlaceholder().get());
  94. createVersionPropertyIfNecessary(descriptor.getVersionId());
  95. eNewDep.addElement("scope").setText(descriptor.getScope().name().toLowerCase());
  96. }
  97. }
  98. return modified;
  99. }
  100. private void createVersionPropertyIfNecessary(VersionId versionId)
  101. {
  102. for (String p : versionId.getPropertyName())
  103. {
  104. Element eProperties = getOrCreateElement(root, "properties");
  105. if (eProperties.element(p) == null)
  106. {
  107. eProperties.addElement(p).setText(versionId.getVersion().getOrElse(""));
  108. }
  109. }
  110. }
  111. @SuppressWarnings("unchecked")
  112. private boolean applyMavenPluginChanges(Iterable<MavenPlugin> mavenPlugins) throws Exception
  113. {
  114. boolean modified = false;
  115. Element ePlugins = getOrCreateElement(root, "build/plugins");
  116. for (MavenPlugin descriptor : mavenPlugins)
  117. {
  118. Document fragDoc = DocumentHelper.parseText("<root>" + descriptor.getXmlContent() + "</root>");
  119. Option<String> groupId = descriptor.getGroupAndArtifactId().getGroupId();
  120. String artifactId = descriptor.getGroupAndArtifactId().getArtifactId();
  121. Predicate<Element> matchGroup = (Predicate<Element>) (groupId.isDefined() ?
  122. childElementValue("groupId", groupId.get()) :
  123. Predicates.or(childElementValue("groupId", ""), childElementValue("groupId", "org.apache.maven.plugins")));
  124. Predicate<Element> match = Predicates.and(matchGroup, childElementValue("artifactId", artifactId));
  125. if (Iterables.any(ePlugins.elements("plugin"), match))
  126. {
  127. modified |= mergeMavenPluginConfig(Iterables.find((List<Element>) ePlugins.elements("plugin"), match), fragDoc.getRootElement());
  128. }
  129. else
  130. {
  131. ePlugins.add(toMavenPluginElement(descriptor, fragDoc.getRootElement()));
  132. modified = true;
  133. }
  134. }
  135. return modified;
  136. }
  137. @SuppressWarnings("unchecked")
  138. private boolean applyAmpsVersionUpdate(Iterable<AmpsVersionUpdate> items)
  139. {
  140. boolean modified = false;
  141. //find the highest version in our items.
  142. //Note: really there should only be 1 change item
  143. DefaultArtifactVersion newAmpsVersion = new DefaultArtifactVersion("0.0");
  144. for(AmpsVersionUpdate changeItem : items)
  145. {
  146. DefaultArtifactVersion changeVersion = new DefaultArtifactVersion(changeItem.getVersion());
  147. if(changeVersion.compareTo(newAmpsVersion) > 0)
  148. {
  149. newAmpsVersion = changeVersion;
  150. }
  151. if(AmpsVersionUpdate.PLUGIN.equalsIgnoreCase(changeItem.getType()) && changeItem.isApplyConfig())
  152. {
  153. modified = applyAmpsPluginVersionUpdate();
  154. }
  155. if(AmpsVersionUpdate.MANAGEMENT.equalsIgnoreCase(changeItem.getType()) && changeItem.isApplyConfig())
  156. {
  157. boolean managementUpdated = applyAmpsPluginManagementVersionUpdate();
  158. if(!modified)
  159. {
  160. modified = managementUpdated;
  161. }
  162. }
  163. if(changeItem.isApplyProp())
  164. {
  165. //add the amps.version prop if needed
  166. Element ampsVersionProperty = getOrCreateElement(getOrCreateElement(root, "properties"),"amps.version");
  167. //update the amps.version prop if our change is a newer version
  168. if(StringUtils.isNotBlank(ampsVersionProperty.getTextTrim()))
  169. {
  170. DefaultArtifactVersion pomVersion = new DefaultArtifactVersion(ampsVersionProperty.getTextTrim());
  171. if(newAmpsVersion.compareTo(pomVersion) > 0)
  172. {
  173. modified = true;
  174. ampsVersionProperty.setText(newAmpsVersion.toString());
  175. }
  176. }
  177. else
  178. {
  179. ampsVersionProperty.setText(newAmpsVersion.toString());
  180. modified = true;
  181. }
  182. }
  183. }
  184. return modified;
  185. }
  186. private boolean applyAmpsPluginVersionUpdate()
  187. {
  188. boolean modified = false;
  189. //update the amps plugin version to the property if needed
  190. Element ampsVersionElement = getOrCreateElement(findAmpsPlugin(),"version");
  191. if(!"${amps.version}".equals(ampsVersionElement.getTextTrim()))
  192. {
  193. ampsVersionElement.setText("${amps.version}");
  194. modified = true;
  195. }
  196. return modified;
  197. }
  198. private boolean applyAmpsPluginManagementVersionUpdate()
  199. {
  200. boolean modified = false;
  201. //update the amps plugin version to the property if needed
  202. Element ampsManagementPlugin = findAmpsPluginManagement();
  203. if(null != ampsManagementPlugin)
  204. {
  205. Element ampsVersionElement = getOrCreateElement(ampsManagementPlugin,"version");
  206. if(!"${amps.version}".equals(ampsVersionElement.getTextTrim()))
  207. {
  208. ampsVersionElement.setText("${amps.version}");
  209. modified = true;
  210. }
  211. }
  212. return modified;
  213. }
  214. public String getAmpsVersionInPom()
  215. {
  216. Element ampsVersion = getElementOrNull(findAmpsPlugin(),"version");
  217. if(null != ampsVersion)
  218. {
  219. return ampsVersion.getTextTrim();
  220. }
  221. return "";
  222. }
  223. public boolean definesProperty(String propName)
  224. {
  225. Element properties = getElementOrNull(root, "properties");
  226. if(null != properties)
  227. {
  228. return null != getElementOrNull(properties,propName);
  229. }
  230. return false;
  231. }
  232. public String getAmpsPluginManagementVersionInPom()
  233. {
  234. Element ampsManagementPlugin = findAmpsPluginManagement();
  235. String version = "";
  236. if(null != ampsManagementPlugin)
  237. {
  238. Element ampsVersion = getElementOrNull(ampsManagementPlugin,"version");
  239. if(null != ampsVersion)
  240. {
  241. version = ampsVersion.getTextTrim();
  242. }
  243. }
  244. return version;
  245. }
  246. @SuppressWarnings("unchecked")
  247. private boolean mergeMavenPluginConfig(Element ePlugin, Element paramsDesc)
  248. {
  249. boolean modified = false;
  250. Element eExecutions = getOrCreateElement(ePlugin, "executions");
  251. for (Object node : paramsDesc.selectNodes("executions/execution"))
  252. {
  253. Element eExecution = (Element) node;
  254. String id = eExecution.elementTextTrim("id");
  255. if (!Iterables.any(eExecutions.elements("execution"), childElementValue("id", id)))
  256. {
  257. detachAndAdd(eExecution, eExecutions);
  258. modified = true;
  259. }
  260. }
  261. return modified;
  262. }
  263. private Element toMavenPluginElement(MavenPlugin descriptor, Element paramsDesc)
  264. {
  265. Element p = createElement("plugin");
  266. for (String groupId : descriptor.getGroupAndArtifactId().getGroupId())
  267. {
  268. p.addElement("groupId").setText(groupId);
  269. }
  270. p.addElement("artifactId").setText(descriptor.getGroupAndArtifactId().getArtifactId());
  271. if (descriptor.getVersionId().isDefined())
  272. {
  273. p.addElement("version").setText(descriptor.getVersionId().getVersionOrPropertyPlaceholder().get());
  274. createVersionPropertyIfNecessary(descriptor.getVersionId());
  275. }
  276. if ("true".equals(paramsDesc.elementText("extensions")))
  277. {
  278. p.addElement("extensions").setText("true");
  279. }
  280. for (Object oParam : paramsDesc.elements())
  281. {
  282. detachAndAdd((Element) oParam, p);
  283. }
  284. return p;
  285. }
  286. private boolean applyBundleInstructionChanges(Iterable<BundleInstruction> instructions)
  287. {
  288. if(!instructions.iterator().hasNext())
  289. {
  290. return false;
  291. }
  292. Element configRoot = getAmpsPluginConfiguration();
  293. boolean modified = false;
  294. Element instructionsRoot = getOrCreateElement(configRoot, "instructions");
  295. for (BundleInstruction instruction : instructions)
  296. {
  297. String categoryName = instruction.getCategory().getElementName();
  298. Element categoryElement = getOrCreateElement(instructionsRoot, categoryName);
  299. String body = categoryElement.getText();
  300. String[] instructionLines = (body == null) ? new String[0] : body.split(",");
  301. if (any(ImmutableList.copyOf(instructionLines), bundleInstructionLineWithPackageName(instruction.getPackageName())))
  302. {
  303. continue;
  304. }
  305. categoryElement.setText(addInstructionLine(instructionLines, instruction));
  306. modified = true;
  307. }
  308. return modified;
  309. }
  310. private static String addInstructionLine(String[] instructionLines, BundleInstruction instruction)
  311. {
  312. String newLine = instruction.getPackageName();
  313. for (String version : instruction.getVersion())
  314. {
  315. newLine = newLine + ";version=\"" + version + "\"";
  316. }
  317. if ((instructionLines.length == 0) || instructionLines[0].trim().equals(""))
  318. {
  319. return newLine;
  320. }
  321. StringBuilder buf = new StringBuilder();
  322. boolean inserted = false;
  323. String indent = "";
  324. Pattern indentRegex = Pattern.compile("^\\n*([ \\t]*).*");
  325. for (String oldLine : instructionLines)
  326. {
  327. if (buf.length() > 0)
  328. {
  329. buf.append(",");
  330. }
  331. if (!inserted && (oldLine.trim().compareTo(newLine) > 0))
  332. {
  333. buf.append("\n").append(indent).append(newLine).append(",\n");
  334. inserted = true;
  335. }
  336. if (indent.equals(""))
  337. {
  338. Matcher m = indentRegex.matcher(oldLine);
  339. if (m.matches())
  340. {
  341. indent = m.group(1);
  342. }
  343. }
  344. buf.append(oldLine);
  345. }
  346. if (!inserted)
  347. {
  348. buf.append(",\n").append(newLine);
  349. }
  350. return buf.toString();
  351. }
  352. @SuppressWarnings("unchecked")
  353. private boolean applyPluginArtifactChanges(Iterable<com.atlassian.plugins.codegen.PluginArtifact> pluginArtifacts)
  354. {
  355. if(!pluginArtifacts.iterator().hasNext())
  356. {
  357. return false;
  358. }
  359. Element configRoot = getAmpsPluginConfiguration();
  360. boolean modified = false;
  361. for (com.atlassian.plugins.codegen.PluginArtifact p : pluginArtifacts)
  362. {
  363. String elementName = p.getType().getElementName();
  364. Element artifactsRoot = getOrCreateElement(configRoot, elementName + "s");
  365. if (!any(artifactsRoot.elements(elementName),
  366. and(childElementValue("groupId", p.getGroupAndArtifactId().getGroupId().getOrElse("")),
  367. childElementValue("artifactId", p.getGroupAndArtifactId().getArtifactId()))))
  368. {
  369. artifactsRoot.add(toArtifactElement(p));
  370. modified = true;
  371. }
  372. }
  373. return modified;
  374. }
  375. private boolean applyAmpsSystemPropertyChanges(Iterable<AmpsSystemPropertyVariable> propertyVariables)
  376. {
  377. if(!propertyVariables.iterator().hasNext())
  378. {
  379. return false;
  380. }
  381. Element configRoot = getAmpsPluginConfiguration();
  382. boolean modified = false;
  383. for (AmpsSystemPropertyVariable propertyVariable : propertyVariables)
  384. {
  385. Element variablesRoot = getOrCreateElement(configRoot, "systemPropertyVariables");
  386. if (variablesRoot.element(propertyVariable.getName()) == null)
  387. {
  388. variablesRoot.addElement(propertyVariable.getName()).setText(propertyVariable.getValue());
  389. modified = true;
  390. }
  391. }
  392. return modified;
  393. }
  394. private Element toArtifactElement(com.atlassian.plugins.codegen.PluginArtifact pluginArtifact)
  395. {
  396. Element ret = createElement(pluginArtifact.getType().getElementName());
  397. for (String groupId : pluginArtifact.getGroupAndArtifactId().getGroupId())
  398. {
  399. ret.addElement("groupId").setText(groupId);
  400. }
  401. ret.addElement("artifactId").setText(pluginArtifact.getGroupAndArtifactId().getArtifactId());
  402. if (pluginArtifact.getVersionId().isDefined())
  403. {
  404. ret.addElement("version").setText(pluginArtifact.getVersionId().getVersionOrPropertyPlaceholder().get());
  405. createVersionPropertyIfNecessary(pluginArtifact.getVersionId());
  406. }
  407. return ret;
  408. }
  409. @SuppressWarnings("unchecked")
  410. private Element findAmpsPlugin()
  411. {
  412. Element plugins = getElementOrNull(root, "build/plugins");
  413. if(null != plugins)
  414. {
  415. for (Element p : (List<Element>) plugins.elements("plugin"))
  416. {
  417. if (p.elementTextTrim("groupId").equals("com.atlassian.maven.plugins")
  418. && AMPS_PLUGIN_IDS.contains(p.elementTextTrim("artifactId")))
  419. {
  420. return p;
  421. }
  422. }
  423. }
  424. throw new IllegalStateException("Could not find AMPS plugin element in POM");
  425. }
  426. @SuppressWarnings("unchecked")
  427. private Element findAmpsPluginManagement()
  428. {
  429. Element plugins = getElementOrNull(root, "build/pluginManagement/plugins");
  430. if(null != plugins)
  431. {
  432. for (Element p : (List<Element>) plugins.elements("plugin"))
  433. {
  434. if (p.elementTextTrim("groupId").equals("com.atlassian.maven.plugins")
  435. && AMPS_PLUGIN_IDS.contains(p.elementTextTrim("artifactId")))
  436. {
  437. return p;
  438. }
  439. }
  440. }
  441. return null;
  442. }
  443. private Element getAmpsPluginConfiguration()
  444. {
  445. return getOrCreateElement(findAmpsPlugin(), "configuration");
  446. }
  447. private static Element getOrCreateElement(Element container, String path)
  448. {
  449. Element last = container;
  450. for (String pathName : path.split("/"))
  451. {
  452. last = container.element(pathName);
  453. if (last == null)
  454. {
  455. last = container.addElement(pathName);
  456. }
  457. container = last;
  458. }
  459. return last;
  460. }
  461. private static Element getElementOrNull(Element container, String path)
  462. {
  463. for (String pathName : path.split("/"))
  464. {
  465. if (container != null)
  466. {
  467. container = container.element(pathName);
  468. }
  469. }
  470. return container;
  471. }
  472. private Document readPom(File f) throws DocumentException, IOException
  473. {
  474. final SAXReader reader = new SAXReader();
  475. reader.setMergeAdjacentText(true);
  476. reader.setStripWhitespaceText(true);
  477. return reader.read(new FileInputStream(f));
  478. }
  479. private void writePom(Document doc, File f) throws IOException
  480. {
  481. FileOutputStream fos = new FileOutputStream(f);
  482. OutputFormat format = OutputFormat.createPrettyPrint();
  483. format.setIndentSize(POM_INDENTATION);
  484. XMLWriter writer = new XMLWriter(fos, format);
  485. try
  486. {
  487. writer.write(doc);
  488. }
  489. finally
  490. {
  491. closeQuietly(fos);
  492. }
  493. }
  494. private Element createElement(String name)
  495. {
  496. return DocumentHelper.createElement(new QName(name, root.getNamespace()));
  497. }
  498. private void fixNamespace(Element e)
  499. {
  500. e.setQName(new QName(e.getName(), root.getNamespace()));
  501. for (Object child : e.elements())
  502. {
  503. fixNamespace((Element) child);
  504. }
  505. }
  506. private void detachAndAdd(Element e, Element container)
  507. {
  508. e.detach();
  509. fixNamespace(e);
  510. container.add(e);
  511. }
  512. private static Predicate<? super Element> childElementValue(final String name, final String value)
  513. {
  514. return new Predicate<Element>()
  515. {
  516. public boolean apply(Element input)
  517. {
  518. Element child = input.element(name);
  519. return (child == null) ? value.equals("") : value.equals(child.getText());
  520. }
  521. };
  522. }
  523. private static Predicate<String> bundleInstructionLineWithPackageName(final String packageName)
  524. {
  525. return new Predicate<String>()
  526. {
  527. public boolean apply(String input)
  528. {
  529. String s = input.trim();
  530. return s.equals(packageName) || s.startsWith(packageName + ";");
  531. }
  532. };
  533. }
  534. }