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