0001: /*
0002: * ====================================================================
0003: * JAFFA - Java Application Framework For All
0004: *
0005: * Copyright (C) 2002 JAFFA Development Group
0006: *
0007: * This library is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU Lesser General Public
0009: * License as published by the Free Software Foundation; either
0010: * version 2.1 of the License, or (at your option) any later version.
0011: *
0012: * This library is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU Lesser General Public
0018: * License along with this library; if not, write to the Free Software
0019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0020: *
0021: * Redistribution and use of this software and associated documentation ("Software"),
0022: * with or without modification, are permitted provided that the following conditions are met:
0023: * 1. Redistributions of source code must retain copyright statements and notices.
0024: * Redistributions must also contain a copy of this document.
0025: * 2. Redistributions in binary form must reproduce the above copyright notice,
0026: * this list of conditions and the following disclaimer in the documentation
0027: * and/or other materials provided with the distribution.
0028: * 3. The name "JAFFA" must not be used to endorse or promote products derived from
0029: * this Software without prior written permission. For written permission,
0030: * please contact mail to: jaffagroup@yahoo.com.
0031: * 4. Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
0032: * appear in their names without prior written permission.
0033: * 5. Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
0034: *
0035: * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
0036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
0039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0046: * SUCH DAMAGE.
0047: * ====================================================================
0048: */
0049: package org.jaffa.tools.domainmeta.jdbc;
0050:
0051: import java.io.File;
0052: import java.io.FileOutputStream;
0053: import java.io.FileWriter;
0054: import java.io.IOException;
0055: import java.io.PrintWriter;
0056: import java.sql.Connection;
0057: import java.sql.DatabaseMetaData;
0058: import java.sql.DriverManager;
0059: import java.sql.ResultSet;
0060: import java.sql.SQLException;
0061: import java.sql.Types;
0062: import java.util.ArrayList;
0063: import java.util.Collection;
0064: import java.util.HashMap;
0065: import java.util.Iterator;
0066: import java.util.LinkedHashMap;
0067: import java.util.LinkedList;
0068: import java.util.List;
0069: import java.util.Map;
0070: import java.util.TreeMap;
0071: import java.util.regex.Pattern;
0072: import org.apache.log4j.Logger;
0073: import org.jaffa.datatypes.DateTime;
0074: import org.jaffa.datatypes.Defaults;
0075: import org.jaffa.tools.domainmeta.common.FieldBean;
0076: import org.jaffa.tools.domainmeta.common.TableBean;
0077: import org.jaffa.util.ListProperties;
0078: import org.jaffa.util.StringHelper;
0079:
0080: /** This tool is used to reverse enginer a database schema and generate
0081: * Jaffa based domain object XML meta data. This can then be used by the app builder
0082: * to build a working CRUD application (See Jaffa's BaseLine project for more details)
0083: * <p>
0084: * This reads the table definitions and all the fields. It looks at the primary
0085: * key definitions, and rejects any table that does not have one.
0086: * It then looks at each table and infers (by field name, datatype, and field length)
0087: * if this table has any foreign keys by looking at all the other tables Primary keys.
0088: * If a match is found, then it assumes these object are related. If they have the
0089: * same primary key, it assumes it is a one-to-one relationship. All relationships
0090: * are created as associations.
0091: * <p>
0092: * There is at present no logic to look at table constraints to infer relationships
0093: * as not everyone puts the contraints in the database, at present we just rely on primary keys.
0094: * <p>
0095: * This is based on generated domain objects for the v1.1 pattern
0096: * <p>
0097: * This is an example of how it can be used to reverse enginer the default
0098: * 'Northwind' that codes wih Microsoft SQL Server 2000
0099: * <p><pre><code>
0100: public static void main(String[] args) {
0101: try {
0102: JDBCSchemaReader s = new JDBCSchemaReader();
0103:
0104: s.setSourceDriverString("com.microsoft.jdbc.sqlserver.SQLServerDriver");
0105: s.setSourceConnection("jdbc:microsoft:sqlserver://localhost:1433;SelectMethod=cursor;databasename=Northwind");
0106: s.setSourceUser("sa");
0107: s.setSourcePassword("sa");
0108: s.setSourceSchema("%");
0109: s.setAppName("Microsoft");
0110: s.setFullPackageNames(false);
0111: s.setModuleName("Northwind");
0112: s.setOutputDirectory("C:/sandbox.sf/northwind");
0113: s.setPackagePrefix("com");
0114: s.process();
0115: if(!s.save())
0116: System.out.println("****SAVE ERROR!!!!");
0117: } catch (Exception e) {
0118: e.printStackTrace(System.out);
0119: }
0120: }
0121: * </pre></code>
0122: * <p>
0123: * This is an example of one of the XML files this tool generated. This is the
0124: * 'Customers' table from 'Northwind'.
0125: * <p><pre><code>
0126: <?xml version="1.0" encoding="UTF-8"?>
0127: <!DOCTYPE Root PUBLIC "-//JAFFA//DTD Domain Creator Meta 1.1//EN" "http://jaffa.sourceforge.net/DTD/domainCreatorMeta_1_1.dtd">
0128: <Root>
0129: <DomainObject>Customers</DomainObject>
0130: <DomainPackage>com.microsoft.northwind.domain</DomainPackage>
0131: <DatabaseTable>Customers</DatabaseTable>
0132: <MappingPackage>resources/jdbcengine</MappingPackage>
0133: <PatternTemplate>patterns/library/domain_creator_1_1/DomainCreatorPattern.xml</PatternTemplate>
0134: <Description>Reverse Engineered on 05/05/2004</Description>
0135: <LabelToken>[label.Microsoft.Northwind.Customers]</LabelToken>
0136: <Fields>
0137: <Field>
0138: <Name>CustomerID</Name>
0139: <DataType>java.lang.String</DataType>
0140: <DatabaseFieldName>CUSTOMERID</DatabaseFieldName>
0141: <DatabaseDataType>STRING</DatabaseDataType>
0142: <PrimaryKey>T</PrimaryKey>
0143: <LabelToken>[label.Microsoft.Northwind.Customers.CustomerID]</LabelToken>
0144: <Mandatory>T</Mandatory>
0145: <IntSize>5</IntSize>
0146: <CaseType>UpperCase</CaseType>
0147: <Ignore>false</Ignore>
0148: </Field>
0149: <Field>
0150: <Name>CompanyName</Name>
0151: <DataType>java.lang.String</DataType>
0152: <DatabaseFieldName>COMPANYNAME</DatabaseFieldName>
0153: <DatabaseDataType>STRING</DatabaseDataType>
0154: <PrimaryKey>F</PrimaryKey>
0155: <LabelToken>[label.Microsoft.Northwind.Customers.CompanyName]</LabelToken>
0156: <Mandatory>F</Mandatory>
0157: <IntSize>40</IntSize>
0158: <CaseType>MixedCase</CaseType>
0159: <Ignore>false</Ignore>
0160: </Field>
0161: <Field>
0162: <Name>ContactName</Name>
0163: <DataType>java.lang.String</DataType>
0164: <DatabaseFieldName>CONTACTNAME</DatabaseFieldName>
0165: <DatabaseDataType>STRING</DatabaseDataType>
0166: <PrimaryKey>F</PrimaryKey>
0167: <LabelToken>[label.Microsoft.Northwind.Customers.ContactName]</LabelToken>
0168: <Mandatory>F</Mandatory>
0169: <IntSize>30</IntSize>
0170: <CaseType>MixedCase</CaseType>
0171: <Ignore>false</Ignore>
0172: </Field>
0173: <Field>
0174: <Name>ContactTitle</Name>
0175: <DataType>java.lang.String</DataType>
0176: <DatabaseFieldName>CONTACTTITLE</DatabaseFieldName>
0177: <DatabaseDataType>STRING</DatabaseDataType>
0178: <PrimaryKey>F</PrimaryKey>
0179: <LabelToken>[label.Microsoft.Northwind.Customers.ContactTitle]</LabelToken>
0180: <Mandatory>F</Mandatory>
0181: <IntSize>30</IntSize>
0182: <CaseType>MixedCase</CaseType>
0183: <Ignore>false</Ignore>
0184: </Field>
0185: <Field>
0186: <Name>Address</Name>
0187: <DataType>java.lang.String</DataType>
0188: <DatabaseFieldName>ADDRESS</DatabaseFieldName>
0189: <DatabaseDataType>STRING</DatabaseDataType>
0190: <PrimaryKey>F</PrimaryKey>
0191: <LabelToken>[label.Microsoft.Northwind.Customers.Address]</LabelToken>
0192: <Mandatory>F</Mandatory>
0193: <IntSize>60</IntSize>
0194: <CaseType>MixedCase</CaseType>
0195: <Ignore>false</Ignore>
0196: </Field>
0197: <Field>
0198: <Name>City</Name>
0199: <DataType>java.lang.String</DataType>
0200: <DatabaseFieldName>CITY</DatabaseFieldName>
0201: <DatabaseDataType>STRING</DatabaseDataType>
0202: <PrimaryKey>F</PrimaryKey>
0203: <LabelToken>[label.Microsoft.Northwind.Customers.City]</LabelToken>
0204: <Mandatory>F</Mandatory>
0205: <IntSize>15</IntSize>
0206: <CaseType>MixedCase</CaseType>
0207: <Ignore>false</Ignore>
0208: </Field>
0209: <Field>
0210: <Name>Region</Name>
0211: <DataType>java.lang.String</DataType>
0212: <DatabaseFieldName>REGION</DatabaseFieldName>
0213: <DatabaseDataType>STRING</DatabaseDataType>
0214: <PrimaryKey>F</PrimaryKey>
0215: <LabelToken>[label.Microsoft.Northwind.Customers.Region]</LabelToken>
0216: <Mandatory>F</Mandatory>
0217: <IntSize>15</IntSize>
0218: <CaseType>MixedCase</CaseType>
0219: <Ignore>false</Ignore>
0220: </Field>
0221: <Field>
0222: <Name>PostalCode</Name>
0223: <DataType>java.lang.String</DataType>
0224: <DatabaseFieldName>POSTALCODE</DatabaseFieldName>
0225: <DatabaseDataType>STRING</DatabaseDataType>
0226: <PrimaryKey>F</PrimaryKey>
0227: <LabelToken>[label.Microsoft.Northwind.Customers.PostalCode]</LabelToken>
0228: <Mandatory>F</Mandatory>
0229: <IntSize>10</IntSize>
0230: <CaseType>MixedCase</CaseType>
0231: <Ignore>false</Ignore>
0232: </Field>
0233: <Field>
0234: <Name>Country</Name>
0235: <DataType>java.lang.String</DataType>
0236: <DatabaseFieldName>COUNTRY</DatabaseFieldName>
0237: <DatabaseDataType>STRING</DatabaseDataType>
0238: <PrimaryKey>F</PrimaryKey>
0239: <LabelToken>[label.Microsoft.Northwind.Customers.Country]</LabelToken>
0240: <Mandatory>F</Mandatory>
0241: <IntSize>15</IntSize>
0242: <CaseType>MixedCase</CaseType>
0243: <Ignore>false</Ignore>
0244: </Field>
0245: <Field>
0246: <Name>Phone</Name>
0247: <DataType>java.lang.String</DataType>
0248: <DatabaseFieldName>PHONE</DatabaseFieldName>
0249: <DatabaseDataType>STRING</DatabaseDataType>
0250: <PrimaryKey>F</PrimaryKey>
0251: <LabelToken>[label.Microsoft.Northwind.Customers.Phone]</LabelToken>
0252: <Mandatory>F</Mandatory>
0253: <IntSize>24</IntSize>
0254: <CaseType>MixedCase</CaseType>
0255: <Ignore>false</Ignore>
0256: </Field>
0257: <Field>
0258: <Name>Fax</Name>
0259: <DataType>java.lang.String</DataType>
0260: <DatabaseFieldName>FAX</DatabaseFieldName>
0261: <DatabaseDataType>STRING</DatabaseDataType>
0262: <PrimaryKey>F</PrimaryKey>
0263: <LabelToken>[label.Microsoft.Northwind.Customers.Fax]</LabelToken>
0264: <Mandatory>F</Mandatory>
0265: <IntSize>24</IntSize>
0266: <CaseType>MixedCase</CaseType>
0267: <Ignore>false</Ignore>
0268: </Field>
0269: </Fields>
0270: <Relationships>
0271: <Relationship>
0272: <ToDomainObject>CustomerCustomerDemo</ToDomainObject>
0273: <ToDomainPackage>com.microsoft.northwind.domain</ToDomainPackage>
0274: <FromCardinality>1</FromCardinality>
0275: <ToCardinality>0..*</ToCardinality>
0276: <Type>association</Type>
0277: <FromFields>
0278: <RelationshipField><Name>CustomerID</Name></RelationshipField>
0279: </FromFields>
0280: <ToFields>
0281: <RelationshipField><Name>CustomerID</Name></RelationshipField>
0282: </ToFields>
0283: </Relationship>
0284: <Relationship>
0285: <ToDomainObject>Orders</ToDomainObject>
0286: <ToDomainPackage>com.microsoft.northwind.domain</ToDomainPackage>
0287: <FromCardinality>1</FromCardinality>
0288: <ToCardinality>0..*</ToCardinality>
0289: <Type>association</Type>
0290: <FromFields>
0291: <RelationshipField><Name>CustomerID</Name></RelationshipField>
0292: </FromFields>
0293: <ToFields>
0294: <RelationshipField><Name>CustomerID</Name></RelationshipField>
0295: </ToFields>
0296: </Relationship>
0297: </Relationships>
0298: </Root>
0299: * </pre></code>
0300: *
0301: * @author paule
0302: * @version 1.0
0303: */
0304: public class JDBCSchemaReader {
0305:
0306: private static Logger log = Logger
0307: .getLogger(JDBCSchemaReader.class);
0308:
0309: /** Should mandatory fields be inferred from NOT-NULL constraints */
0310: private boolean inferMandatory = false;
0311:
0312: /** List of all TableBean objects read from schema */
0313: private TreeMap tables = null;
0314:
0315: /** Prefix to use for generated labels */
0316: private String labelPrefix = null;
0317:
0318: /** Prefix to use for generated classes */
0319: private String packageName = null;
0320:
0321: /** Holds all the generated labels */
0322: private ListProperties moduleLabels = null;
0323:
0324: /** Holds value of property sourceDriverString. */
0325: private String sourceDriverString = "oracle.jdbc.driver.OracleDriver";
0326:
0327: /** Holds value of property sourceConnection. */
0328: private String sourceConnection = "jdbc:oracle:thin:@localhost:1521:my_sid";
0329:
0330: /** Holds value of property sourceUser. */
0331: private String sourceUser = "scott";
0332:
0333: /** Holds value of property sourcePassword. */
0334: private String sourcePassword = "tiger";
0335:
0336: /** Holds value of property tableFilter. */
0337: private String tableFilter;
0338:
0339: /** Holds value of property sourceSchema. */
0340: private String sourceSchema = "SCOTT";
0341:
0342: /** Holds value of property outputDirectory. */
0343: private String outputDirectory = "c:/temp";
0344:
0345: /** Holds value of property packagePrefix. */
0346: private String packagePrefix = "org.jaffa";
0347:
0348: /** Holds value of property appName. */
0349: private String appName = "MyApp";
0350:
0351: /** Holds value of property moduleName. */
0352: private String moduleName = "MyModule";
0353:
0354: /** Holds value of property fullPackageNames. */
0355: private boolean fullPackageNames = false;
0356:
0357: private String mappingPackage = "resources/jdbcengine";
0358:
0359: private String patternTemplate = "patterns/library/domain_creator_1_1/DomainCreatorPattern.xml";
0360:
0361: /** Map of all Key objects indexed on table name */
0362: private Map allKeysMap = null;
0363:
0364: /** Map of base level Key objects indexed on table name */
0365: private Map keyMap = null;
0366:
0367: /** Map of key inheritence keyed on a Key object and containing List's or Key objects that take
0368: * presedence over this one
0369: */
0370: private Map keySearch = null;
0371:
0372: /**
0373: * Holds value of property tableExcludePattern.
0374: */
0375: private String tableExcludePattern;
0376:
0377: /**
0378: * Run the reverse engineer process. This reads the database schema and create an
0379: * array of TableBean objects, each containing an array of FieldBean objects, and
0380: * an array of related TableBean objects.
0381: * @throws Exception General Exception thrown if there is a processing error. Typically the
0382: * underlying exception is an SQLException.
0383: */
0384: public void process() throws Exception {
0385:
0386: // Initialize other data
0387: packageName = packagePrefix.toLowerCase()
0388: + (fullPackageNames ? ".applications." : ".")
0389: + appName.toLowerCase()
0390: + (fullPackageNames ? ".modules." : ".")
0391: + moduleName.toLowerCase() + ".domain";
0392: labelPrefix = "label." + StringHelper.getUpper1(appName) + "."
0393: + StringHelper.getUpper1(moduleName) + ".";
0394: moduleLabels = new ListProperties();
0395:
0396: //------------------------------------
0397: // Load Drivers
0398: try {
0399: Class.forName(sourceDriverString);
0400: } catch (ClassNotFoundException e) {
0401: log.fatal("Source Driver Class Not Found : "
0402: + sourceDriverString);
0403: throw e;
0404: }
0405:
0406: //------------------------------------
0407: // Open Connections
0408: Connection sConn = null;
0409: try {
0410: sConn = DriverManager.getConnection(this .sourceConnection,
0411: this .sourceUser, this .sourcePassword);
0412: } catch (SQLException e) {
0413: log.fatal("Can't Connect to Source : "
0414: + e.getLocalizedMessage());
0415: throw e;
0416: }
0417:
0418: // Create a list of tables based on the target
0419: tables = new TreeMap();
0420:
0421: DatabaseMetaData sMeta = sConn.getMetaData();
0422: ResultSet rs = sMeta.getTables(null, sourceSchema, tableFilter,
0423: new String[] { "TABLE" });
0424: while (rs.next() /*&& rs.getRow() < 40*/) {
0425: TableBean table = new TableBean();
0426: table.setTableName(rs.getString("TABLE_NAME"));
0427: if (tableExcludePattern != null
0428: && Pattern.matches(tableExcludePattern, table
0429: .getTableName()))
0430: continue;
0431:
0432: // Store other table details
0433: log.info(rs.getRow() + ") Process Table "
0434: + table.getTableName());
0435: populateTable(table, rs);
0436:
0437: // Read the tables primary key
0438: String catalog = rs.getString("TABLE_CAT");
0439: String schema = rs.getString("TABLE_SCHEM");
0440: ResultSet rsk = sMeta.getPrimaryKeys(catalog, schema, table
0441: .getTableName());
0442: ArrayList k = new ArrayList();
0443: while (rsk.next()) {
0444: String col = rsk.getString("COLUMN_NAME");
0445: short seq = rsk.getShort("KEY_SEQ");
0446: String name = rsk.getString("PK_NAME");
0447: //System.out.println("Key for " + table.getTableName() + " " + (name!=null?name+" ":"") + seq + " " + col);
0448: k.add(col);
0449: }
0450: if (k.isEmpty()) {
0451: log.warn("Table " + table.getTableName()
0452: + " has no Primary Key... It will be excluded");
0453: continue;
0454: }
0455:
0456: // Add table to list, and write out a property for the table label
0457: tables.put(table.getTableName(), table);
0458: String label = labelPrefix
0459: + StringHelper.getUpper1(table.getDomainName());
0460: String labelText = StringHelper.getSpace(table
0461: .getDomainName());
0462: moduleLabels.put(label, labelText);
0463: table.setLabel("[" + label + "]");
0464:
0465: // Add this key to list
0466: Key pk = new Key(table.getTableName(), k);
0467: addKey(pk);
0468:
0469: // Read Target Fields
0470: ResultSet rsc = sMeta.getColumns(catalog, schema, table
0471: .getTableName(), null);
0472: while (rsc.next()) {
0473: FieldBean field = new FieldBean();
0474: field.setFieldName(rsc.getString("COLUMN_NAME"));
0475: table.getFields().put(field.getFieldName(), field);
0476: field.setPrimaryKey(pk.inKey(field.getFieldName()));
0477:
0478: log.info(" " + rsc.getRow() + ") Field "
0479: + (field.isPrimaryKey() ? "*" : "")
0480: + field.getFieldName());
0481:
0482: // Store other details
0483: populateField(table, field, rsc);
0484: }
0485: rsc.close();
0486:
0487: }
0488: rs.close();
0489: sConn.close();
0490:
0491: // Print Key information
0492: log.info("Keys ...");
0493: if (keyMap != null)
0494: for (Iterator i = keyMap.values().iterator(); i.hasNext();) {
0495: Key key = (Key) i.next();
0496: if (log.isInfoEnabled())
0497: System.out.println(key.toString());
0498: List list = (List) keySearch.get(key);
0499: if (list != null) {
0500: if (log.isInfoEnabled())
0501: System.out.print(" \\--- is extended by");
0502: for (Iterator i2 = list.iterator(); i2.hasNext();) {
0503: Key key2 = (Key) i2.next();
0504: System.out.print(" " + key2.toString());
0505: }
0506: if (log.isInfoEnabled())
0507: System.out.println(".");
0508: }
0509: }
0510:
0511: // For each table, look for foreign keys
0512: if (keyMap != null)
0513: for (Iterator i = tables.keySet().iterator(); i.hasNext();) {
0514: String tableName = (String) i.next();
0515: TableBean tb = (TableBean) tables.get(tableName);
0516:
0517: // See if this table has fields to support a foreign key?
0518: for (Iterator i2 = keyMap.values().iterator(); i2
0519: .hasNext();) {
0520: Key key = (Key) i2.next();
0521: if (isTableRelated(tb, key)) {
0522: //log.debug("Match foreign key " + key + " to table " + tb.getTableName());
0523: // Make sure it is not related to a higher thing
0524: Key[] matchedKeys = getExtendedKeys(tb, key);
0525: if (matchedKeys == null
0526: || matchedKeys.length == 0)
0527: // If No Extended key matched this one, use this key only
0528: matchedKeys = new Key[] { key };
0529:
0530: // Add the matched keys as relationships for to object
0531: for (int idx = 0; idx < matchedKeys.length; idx++) {
0532: Key sourceKey = matchedKeys[idx];
0533: TableBean source = (TableBean) tables
0534: .get(sourceKey.tableName);
0535: Key destKey = (Key) allKeysMap
0536: .get(tableName);
0537: if (destKey == null)
0538: throw new RuntimeException(
0539: "Can't Find Key for Table "
0540: + tableName
0541: + ", matched via key "
0542: + sourceKey);
0543:
0544: // Make sure this is not added twice.
0545: if (source.getRelationships() != null
0546: && source.getRelationships()
0547: .contains(tb))
0548: log
0549: .info("Skip Duplicate Relationship from table "
0550: + sourceKey.tableName
0551: + " to "
0552: + destKey.tableName);
0553: else {
0554: // Make sure all datatype / sizes of fields are ok to relate these things
0555: boolean mismatch = false;
0556: for (Iterator ik = sourceKey.fields
0557: .iterator(); ik.hasNext();) {
0558: String f1 = (String) ik.next();
0559: FieldBean sfld = (FieldBean) source
0560: .getFields().get(f1);
0561: if (sfld == null)
0562: throw new RuntimeException(
0563: "Can't find field "
0564: + f1
0565: + " on table "
0566: + source
0567: .getTableName());
0568: FieldBean dfld = (FieldBean) tb
0569: .getFields().get(f1);
0570: if (dfld == null)
0571: throw new RuntimeException(
0572: "Can't find field "
0573: + f1
0574: + " on table "
0575: + tb
0576: .getTableName());
0577: if (!sfld.getSqlDataType().equals(
0578: dfld.getSqlDataType())) {
0579: mismatch = true;
0580: log
0581: .info("Rejected Relationship from table "
0582: + sourceKey.tableName
0583: + " to "
0584: + destKey.tableName
0585: + ", Joing field "
0586: + f1
0587: + " Datatype mismatch "
0588: + sfld
0589: .getSqlDataType()
0590: + " vs. "
0591: + dfld
0592: .getSqlDataType());
0593: break;
0594: }
0595: if (!sfld.getIntSize().equals(
0596: dfld.getIntSize())) {
0597: mismatch = true;
0598: log
0599: .info("Rejected Relationship from table "
0600: + sourceKey.tableName
0601: + " to "
0602: + destKey.tableName
0603: + ", Joing field "
0604: + f1
0605: + " Field Length mismatch "
0606: + sfld
0607: .getIntSize()
0608: + " vs. "
0609: + dfld
0610: .getIntSize());
0611: break;
0612: }
0613: }
0614: if (!mismatch) {
0615: log
0616: .info("Adding Relationship from table "
0617: + sourceKey.tableName
0618: + " to "
0619: + destKey.tableName);
0620: if (sourceKey.equalsKey(destKey)) {
0621: log
0622: .info("Cardinality : 1-to-1");
0623: source.addRelationship(tb,
0624: false);
0625: } else {
0626: log
0627: .info("Cardinality : 1-to-*");
0628: source
0629: .addRelationship(tb,
0630: true);
0631: }
0632: }
0633: }
0634: } // matched key loop
0635: }
0636: } // key loop
0637: } // table loop
0638:
0639: }
0640:
0641: /** Look at the keys that extend this one, and return all that match */
0642: private Key[] getExtendedKeys(TableBean tb, Key key) {
0643: ArrayList matched = new ArrayList();
0644: List list = (List) keySearch.get(key);
0645: if (list == null)
0646: return null;
0647: for (Iterator i2 = list.iterator(); i2.hasNext();) {
0648: Key key2 = (Key) i2.next();
0649: if (isTableRelated(tb, key2) && !matched.contains(key2)) {
0650: log.debug("Match extended foreign key " + key2
0651: + " to table " + tb.getTableName());
0652:
0653: // @todo check again recusrively...
0654: Key[] matchedKeys = getExtendedKeys(tb, key2);
0655: if (matchedKeys == null || matchedKeys.length == 0)
0656: // If No Extended key matched this one, use this key only
0657: if (!matched.contains(key2))
0658: matched.add(key2);
0659: else
0660: for (int idx = 0; idx < matchedKeys.length; idx++)
0661: if (!matched.contains(matchedKeys[idx]))
0662: matched.add(matchedKeys[idx]);
0663: }
0664: }
0665: if (matched.size() > 0)
0666: return (Key[]) matched.toArray(new Key[] {});
0667: else
0668: return null;
0669: }
0670:
0671: /** See if the table has all the fields needed for this key */
0672: private boolean isTableRelated(TableBean tb, Key key) {
0673: // make sure this is not the key to this table!
0674: if (key.tableName.equals(tb.getTableName()))
0675: return false;
0676:
0677: // check each field in the key exists
0678: for (Iterator it = key.fields.iterator(); it.hasNext();) {
0679: String k1 = (String) it.next();
0680: if (!tb.getFields().containsKey(k1))
0681: return false;
0682: }
0683: return true;
0684: }
0685:
0686: /**
0687: * This will write out each of the TableBean objects to an XML based domain object
0688: * file. The files will be written to the folder specified by setOutputDirectory().
0689: * This folder must exist. It also writes out the related ApplicationResources.pfragment
0690: * file with all the labels as used in the domain object xml files
0691: * @return Returns true if the save works, false if there were errors. In the event of any
0692: * error only some files may be written.
0693: */
0694: public boolean save() {
0695: // Now lets generate the files
0696: for (Iterator i = tables.keySet().iterator(); i.hasNext();) {
0697: String key = (String) i.next();
0698: TableBean tb = (TableBean) tables.get(key);
0699:
0700: String fileName = outputDirectory + File.separator
0701: + tb.getDomainName() + ".xml";
0702: try {
0703: PrintWriter out = new PrintWriter(new FileWriter(
0704: fileName));
0705:
0706: // Write Header
0707: out
0708: .println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
0709: out
0710: .println("<!DOCTYPE Root PUBLIC \"-//JAFFA//DTD Domain Creator Meta 1.1//EN\" \"http://jaffa.sourceforge.net/DTD/domainCreatorMeta_1_1.dtd\">");
0711: out.println("<Root>");
0712:
0713: // Write out the table (with its fields and relationships)
0714: tb.write(out, mappingPackage, patternTemplate);
0715:
0716: // Write Footer
0717: out.println("</Root>");
0718: out.close();
0719:
0720: log.info("Generated Entity " + fileName);
0721: } catch (IOException e) {
0722: log.error(
0723: "Failed to write out entity to - " + fileName,
0724: e);
0725: return false;
0726: }
0727: }
0728:
0729: // Write out labels
0730: String labelFileName = outputDirectory + File.separator
0731: + "ApplicationResources.pfragment";
0732: String header = "# Generated from Schema " + sourceSchema
0733: + " ( " + sourceConnection + " ) on "
0734: + (new DateTime());
0735: try {
0736: FileOutputStream fos = new FileOutputStream(labelFileName);
0737: moduleLabels.sort();
0738: moduleLabels.store(fos, header);
0739: log.info("Generated Labels " + labelFileName);
0740: } catch (IOException e) {
0741: log.error("Failed to write out labels to - "
0742: + labelFileName, e);
0743: return false;
0744: }
0745: return true;
0746: }
0747:
0748: private void addKey(Key key) {
0749: if (allKeysMap == null) {
0750: allKeysMap = new TreeMap();
0751: keyMap = new TreeMap();
0752: keySearch = new HashMap();
0753: }
0754: allKeysMap.put(key.tableName, key);
0755:
0756: boolean extended = false;
0757: // loop through all keys, and see if this new key extends any current keys
0758: if (keyMap.size() > 1)
0759: for (Iterator i = keyMap.values().iterator(); i.hasNext();) {
0760: Key key2 = (Key) i.next();
0761: if (key.extendsKey(key2)) {
0762: // Need to add this to the list of keys that extend k1.
0763: List klist = (List) keySearch.get(key2);
0764: if (klist == null) {
0765: klist = new ArrayList();
0766: keySearch.put(key2, klist);
0767: }
0768: if (!klist.contains(key))
0769: klist.add(key);
0770: log.debug("Key on table " + key.tableName
0771: + " extends key on " + key2.tableName);
0772:
0773: // Remove from master list for efficientcy!!!
0774: extended = true;
0775:
0776: }
0777: }
0778:
0779: // Only add to master list of its not extending something
0780: if (!extended)
0781: keyMap.put(key.tableName, key);
0782:
0783: }
0784:
0785: private void populateTable(TableBean tb, ResultSet rs)
0786: throws SQLException {
0787: // Store other details
0788: tb.setDomainName(tb.getTableName());
0789: tb.setDomainPackage(packageName);
0790: }
0791:
0792: private void populateField(TableBean tb, FieldBean fb, ResultSet rs)
0793: throws SQLException {
0794:
0795: fb.setPropertyName(fb.getFieldName());
0796: fb.setDescription(rs.getString("REMARKS"));
0797: if (inferMandatory)
0798: fb.setMandatory("NO".equals(rs.getString("IS_NULLABLE")));
0799:
0800: int dataType = rs.getInt("DATA_TYPE");
0801: int intSize = rs.getInt("COLUMN_SIZE");
0802: int fracSize = rs.getInt("DECIMAL_DIGITS");
0803:
0804: //String dataTypeName = rs.getString("TYPE_NAME");
0805: String jaffaType = null;
0806:
0807: switch (dataType) {
0808: case Types.VARCHAR:
0809: case Types.CHAR:
0810: case Types.LONGVARCHAR:
0811: jaffaType = Defaults.STRING;
0812: if (intSize == 0)
0813: intSize = 50;
0814: break;
0815: case Types.BIT:
0816: case Types.BOOLEAN:
0817: jaffaType = Defaults.BOOLEAN;
0818: break;
0819: case Types.NUMERIC:
0820: if (fracSize > 0)
0821: jaffaType = Defaults.DECIMAL;
0822: else
0823: jaffaType = Defaults.INTEGER;
0824: break;
0825: case Types.BIGINT:
0826: case Types.INTEGER:
0827: case Types.SMALLINT:
0828: case Types.TINYINT:
0829: jaffaType = Defaults.INTEGER;
0830: break;
0831: case Types.DECIMAL:
0832: case Types.DOUBLE:
0833: case Types.FLOAT:
0834: case Types.REAL:
0835: jaffaType = Defaults.DECIMAL;
0836: break;
0837: case Types.DATE:
0838: case Types.TIME:
0839: case Types.TIMESTAMP:
0840: jaffaType = Defaults.DATETIME;
0841: break;
0842: case Types.CLOB:
0843: jaffaType = Defaults.CLOB;
0844: break;
0845: case Types.BLOB:
0846: jaffaType = Defaults.BLOB;
0847: break;
0848: case Types.LONGVARBINARY:
0849: jaffaType = Defaults.RAW;
0850: break;
0851: default:
0852: throw new RuntimeException("Unhandled Database Type "
0853: + dataType + " on " + tb.getTableName() + "."
0854: + fb.getFieldName());
0855: }
0856:
0857: fb.setSqlDataType(jaffaType);
0858: fb.setIntSize("" + intSize);
0859: if (jaffaType == Defaults.DECIMAL && fracSize > 0)
0860: fb.setFracSize("" + fracSize);
0861: fb.setJavaDataType(Defaults.getClassString(jaffaType));
0862:
0863: //fb.set???(rs.getString("COLUMN_DEF"));
0864: //fb.set???(rs.getInt("CHAR_OCTET_LENGTH"));
0865: //fb.set???(rs.getInt("ORDINAL_POSITION"));
0866:
0867: // Set up label
0868: String label = labelPrefix
0869: + StringHelper.getUpper1(tb.getDomainName()) + "."
0870: + StringHelper.getUpper1(fb.getPropertyName());
0871: fb.setLabelText(StringHelper.getSpace(StringHelper.getUpper1(fb
0872: .getPropertyName())));
0873: moduleLabels.put(label, fb.getLabelText());
0874: fb.setLabel("[" + label + "]");
0875:
0876: }
0877:
0878: /** Get the list of processed tables
0879: * @return A collection of tables, this is in a list sorted by table name
0880: */
0881: public Collection getTables() {
0882: return tables.values();
0883: }
0884:
0885: /** Getter for property sourceDriverString.
0886: * @return Value of property sourceDriverString.
0887: *
0888: */
0889: public String getSourceDriverString() {
0890: return this .sourceDriverString;
0891: }
0892:
0893: /**
0894: * Setter for property sourceDriverString. Default value is "oracle.jdbc.driver.OracleDriver"
0895: * @param sourceDriverString New value of property sourceDriverString.
0896: */
0897: public void setSourceDriverString(String sourceDriverString) {
0898: this .sourceDriverString = sourceDriverString;
0899: }
0900:
0901: /** Getter for property sourceConnection.
0902: * @return Value of property sourceConnection.
0903: *
0904: */
0905: public String getSourceConnection() {
0906: return this .sourceConnection;
0907: }
0908:
0909: /**
0910: * Setter for property sourceConnection. Default value is "jdbc:oracle:thin:@localhost:1521:my_sid"
0911: * @param sourceConnection New value of property sourceConnection.
0912: */
0913: public void setSourceConnection(String sourceConnection) {
0914: this .sourceConnection = sourceConnection;
0915: }
0916:
0917: /** Getter for property sourceUser.
0918: * @return Value of property sourceUser.
0919: *
0920: */
0921: public String getSourceUser() {
0922: return this .sourceUser;
0923: }
0924:
0925: /**
0926: * Setter for property sourceUser. Default value is "scott"
0927: * @param sourceUser New value of property sourceUser.
0928: */
0929: public void setSourceUser(String sourceUser) {
0930: this .sourceUser = sourceUser;
0931: }
0932:
0933: /** Getter for property sourcePassword.
0934: * @return Value of property sourcePassword.
0935: *
0936: */
0937: public String getSourcePassword() {
0938: return this .sourcePassword;
0939: }
0940:
0941: /**
0942: * Setter for property sourcePassword. Default value is "tiger"
0943: * @param sourcePassword New value of property sourcePassword.
0944: */
0945: public void setSourcePassword(String sourcePassword) {
0946: this .sourcePassword = sourcePassword;
0947: }
0948:
0949: /** Getter for property tableFilter.
0950: * @return Value of property tableFilter.
0951: *
0952: */
0953: public String getTableFilter() {
0954: return this .tableFilter;
0955: }
0956:
0957: /**
0958: * Setter for property tableFilter. Default is to get all tables
0959: * @param tableFilter New value of property tableFilter.
0960: */
0961: public void setTableFilter(String tableFilter) {
0962: this .tableFilter = tableFilter;
0963: }
0964:
0965: /** Getter for property sourceSchema.
0966: * @return Value of property sourceSchema.
0967: *
0968: */
0969: public String getSourceSchema() {
0970: return this .sourceSchema;
0971: }
0972:
0973: /**
0974: * Setter for property sourceSchema. Default is to get all schemas
0975: * @param sourceSchema New value of property sourceSchema.
0976: */
0977: public void setSourceSchema(String sourceSchema) {
0978: this .sourceSchema = sourceSchema;
0979: }
0980:
0981: /**
0982: * Getter for property m_outputDirectory.
0983: * @return Value of property m_outputDirectory.
0984: */
0985: public String getOutputDirectory() {
0986: return this .outputDirectory;
0987: }
0988:
0989: /**
0990: * Setter for property outputDirectory. Default is "c:/temp"
0991: * @param outputDirectory New value of property outputDirectory.
0992: */
0993: public void setOutputDirectory(String outputDirectory) {
0994: this .outputDirectory = outputDirectory;
0995: }
0996:
0997: /**
0998: * Getter for property packagePrefix.
0999: * @return Value of property packagePrefix.
1000: */
1001: public String getPackagePrefix() {
1002: return this .packagePrefix;
1003: }
1004:
1005: /**
1006: * Setter for property packagePrefix. Default is "org.jaffa"
1007: * @param packagePrefix New value of property packagePrefix.
1008: */
1009: public void setPackagePrefix(String packagePrefix) {
1010: this .packagePrefix = packagePrefix;
1011: }
1012:
1013: /**
1014: * Getter for property appName.
1015: * @return Value of property appName.
1016: */
1017: public String getAppName() {
1018: return this .appName;
1019: }
1020:
1021: /**
1022: * Setter for property appName. Default is "MyApp"
1023: * @param appName New value of property appName.
1024: */
1025: public void setAppName(String appName) {
1026: this .appName = appName;
1027: }
1028:
1029: /**
1030: * Getter for property moduleName.
1031: * @return Value of property moduleName.
1032: */
1033: public String getModuleName() {
1034: return this .moduleName;
1035: }
1036:
1037: /**
1038: * Setter for property moduleName. Default is "MyModule"
1039: * @param moduleName New value of property moduleName.
1040: */
1041: public void setModuleName(String moduleName) {
1042: this .moduleName = moduleName;
1043: }
1044:
1045: /**
1046: * Getter for property fullPackageNames.
1047: * @return Value of property fullPackageNames.
1048: */
1049: public boolean isFullPackageNames() {
1050: return this .fullPackageNames;
1051: }
1052:
1053: /**
1054: * Setter for property fullPackageNames. Default is false
1055: * @param fullPackageNames New value of property fullPackageNames.
1056: */
1057: public void setFullPackageNames(boolean fullPackageNames) {
1058: this .fullPackageNames = fullPackageNames;
1059: }
1060:
1061: /**
1062: * Getter for property mappingPackage.
1063: * @return Value of property mappingPackage.
1064: */
1065: public String getMappingPackage() {
1066: return this .mappingPackage;
1067: }
1068:
1069: /**
1070: * Setter for property mappingPackage. Default is "resources/jdbcengine"
1071: * @param mappingPackage New value of property mappingPackage.
1072: */
1073: public void setMappingPackage(String mappingPackage) {
1074: this .mappingPackage = mappingPackage;
1075: }
1076:
1077: /**
1078: * Getter for property tableExcludePattern.
1079: * @return Value of property tableExcludePattern.
1080: */
1081: public String getTableExcludePattern() {
1082: return this .tableExcludePattern;
1083: }
1084:
1085: /**
1086: * Setter for property tableExcludePattern. Default is to exclude nothing.
1087: * This expects a regular expression to use as an exclude table filter
1088: * @param tableExcludePattern New value of property tableExcludePattern.
1089: */
1090: public void setTableExcludePattern(String tableExcludePattern) {
1091: this .tableExcludePattern = tableExcludePattern;
1092: }
1093:
1094: /**
1095: * Getter for property inferMandatory.
1096: * @return Value of property inferMandatory.
1097: */
1098: public boolean isInferMandatory() {
1099: return this .inferMandatory;
1100: }
1101:
1102: /**
1103: * Setter for property inferMandatory.
1104: * @param inferMandatory New value of property inferMandatory.
1105: */
1106: public void setInferMandatory(boolean inferMandatory) {
1107: this .inferMandatory = inferMandatory;
1108: }
1109:
1110: /** Defines the Key class for storing a primary key */
1111: class Key {
1112: List fields = null;
1113: String tableName = null;
1114:
1115: Key(String tableName, List fields) {
1116: this .fields = fields;
1117: this .tableName = tableName;
1118: if (fields == null || fields.size() == 0)
1119: throw new RuntimeException(
1120: "Trying to add a key with no fields");
1121: }
1122:
1123: /** True if the keys have the same names, and are for the same table */
1124: boolean equals(Key key) {
1125: return tableName.equals(key.tableName) && equalsKey(key);
1126: }
1127:
1128: /** True if the keys have the same names, and are for the same table */
1129: boolean inKey(String field) {
1130: return fields.contains(field);
1131: }
1132:
1133: public String toString() {
1134: StringBuffer sb = new StringBuffer(50);
1135: sb.append(tableName).append('[');
1136: int i = 0;
1137: for (Iterator it = fields.iterator(); it.hasNext(); i++) {
1138: if (i > 0)
1139: sb.append(',');
1140: sb.append((String) it.next());
1141: }
1142: sb.append(']');
1143: return sb.toString();
1144: }
1145:
1146: //------------------------------------------------
1147: // These implementations don't care about order of columns in keys
1148: //------------------------------------------------
1149: // /** True if the keys have the same names */
1150: // boolean equalsKey(Key key) {
1151: // if(key.fields.size()!=fields.size()) return false;
1152: // for(Iterator i = fields.iterator();i.hasNext();) {
1153: // if(!key.fields.contains( (String)i.next() ) )
1154: // return false;
1155: // }
1156: // return true;
1157: // }
1158: //
1159: // /** True if this key extends the supplied key, it has all the same
1160: // * fields and more!
1161: // */
1162: // boolean extendsKey(Key key) {
1163: // if(key.fields.size() >= fields.size()) return false;
1164: // for(Iterator i = key.fields.iterator();i.hasNext();) {
1165: // if(!fields.contains( (String)i.next() ) )
1166: // return false;
1167: // }
1168: // return true;
1169: // }
1170:
1171: //------------------------------------------------
1172: // These implementations check the order of columns in keys
1173: //------------------------------------------------
1174: /** True if the keys have the same names */
1175: boolean equalsKey(Key key) {
1176: if (key.fields.size() != fields.size())
1177: return false;
1178: int i = 0;
1179: for (Iterator it = fields.iterator(); it.hasNext(); i++) {
1180: if (!((String) it.next()).equals((String) key.fields
1181: .get(i)))
1182: return false;
1183: }
1184: return true;
1185: }
1186:
1187: /** True if this key extends the supplied key, it has all the same
1188: * fields and more!
1189: */
1190: boolean extendsKey(Key baseKey) {
1191: //System.out.println("Is "+this.fields.size()+" > "+baseKey.fields.size()+"?");
1192: if (this .fields.size() <= baseKey.fields.size())
1193: return false;
1194: //System.out.println("Does " + this.toString() + " extend " + baseKey.toString() + "?");
1195: int i = 0;
1196: for (Iterator it = baseKey.fields.iterator(); it.hasNext(); i++) {
1197: String t1 = (String) fields.get(i);
1198: String k1 = (String) it.next();
1199: //System.out.println("Compare " + i + " " + t1 + "=" + k1);
1200: if (!k1.equals(t1))
1201: return false;
1202: }
1203: return true;
1204: }
1205: }
1206:
1207: }
|