| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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. |
| */ |
| package org.apache.openjpa.jdbc.kernel; |
| |
| import java.sql.Connection; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| |
| import org.apache.openjpa.jdbc.meta.ClassMapping; |
| import org.apache.openjpa.jdbc.sql.DBDictionary; |
| import org.apache.openjpa.jdbc.sql.SQLBuffer; |
| import org.apache.openjpa.jdbc.sql.SQLExceptions; |
| import org.apache.openjpa.jdbc.sql.Select; |
| import org.apache.openjpa.kernel.OpenJPAStateManager; |
| import org.apache.openjpa.kernel.StoreContext; |
| import org.apache.openjpa.kernel.VersionLockManager; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.util.LockException; |
| |
| /** |
| * Lock manager that uses exclusive database locks. |
| * |
| * @author Marc Prud'hommeaux |
| */ |
| public class PessimisticLockManager |
| extends VersionLockManager |
| implements JDBCLockManager { |
| |
| public static final int LOCK_DATASTORE_ONLY = 1; |
| |
| private static final Localizer _loc = Localizer.forPackage |
| (PessimisticLockManager.class); |
| |
| private JDBCStore _store; |
| |
| public PessimisticLockManager() { |
| setVersionCheckOnReadLock(false); |
| setVersionUpdateOnWriteLock(false); |
| } |
| |
| public void setContext(StoreContext ctx) { |
| super.setContext(ctx); |
| _store = (JDBCStore) ctx.getStoreManager().getInnermostDelegate(); |
| } |
| |
| public boolean selectForUpdate(Select sel, int lockLevel) { |
| if (lockLevel == LOCK_NONE) |
| return false; |
| |
| DBDictionary dict = _store.getDBDictionary(); |
| if (dict.simulateLocking) |
| return false; |
| dict.assertSupport(dict.supportsSelectForUpdate, |
| "SupportsSelectForUpdate"); |
| |
| if (!sel.supportsLocking()) { |
| if (log.isInfoEnabled()) |
| log.info(_loc.get("cant-lock-on-load", |
| sel.toSelect(false, null).getSQL())); |
| return false; |
| } |
| |
| ensureStoreManagerTransaction(); |
| return true; |
| } |
| |
| public void loadedForUpdate(OpenJPAStateManager sm) { |
| // we set a low lock level to indicate that we don't need datastore |
| // locking, but we don't necessarily have a read or write lock |
| // according to our superclass |
| if (getLockLevel(sm) == LOCK_NONE) |
| setLockLevel(sm, LOCK_DATASTORE_ONLY); |
| } |
| |
| protected void lockInternal(OpenJPAStateManager sm, int level, int timeout, |
| Object sdata) { |
| // we can skip any already-locked instance regardless of level because |
| // we treat all locks the same (though super doesn't) |
| if (getLockLevel(sm) == LOCK_NONE) { |
| // only need to lock if not loaded from locking result |
| ConnectionInfo info = (ConnectionInfo) sdata; |
| if (info == null || info.result == null || !info.result.isLocking()) |
| lockRow(sm, timeout); |
| } |
| super.lockInternal(sm, level, timeout, sdata); |
| } |
| |
| /** |
| * Lock the specified instance row by issuing a "SELECT ... FOR UPDATE" |
| * statement. |
| */ |
| private void lockRow(OpenJPAStateManager sm, int timeout) { |
| // assert that the dictionary supports the "SELECT ... FOR UPDATE" |
| // construct; if not, and we the assertion does not throw an |
| // exception, then just return without locking |
| DBDictionary dict = _store.getDBDictionary(); |
| if (dict.simulateLocking) |
| return; |
| dict.assertSupport(dict.supportsSelectForUpdate, |
| "SupportsSelectForUpdate"); |
| |
| Object id = sm.getObjectId(); |
| ClassMapping mapping = (ClassMapping) sm.getMetaData(); |
| while (mapping.getJoinablePCSuperclassMapping() != null) |
| mapping = mapping.getJoinablePCSuperclassMapping(); |
| |
| // select only the PK columns, since we just want to lock |
| Select select = _store.getSQLFactory().newSelect(); |
| select.select(mapping.getPrimaryKeyColumns()); |
| select.wherePrimaryKey(id, mapping, _store); |
| SQLBuffer sql = select.toSelect(true, _store.getFetchConfiguration()); |
| |
| ensureStoreManagerTransaction(); |
| Connection conn = _store.getConnection(); |
| PreparedStatement stmnt = null; |
| ResultSet rs = null; |
| try { |
| stmnt = prepareStatement(conn, sql); |
| setTimeout(stmnt, timeout); |
| rs = executeQuery(conn, stmnt, sql); |
| checkLock(rs, sm); |
| } catch (SQLException se) { |
| throw SQLExceptions.getStore(se, dict); |
| } finally { |
| if (stmnt != null) |
| try { stmnt.close(); } catch (SQLException se) {} |
| if (rs != null) |
| try { rs.close(); } catch (SQLException se) {} |
| try { conn.close(); } catch (SQLException se) {} |
| } |
| } |
| |
| /** |
| * Enforce that we have an actual transaction in progress so that we can |
| * start locking. The transaction should already be begun when using a |
| * datastore transaction; this will just be used if we are locking in |
| * optimistic mode. |
| */ |
| private void ensureStoreManagerTransaction() { |
| if (!_store.getContext().isStoreActive()) { |
| _store.getContext().beginStore(); |
| if (log.isInfoEnabled()) |
| log.info(_loc.get("start-trans-for-lock")); |
| } |
| } |
| |
| public JDBCStore getStore() { |
| return _store; |
| } |
| |
| /** |
| * This method is to provide override for non-JDBC or JDBC-like |
| * implementation of preparing statement. |
| */ |
| protected PreparedStatement prepareStatement(Connection conn, SQLBuffer sql) |
| throws SQLException { |
| return sql.prepareStatement(conn); |
| } |
| |
| /** |
| * This method is to provide override for non-JDBC or JDBC-like |
| * implementation of setting query timeout. |
| */ |
| protected void setTimeout(PreparedStatement stmnt, int timeout) |
| throws SQLException { |
| DBDictionary dict = _store.getDBDictionary(); |
| if (timeout >= 0 && dict.supportsQueryTimeout) { |
| if (timeout < 1000) { |
| timeout = 1000; |
| if (log.isWarnEnabled()) |
| log.warn(_loc.get("millis-query-timeout")); |
| } |
| stmnt.setQueryTimeout(timeout / 1000); |
| } |
| } |
| |
| /** |
| * This method is to provide override for non-JDBC or JDBC-like |
| * implementation of executing query. |
| */ |
| protected ResultSet executeQuery(Connection conn, PreparedStatement stmnt, |
| SQLBuffer sql) throws SQLException { |
| return stmnt.executeQuery(); |
| } |
| |
| /** |
| * This method is to provide override for non-JDBC or JDBC-like |
| * implementation of checking lock from the result set. |
| */ |
| protected void checkLock(ResultSet rs, OpenJPAStateManager sm) |
| throws SQLException { |
| if (!rs.next()) |
| throw new LockException(sm.getManagedInstance()); |
| return; |
| } |
| } |