PageRenderTime 63ms CodeModel.GetById 2ms app.highlight 55ms RepoModel.GetById 2ms app.codeStats 0ms

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