All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opencastproject.db.DBSessionImpl Maven / Gradle / Ivy

There is a newer version: 16.6
Show newest version
/*
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 *
 * The Apereo Foundation licenses this file to you under the Educational
 * Community 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://opensource.org/licenses/ecl2.txt
 *
 * 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.opencastproject.db;

import org.opencastproject.util.function.ThrowingConsumer;
import org.opencastproject.util.function.ThrowingFunction;

import java.util.Random;
import java.util.function.Consumer;
import java.util.function.Function;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;

public class DBSessionImpl implements DBSession {
  private static final Random RAND = new Random();

  private EntityManagerFactory emf;
  private int maxTransactionRetries = DBSessionFactoryImpl.DEFAULT_MAX_TRANSACTION_RETRIES;

  private final ThreadLocal entityManagerStore = new ThreadLocal<>();

  public DBSessionImpl(EntityManagerFactory emf) {
    this.emf = emf;
  }

  @Override
  public void exec(Consumer fn) {
    exec(em -> {
      fn.accept(em);
      return null;
    });
  }

  @Override
  public  void execChecked(ThrowingConsumer fn) throws E {
    execChecked(em -> {
      fn.accept(em);
      return null;
    });
  }

  @Override
  public  T exec(Function fn) {
    try {
      return execChecked(fn::apply);
    } catch (RuntimeException e) {
      throw e;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public  T execChecked(ThrowingFunction fn) throws E {
    EntityManager em = null;
    try {
      em = emf.createEntityManager();
      return fn.apply(em);
    } finally {
      if (em != null && em.isOpen()) {
        em.close();
      }
    }
  }

  @Override
  public void execTx(Consumer fn) {
    execTx(maxTransactionRetries, fn);
  }

  @Override
  public  void execTxChecked(ThrowingConsumer fn) throws E {
    execTxChecked(maxTransactionRetries, fn);
  }

  @Override
  public void execTx(int maxTransactionRetries, Consumer fn) {
    execTx(maxTransactionRetries, em -> {
      fn.accept(em);
      return null;
    });
  }

  @Override
  public  void execTxChecked(int maxTransactionRetries, ThrowingConsumer fn) throws E {
    execTxChecked(maxTransactionRetries, em -> {
      fn.accept(em);
      return null;
    });
  }

  @Override
  public  T execTx(Function fn) {
    return execTx(maxTransactionRetries, fn);
  }

  @Override
  public  T execTxChecked(ThrowingFunction fn) throws E {
    return execTxChecked(maxTransactionRetries, fn);
  }

  @Override
  public  T execTx(int maxTransactionRetries, Function fn) {
    try {
      return execTxChecked(maxTransactionRetries, fn::apply);
    } catch (RuntimeException e) {
      throw e;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public  T execTxChecked(int maxTransactionRetries, ThrowingFunction fn) throws E {
    EntityManager em = entityManagerStore.get();

    if (em != null) {
      // We are already in a transaction. Opening another one can lead to deadlocks.
      return fn.apply(em);
    }

    EntityTransaction tx = null;
    RuntimeException ex = null;

    for (int attempt = 0; attempt < maxTransactionRetries; attempt++) {
      try {
        em = emf.createEntityManager();
        entityManagerStore.set(em);
        tx = em.getTransaction();
        tx.begin();
        T res = fn.apply(em);
        tx.commit();
        return res;
      } catch (RuntimeException e) { // we only catch RuntimeException as other exceptions are not related to DB errors
        // TODO: do we need to catch all exceptions and look at the cause chain?
        ex = e;

        if (tx != null && tx.isActive()) {
          tx.rollback();
        }

        // only retry if exception has something to do with the transaction
        if (!DBUtils.isTransactionException(e)) {
          throw e;
        }
      } finally {
        if (em != null && em.isOpen()) {
          em.close();
        }
        entityManagerStore.remove();
      }

      // exponential backoff before next iteration
      int sleepMillis = (int) (Math.pow(2, attempt) * 100) + RAND.nextInt(100);
      try {
        Thread.sleep(sleepMillis);
      } catch (InterruptedException ignore) {
      }
    }

    // we only get here if all retries led to an exception: throw the last one up the stack
    throw ex;
  }

  @Override
  public void close() {
    if (emf.isOpen()) {
      emf.close();
    }
  }

  public EntityManagerFactory getEntityManagerFactory() {
    return emf;
  }

  public void setEntityManagerFactory(EntityManagerFactory emf) {
    this.emf = emf;
  }

  public int getMaxTransactionRetries() {
    return maxTransactionRetries;
  }

  public void setMaxTransactionRetries(int maxTransactionRetries) {
    this.maxTransactionRetries = maxTransactionRetries;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy