001    package org.apache.ojb.broker.util.sequence;
002    
003    /* Copyright 2003-2005 The Apache Software Foundation
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    import java.sql.ResultSet;
019    import java.sql.SQLException;
020    import java.sql.Statement;
021    
022    import org.apache.commons.logging.Log;
023    import org.apache.commons.logging.LogFactory;
024    import org.apache.ojb.broker.PersistenceBroker;
025    import org.apache.ojb.broker.accesslayer.JdbcAccess;
026    import org.apache.ojb.broker.metadata.ClassDescriptor;
027    import org.apache.ojb.broker.metadata.FieldDescriptor;
028    import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
029    
030    
031    /**
032     * Sequence manager implementation using native database <tt>Identity columns</tt>
033     * (like MySQL, MSSQL, ...). For proper work some specific metadata settings
034     * needed:
035     * <ul>
036     * <li>field representing the identity column need attribute <code>autoincrement</code> 'true'</li>
037     * <li>field representing the identity column need attribute <code>access</code> set 'readonly'</li>
038     * <li>field representing the identity column need attribute <code>primarykey</code> set 'true'</li>
039     * <li>only possible to declare one identity field per class</li>
040     * </ul>
041     * <p/>
042     * <b>Note:</b>
043     * Make sure that the DB generated identity columns represent values &gt 0, because negative values
044     * intern used by this implementation and 0 could cause problems with primitive FK fields.
045     * </p>
046     * <p/>
047     * Implementation configuration properties:
048     * <table cellspacing="2" cellpadding="2" border="3" frame="box">
049     * <tr>
050     * <td><strong>Property Key</strong></td>
051     * <td><strong>Property Values</strong></td>
052     * </tr>
053     * <tr>
054     * <td>no properties to set</td>
055     * <td>
056     * <p/>
057     * </td>
058     * </tr>
059     * </table>
060     * </p>
061     * <p/>
062     * <p/>
063     * <b>Limitations:</b>
064     * <ul>
065     * <li>Native key generation is not 'extent aware'
066     * when extent classes span several tables! Please
067     * see more in shipped docs 'extents and polymorphism'
068     * or sequence manager docs.
069     * </li>
070     * <li>
071     * Only positive identity values are allowed (see above).
072     * </li>
073     * </ul>
074     * </p>
075     * <br/>
076     * <br/>
077     *
078     * @author <a href="mailto:travis@spaceprogram.com">Travis Reeder</a>
079     * @author <a href="mailto:arminw@apache.org">Armin Waibel</a>
080     * @version $Id: SequenceManagerNativeImpl.java,v 1.1 2007-08-24 22:17:29 ewestfal Exp $
081     */
082    public class SequenceManagerNativeImpl extends AbstractSequenceManager
083    {
084        private Log log = LogFactory.getLog(SequenceManagerNativeImpl.class);
085    
086        /*
087         TODO:
088         1. Find a better solution (if possible) for this problem
089         We need this dummy field to return a negative long value
090         on getUniqueLong(...) call. If we return always the same
091         value, the resulting Identity object was found on cache.
092    
093         2. Problem is that generated oid (by Identity column)
094         must not begin with 0.
095    
096         Use keyword 'volatile' to make decrement of a long value an
097         atomic operation
098         */
099        private static volatile long tempKey = -1;
100    
101        public SequenceManagerNativeImpl(PersistenceBroker broker)
102        {
103            super(broker);
104        }
105    
106        public void afterStore(JdbcAccess dbAccess, ClassDescriptor cld, Object obj) throws SequenceManagerException
107        {
108            FieldDescriptor identityField = extractIdentityColumnField(cld);
109            if(identityField != null)
110            {
111                ifNotReadOnlyFail(identityField);
112                long newId = getLastInsert(cld, identityField);
113                setFieldValue(obj, identityField, new Long(newId));
114            }
115        }
116    
117        /**
118         * Gets the identity column descriptor for the given class
119         * or return <code>null</code> if none defined.
120         *
121         * @param cld The class descriptor
122         * @return The class's identity column or <code>null</code> if it does not have one
123         */
124        private FieldDescriptor extractIdentityColumnField(ClassDescriptor cld)
125        {
126            FieldDescriptor[] pkFields = cld.getPkFields();
127            for(int i = 0; i < pkFields.length; i++)
128            {
129                // to find the identity column we search for a autoincrement
130                // read-only field
131                if(pkFields[i].isAutoIncrement() && pkFields[i].isAccessReadOnly())
132                {
133                    return pkFields[i];
134                }
135            }
136            return null;
137        }
138    
139        private void ifNotReadOnlyFail(FieldDescriptor field) throws SequenceManagerException
140        {
141            // is field declared as read-only?
142            if(!field.isAccessReadOnly())
143            {
144                throw new SequenceManagerException("Can't find Identity column: Identity columns/fields need to be declared as" +
145                        " 'autoincrement' with 'readonly' access in field-descriptor");
146            }
147        }
148    
149        private long getLastInsert(ClassDescriptor cld, FieldDescriptor field) throws SequenceManagerException
150        {
151            long newId = 0;
152            Statement stmt = null;
153            if(field != null)
154            { // an autoinc column exists
155                try
156                {
157                    stmt = getBrokerForClass().serviceConnectionManager().getConnection().createStatement();
158                    ResultSet rs = stmt.executeQuery(lastInsertSelect(cld.getFullTableName()));
159                    if(!rs.next())
160                    {
161                        throw new SequenceManagerException("Could not find native identifier");
162                    }
163                    newId = rs.getLong(1);
164                    rs.close();
165                    if(log.isDebugEnabled()) log.debug("After store - newid=" + newId);
166                }
167                catch(Exception e)
168                {
169                    throw new SequenceManagerException(e);
170                }
171                finally
172                {
173                    try
174                    {
175                        if(stmt != null) stmt.close();
176                    }
177                    catch(SQLException e)
178                    {
179                        if(log.isDebugEnabled())
180                            log.debug("Threw SQLException while in getLastInsert and closing stmt", e);
181                        // ignore it
182                    }
183                }
184            }
185            else
186            {
187                throw new SequenceManagerException("No autoincrement field declared, please check repository for " + cld);
188            }
189            return newId;
190        }
191    
192        /*
193         * query for the last insert id.
194         */
195        protected String lastInsertSelect(String tableName)
196        {
197            return getBrokerForClass().serviceConnectionManager().
198                    getSupportedPlatform().getLastInsertIdentityQuery(tableName);
199        }
200    
201        private void setFieldValue(Object obj, FieldDescriptor field, Long identifier) throws SequenceManagerException
202        {
203            Object result = field.getJdbcType().sequenceKeyConversion(identifier);
204            result = field.getFieldConversion().sqlToJava(result);
205            PersistentField pf = field.getPersistentField();
206            pf.set(obj, result);
207        }
208    
209        /**
210         * returns a negative value
211         */
212        protected long getUniqueLong(FieldDescriptor field) throws SequenceManagerException
213        {
214            /*
215            arminw:
216            workaround for locking problems of new objects
217            We need unique 'dummy keys' for new objects before storing.
218            Variable 'tempKey' is declared volatile, thus decrement should be atomic
219            */
220            return --tempKey;
221        }
222    }