/** * Licensed to JumpMind Inc under one or more contributor * license agreements. See the NOTICE file distributed * with this work for additional information regarding * copyright ownership. JumpMind Inc licenses this file * to you under the GNU General Public License, version 3.0 (GPLv3) * (the "License"); you may not use this file except in compliance * with the License. * * You should have received a copy of the GNU General Public License, * version 3.0 (GPLv3) along with this library; if not, see * <http://www.gnu.org/licenses/>. * * 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.jumpmind.symmetric.service.impl; import java.sql.Types; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jumpmind.db.sql.ISqlRowMapper; import org.jumpmind.db.sql.ISqlTransaction; import org.jumpmind.db.sql.Row; import org.jumpmind.db.sql.UniqueKeyException; import org.jumpmind.symmetric.common.Constants; import org.jumpmind.symmetric.common.ParameterConstants; import org.jumpmind.symmetric.common.TableConstants; import org.jumpmind.symmetric.db.ISymmetricDialect; import org.jumpmind.symmetric.model.Sequence; import org.jumpmind.symmetric.service.IParameterService; import org.jumpmind.symmetric.service.ISequenceService; public class SequenceService extends AbstractService implements ISequenceService { private Map<String, Sequence> sequenceDefinitionCache = new HashMap<String, Sequence>(); public SequenceService(IParameterService parameterService, ISymmetricDialect symmetricDialect) { super(parameterService, symmetricDialect); setSqlMap(new SequenceServiceSqlMap(symmetricDialect.getPlatform(), createSqlReplacementTokens())); } public void init() { Map<String, Sequence> sequences = getAll(); if (sequences.get(Constants.SEQUENCE_OUTGOING_BATCH_LOAD_ID) == null) { initSequence(Constants.SEQUENCE_OUTGOING_BATCH_LOAD_ID, 1); } if (sequences.get(Constants.SEQUENCE_OUTGOING_BATCH) == null) { long maxBatchId = sqlTemplate.queryForLong(getSql("maxOutgoingBatchSql")); initSequence(Constants.SEQUENCE_OUTGOING_BATCH, maxBatchId); } if (sequences.get(Constants.SEQUENCE_TRIGGER_HIST) == null) { long maxTriggerHistId = sqlTemplate.queryForLong(getSql("maxTriggerHistSql")); initSequence(Constants.SEQUENCE_TRIGGER_HIST, maxTriggerHistId); } if (sequences.get(Constants.SEQUENCE_EXTRACT_REQ) == null) { long maxRequestId = sqlTemplate.queryForLong(getSql("maxExtractRequestSql")); initSequence(Constants.SEQUENCE_EXTRACT_REQ, maxRequestId); } } private void initSequence(String name, long initialValue) { try { if (initialValue < 1) { initialValue = 1; } create(new Sequence(name, initialValue, 1, 1, 9999999999l, "system", false)); } catch (UniqueKeyException ex) { log.debug("Failed to create sequence {}. Must be initialized already.", name); } } public long nextVal(String name) { ISqlTransaction transaction = null; try { transaction = sqlTemplate.startSqlTransaction(); long val = nextVal(transaction, name); transaction.commit(); return val; } catch (Error ex) { if (transaction != null) { transaction.rollback(); } throw ex; } catch (RuntimeException ex) { if (transaction != null) { transaction.rollback(); } throw ex; } finally { close(transaction); } } public long nextVal(ISqlTransaction transaction, String name) { if (transaction == null) { return nextVal(name); } else { long sequenceTimeoutInMs = parameterService.getLong( ParameterConstants.SEQUENCE_TIMEOUT_MS, 5000); long ts = System.currentTimeMillis(); do { long nextVal = tryToGetNextVal(transaction, name); if (nextVal > 0) { return nextVal; } } while (System.currentTimeMillis() - sequenceTimeoutInMs < ts); throw new IllegalStateException(String.format( "Timed out after %d ms trying to get the next val for %s", System.currentTimeMillis() - ts, name)); } } protected long tryToGetNextVal(ISqlTransaction transaction, String name) { long currVal = currVal(transaction, name); Sequence sequence = sequenceDefinitionCache.get(name); if (sequence == null) { sequence = get(transaction, name); if (sequence != null) { sequenceDefinitionCache.put(name, sequence); } else { throw new IllegalStateException(String.format( "The sequence named %s is not configured in %s", name, TableConstants.getTableName(getTablePrefix(), TableConstants.SYM_SEQUENCE))); } } long nextVal = currVal + sequence.getIncrementBy(); if (nextVal > sequence.getMaxValue()) { if (sequence.isCycle()) { nextVal = sequence.getMinValue(); } else { throw new IllegalStateException(String.format( "The sequence named %s has reached it's max value. " + "No more numbers can be handled out.", name)); } } else if (nextVal < sequence.getMinValue()) { if (sequence.isCycle()) { nextVal = sequence.getMaxValue(); } else { throw new IllegalStateException(String.format( "The sequence named %s has reached it's min value. " + "No more numbers can be handled out.", name)); } } int updateCount = transaction.prepareAndExecute(getSql("updateCurrentValueSql"), nextVal, name, currVal); if (updateCount != 1) { nextVal = -1; } return nextVal; } public long currVal(ISqlTransaction transaction, String name) { return transaction.queryForLong(getSql("getCurrentValueSql"), name); } public long currVal(String name) { ISqlTransaction transaction = null; try { transaction = sqlTemplate.startSqlTransaction(); long val = currVal(transaction, name); transaction.commit(); return val; } catch (Error ex) { if (transaction != null) { transaction.rollback(); } throw ex; } catch (RuntimeException ex) { if (transaction != null) { transaction.rollback(); } throw ex; } finally { close(transaction); } } public void create(Sequence sequence) { sqlTemplate.update(getSql("insertSequenceSql"), sequence.getSequenceName(), sequence.getCurrentValue(), sequence.getIncrementBy(), sequence.getMinValue(), sequence.getMaxValue(), sequence.isCycle() ? 1 : 0, sequence.getLastUpdateBy()); } protected Sequence get(ISqlTransaction transaction, String name) { List<Sequence> values = transaction.query(getSql("getSequenceSql"), new SequenceRowMapper(), new Object[] {name}, new int [] {Types.VARCHAR}); if (values.size() > 0) { return values.get(0); } else { return null; } } protected Map<String, Sequence> getAll() { Map<String, Sequence> map = new HashMap<String, Sequence>(); List<Sequence> sequences = sqlTemplate.query(getSql("getAllSequenceSql"), new SequenceRowMapper()); for (Sequence sequence : sequences) { map.put(sequence.getSequenceName(), sequence); } return map; } class SequenceRowMapper implements ISqlRowMapper<Sequence> { public Sequence mapRow(Row rs) { Sequence sequence = new Sequence(); sequence.setCreateTime(rs.getDateTime("create_time")); sequence.setCurrentValue(rs.getLong("current_value")); sequence.setIncrementBy(rs.getInt("increment_by")); sequence.setLastUpdateBy(rs.getString("last_update_by")); sequence.setLastUpdateTime(rs.getDateTime("last_update_time")); sequence.setMaxValue(rs.getLong("max_value")); sequence.setMinValue(rs.getLong("min_value")); sequence.setSequenceName(rs.getString("sequence_name")); sequence.setCycle(rs.getBoolean("cycle")); return sequence; } } }