001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */
019: package org.apache.openjpa.jdbc.kernel;
020:
021: import java.sql.Connection;
022: import java.sql.PreparedStatement;
023: import java.sql.ResultSet;
024: import java.sql.SQLException;
025:
026: import org.apache.openjpa.jdbc.meta.ClassMapping;
027: import org.apache.openjpa.jdbc.sql.DBDictionary;
028: import org.apache.openjpa.jdbc.sql.SQLBuffer;
029: import org.apache.openjpa.jdbc.sql.SQLExceptions;
030: import org.apache.openjpa.jdbc.sql.Select;
031: import org.apache.openjpa.kernel.OpenJPAStateManager;
032: import org.apache.openjpa.kernel.StoreContext;
033: import org.apache.openjpa.kernel.VersionLockManager;
034: import org.apache.openjpa.lib.util.Localizer;
035: import org.apache.openjpa.util.LockException;
036:
037: /**
038: * Lock manager that uses exclusive database locks.
039: *
040: * @author Marc Prud'hommeaux
041: */
042: public class PessimisticLockManager extends VersionLockManager
043: implements JDBCLockManager {
044:
045: public static final int LOCK_DATASTORE_ONLY = 1;
046:
047: private static final Localizer _loc = Localizer
048: .forPackage(PessimisticLockManager.class);
049:
050: private JDBCStore _store;
051:
052: public PessimisticLockManager() {
053: setVersionCheckOnReadLock(false);
054: setVersionUpdateOnWriteLock(false);
055: }
056:
057: public void setContext(StoreContext ctx) {
058: super .setContext(ctx);
059: _store = (JDBCStore) ctx.getStoreManager()
060: .getInnermostDelegate();
061: }
062:
063: public boolean selectForUpdate(Select sel, int lockLevel) {
064: if (lockLevel == LOCK_NONE)
065: return false;
066:
067: DBDictionary dict = _store.getDBDictionary();
068: if (dict.simulateLocking)
069: return false;
070: dict.assertSupport(dict.supportsSelectForUpdate,
071: "SupportsSelectForUpdate");
072:
073: if (!sel.supportsLocking()) {
074: if (log.isInfoEnabled())
075: log.info(_loc.get("cant-lock-on-load", sel.toSelect(
076: false, null).getSQL()));
077: return false;
078: }
079:
080: ensureStoreManagerTransaction();
081: return true;
082: }
083:
084: public void loadedForUpdate(OpenJPAStateManager sm) {
085: // we set a low lock level to indicate that we don't need datastore
086: // locking, but we don't necessarily have a read or write lock
087: // according to our superclass
088: if (getLockLevel(sm) == LOCK_NONE)
089: setLockLevel(sm, LOCK_DATASTORE_ONLY);
090: }
091:
092: protected void lockInternal(OpenJPAStateManager sm, int level,
093: int timeout, Object sdata) {
094: // we can skip any already-locked instance regardless of level because
095: // we treat all locks the same (though super doesn't)
096: if (getLockLevel(sm) == LOCK_NONE) {
097: // only need to lock if not loaded from locking result
098: ConnectionInfo info = (ConnectionInfo) sdata;
099: if (info == null || info.result == null
100: || !info.result.isLocking())
101: lockRow(sm, timeout);
102: }
103: super .lockInternal(sm, level, timeout, sdata);
104: }
105:
106: /**
107: * Lock the specified instance row by issuing a "SELECT ... FOR UPDATE"
108: * statement.
109: */
110: private void lockRow(OpenJPAStateManager sm, int timeout) {
111: // assert that the dictionary supports the "SELECT ... FOR UPDATE"
112: // construct; if not, and we the assertion does not throw an
113: // exception, then just return without locking
114: DBDictionary dict = _store.getDBDictionary();
115: if (dict.simulateLocking)
116: return;
117: dict.assertSupport(dict.supportsSelectForUpdate,
118: "SupportsSelectForUpdate");
119:
120: Object id = sm.getObjectId();
121: ClassMapping mapping = (ClassMapping) sm.getMetaData();
122: while (mapping.getJoinablePCSuperclassMapping() != null)
123: mapping = mapping.getJoinablePCSuperclassMapping();
124:
125: // select only the PK columns, since we just want to lock
126: Select select = _store.getSQLFactory().newSelect();
127: select.select(mapping.getPrimaryKeyColumns());
128: select.wherePrimaryKey(id, mapping, _store);
129: SQLBuffer sql = select.toSelect(true, _store
130: .getFetchConfiguration());
131:
132: ensureStoreManagerTransaction();
133: Connection conn = _store.getConnection();
134: PreparedStatement stmnt = null;
135: ResultSet rs = null;
136: try {
137: stmnt = prepareStatement(conn, sql);
138: setTimeout(stmnt, timeout);
139: rs = executeQuery(conn, stmnt, sql);
140: checkLock(rs, sm);
141: } catch (SQLException se) {
142: throw SQLExceptions.getStore(se, dict);
143: } finally {
144: if (stmnt != null)
145: try {
146: stmnt.close();
147: } catch (SQLException se) {
148: }
149: if (rs != null)
150: try {
151: rs.close();
152: } catch (SQLException se) {
153: }
154: try {
155: conn.close();
156: } catch (SQLException se) {
157: }
158: }
159: }
160:
161: /**
162: * Enforce that we have an actual transaction in progress so that we can
163: * start locking. The transaction should already be begun when using a
164: * datastore transaction; this will just be used if we are locking in
165: * optimistic mode.
166: */
167: private void ensureStoreManagerTransaction() {
168: if (!_store.getContext().isStoreActive()) {
169: _store.getContext().beginStore();
170: if (log.isInfoEnabled())
171: log.info(_loc.get("start-trans-for-lock"));
172: }
173: }
174:
175: public JDBCStore getStore() {
176: return _store;
177: }
178:
179: /**
180: * This method is to provide override for non-JDBC or JDBC-like
181: * implementation of preparing statement.
182: */
183: protected PreparedStatement prepareStatement(Connection conn,
184: SQLBuffer sql) throws SQLException {
185: return sql.prepareStatement(conn);
186: }
187:
188: /**
189: * This method is to provide override for non-JDBC or JDBC-like
190: * implementation of setting query timeout.
191: */
192: protected void setTimeout(PreparedStatement stmnt, int timeout)
193: throws SQLException {
194: DBDictionary dict = _store.getDBDictionary();
195: if (timeout >= 0 && dict.supportsQueryTimeout) {
196: if (timeout < 1000) {
197: timeout = 1000;
198: if (log.isWarnEnabled())
199: log.warn(_loc.get("millis-query-timeout"));
200: }
201: stmnt.setQueryTimeout(timeout / 1000);
202: }
203: }
204:
205: /**
206: * This method is to provide override for non-JDBC or JDBC-like
207: * implementation of executing query.
208: */
209: protected ResultSet executeQuery(Connection conn,
210: PreparedStatement stmnt, SQLBuffer sql) throws SQLException {
211: return stmnt.executeQuery();
212: }
213:
214: /**
215: * This method is to provide override for non-JDBC or JDBC-like
216: * implementation of checking lock from the result set.
217: */
218: protected void checkLock(ResultSet rs, OpenJPAStateManager sm)
219: throws SQLException {
220: if (!rs.next())
221: throw new LockException(sm.getManagedInstance());
222: return;
223: }
224: }
|