View Javadoc

1   package org.appfuse.tool;
2   
3   import org.apache.commons.io.FileUtils;
4   import org.apache.maven.artifact.Artifact;
5   import org.apache.maven.plugin.logging.Log;
6   import org.apache.maven.plugin.logging.SystemStreamLog;
7   import org.apache.maven.project.MavenProject;
8   import org.apache.tools.ant.Project;
9   import org.apache.tools.ant.taskdefs.Copy;
10  import org.apache.tools.ant.taskdefs.Echo;
11  import org.apache.tools.ant.taskdefs.LoadFile;
12  import org.apache.tools.ant.taskdefs.Replace;
13  import org.apache.tools.ant.taskdefs.optional.ReplaceRegExp;
14  import org.apache.tools.ant.types.FileSet;
15  import org.appfuse.mojo.installer.AntUtils;
16  
17  import java.io.File;
18  import java.io.IOException;
19  import java.util.ArrayList;
20  
21  /**
22   * This class is responsible for installing generated CRUD artifacts into an AppFuse application.
23   *
24   * @author mraible
25   */
26  public class ArtifactInstaller {
27      private Log log;
28      static final String FILE_SEP = System.getProperty("file.separator");
29      Project antProject;
30      String pojoName;
31      String pojoNameLower;
32      String destinationDirectory;
33      String sourceDirectory;
34      MavenProject project;
35      boolean genericCore;
36      StringUtils util;
37  
38      public ArtifactInstaller(MavenProject project, String pojoName, String sourceDirectory, String destinationDirectory, boolean genericCore) {
39          this.project = project;
40          this.pojoName = pojoName;
41          this.pojoNameLower = pojoLowerCase(pojoName);
42          this.sourceDirectory = sourceDirectory;
43          this.destinationDirectory = destinationDirectory;
44          this.genericCore = genericCore;
45          this.util = new StringUtils();
46      }
47  
48      public void execute() {
49          antProject = AntUtils.createProject();
50  
51          boolean hasDbUnit = projectContainsPluginArtifact("dbunit");
52  
53          if (hasDbUnit) {
54              log("Installing sample data for DbUnit...");
55              installSampleData();
56          }
57  
58          // install dao and manager if jar (modular/core) or war w/o parent (basic)
59          if (project.getPackaging().equals("jar") || (project.getPackaging().equals("war") && project.getParent() == null)) {
60              copyGeneratedObjects(this.sourceDirectory, this.destinationDirectory, "**/model/**/*.java");
61              copyGeneratedObjects(this.sourceDirectory, this.destinationDirectory, "**/dao/**/*.java");
62              copyGeneratedObjects(this.sourceDirectory, this.destinationDirectory, "**/service/**/*.java");
63              if (genericCore) {
64                  log("Installing Spring bean definitions (genericCore == true)...");
65                  installGenericBeanDefinitions();
66              } else {
67                  // APF-1105: Changed to use Spring annotations (@Repository, @Service and @Autowired)
68                  //installDaoAndManagerBeanDefinitions();
69              }
70              // only installs if iBATIS is configured as dao.framework
71              installiBATISFiles();
72          }
73  
74          if (project.getPackaging().equalsIgnoreCase("war")) {
75              copyGeneratedObjects(this.sourceDirectory, this.destinationDirectory, "**/webapp/**/*.java");
76  
77              String webFramework = project.getProperties().getProperty("web.framework");
78  
79              if ("jsf".equalsIgnoreCase(webFramework)) {
80                  log("Installing JSF views and configuring...");
81                  installJSFNavigationAndBeans();
82                  installJSFViews();
83              } else if ("struts".equalsIgnoreCase(webFramework)) {
84                  log("Installing Struts views and configuring...");
85                  // A bean definition for an Action is not used anymore (APF-798)
86                  // installStrutsBeanDefinition();
87                  installStrutsActionDefinitions();
88                  copyGeneratedObjects(sourceDirectory + "/src/main/resources",
89                          destinationDirectory + "/src/main/resources", "**/model/*.xml");
90                  copyGeneratedObjects(sourceDirectory + "/src/main/resources",
91                          destinationDirectory + "/src/main/resources", "**/webapp/action/*.xml");
92                  installStrutsViews();
93              } else if ("spring".equalsIgnoreCase(webFramework)) {
94                  log("Installing Spring views and configuring...");
95                  //Controllers configured by Spring annotations in 2.1+
96                  //installSpringControllerBeanDefinitions();
97                  installSpringValidation();
98                  installSpringViews();
99              } else if ("tapestry".equalsIgnoreCase(webFramework)) {
100                 log("Installing Tapestry views and configuring...");
101                 installTapestryViews();
102             }
103 
104             log("Installing i18n messages...");
105             installInternationalizationKeys(webFramework);
106 
107             log("Installing menu...");
108             installMenu();
109 
110             log("Installing UI tests...");
111             installUITests();
112         }
113     }
114 
115     private boolean projectContainsPluginArtifact(String artifactId) {
116         for (Object artifact : project.getPluginArtifacts()) {
117             if (((Artifact) artifact).getArtifactId().contains(artifactId)) {
118                 return true;
119             }
120         }
121         return false;
122     }
123 
124     private boolean projectContainsArtifact(String artifactId) {
125         for (Object artifact : project.getArtifacts()) {
126             if (((Artifact) artifact).getArtifactId().contains(artifactId)) {
127                 return true;
128             }
129         }
130         return false;
131     }
132 
133     /**
134      * This method will copy files from the source directory to the destination directory based on
135      * the pattern.
136      *
137      * @param inSourceDirectory      The source directory to copy from.
138      * @param inDestinationDirectory The destination directory to copy to.
139      * @param inPattern              The file pattern to match to locate files to copy.
140      */
141     protected void copyGeneratedObjects(final String inSourceDirectory, final String inDestinationDirectory,
142                                         final String inPattern) {
143         Copy copyTask = (Copy) antProject.createTask("copy");
144 
145         FileSet fileSet = AntUtils.createFileset(inSourceDirectory, inPattern, new ArrayList());
146         log("Installing generated files (pattern: " + inPattern + ")...");
147         copyTask.setTodir(new File(inDestinationDirectory));
148         copyTask.addFileset(fileSet);
149         copyTask.execute();
150     }
151 
152     private String pojoLowerCase(String name) {
153         return name.substring(0, 1).toLowerCase() + name.substring(1);
154     }
155 
156     private String getPathToApplicationContext() {
157         if (project.getPackaging().equalsIgnoreCase("war")) {
158             return "/src/main/webapp/WEB-INF/applicationContext.xml";
159         } else { // if (project.getPackaging().equalsIgnoreCase("jar")) {
160             return "/src/main/resources/applicationContext.xml";
161         }
162     }
163 
164     /**
165      * Add sample-data.xml to project's sample-data.xml
166      */
167     private void installSampleData() {
168         createLoadFileTask("src/test/resources/" + pojoName + "-sample-data.xml", "sample.data").execute();
169         File existingFile = new File(destinationDirectory + "/src/test/resources/sample-data.xml");
170 
171         parseXMLFile(existingFile, null, "</dataset>", "sample.data");
172     }
173 
174     /* APF-1105: Changed to use Spring annotations (@Repository, @Service and @Autowired)
175     private void installDaoAndManagerBeanDefinitions() {
176         createLoadFileTask("src/main/resources/" + pojoName + "Dao-bean.xml", "dao.context.file").execute();
177         File generatedFile = new File(destinationDirectory + getPathToApplicationContext());
178 
179         parseXMLFile(generatedFile, pojoName + "Dao", "<!-- Add new DAOs here -->", "dao.context.file");
180 
181         createLoadFileTask("src/main/resources/" + pojoName + "Manager-bean.xml", "mgr.context.file").execute();
182         generatedFile = new File(destinationDirectory + getPathToApplicationContext());
183 
184         parseXMLFile(generatedFile, pojoName + "Manager", "<!-- Add new Managers here -->", "mgr.context.file");
185     }*/
186 
187     private void installiBATISFiles() {
188         if (project.getProperties().getProperty("dao.framework").equals("ibatis")) {
189             log("Installing iBATIS SQL Maps...");
190             createLoadFileTask("src/main/resources/" + pojoName + "-sql-map-config.xml", "sql.map.config").execute();
191             File sqlMapConfig = new File(destinationDirectory + "/src/main/resources/sql-map-config.xml");
192             parseXMLFile(sqlMapConfig, null, "</sqlMapConfig>", "sql.map.config");
193 
194             File sqlMapsDir = new File(destinationDirectory + "/src/main/resources/sqlmaps");
195             if (sqlMapsDir.exists()) {
196                 sqlMapsDir.mkdir();
197             }
198 
199             Copy copy = (Copy) antProject.createTask("copy");
200             copy.setFile(new File(sourceDirectory + "/src/main/resources/sqlmaps/" + pojoName + "SQL.xml"));
201             copy.setTodir(new File(destinationDirectory + "/src/main/resources/sqlmaps"));
202             copy.execute();
203 
204             // Add compass gps bean if it doesn't exist
205             File ctx = new File(destinationDirectory + "/src/main/webapp/WEB-INF/applicationContext.xml");
206             try {
207                 File appCtx = new File(destinationDirectory + "/src/main/webapp/WEB-INF/applicationContext.xml");
208                 String appCtxAsString = FileUtils.readFileToString(ctx);
209                 if (!appCtxAsString.contains("SqlMapClientGpsDevice")) {
210                     log("Adding compassGps bean to applicationContext.xml");
211                     createLoadFileTask("src/main/resources/compass-gps.xml", "compass.gps").execute();
212                     parseXMLFile(appCtx, null, "<!-- Add new DAOs here -->", "compass.gps");
213                 }
214 
215                 if (!appCtxAsString.contains("<value>get" + pojoName)) {
216                     // add value to list of select statement Ids
217                     createLoadFileTask("src/main/resources/" + pojoName + "-select-ids.xml", "select.ids").execute();
218                     parseXMLFile(appCtx, null, "<value>getUsers</value>", "select.ids");
219                 }
220             } catch (IOException e) {
221                 log("Failed to read project's applicationContext.xml!");
222                 e.printStackTrace();
223             }
224         }
225     }
226 
227     private void installGenericBeanDefinitions() {
228         createLoadFileTask("src/main/resources/" + pojoName + "-generic-beans.xml", "context.file").execute();
229         File generatedFile = new File(destinationDirectory + getPathToApplicationContext());
230 
231         parseXMLFile(generatedFile, pojoName + "Manager", "<!-- Add new Managers here -->", "context.file");
232     }
233 
234     private void installJSFNavigationAndBeans() {
235         createLoadFileTask("src/main/webapp/WEB-INF/" + pojoName + "-navigation.xml", "navigation.rules").execute();
236         File generatedFile = new File(destinationDirectory + "/src/main/webapp/WEB-INF/faces-config.xml");
237         parseXMLFile(generatedFile, pojoName + "-nav", "<!-- Add additional rules here -->", "navigation.rules");
238 
239         // JSF managed beans configured by Spring annotations in 2.1+
240         //createLoadFileTask("src/main/webapp/WEB-INF/" + pojoName + "-managed-beans.xml", "managed.beans").execute();
241         //generatedFile = new File(destinationDirectory + "/src/main/webapp/WEB-INF/faces-config.xml");
242         //parseXMLFile(generatedFile, pojoName + "-beans", "<!-- Add additional beans here -->", "managed.beans");
243     }
244 
245     private void installSpringControllerBeanDefinitions() {
246         // Controllers configured by Spring annotations in 2.1+
247         //createLoadFileTask("src/main/webapp/WEB-INF/" + pojoName + "-beans.xml", "dispatcher.servlet").execute();
248         //File generatedFile = new File(destinationDirectory + "/src/main/webapp/WEB-INF/dispatcher-servlet.xml");
249 
250         //parseXMLFile(generatedFile, pojoName, "<!-- Add additional controller beans here -->", "dispatcher.servlet");
251     }
252 
253     private void installSpringValidation() {
254         createLoadFileTask("src/main/webapp/WEB-INF/" + pojoName + "-validation.xml", "spring.validation").execute();
255         File generatedFile = new File(destinationDirectory + "/src/main/webapp/WEB-INF/validation.xml");
256 
257         parseXMLFile(generatedFile, pojoName, "    </formset>", "spring.validation");
258     }
259 
260     private void installStrutsActionDefinitions() {
261         createLoadFileTask("src/main/resources/" + pojoName + "-struts.xml", "struts.file").execute();
262         File existingFile = new File(destinationDirectory + "/src/main/resources/struts.xml");
263 
264         parseXMLFile(existingFile, pojoName + "Action", "<!-- Add additional actions here -->", "struts.file");
265     }
266 
267     // =================== Views ===================
268 
269     private void installJSFViews() {
270         Copy copy = (Copy) antProject.createTask("copy");
271         copy.setFile(new File(sourceDirectory + "/src/main/webapp/" + pojoName + "Form.xhtml"));
272         copy.setTofile(new File(destinationDirectory + "/src/main/webapp/" + pojoNameLower + "Form.xhtml"));
273         copy.execute();
274 
275         copy.setFile(new File(sourceDirectory + "/src/main/webapp/" + pojoName + "s.xhtml"));
276         copy.setTofile(new File(destinationDirectory + "/src/main/webapp/" + util.getPluralForWord(pojoNameLower) + ".xhtml"));
277         copy.execute();
278     }
279 
280     private void installSpringViews() {
281         Copy copy = (Copy) antProject.createTask("copy");
282         copy.setFile(new File(sourceDirectory + "/src/main/webapp/WEB-INF/pages/" + pojoName + "form.jsp"));
283         copy.setTofile(new File(destinationDirectory + "/src/main/webapp/WEB-INF/pages/" + pojoNameLower + "form.jsp"));
284         copy.execute();
285 
286         copy.setFile(new File(sourceDirectory + "/src/main/webapp/WEB-INF/pages/" + pojoName + "s.jsp"));
287         copy.setTofile(new File(destinationDirectory + "/src/main/webapp/WEB-INF/pages/" + util.getPluralForWord(pojoNameLower) + ".jsp"));
288         copy.execute();
289     }
290 
291     private void installStrutsViews() {
292         Copy copy = (Copy) antProject.createTask("copy");
293         copy.setFile(new File(sourceDirectory + "/src/main/webapp/WEB-INF/pages/" + pojoName + "Form.jsp"));
294         copy.setTofile(new File(destinationDirectory + "/src/main/webapp/WEB-INF/pages/" + pojoNameLower + "Form.jsp"));
295         copy.execute();
296 
297         copy.setFile(new File(sourceDirectory + "/src/main/webapp/WEB-INF/pages/" + pojoName + "List.jsp"));
298         copy.setTofile(new File(destinationDirectory + "/src/main/webapp/WEB-INF/pages/" + pojoNameLower + "List.jsp"));
299         copy.execute();
300     }
301 
302     private void installTapestryViews() {
303         Copy copy = (Copy) antProject.createTask("copy");
304         copy.setFile(new File(sourceDirectory + "/src/main/webapp/" + pojoName + "Form.tml"));
305         copy.setTodir(new File(destinationDirectory + "/src/main/webapp"));
306         copy.execute();
307 
308         copy.setFile(new File(sourceDirectory + "/src/main/webapp/" + pojoName + "List.tml"));
309         copy.execute();
310     }
311 
312     // =================== End of Views ===================
313 
314     private void installMenu() {
315         boolean hasStrutsMenu;
316         File menuConfig = new File(destinationDirectory + "/src/main/webapp/WEB-INF/menu-config.xml");
317         hasStrutsMenu = menuConfig.exists();
318 
319         if (hasStrutsMenu) {
320             createLoadFileTask("src/main/webapp/common/" + pojoName + "-menu.jsp", "menu.jsp").execute();
321             File existingFile = new File(destinationDirectory + "/src/main/webapp/common/menu.jsp");
322 
323             parseXMLFile(existingFile, pojoName, "</ul>", "menu.jsp");
324 
325             createLoadFileTask("src/main/webapp/WEB-INF/" + pojoName + "-menu-config.xml", "menu.config").execute();
326             existingFile = new File(destinationDirectory + "/src/main/webapp/WEB-INF/menu-config.xml");
327 
328             parseXMLFile(existingFile, pojoName, "    </Menus>", "menu.config");
329         } else {
330             createLoadFileTask("src/main/webapp/common/" + pojoName + "-menu-light.jsp", "menu-light.jsp").execute();
331             File existingFile = new File(destinationDirectory + "/src/main/webapp/decorators/default.jsp");
332 
333             parseXMLFile(existingFile, pojoName, "<!-- Add new menu items here -->", "menu-light.jsp");
334         }
335     }
336 
337     private void installInternationalizationKeys(String webFramework) {
338         createLoadFileTask("src/main/resources/" + pojoName + "-ApplicationResources.properties", "i18n.file").execute();
339         File existingFile = new File(destinationDirectory + "/src/main/resources/ApplicationResources.properties");
340 
341         // if ApplicationResources doesn't exist, assume appfuse-light and use messages instead
342         if (!existingFile.exists()) {
343             existingFile = new File(destinationDirectory + "/src/main/resources/messages.properties");
344             /*
345             if ("tapestry".equals(webFramework)) {
346                 existingFile = new File(destinationDirectory + "/src/main/webapp/WEB-INF/app.properties");
347             } else {
348                 existingFile = new File(destinationDirectory + "/src/main/resources/messages.properties");
349             }*/
350         }
351 
352         parsePropertiesFile(existingFile, pojoName);
353 
354         Echo echoTask = (Echo) antProject.createTask("echo");
355         echoTask.setFile(existingFile);
356         echoTask.setAppend(true);
357         echoTask.setMessage(antProject.getProperty("i18n.file"));
358         echoTask.execute();
359     }
360 
361     private void installUITests() {
362         // Gracefully handle when ui tests don't exist
363         boolean webTestsExist = new File("src/test/resources/" + pojoName + "-web-tests.xml").exists();
364         File existingFile = new File(destinationDirectory + "/src/test/resources/web-tests.xml");
365         if (webTestsExist && existingFile.exists()) {
366             createLoadFileTask("src/test/resources/" + pojoName + "-web-tests.xml", "web.tests").execute();
367             parseXMLFile(existingFile, pojoName, "</project>", "web.tests");
368 
369             // Add main target to run-all-tests target
370             Replace replace = (Replace) antProject.createTask("replace");
371             replace.setFile(existingFile);
372 
373             try {
374                 if (FileUtils.readFileToString(existingFile).contains("FileUpload")) {
375                     // todo: figure out how to fix the 2 lines below so they don't include pojoNameTest
376                     // multiple times on subsequent installs
377                     replace.setToken(",FileUpload");
378                     replace.setValue(",FileUpload," + pojoName + "Tests");
379                 } else {
380                     // AppFuse Light
381                     replace.setToken("depends=\"UserTests");
382                     replace.setValue("depends=\"UserTests," + pojoName + "Tests");
383                 }
384             } catch (IOException e) {
385                 e.printStackTrace();
386             }
387             replace.execute();
388         } else {
389             log("Project doesn't use Canoo WebTest, disabling UI test generation.");
390             log("Support for jWebUnit will be added in a future release.");
391             log("See http://issues.appfuse.org/browse/EQX-215 for more information.");
392         }
393     }
394 
395     /**
396      * This method will create an ANT based LoadFile task based on an infile and a property name.
397      * The property will be loaded with the infile for use later by the Replace task.
398      *
399      * @param inFile   The file to process
400      * @param propName the name to assign it to
401      * @return The ANT LoadFile task that loads a property with a file
402      */
403     protected LoadFile createLoadFileTask(String inFile, String propName) {
404         inFile = sourceDirectory + FILE_SEP + inFile;
405         LoadFile loadFileTask = (LoadFile) antProject.createTask("loadfile");
406         loadFileTask.init();
407         loadFileTask.setProperty(propName);
408         loadFileTask.setSrcFile(new File(inFile));
409 
410         return loadFileTask;
411     }
412 
413     private void parseXMLFile(File existingFile, String beanName, String tokenToReplace, String fileVariable) {
414         String nameInComment = beanName;
415         if (beanName == null) {
416             nameInComment = pojoName;
417         }
418         Replace replace1 = (Replace) antProject.createTask("replace");
419         replace1.setFile(existingFile);
420         replace1.setToken("<!--" + nameInComment + "-START-->");
421         replace1.setValue("REGULAR-START");
422         replace1.execute();
423 
424         Replace replace2 = (Replace) antProject.createTask("replace");
425         replace2.setFile(existingFile);
426         replace2.setToken("<!--" + nameInComment + "-END-->");
427         replace2.setValue("REGULAR-END");
428         replace2.execute();
429 
430         ReplaceRegExp regExpTask = (ReplaceRegExp) antProject.createTask("replaceregexp");
431         regExpTask.setFile(existingFile);
432         regExpTask.setMatch("REGULAR-START(?s:.)*REGULAR-END");
433         regExpTask.setReplace("");
434         regExpTask.setFlags("g");
435         regExpTask.execute();
436 
437         Replace replaceData = (Replace) antProject.createTask("replace");
438         replaceData.setFile(existingFile);
439         replaceData.setToken(tokenToReplace);
440         String stringWithProperLineEndings = adjustLineEndingsForOS(antProject.getProperty(fileVariable));
441         replaceData.setValue(stringWithProperLineEndings);
442         replaceData.execute();
443     }
444 
445     /**
446      * This file is the same as the method above, except for different comment placeholder formats.
447      * Yeah, I know, it's ugly.
448      *
449      * @param existingFile file to merge with in project
450      * @param beanName     name of placeholder string that goes in comment
451      */
452     private void parsePropertiesFile(File existingFile, String beanName) {
453         String nameInComment = beanName;
454         if (beanName == null) {
455             nameInComment = pojoName;
456         }
457 
458         Replace replace1 = (Replace) antProject.createTask("replace");
459         replace1.setFile(existingFile);
460         replace1.setToken("# -- " + nameInComment + "-START");
461         replace1.setValue("REGULAR-START");
462         replace1.execute();
463 
464         Replace replace2 = (Replace) antProject.createTask("replace");
465         replace2.setFile(existingFile);
466         replace2.setToken("# -- " + nameInComment + "-END");
467         replace2.setValue("REGULAR-END");
468         replace2.execute();
469 
470         ReplaceRegExp regExpTask = (ReplaceRegExp) antProject.createTask("replaceregexp");
471         regExpTask.setFile(existingFile);
472         regExpTask.setMatch("REGULAR-START(?s:.)*REGULAR-END");
473         regExpTask.setReplace("");
474         regExpTask.setFlags("g");
475         regExpTask.execute();
476     }
477 
478     private static String adjustLineEndingsForOS(String property) {
479         String os = System.getProperty("os.name");
480 
481         if (os.startsWith("Linux") || os.startsWith("Mac")) {
482             // remove the \r returns
483             property = property.replaceAll("\r", "");
484         } else if (os.startsWith("Windows")) {
485             // use windows line endings
486             property = property.replaceAll(">\n", ">\r\n");
487         }
488 
489         return property;
490     }
491 
492     private void log(String msg) {
493         getLog().info("[AppFuse] " + msg);
494     }
495 
496     public Log getLog() {
497         if (log == null) {
498             log = new SystemStreamLog();
499         }
500 
501         return log;
502     }
503 
504     public void setProject(MavenProject project) {
505         this.project = project;
506     }
507 
508     public void setGenericCore(boolean genericCore) {
509         this.genericCore = genericCore;
510     }
511 }