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 > 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 }