View Javadoc

1   /*
2    * SkeletValidator.java
3    *
4    * Created on November 25, 2004, 1:10 PM
5    */
6   
7   package com.finalist.jaggenerator;
8   
9   import com.finalist.jaggenerator.modules.*;
10  import java.util.*;
11  import org.w3c.dom.Document;
12  import org.w3c.dom.Element;
13  
14  import javax.swing.*;
15  import javax.swing.event.TreeSelectionEvent;
16  import javax.swing.tree.*;
17  import javax.xml.parsers.DocumentBuilder;
18  import javax.xml.parsers.DocumentBuilderFactory;
19  import javax.xml.parsers.ParserConfigurationException;
20  import javax.xml.transform.dom.DOMSource;
21  import javax.xml.transform.stream.StreamResult;
22  import javax.xml.transform.TransformerFactory;
23  import javax.xml.transform.Transformer;
24  import javax.xml.transform.OutputKeys;
25  import java.awt.*;
26  import java.awt.event.KeyEvent;
27  import java.io.File;
28  import java.io.FileWriter;
29  import java.io.IOException;
30  import java.io.StringWriter;
31  import java.net.URL;
32  import java.util.*;
33  import java.util.List;
34  
35  /***
36   *
37   * @author  Rudie Ekkelenkamp.
38   */
39  public class SkeletValidator {
40      private Root root = null;
41      private javax.swing.JTree tree = null;
42      private ConsoleLogger logger = null;
43      private HashMap entitiesByTableName = null; 
44      
45      /*** Creates a new instance of SkeletValidator */
46      public SkeletValidator(Root root, javax.swing.JTree tree, HashMap entitiesByTableName, ConsoleLogger logger) {
47          this.root = root;
48          this.tree = tree;
49          this.logger = logger;
50          this.entitiesByTableName = entitiesByTableName;
51      }
52     
53      
54      /***
55       * Check on projectname, description, datasource name, jndi-name and mapping, etc..
56       * @return null if valid, returns a message string in case of an error.
57       *
58       */
59      public String validateSkelet() {
60          boolean atLeastOneCompositePK = false;
61          boolean valid = true;
62          boolean autoGeneratedStringInvalidHibernate = false;
63          boolean isRelatedEntityComposite = false;
64          String message = "";
65          String businessTier = (String) root.config.getTemplateSettings().get(JagGenerator.TEMPLATE_BUSINESS_TIER);
66          String appServer = (String) root.config.getTemplateSettings().get(JagGenerator.TEMPLATE_APPLICATION_SERVER);
67          String useJava5 = (String) root.config.getTemplateSettings().get(JagGenerator.TEMPLATE_USE_JAVA5);
68  
69          // Do some basic checks before generation starts.
70          try {
71              // Make sure the projectname has been set!
72              if (root.app.nameText.getText() == null || "".equals(root.app.nameText.getText())) {
73                  valid = false;
74                  message += "No valid application name has been set in the 'Application settings'.\r\n";
75              } else if (!root.app.nameText.getText().toLowerCase().equals(root.app.nameText.getText())) {
76                  // Check if it is a valid name! Should be lowercase only!
77                  valid = false;
78                  message += "No valid application name in the 'Application settings'. Should be only lowercase!.\r\n";
79              }
80          } catch (Exception e) {
81              valid = false;
82              message += "No valid application name has been set in the 'Application settings'.\r\n";
83          }
84  
85          try {
86              // Make sure the applicationname has been set!
87              if (root.app.descriptionText.getText() == null || "".equals(root.app.descriptionText.getText())) {
88                  valid = false;
89                  message += "No valid application description has been set in the 'Application settings'.\r\n";
90              } else {
91                  // Check if the first character is uppercase!
92                  String firstChar = root.app.descriptionText.getText().substring(0, 1);
93                  if (!firstChar.toUpperCase().equals(firstChar)) {
94                      valid = false;
95                      message += "No valid application description has been set in the 'Application settings'. First character should be uppercase!\r\n";
96                  }
97              }
98          } catch (Exception e) {
99              valid = false;
100             message += "No valid application description has been set in the 'Application settings'.\r\n";
101         }
102 
103         try {
104             // Make sure the rootpackage has been set!
105             if (root.app.rootPackageText.getText() == null || "".equals(root.app.rootPackageText.getText())) {
106                 valid = false;
107                 message += "No valid root package set in the 'Application settings'.\r\n";
108             }
109         } catch (Exception e) {
110             valid = false;
111             message += "No valid root package set in the 'Application settings'.\r\n";
112         }
113 
114         try {
115             // Make sure the applicationname has been set!
116             if (root.datasource.jdbcURLCombo.getSelectedItem() == null || "".equals(root.datasource.jdbcURLCombo.getSelectedItem())) {
117                 valid = false;
118                 message += "No valid datasource URL set in the Datasource form.\r\n";
119             }
120         } catch (Exception e) {
121             valid = false;
122             message += "No valid datasource URL set in the Datasource form.\r\n";
123         }
124 
125         try {
126             // Make sure the datasource has been set!
127             if (root.datasource.jndiText.getText() == null || "".equals(root.datasource.jndiText.getText()) || "jdbc/".equals(root.datasource.jndiText.getText())) {
128                 valid = false;
129                 message += "No valid datasource jndi-name set in the Datasource form.\r\n";
130             }
131         } catch (Exception e) {
132             valid = false;
133             message += "No valid datasource jndi-name set in the Datasource form.\r\n";
134         }
135 
136 
137         HashMap entityRefs = new HashMap();
138         ArrayList el = root.getEntityEjbs();
139         for (int i = 0; i < el.size(); i++) {
140             Entity e = (Entity) el.get(i);
141             // Create a hashmap with all current entity refs.
142             entityRefs.put(e.getRefName(), null);
143         }
144 
145         // Validate that the entity beans 'within' the session beans actually exist..
146         try {
147             ArrayList sl = root.getSessionEjbs();
148             for (int i = 0; i < sl.size(); i++) {
149                 Session s = (Session) sl.get(i);
150                 ArrayList allRefs = s.getEntityRefs();
151                 for (int j = 0; j < allRefs.size(); j++) {
152                     if (!entityRefs.containsKey(allRefs.get(j))) {
153                         valid = false;
154                         message += "The reference " + allRefs.get(j) + " in Service Bean " + s.getRefName() + " doesn't exist .\r\n";
155                     }
156                 }
157                 // Make sure that all the entity refs are defined!
158             }
159         } catch (Exception e) {
160             valid = false;
161             message += "Error in Service Bean.\r\n";
162         }
163 
164         // Validate the Primary Keys on the Entity EJBs and composite primary keys.!
165         try {
166             // Make sure the applicationname has been set!
167             ArrayList nl = root.getEntityEjbs();
168             for (int i = 0; i < nl.size(); i++) {
169                 Entity e = (Entity) nl.get(i);
170                 List relations = e.getRelations();
171                 for (int r = 0; r < relations.size(); r++) {
172                    Relation relation = (Relation) relations.get(r);
173                    if (relation.getRelatedEntity().isCompositeKey()) {
174                       // The target entity of a relation shouldn't be a composite.
175                       // JAG doesn't support this.
176                       isRelatedEntityComposite = true;
177                    }
178 
179                 }
180 
181 
182 
183                 // Iterate over all entity fields. Make sure that autoGeneratedCheckBox has not been
184                 // Marked for foreign keys.
185                 List fields = e.getFields();
186                 for (int j = 0; j < fields.size(); j++) {
187                     Field field = (Field) fields.get(j);
188                     if (field.autoGeneratedCheckBox.isSelected() && field.foreignKeyCheckBox.isSelected()) {
189                         logger.log("Field: " + field.getName() + " of entity : " + e.getName() + " cannot have autogenerated primary key selected and be a foreign key!");
190                         valid = false;
191                         message += "Field: " + field.getName() + " of entity : " + e.getName() + " cannot have autogenerated primary key selected and be a foreign key!.\r\n";
192                     }
193                    if (field.autoGeneratedCheckBox.isSelected()) {
194                       if ("java.lang.String".equalsIgnoreCase(field.getType())) {
195                          // In this case, check the size.
196                          // For the hibernate uuid.hex generator, the size should be >= 32
197                          String fieldSize = field.getSize();
198                          try {
199                             int size = Integer.parseInt(fieldSize);
200                             if (size < 32) {
201                                autoGeneratedStringInvalidHibernate = true;
202                             }
203                          } catch (Exception ex) {
204                             // Size could not be determined.
205                             // Assume that size was not set and the size will be large enough.
206                          }
207                       }
208                    }
209 
210                 }
211                 
212                 if (e.isCompositeCombo.getSelectedItem().equals("true")) {
213                     // If composite is set to true,
214                     // The primary key type should be set, the primary key field should be empty
215                     // and the number of fields marked as primary key should be > 1.
216 
217                    atLeastOneCompositePK = true;
218 
219                     if (e.pKeyTypeText.getText() == null || e.pKeyTypeText.getText().equals("")) {
220                         // The primary key type is empty! This is an error!
221                         logger.log("Primary key type is empty, but should be set since composite primary key has been set to true for Entity EJB: " + e.getRefName());
222                         valid = false;
223                         message += "Primary key type is empty, but should be set since composite primary key has been set to true for Entity EJB: " + e.getRefName() + ".\r\n";
224                     }
225 
226                     if (e.pKeyText.getText() != null && !e.pKeyText.getText().equals("")) {
227                         // The primary key type is empty! This is an error!
228                         logger.log("Primary key should be empty since the since composite primary key has been set to true for Entity EJB: " + e.getRefName());
229                         valid = false;
230                         message += "Primary key should be empty, since composite primary key has been set to true for Entity EJB: " + e.getRefName() + ".\r\n";
231                     }
232 
233                     if (e.countPrimaryKeyFields() <= 1) {
234                         logger.log("In the field specifications for Entity EJB: " + e.getRefName() + " there should be more than one field that has been marked as primary key. Now only " + e.countPrimaryKeyFields() + " have been marked as primary key!");
235                         valid = false;
236                         message += "In the field specifications for Entity EJB: " + e.getRefName() + " there should be more than one field that has been marked as primary key. Now only " + e.countPrimaryKeyFields() + " have been marked as primary key!.\r\n";
237                     }
238 
239                 } else if ((e.isCompositeCombo.getSelectedItem()).equals("false")) {
240                     if (e.pKeyTypeText.getText() == null || e.pKeyTypeText.getText().equals("")) {
241                         logger.log("No primary key set for entity bean: " + e.getRefName());
242                         valid = false;
243                         message += "No primary key set for entity bean: " + e.getRefName() + ".\r\n";
244                     }
245 
246                     if (e.countPrimaryKeyFields() > 1) {
247                         // There should be just 1 primary key selected.
248                         if (e.countPrimaryKeyFields() > 1) {
249                             logger.log("More than 1 primary key declared, while composite primary key is set to false for entity bean '" + e.getRefName() + "' .");
250                             valid = false;
251                             message += "More than 1 primary key declared, while composite primary key is set to false for entity bean '" + e.getRefName() + "' .\r\n";
252                         }
253                     }
254 
255                     if (e.countPrimaryKeyFields() == 1) {
256                         // In this case we can check if the specified primary field and type, match the selected field.
257                         String name = e.getFirstPrimaryKeyFieldName();
258                         String type = e.getPrimaryKeyClass();
259 
260                         if ((name == null) || e.pKeyText.getText() == null || !name.equals(e.pKeyText.getText())) {
261                             logger.log("Non-matching primary keys!  In entity bean '" + e.getRefName() + "' the field '" + name + "' has been marked as primary key, yet that entity has primary key '" + e.pKeyText.getText() + "'.");
262                             valid = false;
263                             message += "Non-matching primary keys!  In entity bean '" + e.getRefName() + "' the field '" + name + "' has been marked as primary key, yet that entity has primary key '" + e.pKeyText.getText() + "'.\r\n";
264                         }
265                         if ((type == null) || e.pKeyTypeText.getText() == null || !type.equals(e.pKeyTypeText.getText())) {
266                             logger.log("Non-matching primary key types!  In entity bean '" + e.getRefName() + "' the field '" + name + "' has been marked as primary key and has type '" + type + "', yet the entity has primary key type '" + e.pKeyTypeText.getText() + "'.");
267                             valid = false;
268                             message += "Non-matching primary key types!  In entity bean '" + e.getRefName() + "' the field '" + name + "' has been marked as primary key and has type '" + type + "', yet the entity has primary key type '" + e.pKeyTypeText.getText() + "'.\r\n";
269                         }
270                     }
271                 }
272             }
273 
274         } catch (Exception e) {
275             valid = false;
276             message += "Error in Entity Bean.\r\n";
277         }
278 
279         //check for duplicate relation names within the same entity bean..
280         String badEntity = getEntityWithDuplicateRelationNames();
281         if (badEntity != null) {
282             valid = false;
283             message += "Entity bean " + badEntity + " has more than one relation with the same 'relation name'.\n" +
284                     "This is not allowed!  Please rename at least one of the relation names.\r\n";
285         }
286 
287         //check for relations that reference tables for which there is no entity bean..
288         List unreferencedEntities = getEntityWithUnreferencedRelations();
289         if (!unreferencedEntities.isEmpty()) {
290             valid = false;
291             StringBuffer tmp = new StringBuffer();
292             Iterator i = unreferencedEntities.iterator();
293             while (i.hasNext()) {
294                 String[] details = (String[]) i.next();
295                 if (!"".equals(details[2])) {
296                     tmp.append("Entity bean '" + details[0] + "' contains a relation '" + details[1] +
297                             "' that references a table '" + details[2] + "',\n" +
298                             "which doesn't correspond to any entity bean.  Please either:\n" +
299                             "\ta) delete the relation,\n" +
300                             "\tb) create an entity bean for the table '" + details[2] + "', or\n" +
301                             "\tc) in the relation, correct the 'foreign table' value.\r\n");
302                 }
303             }
304             message += tmp.toString();
305         }
306 
307         //check for any relation with empty/invalid data..
308         String relationErrorMessage = getRelationWithInvalidData();
309         if (relationErrorMessage != null) {
310             valid = false;
311             message += relationErrorMessage;
312         }
313          // Check if the related entity is a composite primary key.
314         if (isRelatedEntityComposite) {
315            String msg = "At least one target entity of a relation has a composite primary key. JAG doesn't support this.\r\n";
316            logger.log(msg);
317            valid = false;
318            message += msg;
319         }
320         // check if the Container-managed relations checkbox was unchecked BUT some CMRs are still defined!
321         String templateValue = (String) root.config.getTemplateSettings().get(JagGenerator.TEMPLATE_USE_RELATIONS);
322         if ("false".equalsIgnoreCase(templateValue)) {
323             // CMR was not selected. Make sure no relations are defined in the model!
324             
325            // Now check if there are still some relations defined.
326            if (isRelationPresentInModel()) { 
327                valid = false;
328                message += "The Container-managed relations checkbox in the Configuration screen was unchecked,\r\n" +
329                           "but there are still relations defined in the project.\r\n" +
330                           "Either enable Container-managed relations or remove the relations from the project.\r\n";
331            }
332         }
333 
334        // Check business tier specific validations.
335        if (atLeastOneCompositePK) {
336           // In case Hibernate 2 was selected, No composites are supported!
337           if (JagGenerator.TEMPLATE_BUSINESS_TIER_HIBERNATE2.equalsIgnoreCase(businessTier) ||
338               (JagGenerator.TEMPLATE_BUSINESS_TIER_HIBERNATE3.equalsIgnoreCase(businessTier) &&
339               "false".equals(useJava5)
340              )
341                   ) {
342              logger.log("JAG does not support composite primary keys for Hibernate.");
343              valid = false;
344              message += "JAG does not support composite primary keys for Hibernate.\r\n";
345           }
346        }
347 
348        if (autoGeneratedStringInvalidHibernate) {
349           // In case Hibernate 2 was selected, autogenerated string's should be at least size 32
350           if (JagGenerator.TEMPLATE_BUSINESS_TIER_HIBERNATE2.equalsIgnoreCase(businessTier) ||
351              JagGenerator.TEMPLATE_BUSINESS_TIER_HIBERNATE3.equalsIgnoreCase(businessTier)
352           ) {
353              String msg = "At least one autogenerated primary key field of type string has size < 32. Hibernate cannot autogenerate this primary key.\r\n";
354              logger.log(msg);
355              valid = false;
356              message += msg;
357           }
358        }
359 
360        // Check application server specific validations.
361 
362        if (JagGenerator.TEMPLATE_APPLICATION_SERVER_TOMCAT_5.equalsIgnoreCase(appServer)) {
363           // Only Hibernate is supported for deployment on tomcat.
364           // All other business tiers are dependent on an EJB container.
365           if (!JagGenerator.TEMPLATE_BUSINESS_TIER_HIBERNATE2.equalsIgnoreCase(businessTier) &&
366               !JagGenerator.TEMPLATE_BUSINESS_TIER_HIBERNATE3.equalsIgnoreCase(businessTier)
367              )
368           {
369              String msg = "Only hibernate can be selected for the Tomcat application server.\r\n";
370              logger.log(msg);
371              valid = false;
372              message += msg;
373           }
374 
375        }
376 
377        if (!JagGenerator.TEMPLATE_APPLICATION_SERVER_JBOSS_4_X.equalsIgnoreCase(appServer)) {
378           if (JagGenerator.TEMPLATE_BUSINESS_TIER_EJB3.equalsIgnoreCase(businessTier)) {
379            // EJB3 is currently only supported for JBoss 4.X
380              logger.log("EJB3 is only supported for the JBoss 4.x application server.");
381              valid = false;
382              message += "EJB3 is only supported for the JBoss 4.x application server.\r\n";
383           }
384        }
385 
386        if (!valid) {
387           return message;
388        } else {
389            return null;
390        }
391     }
392     
393     private String getEntityWithDuplicateRelationNames() {
394         Iterator entities = root.getEntityEjbs().iterator();
395         while (entities.hasNext()) {
396             Entity entity = (Entity) entities.next();
397             Set relationNames = new HashSet();
398             for (int i = 0; i < entity.getChildCount(); i++) {
399                 TreeNode child = entity.getChildAt(i);
400                 if (child instanceof Relation) {
401                     Relation relation = (Relation) child;
402                     String relName = relation.getName();
403                     if (relationNames.contains(relName)) {
404                         tree.setSelectionPath(new TreePath(relation.getPath()));
405                         return entity.getRefName();
406                     }
407                     relationNames.add(relName);
408                 }
409             }
410         }
411         return null;
412     }
413     /***
414      * Finds any invalid relations, whose referenced foreign tables do not correspond to entity beans.
415      *
416      * @return List of String[] (never null), where:
417      *         [0] is the name of the entity bean containing the relation
418      *         [1] is the name of the relation
419      *         [2] is the name of the referenced table, for which there is no entity bean.
420      */
421     private List getEntityWithUnreferencedRelations() {
422         ArrayList result = new ArrayList();
423         Iterator entities = root.getEntityEjbs().iterator();
424         while (entities.hasNext()) {
425             Entity entity = (Entity) entities.next();
426             for (int i = 0; i < entity.getChildCount(); i++) {
427                 TreeNode child = entity.getChildAt(i);
428                 if (child instanceof Relation) {
429                     Relation relation = (Relation) child;
430                     String referencedTable = relation.getForeignTable();
431                     if (entitiesByTableName.get(referencedTable) == null) {
432                         result.add(new String[]{entity.getName().toString(), relation.getName(), referencedTable});
433                     }
434                 }
435             }
436         }
437         return result;
438     }
439 
440     private String getRelationWithInvalidData() {
441         Iterator entities = root.getEntityEjbs().iterator();
442         while (entities.hasNext()) {
443             Entity entity = (Entity) entities.next();
444             for (int i = 0; i < entity.getChildCount(); i++) {
445                 TreeNode child = entity.getChildAt(i);
446                 if (child instanceof Relation) {
447                     Relation relation = (Relation) child;
448 
449                     String foreignTable = relation.getForeignTable();
450                     if (foreignTable == null || "".equals(foreignTable.trim())) {
451                         return "Relation '" + relation + "' within entity '" + entity.getName() +
452                                 "' has no value for 'foreign table' \n" +
453                                 "(the name of the table at the foreign side of this relation).  \n" +
454                                 "Please provide a value!";
455                     }
456 
457                     if (entitiesByTableName.get(foreignTable) == null) {
458                         return null;
459                         //this is needed because otherwise this method throws an NPE.
460                         //@todo Really should look at improving this validation/error handling mess...
461                     }
462 
463                     String foreignPkColumn = relation.getForeignColumn();
464                     if (foreignPkColumn == null || "".equals(foreignPkColumn.trim())) {
465                         return "Relation '" + relation + "' within entity '" + entity.getName() +
466                                 "' has no value for 'foreign table primary key' \n" +
467                                 "(the name of the PK imported from table '" + relation.getForeignTable() +
468                                 "' that represents the foreign side of this relation).  \n" +
469                                 "Please provide a value!";
470                     }
471 
472                     boolean found = false;
473                     List fields = relation.getRelatedEntity().getFields();
474                     Iterator j = fields.iterator();
475                     while (j.hasNext()) {
476                         Field field = (Field) j.next();
477                         if (field.getColumnName().equalsIgnoreCase(foreignPkColumn)) {
478                             found = true;
479                             break;
480                         }
481                     }
482 
483                     if (!found) {
484                         return "Relation '" + relation + "' within entity '" + relation.getRelatedEntity().getName() +
485                                 "' has an invalid value (" + foreignPkColumn + ") for 'foreign table primary key' \n" +
486                                 "(the name of the PK imported from table '" + relation.getRelatedEntity().getLocalTableName() +
487                                 "' that represents the foreign side of this relation).  \n" +
488                                 "Please provide a valid value!";
489                     }
490 
491                 }
492             }
493         }
494         return null;
495     }
496     
497     /***
498      * Helper method to check if there are any relations present in the model.
499      */
500     private boolean isRelationPresentInModel() {
501         Iterator entities = root.getEntityEjbs().iterator();
502         while (entities.hasNext()) {
503             Entity entity = (Entity) entities.next();
504             //for every entity..
505             for (int i = 0; i < entity.getChildCount(); i++) {
506                 TreeNode child = entity.getChildAt(i);
507                 //for every relation in that entity..
508                 if (child instanceof Relation) {
509                     return true;
510                 }
511             }
512         }
513         return false;
514     }
515     
516     
517     
518 }