/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * 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 the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>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. */ /* * Hibernate, Relational Persistence for Idiomatic Java * * Copyright (c) 2010, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.apereo.portal.utils; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.annotation.Resource; import org.apereo.portal.ICounterStore; import org.apereo.portal.jpa.BasePortalJpaDao; import org.hibernate.id.IdentifierGeneratorHelper; import org.hibernate.id.IntegralDataTypeHolder; import org.hibernate.id.enhanced.AccessCallback; import org.hibernate.id.enhanced.Optimizer; import org.hibernate.id.enhanced.OptimizerFactory; import org.hibernate.id.enhanced.TableGenerator; import org.hibernate.type.IntegerType; import org.hibernate.type.Type; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.PreparedStatementSetter; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.stereotype.Repository; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionOperations; /** * Mostly cloned from {@link TableGenerator} * */ @Repository("counterStore") public class HibernateStyleCounterStore implements ICounterStore { private static final String TRANSACTION_OPERATIONS_BEAN_ID = "counterStoreTransactionOperations"; private static final String SELECT_QUERY = "SELECT SEQUENCE_VALUE FROM UP_SEQUENCE WHERE SEQUENCE_NAME=?"; private static final String UPDATE_QUERY = "UPDATE UP_SEQUENCE SET SEQUENCE_VALUE=? WHERE SEQUENCE_NAME=? AND SEQUENCE_VALUE=?"; private static final String INSERT_QUERY = "INSERT INTO UP_SEQUENCE (SEQUENCE_NAME, SEQUENCE_VALUE) VALUES (?, ?)"; private static final int MAX_ATTEMPTS = 3; private final Type identifierType = IntegerType.INSTANCE; private final ConcurrentMap<String, Callable<Optimizer>> counterOptimizers = new ConcurrentHashMap<String, Callable<Optimizer>>(); private TransactionOperations transactionOperations; private JdbcOperations jdbcOperations; private int incrementSize = 50; private int initialValue = 10; @Resource(name = TRANSACTION_OPERATIONS_BEAN_ID) public void setTransactionOperations(TransactionOperations transactionOperations) { this.transactionOperations = transactionOperations; } @Autowired public void setJdbcOperations( @Qualifier(BasePortalJpaDao.PERSISTENCE_UNIT_NAME) JdbcOperations jdbcOperations) { this.jdbcOperations = jdbcOperations; } @Value("${org.apereo.portal.utils.HibernateStyleCounterStore.incrementSize:50}") public void setIncrementSize(int incrementSize) { this.incrementSize = incrementSize; } @Value("${org.apereo.portal.utils.HibernateStyleCounterStore.initialValue:10}") public void setInitialValue(int initialValue) { this.initialValue = initialValue; } private Optimizer getCounterOptimizer(String counterName) { Callable<Optimizer> optimizer = counterOptimizers.get(counterName); if (optimizer == null) { optimizer = new Callable<Optimizer>() { private volatile Optimizer optimizer; @Override public Optimizer call() throws Exception { Optimizer o = optimizer; if (o != null) { return o; } synchronized (this) { o = optimizer; if (o != null) { return o; } o = OptimizerFactory.buildOptimizer( OptimizerFactory.StandardOptimizerDescriptor.POOLED .getExternalName(), identifierType.getReturnedClass(), incrementSize, initialValue); this.optimizer = o; } return o; } }; optimizer = ConcurrentMapUtils.putIfAbsent(counterOptimizers, counterName, optimizer); } try { return optimizer.call(); } catch (Exception e) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } throw new RuntimeException(e); } } @Override public int getNextId(String counterName) { final int id; final Optimizer counterOptimizer = this.getCounterOptimizer(counterName); synchronized (counterOptimizer) { id = getNextIdInternal(counterOptimizer, counterName); } return id; } private int getNextIdInternal(final Optimizer optimizer, final String counterName) { return (Integer) optimizer.generate( new AccessCallback() { @Override public IntegralDataTypeHolder getNextValue() { IntegralDataTypeHolder rslt = null; for (int i = 0; rslt == null && i < MAX_ATTEMPTS; i++) { rslt = transactionOperations.execute( new TransactionCallback< IntegralDataTypeHolder>() { @Override public IntegralDataTypeHolder doInTransaction( TransactionStatus status) { final IntegralDataTypeHolder value = IdentifierGeneratorHelper .getIntegralDataTypeHolder( identifierType .getReturnedClass()); //Try and load the current value, returns true if the expected row exists, null otherwise final boolean selected = jdbcOperations.query( SELECT_QUERY, new ResultSetExtractor< Boolean>() { @Override public Boolean extractData( ResultSet rs) throws SQLException, DataAccessException { if (rs.next()) { value .initialize( rs, 1); return true; } return false; } }, counterName); //No row exists for the counter, insert it if (!selected) { value.initialize(initialValue); jdbcOperations.update( INSERT_QUERY, new PreparedStatementSetter() { @Override public void setValues( PreparedStatement ps) throws SQLException { ps.setString( 1, counterName); value.bind(ps, 2); } }); } //Increment the counter row value final IntegralDataTypeHolder updateValue = value.copy(); if (optimizer .applyIncrementSizeToSourceValues()) { updateValue.add(incrementSize); } else { updateValue.increment(); } //Update the counter row, if rows returns 0 the update failed due to a race condition, it will be retried int rowsAltered = jdbcOperations.update( UPDATE_QUERY, new PreparedStatementSetter() { @Override public void setValues( PreparedStatement ps) throws SQLException { updateValue .bind( ps, 1); ps.setString( 2, counterName); value.bind( ps, 3); } }); return rowsAltered > 0 ? value // Success : null; // Failed; try again... } }); } // End for loop if (rslt == null) { throw new RuntimeException( "Failed to fetch a new batch of sequence values after " + MAX_ATTEMPTS + " tries"); } return rslt; } }); } }