View Javadoc

1   package org.apache.torque.engine.database.model;
2   
3   /*
4    * Copyright 2001-2004 The Apache Software Foundation.
5    * 
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    * 
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.util.ArrayList;
20  import java.util.Iterator;
21  import java.util.List;
22  
23  import org.apache.commons.lang.StringUtils;
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.apache.torque.engine.EngineException;
27  import org.apache.torque.engine.platform.Platform;
28  import org.apache.torque.engine.platform.PlatformDefaultImpl;
29  import org.xml.sax.Attributes;
30  
31  /***
32   * A Class for holding data about a column used in an Application.
33   *
34   * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
35   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
36   * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
37   * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
38   * @author <a href="mailto:byron_foster@byron_foster@yahoo.com>Byron Foster</a>
39   * @author <a href="mailto:mpoeschl@marmot.at>Martin Poeschl</a>
40   * @version $Id: Column.java,v 1.26 2004/09/11 13:56:45 seade Exp $
41   */
42  public class Column
43  {
44      private static final SchemaType DEFAULT_TYPE = SchemaType.VARCHAR;
45      /*** Logging class from commons.logging */
46      private static Log log = LogFactory.getLog(Column.class);
47      private String name;
48      private String description;
49      private Domain domain = new Domain();
50      private String javaName = null;
51      private String javaNamingMethod;
52      private boolean isNotNull = false;
53      private String javaType;
54      private Table parentTable;
55      private int position;
56      private boolean isPrimaryKey = false;
57      private boolean isUnique = false;
58      private boolean isAutoIncrement = false;
59      private List referrers;
60      // only one type is supported currently, which assumes the
61      // column either contains the classnames or a key to
62      // classnames specified in the schema.  Others may be
63      // supported later.
64      private String inheritanceType;
65      private boolean isInheritance;
66      private boolean isEnumeratedClasses;
67      private List inheritanceList;
68      private boolean needsTransactionInPostgres;
69  
70      /*** class name to do input validation on this column */
71      private String inputValidator = null;
72  
73      /***
74       * Creates a new instance with a <code>null</code> name.
75       */
76      public Column()
77      {
78          this(null);
79      }
80  
81      /***
82       * Creates a new column and set the name
83       *
84       * @param name column name
85       */
86      public Column(String name)
87      {
88          this.name = name;
89      }
90  
91      /***
92       * Return a comma delimited string listing the specified columns.
93       *
94       * @param columns Either a list of <code>Column</code> objects, or
95       * a list of <code>String</code> objects with column names.
96       */
97      public static String makeList(List columns)
98      {
99          Object obj = columns.get(0);
100         boolean isColumnList = (obj instanceof Column);
101         if (isColumnList)
102         {
103             obj = ((Column) obj).getName();
104         }
105         StringBuffer buf = new StringBuffer((String) obj);
106         for (int i = 1; i < columns.size(); i++)
107         {
108             obj = columns.get(i);
109             if (isColumnList)
110             {
111                 obj = ((Column) obj).getName();
112             }
113             buf.append(", ").append(obj);
114         }
115         return buf.toString();
116     }
117 
118     /***
119      * Imports a column from an XML specification
120      */
121     public void loadFromXML(Attributes attrib)
122     {
123         String dom = attrib.getValue("domain");
124         if (StringUtils.isNotEmpty(dom)) 
125         {
126             domain = new Domain(getTable().getDatabase().getDomain(dom));
127         } 
128         else
129         {
130             domain = new Domain(getPlatform().getDomainForSchemaType(DEFAULT_TYPE));
131             setType(attrib.getValue("type"));
132         }
133         //Name
134         name = attrib.getValue("name");
135 
136         javaName = attrib.getValue("javaName");
137         javaType = attrib.getValue("javaType");
138         if (javaType != null && javaType.length() == 0)
139         {
140             javaType = null;
141         }
142 
143         // retrieves the method for converting from specified name to
144         // a java name.
145         javaNamingMethod = attrib.getValue("javaNamingMethod");
146         if (javaNamingMethod == null)
147         {
148             javaNamingMethod
149                     = parentTable.getDatabase().getDefaultJavaNamingMethod();
150         }
151 
152         //Primary Key
153         String primaryKey = attrib.getValue("primaryKey");
154         //Avoid NullPointerExceptions on string comparisons.
155         isPrimaryKey = ("true".equals(primaryKey));
156 
157         // If this column is a primary key then it can't be null.
158         if ("true".equals(primaryKey))
159         {
160             isNotNull = true;
161         }
162 
163         // HELP: Should primary key, index, and/or idMethod="native"
164         // affect isNotNull?  If not, please document why here.
165         String notNull = attrib.getValue("required");
166         isNotNull = (notNull != null && "true".equals(notNull));
167 
168         //AutoIncrement/Sequences
169         String autoIncrement = attrib.getValue("autoIncrement");
170         isAutoIncrement = ("true".equals(autoIncrement));
171 
172         //Default column value.
173         domain.replaceDefaultValue(attrib.getValue("default"));
174 
175         domain.replaceSize(attrib.getValue("size"));
176         domain.replaceScale(attrib.getValue("scale"));
177 
178         inheritanceType = attrib.getValue("inheritance");
179         isInheritance = (inheritanceType != null
180                 && !inheritanceType.equals("false"));
181 
182         this.inputValidator = attrib.getValue("inputValidator");
183         description = attrib.getValue("description");
184     }
185 
186     /***
187      * Returns table.column
188      */
189     public String getFullyQualifiedName()
190     {
191         return (parentTable.getName() + '.' + name);
192     }
193 
194     /***
195      * Get the name of the column
196      */
197     public String getName()
198     {
199         return name;
200     }
201 
202     /***
203      * Set the name of the column
204      */
205     public void setName(String newName)
206     {
207         name = newName;
208     }
209 
210     /***
211      * Get the description for the Table
212      */
213     public String getDescription()
214     {
215         return description;
216     }
217 
218     /***
219      * Set the description for the Table
220      *
221      * @param newDescription description for the Table
222      */
223     public void setDescription(String newDescription)
224     {
225         description = newDescription;
226     }
227 
228     /***
229      * Get name to use in Java sources to build method names.
230      * 
231      * @return the capitalised javaName
232      */
233     public String getJavaName()
234     {
235         if (javaName == null)
236         {
237             List inputs = new ArrayList(2);
238             inputs.add(name);
239             inputs.add(javaNamingMethod);
240             try
241             {
242                 javaName = NameFactory.generateName(NameFactory.JAVA_GENERATOR,
243                                                     inputs);
244             }
245             catch (EngineException e)
246             {
247                 log.error(e, e);
248             }
249         }
250         return StringUtils.capitalize(javaName);
251     }
252     
253     /***
254      * Get variable name to use in Java sources (= uncapitalised java name)
255      */    
256     public String getUncapitalisedJavaName()
257     {
258         return StringUtils.uncapitalize(getJavaName());
259     }
260 
261     /***
262      * Set name to use in Java sources
263      */
264     public void setJavaName(String javaName)
265     {
266         this.javaName = javaName;
267     }
268 
269     /***
270      * Get type to use in Java sources
271      */
272     public String getJavaType()
273     {
274         return javaType;
275     }
276 
277     /***
278      * Get the location of this column within the table (one-based).
279      * @return value of position.
280      */
281     public int getPosition()
282     {
283         return position;
284     }
285 
286     /***
287      * Get the location of this column within the table (one-based).
288      * @param v  Value to assign to position.
289      */
290     public void setPosition(int  v)
291     {
292         this.position = v;
293     }
294 
295     /***
296      * Set the parent Table of the column
297      */
298     public void setTable(Table parent)
299     {
300         parentTable = parent;
301     }
302 
303     /***
304      * Get the parent Table of the column
305      */
306     public Table getTable()
307     {
308         return parentTable;
309     }
310 
311     /***
312      * Returns the Name of the table the column is in
313      */
314     public String getTableName()
315     {
316         return parentTable.getName();
317     }
318 
319     /***
320      * A utility function to create a new column
321      * from attrib and add it to this table.
322      */
323     public Inheritance addInheritance(Attributes attrib)
324     {
325         Inheritance inh = new Inheritance();
326         inh.loadFromXML (attrib);
327         addInheritance(inh);
328 
329         return inh;
330     }
331 
332     /***
333      * Adds a new inheritance definition to the inheritance list and set the
334      * parent column of the inheritance to the current column
335      */
336     public void addInheritance(Inheritance inh)
337     {
338         inh.setColumn(this);
339         if (inheritanceList == null)
340         {
341             inheritanceList = new ArrayList();
342             isEnumeratedClasses = true;
343         }
344         inheritanceList.add(inh);
345     }
346 
347     /***
348      * Get the inheritance definitions.
349      */
350     public List getChildren()
351     {
352         return inheritanceList;
353     }
354 
355     /***
356      * Determine if this column is a normal property or specifies a
357      * the classes that are represented in the table containing this column.
358      */
359     public boolean isInheritance()
360     {
361         return isInheritance;
362     }
363 
364     /***
365      * Determine if possible classes have been enumerated in the xml file.
366      */
367     public boolean isEnumeratedClasses()
368     {
369         return isEnumeratedClasses;
370     }
371 
372     /***
373      * Return the isNotNull property of the column
374      */
375     public boolean isNotNull()
376     {
377         return isNotNull;
378     }
379 
380     /***
381      * Set the isNotNull property of the column
382      */
383     public void setNotNull(boolean status)
384     {
385         isNotNull = status;
386     }
387     
388     /***
389      * Return NOT NULL String for this column
390      * 
391      * @return "NOT NULL" if null values are not allowed or an empty String.
392      */
393     public String getNotNullString()
394     {
395         return getTable().getDatabase().getPlatform()
396                 .getNullString(this.isNotNull());
397     }
398 
399     /***
400      * Set if the column is a primary key or not
401      */
402     public void setPrimaryKey(boolean pk)
403     {
404         isPrimaryKey = pk;
405     }
406 
407     /***
408      * Return true if the column is a primary key
409      */
410     public boolean isPrimaryKey()
411     {
412         return isPrimaryKey;
413     }
414 
415     /***
416      * Set true if the column is UNIQUE
417      */
418     public void setUnique (boolean u)
419     {
420         isUnique = u;
421     }
422 
423     /***
424      * Get the UNIQUE property
425      */
426     public boolean isUnique()
427     {
428         return isUnique;
429     }
430 
431     /***
432      * Return true if the column requires a transaction in Postgres
433      */
434     public boolean requiresTransactionInPostgres()
435     {
436         return needsTransactionInPostgres;
437     }
438 
439     /***
440      * Utility method to determine if this column is a foreign key.
441      */
442     public boolean isForeignKey()
443     {
444         return (getForeignKey() != null);
445     }
446 
447     /***
448      * Determine if this column is a foreign key that refers to the
449      * same table as another foreign key column in this table.
450      */
451     public boolean isMultipleFK()
452     {
453         ForeignKey fk = getForeignKey();
454         if (fk != null)
455         {
456             Iterator fks = parentTable.getForeignKeys().iterator();
457             while (fks.hasNext())
458             {
459                 ForeignKey key = (ForeignKey) fks.next();
460                 if (key.getForeignTableName().equals(fk.getForeignTableName())
461                         && !key.getLocalColumns().contains(this.name))
462                 {
463                     return true;
464                 }
465             }
466         }
467 
468         // No multiple foreign keys.
469         return false;
470     }
471 
472     /***
473      * get the foreign key object for this column
474      * if it is a foreign key or part of a foreign key
475      */
476     public ForeignKey getForeignKey()
477     {
478         return parentTable.getForeignKey(this.name);
479     }
480 
481     /***
482      * Utility method to get the related table of this column if it is a foreign
483      * key or part of a foreign key
484      */
485     public String getRelatedTableName()
486     {
487         ForeignKey fk = getForeignKey();
488         return (fk == null ? null : fk.getForeignTableName());
489     }
490 
491     /***
492      * Utility method to get the related column of this local column if this
493      * column is a foreign key or part of a foreign key.
494      */
495     public String getRelatedColumnName()
496     {
497         ForeignKey fk = getForeignKey();
498         if (fk == null)
499         {
500             return null;
501         }
502         else
503         {
504             return fk.getLocalForeignMapping().get(this.name).toString();
505         }
506     }
507 
508     /***
509      * Adds the foreign key from another table that refers to this column.
510      */
511     public void addReferrer(ForeignKey fk)
512     {
513         if (referrers == null)
514         {
515             referrers = new ArrayList(5);
516         }
517         referrers.add(fk);
518     }
519 
520     /***
521      * Get list of references to this column.
522      */
523     public List getReferrers()
524     {
525         if (referrers == null)
526         {
527             referrers = new ArrayList(5);
528         }
529         return referrers;
530     }
531 
532     /***
533      * Sets the colunm type
534      */
535     public void setType(String torqueType)
536     {
537         SchemaType type = SchemaType.getEnum(torqueType);
538         if (type == null)
539         {
540             log.warn("SchemaType " + torqueType + " does not exist");
541             type = Column.DEFAULT_TYPE;
542         }
543         setType(type);
544     }
545 
546     /***
547      * Sets the colunm type
548      */
549     public void setType(SchemaType torqueType)
550     {
551         domain = new Domain(getPlatform().getDomainForSchemaType(torqueType));
552         if (torqueType.equals(SchemaType.VARBINARY)
553                 || torqueType.equals(SchemaType.BLOB))
554         {
555             needsTransactionInPostgres = true;
556         }
557     }
558     
559     /***
560      * Returns the column jdbc type as an object
561      * 
562      * @deprecated the type conversion is handled by the platform package
563      *             (since torque 3.2)
564      */
565     public Object getType()
566     {
567         return TypeMap.getJdbcType(domain.getType()).getName();
568     }
569 
570     /***
571      * Returns the column type as given in the schema as an object
572      */
573     public Object getTorqueType()
574     {
575         return domain.getType().getName();
576     }
577 
578     /***
579      * Utility method to see if the column is a string
580      *
581      * @deprecated will be removed after the 3.2 release
582      */
583     public boolean isString()
584     {
585         return (domain.getType().getName().indexOf ("CHAR") != -1);
586     }
587 
588     /***
589      * Utility method to return the value as an element to be usable
590      * in an SQL insert statement. This is used from the SQL loader task
591      */
592     public boolean needEscapedValue()
593     {
594         String torqueType = domain.getType().getName();
595         return (torqueType != null) && (torqueType.equals("VARCHAR")
596                         || torqueType.equals("LONGVARCHAR")
597                         || torqueType.equals("DATE")
598                         || torqueType.equals("DATETIME")
599                         || torqueType.equals("TIMESTAMP")
600                         || torqueType.equals("TIME")
601                         || torqueType.equals("CHAR")
602                         || torqueType.equals("CLOB"));
603     }
604 
605     /***
606      * String representation of the column. This is an xml representation.
607      *
608      * @return string representation in xml
609      */
610     public String toString()
611     {
612         StringBuffer result = new StringBuffer();
613         result.append("    <column name=\"").append(name).append('"');
614 
615         if (javaName != null)
616         {
617             result.append(" javaName=\"").append(javaName).append('"');
618         }
619 
620         if (isPrimaryKey)
621         {
622             result.append(" primaryKey=\"").append(isPrimaryKey).append('"');
623         }
624 
625         if (isNotNull)
626         {
627             result.append(" required=\"true\"");
628         }
629         else
630         {
631             result.append(" required=\"false\"");
632         }
633 
634         result.append(" type=\"").append(domain.getType().getName()).append('"');
635 
636         if (domain.getSize() != null)
637         {
638             result.append(" size=\"").append(domain.getSize()).append('"');
639         }
640 
641         if (domain.getScale() != null)
642         {
643             result.append(" scale=\"").append(domain.getScale()).append('"');
644         }
645 
646         if (domain.getDefaultValue() != null)
647         {
648             result.append(" default=\"").append(domain.getDefaultValue()).append('"');
649         }
650 
651         if (isInheritance())
652         {
653             result.append(" inheritance=\"").append(inheritanceType)
654                 .append('"');
655         }
656 
657         // Close the column.
658         result.append(" />\n");
659 
660         return result.toString();
661     }
662 
663     /***
664      * Returns the size of the column
665      */
666     public String getSize()
667     {
668         return domain.getSize();
669     }
670 
671     /***
672      * Set the size of the column
673      */
674     public void setSize(String newSize)
675     {
676         domain.setSize(newSize);
677     }
678 
679     /***
680      * Returns the scale of the column
681      */
682     public String getScale()
683     {
684         return domain.getScale();
685     }
686 
687     /***
688      * Set the scale of the column
689      */
690     public void setScale(String newScale)
691     {
692         domain.setScale(newScale);
693     }
694 
695     /***
696      * Return the size and scale in brackets for use in an sql schema.
697      *
698      * @return size and scale or an empty String if there are no values
699      *         available.
700      */
701     public String printSize()
702     {
703         return domain.printSize();
704     }
705 
706     /***
707      * Return a string that will give this column a default value.
708      * @deprecated
709      */
710      public String getDefaultSetting()
711      {
712          return domain.getDefaultSetting();
713      }
714 
715     /***
716      * Set a string that will give this column a default value.
717      */
718     public void setDefaultValue(String def)
719     {
720         domain.setDefaultValue(def);
721     }
722 
723     /***
724      * Get a string that will give this column a default value.
725      */
726     public String getDefaultValue()
727     {
728         return domain.getDefaultValue();
729     }
730 
731     /***
732      * Returns the class name to do input validation
733      */
734     public String getInputValidator()
735     {
736        return this.inputValidator;
737     }
738 
739     /***
740      * Return auto increment/sequence string for the target database. We need to
741      * pass in the props for the target database!
742      */
743     public boolean isAutoIncrement()
744     {
745         return isAutoIncrement;
746     }
747 
748     /***
749      * Set the auto increment value.
750      * Use isAutoIncrement() to find out if it is set or not.
751      */
752     public void setAutoIncrement(boolean value)
753     {
754         isAutoIncrement = value;
755     }
756 
757     public String getAutoIncrementString()
758     {
759         if (isAutoIncrement() 
760                 && IDMethod.NATIVE.equals(getTable().getIdMethod()))
761         {
762             return getPlatform().getAutoIncrement();
763         }
764         return "";
765     }
766     
767     /***
768      * Set the column type from a string property
769      * (normally a string from an sql input file)
770      */
771     public void setTypeFromString(String typeName, String size)
772     {
773         String tn = typeName.toUpperCase();
774         setType(tn);
775 
776         if (size != null)
777         {
778             domain.setSize(size);
779         }
780 
781         if (tn.indexOf("CHAR") != -1)
782         {
783             domain.setType(SchemaType.VARCHAR);
784         }
785         else if (tn.indexOf("INT") != -1)
786         {
787             domain.setType(SchemaType.INTEGER);
788         }
789         else if (tn.indexOf("FLOAT") != -1)
790         {
791             domain.setType(SchemaType.FLOAT);
792         }
793         else if (tn.indexOf("DATE") != -1)
794         {
795             domain.setType(SchemaType.DATE);
796         }
797         else if (tn.indexOf("TIME") != -1)
798         {
799             domain.setType(SchemaType.TIMESTAMP);
800         }
801         else if (tn.indexOf("BINARY") != -1)
802         {
803             domain.setType(SchemaType.LONGVARBINARY);
804         }
805         else
806         {
807             domain.setType(SchemaType.VARCHAR);
808         }
809     }
810 
811     /***
812      * Return a string representation of the
813      * Java object which corresponds to the JDBC
814      * type of this column. Use in the generation
815      * of MapBuilders.
816      */
817     public String getJavaObject()
818     {
819         return TypeMap.getJavaObject(domain.getType());
820     }
821 
822     /***
823      * Return a string representation of the primitive java type which
824      * corresponds to the JDBC type of this column.
825      *
826      * @return string representation of the primitive java type
827      */
828     public String getJavaPrimitive()
829     {
830         return TypeMap.getJavaNative(domain.getType());
831     }
832 
833     /***
834      * Return a string representation of the native java type which corresponds
835      * to the JDBC type of this column. Use in the generation of Base objects.
836      * This method is used by torque, so it returns Key types for primaryKey and
837      * foreignKey columns
838      *
839      * @return java datatype used by torque
840      */
841     public String getJavaNative()
842     {
843         String jtype = TypeMap.getJavaNativeObject(domain.getType());
844         if (isUsePrimitive())
845         {
846             jtype = TypeMap.getJavaNative(domain.getType());
847         }
848 
849         return jtype;
850     }
851 
852     /***
853      * Return Village asX() method which corresponds to the JDBC type
854      * which represents this column.
855      */
856     public String getVillageMethod()
857     {
858         String vmethod = TypeMap.getVillageObjectMethod(domain.getType());
859         if (isUsePrimitive())
860         {
861             vmethod = TypeMap.getVillageMethod(domain.getType());
862         }
863 
864         return vmethod;
865     }
866 
867     /***
868      * Return ParameterParser getX() method which
869      * corresponds to the JDBC type which represents this column.
870      */
871     public String getParameterParserMethod()
872     {
873         return TypeMap.getPPMethod(domain.getType());
874     }
875 
876     /***
877      * Returns true if the column type is boolean in the
878      * java object and a numeric (1 or 0) in the db.
879      */
880     public boolean isBooleanInt()
881     {
882         return TypeMap.isBooleanInt(domain.getType());
883     }
884 
885     /***
886      * Returns true if the column type is boolean in the
887      * java object and a String ("Y" or "N") in the db.
888      */
889     public boolean isBooleanChar()
890     {
891         return TypeMap.isBooleanChar(domain.getType());
892     }
893 
894     /***
895      * Returns true if the column type is boolean in the
896      * java object and a Bit ("1" or "0") in the db.
897      */
898     public boolean isBit()
899     {
900         return TypeMap.isBit(domain.getType());
901     }
902 
903     /***
904      * returns true, if the columns java native type is an
905      * boolean, byte, short, int, long, float, double, char
906      */
907     public boolean isPrimitive()
908     {
909         String t = getJavaNative();
910         return "boolean".equals(t)
911             || "byte".equals(t)
912             || "short".equals(t)
913             || "int".equals(t)
914             || "long".equals(t)
915             || "float".equals(t)
916             || "double".equals(t)
917             || "char".equals(t);
918     }
919 
920     public boolean isUsePrimitive()
921     {
922         String s = getJavaType();
923         return (s != null && s.equals("primitive"))
924             || (s == null && !"object".equals(
925                getTable().getDatabase().getDefaultJavaType()));
926     }
927 
928     /***
929      * @return Returns the domain.
930      */
931     public Domain getDomain()
932     {
933         return domain;
934     }
935 
936     /***
937      * @param domain The domain to set.
938      */
939     public void setDomain(Domain domain)
940     {
941         this.domain = domain;
942     }
943 
944     private Platform getPlatform()
945     {
946         try
947         {
948             return getTable().getDatabase().getPlatform();
949         }
950         catch (Exception ex)
951         {
952             log.warn("could not load platform implementation");
953         }
954         return new PlatformDefaultImpl();
955     }
956     
957     public String getSqlString()
958     {
959         StringBuffer sb = new StringBuffer();
960         sb.append(getName());
961         sb.append(' ');
962         sb.append(getDomain().getSqlType());
963         if (getPlatform().hasSize(getDomain().getSqlType()))
964         {
965             sb.append(getDomain().printSize());
966             sb.append(' ');
967         }
968         if (getDomain().getDefaultValue() != null)
969         {
970             sb.append("default ");
971             if (TypeMap.isTextType(getDomain().getType()))
972             {
973                 // TODO: Properly SQL-escape the text.
974                 sb.append('\'').append(getDefaultValue()).append('\'');
975             }
976             else
977             {
978                 sb.append(getDefaultValue());
979             }
980             sb.append(' ');
981         }
982         sb.append(getNotNullString());
983         sb.append(' ');
984         sb.append(getAutoIncrementString());
985         return sb.toString();
986     }
987 }