View Javadoc

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