/* * Copyright 2006 Assaf Arkin, Thomas Yip, Bruce Snyder, Werner Guttmann, Ralf Joachim * * 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. * * $Id$ */ package org.exolab.castor.jdo.engine; import java.sql.Connection; import java.util.HashMap; import java.util.Stack; import java.util.Vector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.castor.core.util.Messages; import org.castor.cpa.persistence.sql.engine.CastorConnection; import org.castor.cpa.persistence.sql.engine.SQLStatementDelete; import org.castor.cpa.persistence.sql.engine.SQLStatementInsert; import org.castor.cpa.persistence.sql.engine.SQLStatementUpdate; import org.castor.cpa.persistence.sql.engine.info.TableInfo; import org.castor.persist.ProposedEntity; import org.exolab.castor.jdo.Database; import org.exolab.castor.jdo.PersistenceException; import org.exolab.castor.jdo.QueryException; import org.exolab.castor.jdo.engine.nature.ClassDescriptorJDONature; import org.exolab.castor.jdo.engine.nature.FieldDescriptorJDONature; import org.exolab.castor.mapping.AccessMode; import org.exolab.castor.mapping.ClassDescriptor; import org.exolab.castor.mapping.FieldDescriptor; import org.exolab.castor.mapping.MappingException; import org.exolab.castor.mapping.TypeConvertor; import org.exolab.castor.mapping.loader.ClassDescriptorImpl; import org.exolab.castor.mapping.loader.FieldHandlerImpl; import org.exolab.castor.persist.SQLRelationLoader; import org.exolab.castor.persist.spi.Identity; import org.exolab.castor.persist.spi.Persistence; import org.exolab.castor.persist.spi.PersistenceFactory; import org.exolab.castor.persist.spi.PersistenceQuery; import org.exolab.castor.persist.spi.QueryExpression; /** * The SQL engine performs persistence of one object type against one * SQL database. It can only persist simple objects and extended * relationships. An SQL engine is created for each object type * represented by a database. When persisting, it requires a physical * connection that maps to the SQL database and the transaction * running on that database * * @author <a href="mailto:arkin AT intalio DOT com">Assaf Arkin</a> * @author <a href="mailto:yip AT intalio DOT com">Thomas Yip</a> * @author <a href="mailto:ferret AT frii DOT com">Bruce Snyder</a> * @author <a href="mailto:werner DOT guttmann AT gmx DOT net">Werner Guttmann</a> * @author <a href="mailto:ralf DOT joachim AT syscon DOT eu">Ralf Joachim</a> * @version $Revision$ $Date: 2006-04-26 16:24:34 -0600 (Wed, 26 Apr 2006) $ */ public final class SQLEngine implements Persistence { /** The <a href="http://jakarta.apache.org/commons/logging/">Jakarta * Commons Logging</a> instance used for all logging. */ private static final Log LOG = LogFactory.getLog(SQLEngine.class); private static final String JDO_FIELD_NATURE = FieldDescriptorJDONature.class.getName(); private final SQLFieldInfo[] _fields; private final SQLColumnInfo[] _ids; private SQLEngine _extends; private final PersistenceFactory _factory; private final ClassDescriptor _clsDesc; private final SQLStatementQuery _queryStatement; private final SQLStatementLoad _loadStatement; private final SQLStatementInsert _createStatement; private final SQLStatementDelete _removeStatement; private final SQLStatementUpdate _storeStatement; private final TableInfo _tableInfo; private HashMap<ClassDescriptor, TableInfo> _tblMap = new HashMap<ClassDescriptor, TableInfo>(); public SQLEngine(final ClassDescriptor clsDesc, final PersistenceFactory factory) throws MappingException { _clsDesc = clsDesc; _factory = factory; // construct field and id info Vector<SQLColumnInfo> idsInfo = new Vector<SQLColumnInfo>(); Vector<SQLFieldInfo> fieldsInfo = new Vector<SQLFieldInfo>(); /* * Implementation Note: * Extends and Depends has some special mutual exclusive * properties, which implementator should aware of. * * A Depended class may depends on another depended class * A class should either extends or depends on other class * A class should not depend on extending class. * because, it is the same as depends on the base class * A class may be depended by zero or more classes * A class may be extended by zero or more classes * A class may extends only zero or one class * A class may depends only zero or one class * A class may depend on extended class * A class may extend a dependent class. * A class may extend a depended class. * No loop or circle should exist */ // then, we put depended class ids in the back ClassDescriptor base = clsDesc; // walk until the base class which this class extends base = clsDesc; Stack<ClassDescriptor> stack = new Stack<ClassDescriptor>(); stack.push(base); while (base.getExtends() != null) { // if (base.getDepends() != null) { // throw new MappingException( // "Class should not both depends on and extended other classes"); // } base = base.getExtends(); stack.push(base); // do we need to add loop detection? } // now base is either the base of extended class, or // clsDesc // we always put the original id info in front // [oleg] except for SQL name, it may differ. FieldDescriptor[] baseIdDescriptors = ((ClassDescriptorImpl) base).getIdentities(); FieldDescriptor[] idDescriptors = ((ClassDescriptorImpl) clsDesc).getIdentities(); for (int i = 0; i < baseIdDescriptors.length; i++) { if (baseIdDescriptors[i].hasNature(FieldDescriptorJDONature.class.getName())) { String name = baseIdDescriptors[i].getFieldName(); String[] sqlName = new FieldDescriptorJDONature(baseIdDescriptors[i]).getSQLName(); int[] sqlType = new FieldDescriptorJDONature(baseIdDescriptors[i]).getSQLType(); FieldHandlerImpl fh = (FieldHandlerImpl) baseIdDescriptors[i].getHandler(); // The extending class may have other SQL names for identity fields for (int j = 0; j < idDescriptors.length; j++) { if (name.equals(idDescriptors[j].getFieldName()) && (idDescriptors[j].hasNature(JDO_FIELD_NATURE))) { sqlName = new FieldDescriptorJDONature(idDescriptors[j]).getSQLName(); break; } } idsInfo.add(new SQLColumnInfo(sqlName[0], sqlType[0], fh.getConvertTo(), fh.getConvertFrom())); } else { throw new MappingException("Except JDOFieldDescriptor"); } } // then do the fields while (!stack.empty()) { base = stack.pop(); FieldDescriptor[] fieldDescriptors = base.getFields(); for (int i = 0; i < fieldDescriptors.length; i++) { // fieldDescriptors[i] is persistent in db if it is not transient // and it is a JDOFieldDescriptor or has a ClassDescriptor if (!fieldDescriptors[i].isTransient()) { if ((fieldDescriptors[i].hasNature(FieldDescriptorJDONature.class.getName())) || (fieldDescriptors[i].getClassDescriptor() != null)) { SQLFieldInfo inf = new SQLFieldInfo(clsDesc, fieldDescriptors[i], new ClassDescriptorJDONature(base).getTableName(), !stack.empty()); fieldsInfo.add(inf); if (inf.isJoined()) { String alias = inf.getTableName() + "_f" + i; inf.setTableAlias(alias); } else { inf.setTableAlias(inf.getTableName()); } } } } } _tableInfo = new TableInfo(clsDesc, _tblMap); for (TableInfo tblInf : _tblMap.values()) { tblInf.adjustTableLinks(); } _ids = new SQLColumnInfo[idsInfo.size()]; idsInfo.copyInto(_ids); _fields = new SQLFieldInfo[fieldsInfo.size()]; fieldsInfo.copyInto(_fields); _queryStatement = new SQLStatementQuery(this, factory); _loadStatement = new SQLStatementLoad(this, factory); _createStatement = new SQLStatementInsert(this, factory); _removeStatement = new SQLStatementDelete(this); _storeStatement = new SQLStatementUpdate(this); } public SQLRelationLoader createSQLRelationLoader(final String manyTable, final String[] idSQL, final int[] idType, final TypeConvertor[] idTo, final TypeConvertor[] idFrom, final String[] relatedIdSQL, final int[] relatedIdType, final TypeConvertor[] ridTo, final TypeConvertor[] ridFrom) { return new SQLRelationLoader(manyTable, idSQL, idType, idTo, idFrom, relatedIdSQL, relatedIdType, ridTo, ridFrom, _factory); } public SQLColumnInfo[] getColumnInfoForIdentities() { return _ids; } public SQLFieldInfo[] getInfo() { return _fields; } /** * Mutator method for setting extends SQLEngine. * * @param engine */ public void setExtends(final SQLEngine engine) { _extends = engine; } public SQLEngine getExtends() { return _extends; } /** * Used by {@link org.exolab.castor.jdo.OQLQuery} to retrieve the class descriptor. * @return the JDO class descriptor. */ public ClassDescriptor getDescriptor() { return _clsDesc; } public PersistenceQuery createQuery(final QueryExpression query, final Class[] types, final AccessMode accessMode) throws QueryException { AccessMode mode = (accessMode != null) ? accessMode : new ClassDescriptorJDONature(_clsDesc).getAccessMode(); String sql = query.getStatement(mode == AccessMode.DbLocked); if (LOG.isDebugEnabled()) { LOG.debug(Messages.format("jdo.createSql", sql)); } return new SQLQuery(this, _factory, sql, types, false); } public PersistenceQuery createCall(final String spCall, final Class[] types) { FieldDescriptor[] fields; String[] jdoFields0; String[] jdoFields; String sql; int[] sqlTypes0; int[] sqlTypes; int count; // changes for the SQL Direct interface begins here if (spCall.startsWith("SQL")) { sql = spCall.substring(4); if (LOG.isDebugEnabled()) { LOG.debug(Messages.format("jdo.directSQL", sql)); } return new SQLQuery(this, _factory, sql, types, true); } if (LOG.isDebugEnabled()) { LOG.debug(Messages.format("jdo.spCall", spCall)); } fields = _clsDesc.getFields(); jdoFields0 = new String[fields.length + 1]; sqlTypes0 = new int[fields.length + 1]; // the first field is the identity count = 1; jdoFields0[0] = _clsDesc.getIdentity().getFieldName(); sqlTypes0[0] = new FieldDescriptorJDONature(_clsDesc.getIdentity()).getSQLType()[0]; for (int i = 0; i < fields.length; ++i) { if (fields[i].hasNature(FieldDescriptorJDONature.class.getName())) { jdoFields0[count] = new FieldDescriptorJDONature(fields[i]).getSQLName()[0]; sqlTypes0[count] = new FieldDescriptorJDONature(fields[i]).getSQLType()[0]; ++count; } } jdoFields = new String[count]; sqlTypes = new int[count]; System.arraycopy(jdoFields0, 0, jdoFields, 0, count); System.arraycopy(sqlTypes0, 0, sqlTypes, 0, count); return _factory.getCallQuery(spCall, types, _clsDesc.getJavaClass(), jdoFields, sqlTypes); } public QueryExpression getQueryExpression() { return _factory.getQueryExpression(); } public QueryExpression getFinder() { return _queryStatement.getQueryExpression(); } public TableInfo getTableInfo() { return _tableInfo; } public HashMap<ClassDescriptor, TableInfo> getTableMap() { return _tblMap; } protected Object idToJava(final int index, final Object object) { if ((object == null) || (_ids[index].getConvertTo() == null)) { return object; } return _ids[index].getConvertTo().convert(object); } protected Object toJava(final int field, final int column, final Object object) { SQLColumnInfo col = _fields[field].getColumnInfo()[column]; if ((object == null) || (col.getConvertTo() == null)) { return object; } return col.getConvertTo().convert(object); } public Identity create(final Database database, final Object conn, final ProposedEntity entity, final Identity identity) throws PersistenceException { CastorConnection castorConn = new CastorConnection((Connection) conn, _factory); return (Identity) _createStatement.executeStatement( database, castorConn, identity, entity); } public void store(final Object conn, final Identity identity, final ProposedEntity newentity, final ProposedEntity oldentity) throws PersistenceException { // check size of identity columns if (identity.size() != _ids.length) { throw new PersistenceException("Size of identity field mismatched!"); } CastorConnection castorConn = new CastorConnection((Connection) conn, _factory); _storeStatement.executeStatement(castorConn, identity, newentity, oldentity); } public void delete(final Object conn, final Identity identity) throws PersistenceException { // check size of identity columns if (identity.size() != _ids.length) { throw new PersistenceException("Size of identity field mismatched!"); } CastorConnection castorConn = new CastorConnection((Connection) conn, _factory); _removeStatement.executeStatement(castorConn, identity); } /** * Loads the object from persistence storage. This method will load * the object fields from persistence storage based on the object's * identity. This method may return a stamp which can be used at a * later point to determine whether the copy of the object in * persistence storage is newer than the cached copy (see {@link * #store}). If <tt>lock</tt> is true the object must be * locked in persistence storage to prevent concurrent updates. * * @param conn An open connection * @param entity An Object[] to load field values into * @param identity Identity of the object to load. * @param accessMode The access mode (null equals shared) * @throws PersistenceException A persistence error occured */ public void load(final Object conn, final ProposedEntity entity, final Identity identity, final AccessMode accessMode) throws PersistenceException { if (identity.size() != _ids.length) { throw new PersistenceException("Size of identity field mismatched!"); } CastorConnection castorConn = new CastorConnection((Connection) conn, _factory); _loadStatement.executeStatement(castorConn, identity, entity, accessMode); } public String toString() { return _clsDesc.toString(); } }