com.bitsofproof.supernode.model.JpaStore.java Source code

Java tutorial

Introduction

Here is the source code for com.bitsofproof.supernode.model.JpaStore.java

Source

/*
 * Copyright 2013 bits of proof zrt.
 *
 * 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.bitsofproof.supernode.model;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.hibernate.exception.ConstraintViolationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.bitsofproof.supernode.api.BloomFilter;
import com.bitsofproof.supernode.api.Hash;
import com.bitsofproof.supernode.api.ValidationException;
import com.bitsofproof.supernode.core.CachedBlockStore;
import com.bitsofproof.supernode.core.ColorStore;
import com.bitsofproof.supernode.core.Discovery;
import com.bitsofproof.supernode.core.PeerStore;
import com.bitsofproof.supernode.core.TxOutCache;
import com.mysema.query.jpa.impl.JPAQuery;

public class JpaStore extends CachedBlockStore implements Discovery, PeerStore, ColorStore {
    private static final Logger log = LoggerFactory.getLogger(JpaStore.class);

    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Override
    protected void clearStore() {
    }

    @Override
    protected void startBatch() {
    }

    @Override
    protected void endBatch() {
    }

    @Override
    protected void cancelBatch() {
    }

    @Override
    public void scan(BloomFilter filter, TransactionProcessor processor) {
        log.error("Bloom scan not yet implemented in JpaStore");
        processor.process(null);
    }

    @Override
    protected void cacheUTXO(int lookback, TxOutCache cache) {
        long after = Math.max(currentHead.getHeight() - lookback, 0L);
        QTxOut txout = QTxOut.txOut;
        JPAQuery q = new JPAQuery(entityManager);
        for (TxOut o : q.from(txout).where(txout.available.eq(true).and(txout.height.gt(after))).list(txout)) {
            cache.add(o.flatCopy(null));
            entityManager.detach(o);
        }
    }

    @Override
    protected void cacheChain() {
        JPAQuery q;
        QBlk block = QBlk.blk;
        q = new JPAQuery(entityManager);
        for (Blk b : q.from(block).orderBy(block.id.asc()).list(block)) {
            CachedBlock cb = null;
            if (!b.getPreviousHash().equals(Hash.ZERO_HASH_STRING)) {
                cb = new CachedBlock(b.getHash(), b.getId(), cachedBlocks.get(b.getPreviousHash()),
                        b.getCreateTime(), b.getHeight(), (int) b.getVersion(), b.getFilterMap(),
                        b.getFilterFunctions());
            } else {
                cb = new CachedBlock(b.getHash(), b.getId(), null, b.getCreateTime(), b.getHeight(),
                        (int) b.getVersion(), b.getFilterMap(), b.getFilterFunctions());
            }
            cachedBlocks.put(b.getHash(), cb);
            CachedHead h = cachedHeads.get(b.getHeadId());
            h.getBlocks().add(cb);
            h.setLast(cb);
        }
    }

    @Override
    protected void cacheHeads() {
        QHead head = QHead.head;
        JPAQuery q = new JPAQuery(entityManager);
        for (Head h : q.from(head).orderBy(head.id.asc()).list(head)) {
            CachedHead sh = new CachedHead();
            sh.setId(h.getId());
            sh.setChainWork(h.getChainWork());
            sh.setHeight(h.getHeight());
            if (h.getPreviousId() != null) {
                sh.setPrevious(cachedHeads.get(h.getPreviousId()));
                sh.setPreviousHeight(h.getPreviousHeight());
            }
            cachedHeads.put(h.getId(), sh);
            if (currentHead == null || currentHead.getChainWork() < sh.getChainWork()) {
                currentHead = sh;
            }
        }
    }

    @Override
    protected void cacheColors() {
        JPAQuery q = new JPAQuery(entityManager);
        QStoredColor color = QStoredColor.storedColor;
        for (StoredColor c : q.from(color).list(color)) {
            entityManager.detach(c);
            cachedColors.put(c.getTxHash(), c);
        }
    }

    @Override
    protected void backwardCache(Blk b, TxOutCache cache, boolean modify) {
        List<Tx> txs = new ArrayList<Tx>();
        txs.addAll(b.getTransactions());
        Collections.reverse(txs);
        for (Tx t : txs) {
            for (TxOut out : t.getOutputs()) {
                if (modify) {
                    out.setAvailable(false);
                }
                cache.remove(t.getHash(), out.getIx());
            }

            for (TxIn in : t.getInputs()) {
                if (!in.getSourceHash().equals(Hash.ZERO_HASH_STRING)) {
                    TxOut source = in.getSource();
                    if (modify) {
                        source.setAvailable(true);
                    }
                    cache.add(source.flatCopy(null));
                }
            }
        }
    }

    @Override
    protected void forwardCache(Blk b, TxOutCache cache, boolean modify) {
        for (Tx t : b.getTransactions()) {
            for (TxOut out : t.getOutputs()) {
                if (modify) {
                    out.setAvailable(true);
                }
                cache.add(out.flatCopy(null));
            }

            for (TxIn in : t.getInputs()) {
                if (!in.getSourceHash().equals(Hash.ZERO_HASH_STRING)) {
                    if (modify) {
                        in.getSource().setAvailable(false);
                    }
                    cache.remove(in.getSourceHash(), in.getIx());
                }
            }
        }
    }

    @Override
    protected List<TxOut> findTxOuts(Map<String, HashSet<Long>> need) {
        List<TxOut> fromDB = new ArrayList<TxOut>();
        QTxOut txout = QTxOut.txOut;
        JPAQuery q = new JPAQuery(entityManager);
        for (TxOut o : q.from(txout).where(txout.txHash.in(need.keySet())).list(txout)) {
            if (o.isAvailable() && need.get(o.getTxHash()).contains(o.getIx())) {
                fromDB.add(o);
            }
        }
        return fromDB;
    }

    @Override
    protected void checkBIP30Compliance(Set<String> txs, int untilHeight) throws ValidationException {
        QTxOut txout = QTxOut.txOut;
        JPAQuery q = new JPAQuery(entityManager);
        for (TxOut o : q.from(txout).where(txout.txHash.in(txs)).list(txout)) {
            if (o.isAvailable() && o.getTransaction().getBlock().getHeight() <= untilHeight) {
                throw new ValidationException("BIP30 violation block contains unspent tx " + o.getTxHash());
            }
        }
    }

    @Override
    protected Blk retrieveBlock(CachedBlock cached) {
        return entityManager.find(Blk.class, cached.getId());
    }

    @Override
    protected Blk retrieveBlockHeader(CachedBlock cached) {
        return entityManager.find(Blk.class, cached.getId());
    }

    @Override
    protected void insertHead(Head head) {
        entityManager.persist(head);
    }

    @Override
    protected Head updateHead(Head head) {
        return entityManager.merge(head);
    }

    @Override
    protected Head retrieveHead(CachedHead cached) {
        return entityManager.find(Head.class, cached.getId());
    }

    @Override
    protected void insertBlock(Blk b) {
        entityManager.persist(b);
    }

    @Override
    public boolean isEmpty() {
        QHead head = QHead.head;
        JPAQuery q = new JPAQuery(entityManager);
        return q.from(head).list(head).isEmpty();
    }

    @Override
    protected TxOut getSourceReference(TxOut source) {
        return entityManager.getReference(TxOut.class, source.getId());
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, readOnly = true)
    public Tx getTransaction(String hash) {
        JPAQuery q = new JPAQuery(entityManager);
        QTx tx = QTx.tx;

        Tx t = q.from(tx).where(tx.hash.eq(hash)).uniqueResult(tx);
        if (t != null) {
            // trigger lazy loading
            t.getInputs().size();
            t.getOutputs().size();
            entityManager.detach(t);
            return t;
        }
        return null;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public Collection<KnownPeer> getConnectablePeers() {
        QKnownPeer kp = QKnownPeer.knownPeer;
        JPAQuery q = new JPAQuery(entityManager);
        List<KnownPeer> pl = q.from(kp).where(kp.banned.lt(System.currentTimeMillis() / 1000))
                .orderBy(kp.preference.desc()).orderBy(kp.height.desc()).orderBy(kp.responseTime.desc())
                .orderBy(kp.connected.desc()).list(kp);
        log.trace("Retrieved " + pl.size() + " peers from store");
        return pl;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void store(KnownPeer peer) {
        try {
            KnownPeer stored;
            if ((stored = findPeer(InetAddress.getByName(peer.getAddress()))) == null) {
                entityManager.persist(peer);
            } else {
                stored.setAgent(peer.getAgent());
                stored.setBanned(peer.getBanned());
                stored.setBanReason(peer.getBanReason());
                stored.setConnected(peer.getConnected());
                stored.setDisconnected(peer.getDisconnected());
                stored.setHeight(peer.getHeight());
                stored.setName(peer.getName());
                stored.setPreference(peer.getPreference());
                stored.setResponseTime(peer.getResponseTime());
                stored.setServices(peer.getServices());
                stored.setTrafficIn(peer.getTrafficIn());
                stored.setTrafficOut(peer.getTrafficOut());
                stored.setVersion(peer.getVersion());
                entityManager.merge(stored);
            }
        } catch (ConstraintViolationException e) {
        } catch (UnknownHostException e) {
        }
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public KnownPeer findPeer(InetAddress address) {
        QKnownPeer kp = QKnownPeer.knownPeer;
        JPAQuery q = new JPAQuery(entityManager);
        return q.from(kp).where(kp.address.eq(address.getHostAddress())).uniqueResult(kp);
    }

    @Override
    public List<InetAddress> discover() {
        log.trace("Discovering stored peers");
        List<InetAddress> peers = new ArrayList<InetAddress>();
        for (KnownPeer kp : getConnectablePeers()) {
            try {
                peers.add(InetAddress.getByName(kp.getAddress()));
            } catch (UnknownHostException e) {
            }
        }
        return peers;
    }

    @Override
    @Transactional(propagation = Propagation.MANDATORY)
    public void storeColor(StoredColor color) {
        entityManager.persist(color);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public StoredColor findColor(String hash) {
        QStoredColor sc = QStoredColor.storedColor;
        JPAQuery q = new JPAQuery(entityManager);
        return q.from(sc).where(sc.txHash.eq(hash)).uniqueResult(sc);
    }

    @Override
    @Transactional(propagation = Propagation.MANDATORY)
    protected void updateColor(TxOut root, String fungibleName) {
        root.setColor(fungibleName);
    }
}