package org.appfuse.tool; import org.apache.commons.io.FileUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.logging.Log; import org.apache.maven.plugin.logging.SystemStreamLog; import org.apache.maven.project.MavenProject; import org.apache.tools.ant.Project; import org.apache.tools.ant.taskdefs.Copy; import org.apache.tools.ant.taskdefs.Echo; import org.apache.tools.ant.taskdefs.LoadFile; import org.apache.tools.ant.taskdefs.Replace; import org.apache.tools.ant.taskdefs.optional.ReplaceRegExp; import org.apache.tools.ant.types.FileSet; import org.appfuse.mojo.installer.AntUtils; import java.io.File; import java.io.IOException; import java.util.ArrayList; /** * This class is responsible for installing generated CRUD artifacts into an AppFuse application. * * @author mraible */ public class ArtifactInstaller { private Log log; static final String FILE_SEP = System.getProperty("file.separator"); Project antProject; String pojoName; String pojoNameLower; String destinationDirectory; String sourceDirectory; MavenProject project; boolean genericCore; StringUtils util; public ArtifactInstaller(MavenProject project, String pojoName, String sourceDirectory, String destinationDirectory, boolean genericCore) { this.project = project; this.pojoName = pojoName; this.pojoNameLower = pojoLowerCase(pojoName); this.sourceDirectory = sourceDirectory; this.destinationDirectory = destinationDirectory; this.genericCore = genericCore; this.util = new StringUtils(); } public void execute() { antProject = AntUtils.createProject(); boolean hasDbUnit = projectContainsPluginArtifact("dbunit"); if (hasDbUnit) { log("Installing sample data for DbUnit..."); installSampleData(); } // install dao and manager if jar (modular/core) or war w/o parent (basic) if (project.getPackaging().equals("jar") || (project.getPackaging().equals("war") && project.getParentArtifact().getGroupId().contains("appfuse"))) { copyGeneratedObjects(this.sourceDirectory, this.destinationDirectory, "**/model/**/*.java"); copyGeneratedObjects(this.sourceDirectory, this.destinationDirectory, "**/dao/**/*.java"); copyGeneratedObjects(this.sourceDirectory, this.destinationDirectory, "**/service/**/*.java"); if (genericCore) { log("Installing Spring bean definitions (genericCore == true)..."); installGenericBeanDefinitions(); } // only installs if iBATIS is configured as dao.framework installiBATISFiles(); } if (project.getPackaging().equalsIgnoreCase("war")) { copyGeneratedObjects(this.sourceDirectory, this.destinationDirectory, "**/webapp/**/*.java"); String webFramework = project.getProperties().getProperty("web.framework"); if ("jsf".equalsIgnoreCase(webFramework)) { log("Installing JSF views and configuring..."); installJSFNavigationAndBeans(); installJSFViews(); } else if ("struts".equalsIgnoreCase(webFramework)) { log("Installing Struts views and configuring..."); installStrutsActionDefinitions(); copyGeneratedObjects(sourceDirectory + "/src/main/resources", destinationDirectory + "/src/main/resources", "**/model/*.xml"); copyGeneratedObjects(sourceDirectory + "/src/main/resources", destinationDirectory + "/src/main/resources", "**/webapp/action/*.xml"); installStrutsViews(); } else if ("spring".equalsIgnoreCase(webFramework) || "spring-security".equalsIgnoreCase(webFramework)) { log("Installing Spring views..."); installSpringValidation(); installSpringViews(); } else if ("spring-freemarker".equalsIgnoreCase(webFramework)) { log("Installing Freemarker views..."); installSpringFreemarkerViews(); } else if ("stripes".equalsIgnoreCase(webFramework)) { log("Installing Stripes views..."); installStripesViews(); } else if ("tapestry".equalsIgnoreCase(webFramework)) { log("Installing Tapestry views..."); installTapestryViews(); } else if ("wicket".equalsIgnoreCase(webFramework)) { log("Installing Wicket views..."); installWicketViews(); } log("Installing i18n messages..."); installInternationalizationKeys(webFramework); if (!"tapestry".equalsIgnoreCase(webFramework)) { log("Installing menu..."); installMenu(); } log("Installing UI tests..."); installUITests(webFramework); } } private boolean projectContainsPluginArtifact(String artifactId) { for (Object artifact : project.getPluginArtifacts()) { if (((Artifact) artifact).getArtifactId().contains(artifactId)) { return true; } } return false; } private boolean projectContainsArtifact(String artifactId) { for (Object artifact : project.getArtifacts()) { if (((Artifact) artifact).getArtifactId().contains(artifactId)) { return true; } } return false; } /** * This method will copy files from the source directory to the destination directory based on * the pattern. * * @param inSourceDirectory The source directory to copy from. * @param inDestinationDirectory The destination directory to copy to. * @param inPattern The file pattern to match to locate files to copy. */ protected void copyGeneratedObjects(final String inSourceDirectory, final String inDestinationDirectory, final String inPattern) { Copy copyTask = (Copy) antProject.createTask("copy"); FileSet fileSet = AntUtils.createFileset(inSourceDirectory, inPattern, new ArrayList()); log("Installing generated files (pattern: " + inPattern + ")..."); copyTask.setTodir(new File(inDestinationDirectory)); copyTask.addFileset(fileSet); copyTask.execute(); } private String pojoLowerCase(String name) { return name.substring(0, 1).toLowerCase() + name.substring(1); } private String getPathToApplicationContext() { if (project.getPackaging().equalsIgnoreCase("war")) { return "/src/main/webapp/WEB-INF/applicationContext.xml"; } else { // if (project.getPackaging().equalsIgnoreCase("jar")) { return "/src/main/resources/applicationContext.xml"; } } /** * Add sample-data.xml to project's sample-data.xml */ private void installSampleData() { createLoadFileTask("src/test/resources/" + pojoName + "-sample-data.xml", "sample.data").execute(); File existingFile = new File(destinationDirectory + "/src/test/resources/sample-data.xml"); parseXMLFile(existingFile, null, "</dataset>", "sample.data"); } private void installiBATISFiles() { if (project.getProperties().getProperty("dao.framework").equals("ibatis")) { log("Installing iBATIS SQL Maps..."); createLoadFileTask("src/main/resources/" + pojoName + "-sql-map-config.xml", "sql.map.config").execute(); File sqlMapConfig = new File(destinationDirectory + "/src/main/resources/sql-map-config.xml"); parseXMLFile(sqlMapConfig, null, "</sqlMapConfig>", "sql.map.config"); File sqlMapsDir = new File(destinationDirectory + "/src/main/resources/sqlmaps"); if (sqlMapsDir.exists()) { sqlMapsDir.mkdir(); } Copy copy = (Copy) antProject.createTask("copy"); copy.setFile(new File(sourceDirectory + "/src/main/resources/sqlmaps/" + pojoName + "SQL.xml")); copy.setTodir(new File(destinationDirectory + "/src/main/resources/sqlmaps")); copy.execute(); // Add compass gps bean if it doesn't exist File ctx = new File(destinationDirectory + "/src/main/webapp/WEB-INF/applicationContext.xml"); try { File appCtx = new File(destinationDirectory + "/src/main/webapp/WEB-INF/applicationContext.xml"); String appCtxAsString = FileUtils.readFileToString(ctx); if (!appCtxAsString.contains("SqlMapClientGpsDevice")) { log("Adding compassGps bean to applicationContext.xml"); createLoadFileTask("src/main/resources/compass-gps.xml", "compass.gps").execute(); parseXMLFile(appCtx, null, "<!-- Add new DAOs here -->", "compass.gps"); } if (!appCtxAsString.contains("<value>get" + pojoName)) { // add value to list of select statement Ids createLoadFileTask("src/main/resources/" + pojoName + "-select-ids.xml", "select.ids").execute(); parseXMLFile(appCtx, null, "<value>getUsers</value>", "select.ids"); } } catch (IOException e) { log("Failed to read project's applicationContext.xml!"); e.printStackTrace(); } } } private void installGenericBeanDefinitions() { createLoadFileTask("src/main/resources/" + pojoName + "-generic-beans.xml", "context.file").execute(); File generatedFile = new File(destinationDirectory + getPathToApplicationContext()); parseXMLFile(generatedFile, pojoName + "Manager", "<!-- Add new Managers here -->", "context.file"); } private void installJSFNavigationAndBeans() { createLoadFileTask("src/main/webapp/WEB-INF/" + pojoName + "-navigation.xml", "navigation.rules").execute(); File generatedFile = new File(destinationDirectory + "/src/main/webapp/WEB-INF/faces-config.xml"); parseXMLFile(generatedFile, pojoName + "-nav", "<!-- Add additional rules here -->", "navigation.rules"); } private void installSpringValidation() { createLoadFileTask("src/main/webapp/WEB-INF/" + pojoName + "-validation.xml", "spring.validation").execute(); File generatedFile = new File(destinationDirectory + "/src/main/webapp/WEB-INF/validation.xml"); parseXMLFile(generatedFile, pojoName, " </formset>", "spring.validation"); } private void installStrutsActionDefinitions() { createLoadFileTask("src/main/resources/" + pojoName + "-struts.xml", "struts.file").execute(); File existingFile = new File(destinationDirectory + "/src/main/resources/struts.xml"); parseXMLFile(existingFile, pojoName + "Action", "<!-- Add additional actions here -->", "struts.file"); } // =================== Views =================== private void installJSFViews() { Copy copy = (Copy) antProject.createTask("copy"); copy.setFile(new File(sourceDirectory + "/src/main/webapp/" + pojoName + "Form.xhtml")); copy.setTofile(new File(destinationDirectory + "/src/main/webapp/" + pojoNameLower + "Form.xhtml")); copy.execute(); copy.setFile(new File(sourceDirectory + "/src/main/webapp/" + pojoName + "s.xhtml")); copy.setTofile(new File(destinationDirectory + "/src/main/webapp/" + util.getPluralForWord(pojoNameLower) + ".xhtml")); copy.execute(); } private void installSpringViews() { Copy copy = (Copy) antProject.createTask("copy"); copy.setFile(new File(sourceDirectory + "/src/main/webapp/WEB-INF/pages/" + pojoName + "form.jsp")); copy.setTofile(new File(destinationDirectory + "/src/main/webapp/WEB-INF/pages/" + pojoNameLower + "form.jsp")); copy.execute(); copy.setFile(new File(sourceDirectory + "/src/main/webapp/WEB-INF/pages/" + pojoName + "s.jsp")); copy.setTofile(new File(destinationDirectory + "/src/main/webapp/WEB-INF/pages/" + util.getPluralForWord(pojoNameLower) + ".jsp")); copy.execute(); } private void installSpringFreemarkerViews() { Copy copy = (Copy) antProject.createTask("copy"); copy.setFile(new File(sourceDirectory + "/src/main/webapp/" + pojoName + "form.ftl")); copy.setTofile(new File(destinationDirectory + "/src/main/webapp/" + pojoNameLower + "form.ftl")); copy.execute(); copy.setFile(new File(sourceDirectory + "/src/main/webapp/" + pojoName + "list.ftl")); copy.setTofile(new File(destinationDirectory + "/src/main/webapp/" + util.getPluralForWord(pojoNameLower) + ".ftl")); copy.execute(); } private void installStripesViews() { Copy copy = (Copy) antProject.createTask("copy"); copy.setFile(new File(sourceDirectory + "/src/main/webapp/" + pojoName + "Form.jsp")); copy.setTofile(new File(destinationDirectory + "/src/main/webapp/" + pojoNameLower + "Form.jsp")); copy.execute(); copy.setFile(new File(sourceDirectory + "/src/main/webapp/" + pojoName + "List.jsp")); copy.setTofile(new File(destinationDirectory + "/src/main/webapp/" + pojoNameLower + "List.jsp")); copy.execute(); } private void installStrutsViews() { Copy copy = (Copy) antProject.createTask("copy"); copy.setFile(new File(sourceDirectory + "/src/main/webapp/WEB-INF/pages/" + pojoName + "Form.jsp")); copy.setTofile(new File(destinationDirectory + "/src/main/webapp/WEB-INF/pages/" + pojoNameLower + "Form.jsp")); copy.execute(); copy.setFile(new File(sourceDirectory + "/src/main/webapp/WEB-INF/pages/" + pojoName + "List.jsp")); copy.setTofile(new File(destinationDirectory + "/src/main/webapp/WEB-INF/pages/" + pojoNameLower + "List.jsp")); copy.execute(); } private void installTapestryViews() { Copy copy = (Copy) antProject.createTask("copy"); copy.setFile(new File(sourceDirectory + "/src/main/webapp/" + pojoName + "Form.tml")); copy.setTodir(new File(destinationDirectory + "/src/main/webapp")); copy.execute(); copy.setFile(new File(sourceDirectory + "/src/main/webapp/" + pojoName + "List.tml")); copy.execute(); log("Installing menu..."); createLoadFileTask("src/main/webapp/" + pojoName + "-menu.tml", "tapestry-menu").execute(); File existingFile = new File(destinationDirectory + "/src/main/resources/" + project.getGroupId().replace(".", "/") + "/webapp/components/Layout.tml"); parseXMLFile(existingFile, pojoName, "<!-- Add new menu items here -->", "tapestry-menu"); } private void installWicketViews() { copyGeneratedObjects(this.sourceDirectory, this.destinationDirectory, "**/pages/*.html"); } private boolean isAppFuse() { return (project.getParent().getArtifactId().contains("appfuse-web") || project.getParent().getParent().getGroupId().contains("appfuse")); } // =================== End of Views =================== private void installMenu() { boolean hasStrutsMenu; File menuConfig = new File(destinationDirectory + "/src/main/webapp/WEB-INF/menu-config.xml"); hasStrutsMenu = menuConfig.exists(); if (hasStrutsMenu) { createLoadFileTask("src/main/webapp/common/" + pojoName + "-menu.jsp", "menu.jsp").execute(); File existingFile = new File(destinationDirectory + "/src/main/webapp/common/menu.jsp"); parseXMLFile(existingFile, pojoName, "</ul>", "menu.jsp"); createLoadFileTask("src/main/webapp/WEB-INF/" + pojoName + "-menu-config.xml", "menu.config").execute(); existingFile = new File(destinationDirectory + "/src/main/webapp/WEB-INF/menu-config.xml"); parseXMLFile(existingFile, pojoName, " </Menus>", "menu.config"); } else { File freemarker = new File(destinationDirectory + "/src/main/webapp/decorators/default.ftl"); if (freemarker.exists()) { createLoadFileTask("src/main/webapp/common/" + pojoName + "-menu-light.ftl", "menu-light.jsp").execute(); } else { createLoadFileTask("src/main/webapp/common/" + pojoName + "-menu-light.jsp", "menu-light.jsp").execute(); } File existingFile = new File(destinationDirectory + "/src/main/webapp/decorators/default.jsp"); File jsfConfig = new File(destinationDirectory + "/src/main/webapp/WEB-INF/faces-config.xml"); if (jsfConfig.exists()) { existingFile = new File(destinationDirectory + "/src/main/webapp/layouts/default.xhtml"); } if (freemarker.exists()) { existingFile = new File(destinationDirectory + "/src/main/webapp/decorators/default.ftl"); } parseXMLFile(existingFile, pojoName, "<!-- Add new menu items here -->", "menu-light.jsp"); } } private void installInternationalizationKeys(String webFramework) { createLoadFileTask("src/main/resources/" + pojoName + "-ApplicationResources.properties", "i18n.file").execute(); File existingFile = new File(destinationDirectory + "/src/main/resources/ApplicationResources.properties"); // if ApplicationResources doesn't exist, assume appfuse-light and use messages instead if (!existingFile.exists()) { if ("wicket".equalsIgnoreCase(webFramework)) { existingFile = new File(destinationDirectory + "/src/main/java/" + project.getGroupId().replace(".", "/") + "/webapp/pages/AbstractWebPage.properties"); } else { existingFile = new File(destinationDirectory + "/src/main/resources/messages.properties"); } } parsePropertiesFile(existingFile, pojoName); Echo echoTask = (Echo) antProject.createTask("echo"); echoTask.setFile(existingFile); echoTask.setAppend(true); echoTask.setMessage(antProject.getProperty("i18n.file")); echoTask.execute(); } private void installUITests(String webFramework) { // Gracefully handle when ui tests don't exist boolean webTestsExist = new File("src/test/resources/" + "web-tests.xml").exists(); File existingFile = new File(destinationDirectory + "/src/test/resources/web-tests.xml"); if (webTestsExist && existingFile.exists()) { createLoadFileTask("src/test/resources/" + pojoName + "-web-tests.xml", "web.tests").execute(); parseXMLFile(existingFile, pojoName, "</project>", "web.tests"); // Add main target to run-all-tests target Replace replace = (Replace) antProject.createTask("replace"); replace.setFile(existingFile); try { if (FileUtils.readFileToString(existingFile).contains("FileUpload")) { // todo: figure out how to fix the 2 lines below so they don't include pojoNameTest // multiple times on subsequent installs if ("wicket".equalsIgnoreCase(webFramework)) { replace.setToken(",DWR"); replace.setValue(",DWR," + pojoName + "Tests"); } else { replace.setToken(",FileUpload"); replace.setValue(",FileUpload," + pojoName + "Tests"); } } else { // AppFuse Light replace.setToken("depends=\"UserTests"); replace.setValue("depends=\"UserTests," + pojoName + "Tests"); } } catch (IOException e) { e.printStackTrace(); } replace.execute(); // Delete any jWebUnit artifacts that may have been installed File jwebunitTest = new File(destinationDirectory + "/src/test/java/" + project.getGroupId().replace(".", "/") + "/webapp/" + pojoName + "WebTest.java"); jwebunitTest.delete(); } } /** * This method will create an ANT based LoadFile task based on an infile and a property name. * The property will be loaded with the infile for use later by the Replace task. * * @param inFile The file to process * @param propName the name to assign it to * @return The ANT LoadFile task that loads a property with a file */ protected LoadFile createLoadFileTask(String inFile, String propName) { inFile = sourceDirectory + FILE_SEP + inFile; LoadFile loadFileTask = (LoadFile) antProject.createTask("loadfile"); loadFileTask.init(); loadFileTask.setProperty(propName); loadFileTask.setSrcFile(new File(inFile)); return loadFileTask; } private void parseXMLFile(File existingFile, String beanName, String tokenToReplace, String fileVariable) { String nameInComment = beanName; if (beanName == null) { nameInComment = pojoName; } Replace replace1 = (Replace) antProject.createTask("replace"); replace1.setFile(existingFile); replace1.setToken("<!--" + nameInComment + "-START-->"); replace1.setValue("REGULAR-START"); replace1.execute(); Replace replace2 = (Replace) antProject.createTask("replace"); replace2.setFile(existingFile); replace2.setToken("<!--" + nameInComment + "-END-->"); replace2.setValue("REGULAR-END"); replace2.execute(); ReplaceRegExp regExpTask = (ReplaceRegExp) antProject.createTask("replaceregexp"); regExpTask.setFile(existingFile); regExpTask.setMatch("REGULAR-START(?s:.)*REGULAR-END"); regExpTask.setReplace(""); regExpTask.setFlags("g"); regExpTask.execute(); Replace replaceData = (Replace) antProject.createTask("replace"); replaceData.setFile(existingFile); replaceData.setToken(tokenToReplace); String stringWithProperLineEndings = adjustLineEndingsForOS(antProject.getProperty(fileVariable)); replaceData.setValue(stringWithProperLineEndings); replaceData.execute(); } /** * This file is the same as the method above, except for different comment placeholder formats. * Yeah, I know, it's ugly. * * @param existingFile file to merge with in project * @param beanName name of placeholder string that goes in comment */ private void parsePropertiesFile(File existingFile, String beanName) { String nameInComment = beanName; if (beanName == null) { nameInComment = pojoName; } Replace replace1 = (Replace) antProject.createTask("replace"); replace1.setFile(existingFile); replace1.setToken("# -- " + nameInComment + "-START"); replace1.setValue("REGULAR-START"); replace1.execute(); Replace replace2 = (Replace) antProject.createTask("replace"); replace2.setFile(existingFile); replace2.setToken("# -- " + nameInComment + "-END"); replace2.setValue("REGULAR-END"); replace2.execute(); ReplaceRegExp regExpTask = (ReplaceRegExp) antProject.createTask("replaceregexp"); regExpTask.setFile(existingFile); regExpTask.setMatch("REGULAR-START(?s:.)*REGULAR-END"); regExpTask.setReplace(""); regExpTask.setFlags("g"); regExpTask.execute(); } private static String adjustLineEndingsForOS(String property) { String os = System.getProperty("os.name"); if (os.startsWith("Linux") || os.startsWith("Mac")) { // remove the \r returns property = property.replaceAll("\r", ""); } else if (os.startsWith("Windows")) { // use windows line endings property = property.replaceAll(">\n", ">\r\n"); } return property; } private void log(String msg) { getLog().info("[AppFuse] " + msg); } public Log getLog() { if (log == null) { log = new SystemStreamLog(); } return log; } public void setProject(MavenProject project) { this.project = project; } public void setGenericCore(boolean genericCore) { this.genericCore = genericCore; } }