com.haulmont.cuba.core.app.UniqueNumbers.java Source code

Java tutorial

Introduction

Here is the source code for com.haulmont.cuba.core.app.UniqueNumbers.java

Source

/*
 * Copyright (c) 2008-2016 Haulmont.
 *
 * Licensed 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 com.haulmont.cuba.core.app;

import com.haulmont.bali.db.DbUtils;
import com.haulmont.cuba.core.EntityManager;
import com.haulmont.cuba.core.Persistence;
import com.haulmont.cuba.core.Query;
import com.haulmont.cuba.core.Transaction;
import com.haulmont.cuba.core.global.Stores;
import com.haulmont.cuba.core.sys.persistence.DbmsSpecificFactory;
import com.haulmont.cuba.core.sys.persistence.SequenceSupport;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.text.StrTokenizer;

import org.springframework.stereotype.Component;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Pattern;

/**
 * Provides unique numbers based on database sequences.
 */
@Component(UniqueNumbersAPI.NAME)
public class UniqueNumbers implements UniqueNumbersAPI {

    @Inject
    protected Persistence persistence;

    protected ReadWriteLock lock = new ReentrantReadWriteLock();

    @GuardedBy("lock")
    protected Set<String> existingSequences = new HashSet<>();

    public static final Pattern SEQ_PATTERN = Pattern.compile("[a-zA-Z0-9_]+");

    @Override
    public long getNextNumber(String domain) {
        String sqlScript = getSequenceSupport(domain).getNextValueSql(getSequenceName(domain));
        try {
            lock.readLock().lock();
            return getResult(domain, sqlScript);
        } finally {
            lock.readLock().unlock();
        }
    }

    @Override
    public long getCurrentNumber(String domain) {
        String sqlScript = getSequenceSupport(domain).getCurrentValueSql(getSequenceName(domain));
        try {
            lock.readLock().lock();
            return getResult(domain, sqlScript);
        } finally {
            lock.readLock().unlock();
        }
    }

    @Override
    public void setCurrentNumber(String domain, long value) {
        String sqlScript = getSequenceSupport(domain).modifySequenceSql(getSequenceName(domain), value);

        Transaction tx = persistence.getTransaction(getDataStore(domain));
        try {
            lock.readLock().lock();
            checkSequenceExists(domain);
            executeScript(domain, sqlScript);
            tx.commit();
        } finally {
            lock.readLock().unlock();
            tx.end();
        }
    }

    @Override
    public void deleteSequence(String domain) {
        String seqName = getSequenceName(domain);

        if (!containsSequence(seqName)) {
            throw new IllegalStateException("Attempt to delete nonexistent sequence " + domain);
        }

        String sqlScript = getSequenceSupport(domain).deleteSequenceSql(seqName);

        Transaction tx = persistence.getTransaction(getDataStore(domain));
        try {
            lock.writeLock().lock();
            if (!containsSequence(seqName)) {
                tx.commit();
                return;
            }

            executeScript(domain, sqlScript);
            tx.commit();
            existingSequences.remove(seqName);
        } finally {
            lock.writeLock().unlock();
            tx.end();
        }
    }

    /**
     * Override this method if you want to control in what datastore a sequence is created for a particular domain
     *
     * @param domain    sequence identifier passed to the interface methods
     * @return          datastore id (by default, the main datastore)
     */
    protected String getDataStore(String domain) {
        return Stores.MAIN;
    }

    protected SequenceSupport getSequenceSupport(String domain) {
        return DbmsSpecificFactory.getSequenceSupport(getDataStore(domain));
    }

    protected long getResult(String domain, String sqlScript) {
        Transaction tx = persistence.getTransaction(getDataStore(domain));
        try {
            checkSequenceExists(domain);

            Object value = executeScript(domain, sqlScript);
            tx.commit();
            if (value instanceof Long)
                return (Long) value;
            else if (value instanceof BigDecimal)
                return ((BigDecimal) value).longValue();
            else if (value instanceof BigInteger)
                return ((BigInteger) value).longValue();
            else if (value instanceof String)
                return Long.parseLong((String) value);
            else if (value == null)
                throw new IllegalStateException("No value returned");
            else
                throw new IllegalStateException("Unsupported value type: " + value.getClass());
        } finally {
            tx.end();
        }
    }

    protected Object executeScript(String domain, String sqlScript) {
        EntityManager em = persistence.getEntityManager(getDataStore(domain));
        StrTokenizer tokenizer = new StrTokenizer(sqlScript, SequenceSupport.SQL_DELIMITER);
        Object value = null;
        Connection connection = em.getConnection();
        while (tokenizer.hasNext()) {
            String sql = tokenizer.nextToken();
            try {
                PreparedStatement statement = connection.prepareStatement(sql);
                try {
                    if (statement.execute()) {
                        ResultSet rs = statement.getResultSet();
                        if (rs.next())
                            value = rs.getLong(1);
                    }
                } finally {
                    DbUtils.closeQuietly(statement);
                }
            } catch (SQLException e) {
                throw new IllegalStateException("Error executing SQL for getting next number", e);
            }
        }
        return value;
    }

    protected void checkSequenceExists(String domain) {
        String seqName = getSequenceName(domain);
        if (containsSequence(seqName))
            return;

        // Create sequence in separate transaction because it's name is cached and we want to be sure it is created
        // regardless of possible errors in the invoking code
        Transaction tx = persistence.createTransaction(getDataStore(domain));
        try {
            lock.readLock().unlock();
            lock.writeLock().lock();

            EntityManager em = persistence.getEntityManager(getDataStore(domain));

            Query query = em.createNativeQuery(getSequenceSupport(domain).sequenceExistsSql(seqName));
            List list = query.getResultList();
            if (list.isEmpty()) {
                query = em.createNativeQuery(getSequenceSupport(domain).createSequenceSql(seqName, 1, 1));
                query.executeUpdate();
            }
            tx.commit();
            existingSequences.add(seqName);
        } finally {
            lock.readLock().lock();
            lock.writeLock().unlock();
            tx.end();
        }
    }

    protected boolean containsSequence(String name) {
        try {
            lock.readLock().lock();
            return existingSequences.contains(name);
        } finally {
            lock.readLock().unlock();
        }
    }

    protected String getSequenceName(String domain) {
        if (StringUtils.isBlank(domain))
            throw new IllegalArgumentException("Sequence name can not be blank");
        if (!SEQ_PATTERN.matcher(domain).matches())
            throw new IllegalArgumentException("Invalid sequence name: '" + domain
                    + "'. It can contain only alphanumeric characters and underscores");

        return "seq_un_" + domain;
    }
}