View Javadoc

1   package org.appfuse.mojo.exporter;
2   
3   
4   import org.apache.commons.io.FileUtils;
5   import org.apache.maven.plugin.MojoExecutionException;
6   import org.apache.maven.plugin.MojoFailureException;
7   import org.appfuse.mojo.HibernateExporterMojo;
8   import org.appfuse.tool.AppFuseExporter;
9   import org.appfuse.tool.ArtifactInstaller;
10  import org.codehaus.plexus.components.interactivity.Prompter;
11  import org.codehaus.plexus.components.interactivity.PrompterException;
12  import org.hibernate.tool.hbm2x.Exporter;
13  import org.xml.sax.Attributes;
14  import org.xml.sax.InputSource;
15  import org.xml.sax.SAXException;
16  import org.xml.sax.helpers.DefaultHandler;
17  
18  import javax.xml.parsers.SAXParser;
19  import javax.xml.parsers.SAXParserFactory;
20  import java.io.*;
21  import java.util.Collection;
22  
23  /**
24   * Generates Java classes from set of annotated POJOs. Use -DdisableInstallation to prevent installation.
25   * If using this goal in a "core" module or project, only DAOs and Managers will be created. For "web"
26   * modules, the same principle applies.
27   *
28   * @author <a href="mailto:matt@raibledesigns.com">Matt Raible</a>
29   * @goal gen
30   * @phase generate-sources
31   * @execute phase="compile"
32   */
33  public class AppFuseGeneratorMojo extends HibernateExporterMojo {
34      boolean generateCoreOnly;
35      boolean generateWebOnly;
36      String pojoName;
37  
38      /**
39       * This is a prompter that can be user within the maven framework.
40       *
41       * @component
42       */
43      Prompter prompter;
44  
45      /**
46       * The path where the generated artifacts will be placed. This is intentionally not set to the
47       * default location for maven generated sources. This is to keep these files out of the
48       * eclipse/idea generated sources directory as the intention is that these files will be copied
49       * to a source directory to be edited and modified and not re generated each time the plugin is
50       * run. If you want to regenerate the files each time you build the project just set this value
51       * to ${basedir}/target/generated-sources or set the flag on eclipse/idea plugin to include this
52       * file in your project file as a source directory.
53       *
54       * @parameter expression="${appfuse.destinationDirectory}" default-value="${basedir}"
55       * @noinspection UnusedDeclaration
56       */
57      private String destinationDirectory;
58  
59      /**
60       * The directory containing the source code.
61       *
62       * @parameter expression="${appfuse.sourceDirectory}" default-value="${basedir}/target/appfuse/generated-sources"
63       * @noinspection UnusedDeclaration
64       */
65      private String sourceDirectory;
66  
67      /**
68       * Allows disabling installation - for tests and end users that don't want to do a full installation
69       *
70       * @parameter expression="${appfuse.disableInstallation}" default-value="false"
71       */
72      private boolean disableInstallation;
73  
74      /**
75       * @parameter expression="${appfuse.genericCore}" default-value="true"
76       * @noinspection UnusedDeclaration
77       */
78      private boolean genericCore;
79  
80      /**
81       * @parameter expression="${appfuse.templateDirectory}" default-value="${basedir}/src/test/resources"
82       * @noinspection UnusedDeclaration
83       */
84      private String templateDirectory;
85  
86      /**
87       * Path for where to generate files at.
88       */
89  
90      private String fullPath = null;
91  
92      /**
93       * Default constructor.
94       */
95      public AppFuseGeneratorMojo() {
96          addDefaultComponent("target/appfuse/generated-sources", "configuration", false);
97          addDefaultComponent("target/appfuse/generated-sources", "annotationconfiguration", true);
98      }
99  
100 // --------------------- Interface ExporterMojo ---------------------
101 
102     /**
103      * Returns <b>gen</b>.
104      *
105      * @return String goal's name
106      */
107     public String getName() {
108         return "gen";
109     }
110 
111     @Override
112     public void execute() throws MojoExecutionException, MojoFailureException {
113         // if project is of type "pom", throw an error
114         if (getProject().getPackaging().equalsIgnoreCase("pom")) {
115             String errorMsg = "[ERROR] This plugin cannot be run from a pom project, please run it from a jar or war project (i.e. core or web).";
116             //getLog().error(errorMsg);
117             throw new MojoFailureException(errorMsg);
118         }
119 
120         pojoName = System.getProperty("entity");
121 
122         if (pojoName == null) {
123             try {
124                 pojoName = prompter.prompt("What is the name of your pojo (i.e. Person)?");
125             } catch (PrompterException pe) {
126                 pe.printStackTrace();
127             }
128         }
129 
130         if (pojoName == null || "".equals(pojoName.trim())) {
131             throw new MojoExecutionException("You must specify an entity name to continue.");
132         }
133 
134         // A dot in the entity name means the person is specifying the package.
135         if (pojoName.contains(".")) {
136             if (!pojoName.contains("model")) {
137                 throw new MojoExecutionException("You must specify 'model' as the last package in your entity name.");
138             }
139             fullPath = pojoName.substring(0, pojoName.indexOf(".model"));
140             // allow ~ to be used for groupId
141             fullPath = fullPath.replace("~", getProject().getGroupId());
142 
143             pojoName = pojoName.substring(pojoName.lastIndexOf(".") + 1);
144             log("Package name set to: " + fullPath);
145         }
146 
147         String daoFramework = getProject().getProperties().getProperty("dao.framework");
148 
149         // If dao.framework is jpa, change to jpaconfiguration and persistence.xml should be found in classpath.
150         // No other configuration is needed.
151         if (daoFramework.contains("jpa")) {
152             getComponentProperties().put("implementation", "jpaconfiguration");
153             checkEntityExists();
154         }
155 
156         // for war projects that have a parent pom, don't reset classpath
157         // this is to allow using hibernate.cfg.xml from core module
158         if (getProject().getPackaging().equals("war") && (getProject().hasParent()
159             && !getProject().getParentArtifact().getGroupId().contains("appfuse"))) {
160             // assume first module in parent project has hibernate.cfg.xml
161             String moduleName = (String) getProject().getParent().getModules().get(0);
162             String pathToParent = getProject().getOriginalModel().getParent().getRelativePath();
163             pathToParent = pathToParent.substring(0, pathToParent.lastIndexOf('/') + 1);
164             log("Assuming '" + moduleName + "' has hibernate.cfg.xml in its src/main/resources directory");
165             getComponentProperties().put("configurationfile",
166                 getProject().getBasedir() + "/" + pathToParent + moduleName + "/src/main/resources/hibernate.cfg.xml");
167         }
168 
169         if (getComponentProperty("configurationfile") == null) {
170             getComponentProperties().put("configurationfile", "src/main/resources/hibernate.cfg.xml");
171         }
172 
173         // if entity is not in hibernate.cfg.xml, add it
174         String hibernateConfig = getComponentProperty("configurationfile");
175 
176         if (daoFramework.equals("hibernate")) {
177             try {
178                 String hibernateCfgXml = FileUtils.readFileToString(new File(hibernateConfig));
179                 addEntityToHibernateCfgXml(hibernateCfgXml);
180             } catch (IOException io) {
181                 throw new MojoFailureException(io.getMessage());
182             }
183         }
184 
185         // If dao.framework is ibatis, programmatically create a hibernate.cfg.xml and put it in the classpath
186         if (daoFramework.equalsIgnoreCase("ibatis")) {
187             try {
188                 String pomAsString = FileUtils.readFileToString(new File("pom.xml"));
189                 if (pomAsString.contains("<implementation>annotationconfiguration</implementation>")) {
190                     // if no hibernate.cfg.xml exists, create one from template in plugin
191                     log("Creating hibernate.cfg.xml from template...");
192                     String hibernateCfgXml;
193                     File existingConfig = new File(hibernateConfig);
194                     if (!existingConfig.exists()) {
195                         InputStream in = this.getClass().getResourceAsStream("/appfuse/dao/ibatis/hibernate.cfg.ftl");
196                         StringBuilder configFile = new StringBuilder();
197                         try {
198                             InputStreamReader isr = new InputStreamReader(in);
199                             BufferedReader reader = new BufferedReader(isr);
200                             String line;
201                             while ((line = reader.readLine()) != null) {
202                                 configFile.append(line).append("\n");
203                             }
204                             reader.close();
205                         } catch (IOException io) {
206                             throw new MojoFailureException(io.getMessage());
207                         }
208 
209                         hibernateCfgXml = configFile.toString();
210                     } else {
211                         hibernateCfgXml = FileUtils.readFileToString(existingConfig);
212                     }
213 
214                     addEntityToHibernateCfgXml(hibernateCfgXml);
215                 } else {
216                     log("[WARN] Detected JPA configuration");
217                 }
218             } catch (IOException io) {
219                 io.printStackTrace();
220                 getLog().error(io.getMessage());
221             }
222         }
223 
224         super.execute();
225     }
226 
227     /**
228      * @see org.appfuse.mojo.HibernateExporterMojo#configureExporter(org.hibernate.tool.hbm2x.Exporter)
229      */
230     protected Exporter configureExporter(Exporter exp) throws MojoExecutionException {
231         // Read in AppFuseExporter#configureExporter to decide if a class should be generated or not
232         System.setProperty("appfuse.entity", pojoName);
233 
234         // add output directory to compile roots
235         getProject().addCompileSourceRoot(new File(getComponent().getOutputDirectory()).getPath());
236 
237         // now set the extra properties for the AppFuseExporter
238         AppFuseExporter exporter = (AppFuseExporter) super.configureExporter(exp);
239         exporter.getProperties().setProperty("ejb3", getComponentProperty("ejb3", "true"));
240         exporter.getProperties().setProperty("jdk5", getComponentProperty("jdk5", "true"));
241 
242         if (generateCoreOnly) {
243             exporter.getProperties().setProperty("generate-core", "true");
244         } else if (generateWebOnly) {
245             exporter.getProperties().setProperty("generate-web", "true");
246         }
247 
248         String rootPackage = (fullPath != null) ? fullPath : getProject().getGroupId();
249 
250         // AppFuse-specific values
251         exporter.getProperties().setProperty("basepackage", rootPackage);
252         exporter.getProperties().setProperty("daoframework", getProject().getProperties().getProperty("dao.framework"));
253 
254         String webFramework = (getProject().getProperties().containsKey("web.framework")) ?
255             getProject().getProperties().getProperty("web.framework") : "";
256 
257         exporter.getProperties().setProperty("webframework", webFramework);
258 
259         exporter.getProperties().setProperty("packaging", getProject().getPackaging());
260         exporter.getProperties().setProperty("genericcore", String.valueOf(genericCore));
261 
262         if (templateDirectory != null) {
263             exporter.getProperties().setProperty("templatedirectory", templateDirectory);
264         }
265 
266         if (isFullSource())
267             exporter.getProperties().setProperty("appfusepackage", rootPackage);
268         else {
269             exporter.getProperties().setProperty("appfusepackage", "org.appfuse");
270         }
271 
272         // See if the project has security enabled
273         boolean hasSecurity = false;
274         if (getProject().getPackaging().equals("war")) {
275             Collection<File> sourceFiles = FileUtils.listFiles(getProject().getBasedir(), new String[]{"xml"}, true);
276             for (File file : sourceFiles) {
277                 if (file.getPath().contains("security.xml")) {
278                     hasSecurity = true;
279                     break;
280                 }
281             }
282         }
283         exporter.getProperties().setProperty("hasSecurity", String.valueOf(hasSecurity));
284 
285         // determine if using Main or Home for Tapestry
286         if (webFramework.equals("tapestry")) {
287             boolean useHome = true;
288             Collection<File> sourceFiles = FileUtils.listFiles(getProject().getBasedir(), new String[]{"java"}, true);
289             for (File file : sourceFiles) {
290                 if (file.getPath().contains("Home.java")) {
291                     useHome = false;
292                     break;
293                 }
294             }
295             exporter.getProperties().setProperty("useHome", String.valueOf(useHome));
296         }
297 
298         return exporter;
299     }
300 
301     /**
302      * Executes the plugin in an isolated classloader.
303      *
304      * @throws MojoExecutionException When there is an error executing the plugin
305      */
306     @Override
307     protected void doExecute() throws MojoExecutionException {
308         super.doExecute();
309 
310         if (System.getProperty("disableInstallation") != null) {
311             disableInstallation = Boolean.valueOf(System.getProperty("disableInstallation"));
312         }
313 
314         // allow installation to be suppressed when testing
315         if (!disableInstallation) {
316             ArtifactInstaller installer = new ArtifactInstaller(getProject(), pojoName, sourceDirectory, destinationDirectory, genericCore);
317             installer.execute();
318         }
319     }
320 
321     /**
322      * Instantiates a org.appfuse.tool.AppFuseExporter object.
323      *
324      * @return POJOExporter
325      */
326     protected Exporter createExporter() {
327         return new AppFuseExporter();
328     }
329 
330     protected void setGenerateCoreOnly(boolean generateCoreOnly) {
331         this.generateCoreOnly = generateCoreOnly;
332     }
333 
334     protected void setGenerateWebOnly(boolean generateWebOnly) {
335         this.generateWebOnly = generateWebOnly;
336     }
337 
338     private void log(String msg) {
339         getLog().info("[AppFuse] " + msg);
340     }
341 
342     private void addEntityToHibernateCfgXml(String hibernateCfgXml) throws MojoFailureException {
343         String className = (fullPath != null) ? fullPath : getProject().getGroupId();
344 
345         className += ".model." + pojoName;
346         log("Constructed class name : " + className);
347 
348         POJOSearcher pojoSearcher = new POJOSearcher(hibernateCfgXml);
349         if (!pojoSearcher.searchForPojo(pojoName)) {
350             // check that class exists and has an @Entity annotation
351             checkEntityExists();
352 
353             hibernateCfgXml = hibernateCfgXml.replace("</session-factory>",
354                 "    <mapping class=\"" + className + "\"/>"
355                     + "\n    </session-factory>");
356             log("Adding '" + pojoName + "' to hibernate.cfg.xml...");
357         }
358 
359         hibernateCfgXml = hibernateCfgXml.replaceAll("\\$\\{appfusepackage}",
360             (isFullSource()) ? getProject().getGroupId() : "org.appfuse");
361 
362         try {
363             FileUtils.writeStringToFile(new File(
364                 getComponentProperty("configurationfile", "src/main/resources/hibernate.cfg.xml")), hibernateCfgXml);
365         } catch (IOException io) {
366             throw new MojoFailureException(io.getMessage());
367         }
368     }
369 
370     private void checkEntityExists() throws MojoFailureException {
371         // allow check to be bypassed when -Dentity.check=false
372         if (!"false".equals(System.getProperty("entity.check"))) {
373             final String FILE_SEP = System.getProperty("file.separator");
374             String pathToModelPackage = "src" + FILE_SEP + "main" + FILE_SEP + "java" + FILE_SEP;
375             if (getProject().getPackaging().equals("war") && (getProject().hasParent()
376                 && !getProject().getParentArtifact().getGroupId().contains("appfuse"))) {
377                 String moduleName = (String) getProject().getParent().getModules().get(0);
378                 String pathToParent = getProject().getOriginalModel().getParent().getRelativePath();
379                 pathToParent = pathToParent.substring(0, pathToParent.lastIndexOf('/') + 1);
380                 pathToParent = pathToParent.replaceAll("/", FILE_SEP);
381                 pathToModelPackage = getProject().getBasedir() + FILE_SEP + pathToParent + moduleName + "/" + pathToModelPackage;
382             }
383 
384             // refactor to check classpath instead of filesystem
385             String groupIdAsPath = getProject().getGroupId().replace(".", FILE_SEP);
386 
387             String fullPathPackage = pathToModelPackage + groupIdAsPath;
388             if (fullPath != null) {
389                 fullPathPackage += FILE_SEP + fullPath.replace(".", FILE_SEP);
390             } else {
391                 fullPathPackage += FILE_SEP + "model";
392             }
393             log("Looking for entity in " + fullPathPackage);
394 
395             File modelPackage = new File(fullPathPackage);
396             boolean entityExists = false;
397 
398             if (modelPackage.exists()) {
399                 String[] entities = modelPackage.list();
400                 for (String entity : entities) {
401                     log("Found '" + entity + "' in model package...");
402                     if (entity.contains(pojoName + ".java")) {
403                         entityExists = true;
404                         break;
405                     }
406                 }
407             } else {
408                 getLog().error("The path '" + fullPathPackage + groupIdAsPath + FILE_SEP + "model' does not exist!");
409             }
410 
411             if (!entityExists) {
412                 throw new MojoFailureException("[ERROR] The '" + pojoName + "' entity does not exist in '" + modelPackage + "'.");
413             } else {
414                 // Entity found, make sure it has @Entity annotation
415                 try {
416                     File pojoFile = new File(modelPackage + FILE_SEP + pojoName + ".java");
417                     String entityAsString = FileUtils.readFileToString(pojoFile);
418                     if (!entityAsString.contains("@Entity")) {
419                         String msg = "Entity '" + pojoName + "' found, but it doesn't contain an @Entity annotation. Please add one.";
420                         throw new MojoFailureException(msg);
421                     }
422                 } catch (IOException io) {
423                     throw new MojoFailureException("[ERROR] Class '" + pojoName + ".java' not found in '" + modelPackage + "'");
424                 }
425             }
426         }
427     }
428 
429     public class POJOSearcher extends DefaultHandler {
430         private String pojoName;
431         private boolean foundPojo;
432         private String xmlString;
433 
434         public POJOSearcher(String xmlString) {
435             this.xmlString = xmlString;
436         }
437 
438         public boolean searchForPojo(String pojoName) {
439             this.pojoName = pojoName;
440             this.foundPojo = false;
441 
442             SAXParserFactory spf = SAXParserFactory.newInstance();
443             try {
444                 spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
445 
446                 SAXParser sp = spf.newSAXParser();
447                 sp.parse(new InputSource(new StringReader(xmlString)), this);
448             } catch (Exception ex) {
449                 System.out.println(ex.getMessage());
450                 ex.printStackTrace();
451             }
452 
453             return foundPojo;
454         }
455 
456         @Override
457         public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
458             super.startElement(uri, localName, name, attributes);
459             if (name.equals("mapping")) {
460                 String classValue = attributes.getValue("class");
461                 if (classValue != null) {
462                     if (classValue.endsWith(pojoName)) {
463                         foundPojo = true;
464                     }
465                 }
466             }
467         }
468     }
469 }