Java tutorial
/* * Copyright 2011 Assaf Arkin, Thomas Yip, Bruce Snyder, Werner Guttmann, * Ralf Joachim, Johannes Venzke * * 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.castor.cpa.persistence.sql.engine; 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.info.EntityTableInfo; import org.castor.cpa.persistence.sql.engine.info.ForeignReferenceInfo; import org.castor.cpa.persistence.sql.engine.info.InfoFactory; import org.castor.cpa.persistence.sql.engine.info.RelationTableInfo; 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.SQLColumnInfo; import org.exolab.castor.jdo.engine.SQLFieldInfo; import org.exolab.castor.jdo.engine.SQLQuery; import org.exolab.castor.jdo.engine.SQLStatementQuery; 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.loader.ClassDescriptorImpl; import org.exolab.castor.mapping.loader.FieldHandlerImpl; 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> * @author <a href="mailto:johannes DOT venzke AT revival DOT de">Johannes Venzke</a> * @version $Revision$ $Date$ */ 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 EntityTableInfo _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()); } } } } } InfoFactory infoFactory = new InfoFactory(); _tableInfo = infoFactory.createTableInfo(clsDesc); _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); } //----------------------------------------------------------------------------------- /** * {@inheritDoc} */ public SQLRelationLoader createSQLRelationLoader(final FieldDescriptor fieldDescriptor) throws MappingException { if (!fieldDescriptor.hasNature(FieldDescriptorJDONature.class.getName()) || (new FieldDescriptorJDONature(fieldDescriptor).getManyTable() == null)) { return null; } String fieldName = fieldDescriptor.getFieldName(); EntityTableInfo entity = _tableInfo; RelationTableInfo relation = null; while (relation == null) { for (ForeignReferenceInfo reference : entity.getForeignReferences()) { if (fieldName.equals(reference.getFieldName())) { relation = (RelationTableInfo) reference.getFromTable(); } } entity = entity.getExtendedTable(); } return new SQLRelationLoader(relation); } 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; } /** * 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 EntityTableInfo getTableInfo() { return _tableInfo; } public Identity create(final Database database, final CastorConnection conn, final ProposedEntity entity, final Identity identity) throws PersistenceException { Identity internalIdentity = identity; // must create record in the parent table first. all other dependents // are created afterwards. quick and very dirty hack to try to make // multiple class on the same table work. if (_extends != null) { String thisTable = new ClassDescriptorJDONature(_clsDesc).getTableName(); String extTable = new ClassDescriptorJDONature(_extends._clsDesc).getTableName(); if (!extTable.equals(thisTable)) { internalIdentity = _extends.create(database, conn, entity, internalIdentity); } } return (Identity) _createStatement.executeStatement(database, conn, internalIdentity, entity); } public void store(final CastorConnection 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!"); } _storeStatement.executeStatement(conn, identity, newentity, oldentity); // Must store values of whole extends hierarchy if (_extends != null) { _extends.store(conn, identity, newentity, oldentity); } } public void delete(final CastorConnection conn, final Identity identity) throws PersistenceException { // check size of identity columns if (identity.size() != _ids.length) { throw new PersistenceException("Size of identity field mismatched!"); } _removeStatement.executeStatement(conn, identity); // Must also delete record of extend path from extending to root class if (_extends != null) { _extends.delete(conn, 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 A CastorConnection object holding 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 CastorConnection 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!"); } _loadStatement.executeStatement(conn, identity, entity, accessMode); } public PersistenceFactory getPersistenceFactory() { return _factory; } public String toString() { return _clsDesc.toString(); } //----------------------------------------------------------------------------------- }