/*
* Copyright 2003, 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ws.jaxme.pm.generator.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import javax.naming.NamingException;
import org.apache.ws.jaxme.generator.Generator;
import org.apache.ws.jaxme.generator.SchemaReader;
import org.apache.ws.jaxme.generator.sg.AttributeSG;
import org.apache.ws.jaxme.generator.sg.AttributeSGChain;
import org.apache.ws.jaxme.generator.sg.ComplexContentSG;
import org.apache.ws.jaxme.generator.sg.ComplexTypeSG;
import org.apache.ws.jaxme.generator.sg.Context;
import org.apache.ws.jaxme.generator.sg.GroupSG;
import org.apache.ws.jaxme.generator.sg.ObjectSG;
import org.apache.ws.jaxme.generator.sg.ParticleSG;
import org.apache.ws.jaxme.generator.sg.SGFactory;
import org.apache.ws.jaxme.generator.sg.SGFactoryChain;
import org.apache.ws.jaxme.generator.sg.SchemaSGChain;
import org.apache.ws.jaxme.generator.sg.TypeSGChain;
import org.apache.ws.jaxme.generator.sg.impl.AttributeSGImpl;
import org.apache.ws.jaxme.generator.sg.impl.JaxMeSchemaReader;
import org.apache.ws.jaxme.generator.sg.impl.SGFactoryChainImpl;
import org.apache.ws.jaxme.logging.Logger;
import org.apache.ws.jaxme.logging.LoggerAccess;
import org.apache.ws.jaxme.sqls.Column;
import org.apache.ws.jaxme.sqls.SQLFactory;
import org.apache.ws.jaxme.sqls.Table;
import org.apache.ws.jaxme.sqls.impl.SQLFactoryImpl;
import org.apache.ws.jaxme.util.ClassLoader;
import org.apache.ws.jaxme.xs.XSAnnotation;
import org.apache.ws.jaxme.xs.XSAttribute;
import org.apache.ws.jaxme.xs.XSObject;
import org.apache.ws.jaxme.xs.XSSchema;
import org.apache.ws.jaxme.xs.XSType;
import org.apache.ws.jaxme.xs.parser.XsObjectCreator;
import org.apache.ws.jaxme.xs.parser.impl.LocSAXException;
import org.apache.ws.jaxme.xs.types.XSBase64Binary;
import org.apache.ws.jaxme.xs.types.XSBoolean;
import org.apache.ws.jaxme.xs.types.XSByte;
import org.apache.ws.jaxme.xs.types.XSDate;
import org.apache.ws.jaxme.xs.types.XSDateTime;
import org.apache.ws.jaxme.xs.types.XSDouble;
import org.apache.ws.jaxme.xs.types.XSFloat;
import org.apache.ws.jaxme.xs.types.XSInt;
import org.apache.ws.jaxme.xs.types.XSShort;
import org.apache.ws.jaxme.xs.types.XSString;
import org.apache.ws.jaxme.xs.types.XSTime;
import org.apache.ws.jaxme.xs.xml.XsEAppinfo;
import org.apache.ws.jaxme.xs.xml.XsObject;
import org.apache.ws.jaxme.xs.xml.XsQName;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
/** <p>A schema writer for creation of an object relational
* mapping.</p>
*
* @author <a href="mailto:joe@ispsoft.de">Jochen Wiedmann</a>
*/
public class JaxMeJdbcSG extends SGFactoryChainImpl {
Logger log = LoggerAccess.getLogger(JaxMeJdbcSG.class);
public static class Mode {
private String name;
private Mode(String pName) { name = pName; }
public String toString() { return name; }
public String getName() { return name; }
public int hashCode() { return name.hashCode(); }
public boolean equals(Object o) {
if (o == null || !(o instanceof Mode)) {
return false;
} else {
return name.equals(((Mode) o).name);
}
}
public static Mode valueOf(String pMode) {
if ("GENERIC".equalsIgnoreCase(pMode)) {
return GENERIC;
} else if ("ORACLE".equalsIgnoreCase(pMode)) {
return ORACLE;
} else {
throw new IllegalArgumentException("Valid database modes are either of 'generic' or 'oracle', not " + pMode);
}
}
/** <p>Default database mode; types are taken as reported by the JDBC
* driver.</p>
*/
public static final Mode GENERIC = new Mode("Generic");
/** <p>Oracle database mode; the type NUMERIC is interpreted as FLOAT,
* TINYINT, SMALLINT, INTEGER, BIGINT, or DOUBLE, depending on scale
* and precision. This mode is turned on by setting the option
* <code>jdbc.dbmode</code> or if the method
* {@link java.sql.DatabaseMetaData#getDatabaseProductName()}
* returns the value "Oracle".</p>
*/
public static final Mode ORACLE = new Mode("Oracle");
}
private class JdbcAttribute implements XSAttribute {
private final Locator locator;
private final XsQName name;
private final XSType type;
private final boolean isOptional;
private final XSObject parent;
public JdbcAttribute(XSObject pParent, Locator pLocator, XsQName pName, XSType pType, boolean pOptional) {
parent = pParent;
locator = pLocator;
name = pName;
type = pType;
isOptional = pOptional;
}
public boolean isGlobal() { return false; }
public void setGlobal(boolean pGlobal) {
if (!pGlobal) {
throw new IllegalStateException("This attribute cannot be made global");
}
}
public XsQName getName() { return name; }
public XSType getType() { return type; }
public boolean isOptional() { return isOptional; }
public XSAnnotation[] getAnnotations() { return new XSAnnotation[0]; }
public Locator getLocator() { return locator; }
public void validate() throws SAXException {}
public boolean isTopLevelObject() { return false; }
public XSObject getParentObject() { return parent; }
public XSSchema getXSSchema() { return parent.getXSSchema(); }
public String getDefault() { return null; }
public String getFixed() { return null; }
public Attributes getOpenAttributes() { return null; }
}
/** <p>Namespace URI of the JDBC schema writer.</p>
*/
public static final String JAXME_JDBC_SCHEMA_URI = "http://ws.apache.org/jaxme/namespaces/jaxme2/jdbc-mapping";
private SGFactory sgFactory;
private SQLFactory sqlFactory;
private String key;
public JaxMeJdbcSG(SGFactoryChain o) {
super(o);
}
public String getKey() {
return key;
}
public void init(SGFactory pFactory) {
super.init(pFactory);
sgFactory = pFactory;
SchemaReader schemaReader = pFactory.getGenerator().getSchemaReader();
if (schemaReader instanceof JaxMeSchemaReader) {
JaxMeSchemaReader jaxmeSchemaReader = (JaxMeSchemaReader) schemaReader;
jaxmeSchemaReader.addXsObjectCreator(new XsObjectCreator(){
public XsObject newBean(XsObject pParent, Locator pLocator, XsQName pQName) throws SAXException {
if (pParent instanceof XsEAppinfo) {
if (JAXME_JDBC_SCHEMA_URI.equals(pQName.getNamespaceURI())) {
if ("table".equals(pQName.getLocalName())) {
return new TableDetails(JaxMeJdbcSG.this, pParent);
} else if ("connection".equals(pQName.getLocalName())) {
return new ConnectionDetails(JaxMeJdbcSG.this, pParent);
} else {
throw new LocSAXException("Invalid element name: " + pQName, pLocator);
}
}
}
return null;
}
});
} else {
throw new IllegalStateException("The schema reader " + schemaReader.getClass().getName() +
" is not an instance of " + JaxMeSchemaReader.class.getName());
}
String s = schemaReader.getGenerator().getProperty("jdbc.sqlFactory");
if (s == null) {
sqlFactory = new SQLFactoryImpl();
} else {
Class c;
try {
c = ClassLoader.getClass(s, SQLFactory.class);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("SQLFactory class " + s + ", specified by property jdbc.sqlFactory, not found.");
}
try {
sqlFactory = (SQLFactory) c.newInstance();
} catch (InstantiationException e) {
throw new IllegalStateException("Unable to instantiate SQLFactory class " + c.getName());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Illegal access to the default constructor of SQLFactory class " + c.getName());
}
}
key = pFactory.getGenerator().getKey();
}
protected SGFactory getSGFactory() {
return sgFactory;
}
public Generator getGenerator(SGFactory pFactory) {
return super.getGenerator(pFactory);
}
protected Mode getDatabaseMode(ConnectionDetails pDetails,
Connection pConn) throws SQLException {
if (pDetails == null) {
String v = pConn.getMetaData().getDatabaseProductName();
try {
return Mode.valueOf(v);
} catch (Exception e) {
return Mode.GENERIC;
}
} else {
return pDetails.getDbMode();
}
}
/** <p>Guess an SQL type, based on reported type, scale and
* precision.</p>
*/
protected int getDbType(Mode pDbMode, int pDbType, long pScale,
long pPrecision, String pDbTypeName) {
if (Mode.GENERIC.equals(pDbMode)) {
return pDbType;
} else if (pDbType == Types.OTHER) {
if ("CLOB".equalsIgnoreCase(pDbTypeName)) {
return Types.CLOB;
} else if ("BLOB".equalsIgnoreCase(pDbTypeName)) {
return Types.BLOB;
} else {
return Types.OTHER;
}
} else if (pDbType == Types.NUMERIC || "NUMBER".equalsIgnoreCase(pDbTypeName)) {
if (pScale == 0) {
if (pPrecision == 0) { return Types.FLOAT; }
if (pPrecision <= 2) { return Types.TINYINT; }
if (pPrecision <= 4) { return Types.SMALLINT; }
if (pPrecision <= 9) { return Types.INTEGER; }
return Types.BIGINT;
} else if (pScale == -127) {
// Uses binary precision - See page 4-37 of the OCI book
if (pPrecision < 24) {
return Types.FLOAT; // 53 is double cutoff
}
} else {
// Uses decimal precision - See page 4-37 of the OCI book
if (pPrecision < 8) {
return Types.FLOAT; // 15 is double cutoff
}
}
} else if (pDbType != Types.NUMERIC) {
return pDbType;
}
return Types.DOUBLE;
}
/** <p>Creates a new attribute or chooses an existing atomic
* child element. Returns the {@link AttributeSG} or
* {@link ParticleSG}.</p>
*/
protected Object addColumn(ComplexTypeSG pTypeSG, XSType pType, Column pColumn) throws SAXException {
final String mName = "addColumn";
log.entering(mName, pColumn.getQName());
/* May be there already is an attribute or child element
* with default settings?
*/
List allChilds = new ArrayList();
AttributeSG[] attributes = pTypeSG.getAttributes();
if (attributes != null) {
allChilds.addAll(Arrays.asList(attributes));
}
if (!pTypeSG.hasSimpleContent()) {
ComplexContentSG cct = pTypeSG.getComplexContentSG();
GroupSG groupSG = cct.getGroupSG();
ParticleSG[] childs = groupSG.getParticles();
if (childs != null) {
for (int i = 0; i < childs.length; i++) {
ParticleSG child = childs[i];
// Accept only simple elements
if (child.isElement()) {
ObjectSG objectSG = child.getObjectSG();
if (!objectSG.getTypeSG().isComplex()) {
allChilds.add(child);
}
}
}
}
}
Object theChild = null;
for (Iterator iter = allChilds.iterator(); iter.hasNext(); ) {
Object currentChild = iter.next();
String localName;
if (currentChild instanceof AttributeSG) {
localName = ((AttributeSG) currentChild).getName().getLocalName();
} else if (currentChild instanceof ParticleSG) {
localName = ((ParticleSG) currentChild).getObjectSG().getName().getLocalName();
} else {
throw new IllegalStateException("Expected either attribute or element.");
}
if (localName != null && localName.equalsIgnoreCase(pColumn.getName().getName())) {
if (theChild == null) {
theChild = currentChild;
} else {
log.warn(mName, "Multiple matching attributes or child elements found for column " +
pColumn.getQName());
}
}
}
if (theChild == null) {
// We have to create a new attribute matching the column
XSType xsType;
if (pColumn.isBinaryColumn()) {
xsType = XSBase64Binary.getInstance();
} else if (pColumn.isStringColumn()) {
xsType = XSString.getInstance();
} else {
Column.Type myType = pColumn.getType();
if (Column.Type.BIT.equals(myType)) {
xsType = XSBoolean.getInstance();
} else if (Column.Type.DATE.equals(myType)) {
xsType = XSDate.getInstance();
} else if (Column.Type.DOUBLE.equals(myType)) {
xsType = XSDouble.getInstance();
} else if (Column.Type.FLOAT.equals(myType)) {
xsType = XSFloat.getInstance();
} else if (Column.Type.SMALLINT.equals(myType)) {
xsType = XSShort.getInstance();
} else if (Column.Type.TIME.equals(myType)) {
xsType = XSTime.getInstance();
} else if (Column.Type.TIMESTAMP.equals(myType)) {
xsType = XSDateTime.getInstance();
} else if (Column.Type.TINYINT.equals(myType)) {
xsType = XSByte.getInstance();
} else if (Column.Type.INTEGER.equals(myType)) {
xsType = XSInt.getInstance();
} else {
throw new IllegalStateException("Unknown column type: " + myType);
}
}
XSAttribute attr = new JdbcAttribute(pType, pType.getLocator(),
new XsQName((String) null, pColumn.getName().getName()), xsType,
pColumn.isNullable());
AttributeSGChain chain = (AttributeSGChain) pTypeSG.newAttributeSG(attr);
AttributeSG attributeSG = new AttributeSGImpl(chain);
pTypeSG.addAttributeSG(attributeSG);
theChild = attributeSG;
}
return theChild;
}
/** <p>We use the interface Connector in order to allow people use of this
* class, even if they don't have the javax.sql package around.</p>
*/
private interface Connector {
public Connection getConnection(TableDetails pDetails) throws SAXException;
}
private class DriverManagerConnector implements Connector {
public Connection getConnection(TableDetails pTableDetails) throws SAXException {
final String mName = "DriverManagerConnector.getConnection";
Class c = null;
Exception ex = null;
log.fine(mName, "Loading driver " + pTableDetails.getDriver());
try {
Class.forName(pTableDetails.getDriver());
} catch (Exception e) {
}
if (c == null) {
try {
c = Thread.currentThread().getContextClassLoader().loadClass(pTableDetails.getDriver());
} catch (Exception e) {
if (ex == null) { ex = e; }
}
}
if (c == null) {
if (ex == null) { ex = new ClassNotFoundException(); }
throw new LocSAXException("Unable to load driver class " + pTableDetails.getDriver()
+ ": " + ex.getClass().getName() + ", " + ex.getMessage(),
pTableDetails.getLocator());
}
log.fine(mName, "Connecting to database " + pTableDetails.getUrl() + " as " + pTableDetails.getUser());
try {
Connection conn = DriverManager.getConnection(pTableDetails.getUrl(), pTableDetails.getUser(),
pTableDetails.getPassword());
if (conn == null) {
throw new LocSAXException("Unable to connect to " + pTableDetails.getUrl()
+ " as user " + pTableDetails.getUser()
+ ": DriverManager returned a null connection",
pTableDetails.getLocator());
}
return conn;
} catch (SQLException e) {
throw new LocSAXException("Unable to connect to " + pTableDetails.getUrl()
+ " as user " + pTableDetails.getUser()
+ ": SQL State = " + e.getSQLState() + ", error code = "
+ e.getErrorCode() + ", " + e.getMessage(),
pTableDetails.getLocator(), e);
}
}
}
private class DatasourceConnector implements Connector {
public Connection getConnection(TableDetails pTableDetails) throws SAXException {
javax.naming.InitialContext ic;
try {
ic = new javax.naming.InitialContext();
} catch (NamingException e) {
throw new LocSAXException("Failed to create initial JNDI context: "
+ e.getClass().getName() + ", " + e.getMessage(),
pTableDetails.getLocator(), e);
}
javax.sql.DataSource ds;
try {
ds = (javax.sql.DataSource) ic.lookup(pTableDetails.getDatasource());
} catch (NamingException e) {
throw new LocSAXException("Failed to lookup datasource " + pTableDetails.getDatasource()
+ ": " + e.getClass().getName() + ", " + e.getMessage(),
pTableDetails.getLocator(), e);
}
try {
Connection conn = ds.getConnection(pTableDetails.getUser(), pTableDetails.getPassword());
if (conn == null) {
throw new LocSAXException("Unable to connect to " + pTableDetails.getUrl()
+ " as user " + pTableDetails.getUser()
+ ": Datasource returned a null connection",
pTableDetails.getLocator());
}
return conn;
} catch (SQLException e) {
throw new LocSAXException("Unable to connect to datasource " + pTableDetails.getUrl()
+ " as user " + pTableDetails.getUser()
+ ": SQL State = " + e.getSQLState() + ", error code = "
+ e.getErrorCode() + ", " + e.getMessage(),
pTableDetails.getLocator(), e);
}
}
}
protected CustomTableData addTableData(ComplexTypeSG pTypeSG, XSType pType,
TableDetails pTableDetails) throws SAXException {
final String mName = "addTableData";
log.entering(mName, new Object[]{pTypeSG, pTableDetails});
String tableName = pTableDetails.getName();
Connection conn;
if (pTableDetails.getDriver() == null) {
conn = new DatasourceConnector().getConnection(pTableDetails);
} else {
conn = new DriverManagerConnector().getConnection(pTableDetails);
}
try {
int offset = tableName.indexOf('.');
String schemaName;
if (offset > 0) {
schemaName = tableName.substring(0, offset);
tableName = tableName.substring(offset+1);
} else {
schemaName = null;
}
Table table;
try {
table = sqlFactory.getTable(conn, schemaName, tableName);
conn.close();
conn = null;
} catch (SQLException e) {
throw new SAXException("Failed to read table " + pTableDetails.getName() + ": " + e.getMessage(), e);
}
if (table.getPrimaryKey() == null) {
throw new IllegalStateException("The table " + table.getQName() + " does not have a primary key.");
}
CustomTableData customTableData = new CustomTableData(this, table, pTypeSG, pTableDetails);
for (Iterator iter = table.getColumns(); iter.hasNext(); ) {
Column col = (Column) iter.next();
Object sg = addColumn(pTypeSG, pType, col);
CustomColumnData colData = new CustomColumnData(col.getName().getName(), sg);
col.setCustomData(colData);
}
return customTableData;
} finally {
if (conn != null) { try { conn.close(); } catch (Throwable ignore) {} }
}
}
public Object newTypeSG(SGFactory pController, XSType pType) throws SAXException {
return new JdbcTypeSG(this, (TypeSGChain) super.newTypeSG(pController, pType), pType);
}
public Object newTypeSG(SGFactory pController, XSType pType, XsQName pName) throws SAXException {
return new JdbcTypeSG(this, (TypeSGChain) super.newTypeSG(pController, pType, pName), pType);
}
public Object newTypeSG(SGFactory pController, XSType pType, Context pClassContext, XsQName pName) throws SAXException {
return new JdbcTypeSG(this, (TypeSGChain) super.newTypeSG(pController, pType, pClassContext, pName), pType);
}
public Object newSchemaSG(SGFactory pController, XSSchema pSchema) throws SAXException {
SchemaSGChain chain = (SchemaSGChain) super.newSchemaSG(pController, pSchema);
chain = new JdbcSchemaSG(this, chain);
return chain;
}
}