eu.interedition.text.rdbms.RelationalNameRepository.java Source code

Java tutorial

Introduction

Here is the source code for eu.interedition.text.rdbms.RelationalNameRepository.java

Source

/*
 * #%L
 * Text: A text model with range-based markup via standoff annotations.
 * %%
 * Copyright (C) 2010 - 2011 The Interedition Development Group
 * %%
 * 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.
 * #L%
 */
package eu.interedition.text.rdbms;

import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Sets;
import eu.interedition.text.Name;
import eu.interedition.text.NameRepository;
import eu.interedition.text.util.SQL;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

import javax.sql.DataSource;
import java.net.URI;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static java.util.Collections.singleton;

public class RelationalNameRepository implements NameRepository, InitializingBean {

    private DataSource dataSource;
    private PlatformTransactionManager transactionManager;
    private RelationalDatabaseKeyFactory keyFactory;
    private int cacheSize = 1000;

    private JdbcTemplate jt;
    private SimpleJdbcInsert nameInsert;
    private DataFieldMaxValueIncrementer nameIdIncrementer;

    private Map<Name, Long> nameCache;
    private TransactionTemplate tt;

    public Name get(Name name) {
        return Iterables.getOnlyElement(get(singleton(name)));
    }

    public Set<Name> load(Set<Integer> ids) {
        if (ids == null || ids.isEmpty()) {
            return Sets.newHashSet();
        }

        final List<Integer> ps = Lists.newArrayList(ids);
        final StringBuilder sql = new StringBuilder("select ").append(selectNameFrom("n"))
                .append(" from text_qname n where n.id in (");
        for (int i = 0; i < ps.size(); i++) {
            sql.append(i == 0 ? "" : ", ").append("?");
        }
        sql.append(")");

        return new HashSet<Name>(jt.query(sql.toString(), ROW_MAPPER, ps.toArray(new Object[ps.size()])));
    }

    public synchronized Set<Name> get(Set<Name> names) {
        if (nameCache == null) {
            initCache();
        }

        final Set<Name> requested = Sets.newHashSet(names);
        final Set<Name> found = Sets.newHashSetWithExpectedSize(requested.size());

        for (Iterator<Name> it = requested.iterator(); it.hasNext();) {
            final Name name = it.next();
            final Long id = nameCache.get(name);
            if (id != null) {
                found.add(new RelationalName(name, id));
                it.remove();
            }
        }

        if (requested.isEmpty()) {
            return found;
        }

        return tt.execute(new TransactionCallback<Set<Name>>() {
            @Override
            public Set<Name> doInTransaction(TransactionStatus status) {
                final List<Object> ps = Lists.newArrayList();
                final StringBuilder sql = new StringBuilder("select ").append(selectNameFrom("n"))
                        .append(" from text_qname n where ");
                for (Iterator<Name> it = requested.iterator(); it.hasNext();) {
                    sql.append("(");
                    final Name name = it.next();

                    sql.append("n.local_name = ? and ");
                    ps.add(name.getLocalName());

                    final URI ns = name.getNamespace();
                    if (ns == null) {
                        sql.append("n.namespace is null");
                    } else {
                        sql.append("n.namespace = ?");
                        ps.add(ns.toString());
                    }

                    sql.append(")").append(it.hasNext() ? " or " : "");
                }

                for (RelationalName name : jt.query(sql.toString(), ROW_MAPPER,
                        ps.toArray(new Object[ps.size()]))) {
                    found.add(name);
                    requested.remove(name);
                    nameCache.put(name, name.getId());
                }

                final List<RelationalName> created = Lists.newArrayListWithExpectedSize(requested.size());
                final List<MapSqlParameterSource> nameBatch = Lists.newArrayListWithExpectedSize(requested.size());
                for (Name name : requested) {
                    final long id = nameIdIncrementer.nextLongValue();
                    final String localName = name.getLocalName();
                    final URI ns = name.getNamespace();

                    nameBatch.add(new MapSqlParameterSource().addValue("id", id).addValue("local_name", localName)
                            .addValue("namespace", ns == null ? null : ns.toString()));

                    created.add(new RelationalName(name, id));
                }
                nameInsert.executeBatch(nameBatch.toArray(new MapSqlParameterSource[nameBatch.size()]));

                for (RelationalName n : created) {
                    found.add(n);
                    nameCache.put(n, n.getId());
                }

                return found;
            }
        });
    }

    public synchronized void clearCache() {
        nameCache = null;
    }

    @Required
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Required
    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    @Required
    public void setKeyFactory(RelationalDatabaseKeyFactory keyFactory) {
        this.keyFactory = keyFactory;
    }

    public void setCacheSize(int cacheSize) {
        this.cacheSize = cacheSize;
    }

    public void afterPropertiesSet() throws Exception {
        this.jt = (dataSource == null ? null : new JdbcTemplate(dataSource));
        this.nameInsert = new SimpleJdbcInsert(dataSource).withTableName("text_qname");
        this.nameIdIncrementer = this.keyFactory.create("text_qname");

        this.tt = new TransactionTemplate(transactionManager);
        this.tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
    }

    private void initCache() {
        nameCache = new MapMaker().maximumSize(cacheSize).makeMap();
        if (jt.queryForInt("select count(*) from text_qname") <= cacheSize) {
            // warm-up cache
            for (RelationalName name : jt.query("select " + selectNameFrom("n") + " from text_qname n",
                    ROW_MAPPER)) {
                nameCache.put(name, name.getId());
            }
        }
    }

    public static String selectNameFrom(String tableName) {
        return SQL.select(tableName, "id", "local_name", "namespace");
    }

    public static RelationalName mapNameFrom(ResultSet rs, String prefix) throws SQLException {
        final String namespaceStr = rs.getString(prefix + "_namespace");
        return new RelationalName(namespaceStr == null ? null : URI.create(namespaceStr), //
                rs.getString(prefix + "_local_name"), //
                rs.getLong(prefix + "_id"));
    }

    private static final RowMapper<RelationalName> ROW_MAPPER = new RowMapper<RelationalName>() {

        public RelationalName mapRow(ResultSet rs, int rowNum) throws SQLException {
            return mapNameFrom(rs, "n");
        }
    };
}