View Javadoc

1   package de.matthias_burbach.deputy.core;
2   
3   import java.util.Date;
4   import java.util.Iterator;
5   import java.util.List;
6   
7   import org.jdom.Comment;
8   import org.jdom.Document;
9   import org.jdom.Element;
10  import org.jdom.xpath.XPath;
11  
12  import de.matthias_burbach.deputy.core.project.Project;
13  import de.matthias_burbach.deputy.core.rule.DeprecationRule;
14  import de.matthias_burbach.deputy.core.rule.EnforcementRule;
15  import de.matthias_burbach.deputy.core.rule.RemovalRule;
16  import de.matthias_burbach.deputy.core.rule.ReplacementRule;
17  import de.matthias_burbach.deputy.core.rule.RetentionRule;
18  import de.matthias_burbach.deputy.core.util.FileUtils;
19  import de.matthias_burbach.deputy.core.util.XmlUtils;
20  
21  /***
22   * Generates a new XML document for a Maven project from its current XML file
23   * and the changes made in the application.
24   *
25   * @author Matthias Burbach
26   */
27  public class ProjectGenerator {
28      /***
29       * The version of the Deputy application.
30       * To be printed in the generator comment.
31       */
32      private String deputyVersion = "";
33  
34      /***
35       * @param deputyVersion The version of the Deputy application.
36       *                      To be printed in the generator comment.
37       */
38      public ProjectGenerator(final String deputyVersion) {
39          this.deputyVersion = deputyVersion;
40      }
41  
42      /***
43       * Generates a new XML document for a Maven project from its current XML
44       * file and the changes made in the application.
45       *
46       * @param project The project to generate the document for.
47       * @param projectFileName The full path of the current project file.
48       * @return The generated document.
49       * @throws Exception if anything goes unexpectedly wrong
50       */
51      public Document createUpdatedDocument(
52              final Project project,
53              final String projectFileName)
54              throws Exception {
55          Document newDocument = XmlUtils.loadXmlDocument(projectFileName);
56          generateDependencies(project, newDocument);
57          generateRules(project, newDocument);
58          return newDocument;
59      }
60  
61      /***
62       * Generates a new XML document for a Maven project from its current XML
63       * file and the changes made in the application.
64       * <p/>
65       * Saves a backup of the previous version of the XML file under the path and
66       * name of the original file plus the suffix '.bak' if the original file
67       * is overridden.
68       *
69       * @param project The project to generate the document for.
70       * @param oldProjectFileName The full path of the current project file.
71       * @param newProjectFileName The full path to save the generated document
72       *                           under.
73       * @throws Exception if anything goes unexpectedly wrong
74       */
75      public void createAndSaveUpdatedDocument(
76              final Project project,
77              final String oldProjectFileName,
78              final String newProjectFileName)
79              throws Exception {
80          if (newProjectFileName.equals(oldProjectFileName)) {
81              backupFile(oldProjectFileName);
82          }
83          Document updatedDocument =
84              createUpdatedDocument(project, oldProjectFileName);
85          XmlUtils.saveXmlDocument(newProjectFileName, updatedDocument);
86      }
87  
88      /***
89       * @param project The project to generate dependencies for.
90       * @param document The document to generate into.
91       * @throws Exception if anything goes unexpectedly wrong
92       */
93      private void generateDependencies(
94              final Project project,
95              final Document document)
96              throws Exception {
97          //get the dependency mappings element from the document
98          Element dependenciesElement = getDependenciesElement(document);
99  
100         //remove the contents of the dependencies element
101         //because it may contain old dependency elements
102         dependenciesElement.getContent().clear();
103 
104         //add comment to output
105         dependenciesElement.addContent(getGeneratorComment());
106 
107         //generate the direct (=top level) dependency elements
108         Iterator iter = project.getDependencies();
109         while (iter.hasNext()) {
110             Project dependency = (Project) iter.next();
111             generateDependency(
112                 dependenciesElement,
113                 dependency,
114                 true,
115                 project.isAssembly(),
116                 project.getDependencyPropertyList(dependency.getArtifactId()));
117         }
118 
119         if (project.isAssembly()) {
120             //generate the indirect dependency elements
121             //add comment to output
122             dependenciesElement.addContent(getIndirectDependenciesComment());
123             iter = project.getIndirectDependencies();
124             while (iter.hasNext()) {
125                 Project dependency = (Project) iter.next();
126                 generateDependency(
127                     dependenciesElement,
128                     dependency,
129                     false,
130                     project.isAssembly(),
131                     project.getDependencyPropertyList(
132                         dependency.getArtifactId()));
133             }
134         }
135     }
136 
137     /***
138      * @param dependenciesElement The parent Element to add the generated
139      *                            dependency element to.
140      * @param dependency The dependency to generate an XML element for.
141      * @param isTop Whether this is a direct (or: top level) dependency.
142      * @param isAssembly Whether we generate an assembly or a component project.
143      * @param preservableProperties The dependency properties to be nested into
144      *                              the dependency.
145      */
146     private void generateDependency(
147             final Element dependenciesElement,
148             final Project dependency,
149             final boolean isTop,
150             final boolean isAssembly,
151             final List preservableProperties) {
152         Element dependencyElement = new Element("dependency");
153         addChildElement(
154             dependencyElement, "groupId", dependency.getGroupId());
155         addChildElement(
156             dependencyElement, "artifactId", dependency.getArtifactId());
157         addChildElement(
158             dependencyElement, "version", dependency.getVersion());
159         addChildElement(
160             dependencyElement, "jar", dependency.getJar());
161 
162         String type = dependency.getType();
163         if ("war".equals(type) && !isAssembly) {
164             /*
165              * WAR files must be referenced as JAR files by components.
166              */
167             type = null;
168         }
169         addChildElement(
170             dependencyElement, "type", type);
171 
172         addChildElement(
173             dependencyElement, "url", dependency.getUrl());
174 
175         Element propertiesElement = new Element("properties");
176         Element topElement = new Element("top");
177         topElement.setText((Boolean.valueOf(isTop)).toString());
178         propertiesElement.addContent(topElement);
179         /*
180          * Add additional foreign properties which must be preserved
181          */
182         if (preservableProperties != null) {
183             Iterator iter = preservableProperties.iterator();
184             while (iter.hasNext()) {
185                 Element element = (Element) iter.next();
186                 propertiesElement.addContent((Element) element.clone());
187             }
188         }
189         dependencyElement.addContent(propertiesElement);
190 
191         //add generated dependency to parent
192         dependenciesElement.addContent(dependencyElement);
193     }
194 
195     /***
196      * @param project The project to generate the rules for.
197      * @param document The document to generate into.
198      * @throws Exception if anything goes unexpectedly wrong
199      */
200     private void generateRules(
201             final Project project, final Document document) throws Exception {
202         //get the rules element from the document
203         Element rulesElement = getRulesElement(project, document);
204 
205         //remove the contents of the rules element
206         //because it may contain old rule elements
207         rulesElement.getContent().clear();
208 
209         //generate the default element
210         Element defaultElement = new Element("default");
211         String value = project.getRuleSet().getDefaultRule();
212         defaultElement.setAttribute("value", value);
213         rulesElement.addContent(defaultElement);
214 
215         //generate the enforcement elements
216         Element enforcementsElement = new Element("enforcements");
217         Iterator iter = project.getRuleSet().getEnforcementRules().iterator();
218         while (iter.hasNext()) {
219             EnforcementRule rule = (EnforcementRule) iter.next();
220             generateRule(enforcementsElement, rule);
221         }
222         rulesElement.addContent(enforcementsElement);
223 
224         //generate the deprecation elements
225         Element deprecationsElement = new Element("deprecations");
226         iter = project.getRuleSet().getDeprecationRules().iterator();
227         while (iter.hasNext()) {
228             DeprecationRule rule = (DeprecationRule) iter.next();
229             generateRule(deprecationsElement, rule);
230         }
231         rulesElement.addContent(deprecationsElement);
232 
233         //generate the replacement elements
234         Element replacementsElement = new Element("replacements");
235         iter = project.getRuleSet().getReplacementRules().iterator();
236         while (iter.hasNext()) {
237             ReplacementRule rule = (ReplacementRule) iter.next();
238             generateRule(replacementsElement, rule);
239         }
240         rulesElement.addContent(replacementsElement);
241 
242         //generate the removal elements
243         Element removalsElement = new Element("removals");
244         iter = project.getRuleSet().getRemovalRules().iterator();
245         while (iter.hasNext()) {
246             RemovalRule rule = (RemovalRule) iter.next();
247             generateRule(removalsElement, rule);
248         }
249         rulesElement.addContent(removalsElement);
250 
251         //generate the retention elements
252         Element retentionsElement = new Element("retentions");
253         iter = project.getRuleSet().getRetentionRules().iterator();
254         while (iter.hasNext()) {
255             RetentionRule rule = (RetentionRule) iter.next();
256             generateRule(retentionsElement, rule);
257         }
258         rulesElement.addContent(retentionsElement);
259     }
260 
261     /***
262      * @param enforcementsElement The parent to add the generated element to.
263      * @param rule The rule to generate an XML element for.
264      */
265     private void generateRule(
266             final Element enforcementsElement,
267             final EnforcementRule rule) {
268         Element element = new Element("enforcement");
269         addChildElement(element, "groupId", rule.getGroupId());
270         addChildElement(element, "artifactId", rule.getArtifactId());
271         addChildElement(element, "version", rule.getVersion());
272         if (rule.isDerived()) {
273             addChildElement(
274                     element, "derived", Boolean.toString(rule.isDerived()));
275         }
276 
277         //add generated element to parent
278         enforcementsElement.addContent(element);
279     }
280 
281     /***
282      * @param deprecationsElement The parent to add the generated element to.
283      * @param rule The rule to generate an XML element for.
284      */
285     private void generateRule(
286             final Element deprecationsElement,
287             final DeprecationRule rule) {
288         Element element = new Element("deprecation");
289         addChildElement(element, "groupId", rule.getGroupId());
290         addChildElement(element, "artifactId", rule.getArtifactId());
291         addChildElement(element, "version", rule.getVersion());
292 
293         //add generated element to parent
294         deprecationsElement.addContent(element);
295     }
296 
297     /***
298      * @param replacementsElement The parent to add the generated element to.
299      * @param rule The rule to generate an XML element for.
300      */
301     private void generateRule(
302             final Element replacementsElement,
303             final ReplacementRule rule) {
304         Element element = new Element("replacement");
305 
306         Element displacementElement = new Element("displacement");
307         addChildElement(
308             displacementElement,
309             "groupId",
310             rule.getDisplacedGroupId());
311         addChildElement(
312             displacementElement,
313             "artifactId",
314             rule.getDisplacedArtifactId());
315         if (rule.getDisplacedVersion() != null) {
316             addChildElement(
317                 displacementElement,
318                 "version",
319                 rule.getDisplacedVersion());
320         }
321 
322         Element placementElement = new Element("placement");
323         addChildElement(
324             placementElement,
325             "groupId",
326             rule.getGroupId());
327         addChildElement(
328             placementElement,
329             "artifactId",
330             rule.getArtifactId());
331         if (rule.getVersion() != null) {
332             addChildElement(
333                 placementElement,
334                 "version",
335                 rule.getVersion());
336         }
337 
338         element.addContent(displacementElement);
339         element.addContent(placementElement);
340         //add generated element to parent
341         replacementsElement.addContent(element);
342     }
343 
344     /***
345      * @param removalsElement The parent to add the generated element to.
346      * @param rule The rule to generate an XML element for.
347      */
348     private void generateRule(
349             final Element removalsElement,
350             final RemovalRule rule) {
351         Element element = new Element("removal");
352         addChildElement(element, "groupId", rule.getGroupId());
353         addChildElement(element, "artifactId", rule.getArtifactId());
354         if (rule.getVersion() != null) {
355             addChildElement(element, "version", rule.getVersion());
356         }
357         //add generated element to parent
358         removalsElement.addContent(element);
359     }
360 
361     /***
362      * @param retentionsElement The parent to add the generated element to.
363      * @param rule The rule to generate an XML element for.
364      */
365     private void generateRule(
366             final Element retentionsElement,
367             final RetentionRule rule) {
368         Element element = new Element("retention");
369         addChildElement(element, "groupId", rule.getGroupId());
370         addChildElement(element, "artifactId", rule.getArtifactId());
371         if (rule.getVersion() != null) {
372             addChildElement(element, "version", rule.getVersion());
373         }
374         //add generated element to parent
375         retentionsElement.addContent(element);
376     }
377 
378     /***
379      * Adds an element to the parent if the text is not <code>null</code>.
380      *
381      * @param parentElement The parent to add to.
382      * @param childName The name of the child element to add.
383      * @param childText The contents of the child element to add. Can be
384      *                  <code>null</code> which will make this operation a noop.
385      */
386     private void addChildElement(
387             final Element parentElement,
388             final String childName,
389             final String childText) {
390         if (childText != null) {
391             Element childElement = new Element(childName);
392             childElement.setText(childText);
393             parentElement.addContent(childElement);
394         }
395     }
396 
397     /***
398      * Finds or creates the dependencies element in the document.
399      *
400      * @param document The document to parse for a potentially already existing
401      *                 dependencies element.
402      * @return The dependencies element.
403      * @throws Exception if anything goes unexpectedly wrong
404      */
405     private Element getDependenciesElement(
406             final Document document) throws Exception {
407         Element dependenciesElement = null;
408         if (document != null) {
409             dependenciesElement =
410                 (Element) XPath.newInstance("//project/dependencies")
411                                .selectSingleNode(document);
412         }
413         if (dependenciesElement == null) {
414             dependenciesElement = new Element("dependencies");
415             Element projectElement =
416                 (Element) XPath.newInstance("//project")
417                                .selectSingleNode(document);
418             if (projectElement != null) {
419                 projectElement.addContent(dependenciesElement);
420             }
421         }
422         return dependenciesElement;
423     }
424 
425     /***
426      * Finds or creates the rules element in the document.
427      *
428      * @param project The current project.
429      * @param document The document to parse for a potentially already existing
430      *                 rules element.
431      * @return The rules element.
432      * @throws Exception if anything goes unexpectedly wrong
433      */
434     private Element getRulesElement(
435             final Project project,
436             final Document document) throws Exception {
437         Element resultElement = null;
438         Element deputyElement = getDeputyElement(project, document);
439         if (deputyElement != null) {
440             resultElement =
441                 (Element) XPath.newInstance("rules").selectSingleNode(
442                     deputyElement);
443             if (resultElement == null) {
444                 resultElement = new Element("rules");
445                 deputyElement.addContent(resultElement);
446             }
447         }
448         return resultElement;
449     }
450 
451     /***
452      * Finds or creates the deputy element in the document.
453      *
454      * @param project The current project.
455      * @param document The document to parse for a potentially already existing
456      *                 deputy element.
457      *
458      * @return The deputy element from the project's properties.
459      * @throws Exception if anything goes unexpectedly wrong
460      */
461     private Element getDeputyElement(
462             final Project project,
463             final Document document) throws Exception {
464         Element resultElement = null;
465         Element propertiesElement = getPropertiesElement(document);
466         if (propertiesElement != null) {
467             resultElement =
468                 (Element) XPath.newInstance("deputy").selectSingleNode(
469                     propertiesElement);
470             if (resultElement == null) {
471                 resultElement = new Element("deputy");
472                 propertiesElement.addContent(resultElement);
473             }
474         }
475         //set the isAssembly attribute
476         resultElement.setAttribute(
477             "isAssembly",
478             Boolean.valueOf(project.isAssembly()).toString());
479         return resultElement;
480     }
481 
482     /***
483      * Finds or creates the project's properties element.
484      *
485      * @param document The document to parse for the potentially already
486      *                 existing properties element.
487      * @return The properties element.
488      * @throws Exception if anything goes unexpectedly wrong
489      */
490     private Element getPropertiesElement(
491             final Document document) throws Exception {
492         Element resultElement = null;
493         Element projectElement = getProjectElement(document);
494         if (projectElement != null) {
495             resultElement =
496                 (Element) XPath.newInstance("properties").selectSingleNode(
497                     projectElement);
498             if (resultElement == null) {
499                 resultElement = new Element("properties");
500                 projectElement.addContent(resultElement);
501             }
502         }
503         return resultElement;
504     }
505 
506     /***
507      * Finds or creates the project element.
508      *
509      * @param document The document to parse for the potentially already
510      *                 existing project element.
511      * @return The project element.
512      * @throws Exception if anything goes unexpectedly wrong
513      */
514     private Element getProjectElement(
515             final Document document) throws Exception {
516         Element resultElement = null;
517         if (document != null) {
518             resultElement =
519                 (Element) XPath.newInstance("//project").selectSingleNode(
520                     document);
521         }
522         return resultElement;
523     }
524 
525     /***
526      * @return The comment stating the name of the generator tool and the time
527      *         of the generation.
528      */
529     private Comment getGeneratorComment() {
530         return new Comment(
531                 "Generated by Deputy " + deputyVersion + " at " + new Date());
532     }
533 
534     /***
535      * @return The comment heading the indirect dependencies in assemblies.
536      */
537     private Comment getIndirectDependenciesComment() {
538         return new Comment("Indirect Dependencies");
539     }
540 
541     /***
542      * Backs up the file passed in.
543      * <p/>
544      * The backup is copied to the same directory the original is in. It is
545      * named '&lt;original-file-name&gt;.bak'.
546      *
547      * @param pathAndFile The file to be backed up.
548      * @throws Exception if anything goes unexpectedly wrong
549      */
550     private void backupFile(final String pathAndFile) throws Exception {
551         FileUtils.copy(pathAndFile, pathAndFile + ".bak");
552     }
553 }