Package de.scoopgmbh.copper.persistent

Source Code of de.scoopgmbh.copper.persistent.ScottyDBStorage

/*
* Copyright 2002-2013 SCOOP Software GmbH
*
* 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 de.scoopgmbh.copper.persistent;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.scoopgmbh.copper.Acknowledge;
import de.scoopgmbh.copper.Response;
import de.scoopgmbh.copper.Workflow;
import de.scoopgmbh.copper.batcher.BatchCommand;
import de.scoopgmbh.copper.batcher.Batcher;
import de.scoopgmbh.copper.management.BatcherMXBean;
import de.scoopgmbh.copper.management.DatabaseDialectMXBean;
import de.scoopgmbh.copper.management.ScottyDBStorageMXBean;
import de.scoopgmbh.copper.persistent.txn.DatabaseTransaction;
import de.scoopgmbh.copper.persistent.txn.TransactionController;

/**
* Implementation of the {@link ScottyDBStorageInterface}.
*
* @author austermann
*
*/
public class ScottyDBStorage implements ScottyDBStorageInterface, ScottyDBStorageMXBean {

  private static final Logger logger = LoggerFactory.getLogger(ScottyDBStorage.class);

  private DatabaseDialect dialect;
  private TransactionController transactionController;

  private Batcher batcher;
  private long deleteStaleResponsesIntervalMsec = 60L*60L*1000L;

  private Thread enqueueThread;
  private ScheduledExecutorService scheduledExecutorService;
  private boolean shutdown = false;
  private boolean checkDbConsistencyAtStartup = false;
 
  private CountDownLatch enqueueThreadTerminated = new CountDownLatch(1);

  public ScottyDBStorage() {

  }

  public void setCheckDbConsistencyAtStartup(boolean checkDbConsistencyAtStartup) {
    this.checkDbConsistencyAtStartup = checkDbConsistencyAtStartup;
  }

  public void setTransactionController(TransactionController transactionController) {
    this.transactionController = transactionController;
  }

  public void setDialect(DatabaseDialect dialect) {
    this.dialect = dialect;
  }

  protected  <T> T run(final DatabaseTransaction<T> txn) throws Exception {
    return transactionController.run(txn);
  }

  public void setBatcher(Batcher batcher) {
    this.batcher = batcher;
  }

  public void setDeleteStaleResponsesIntervalMsec(long deleteStaleResponsesIntervalMsec) {
    this.deleteStaleResponsesIntervalMsec = deleteStaleResponsesIntervalMsec;
  }

  /* (non-Javadoc)
   * @see de.scoopgmbh.copper.persistent.ScottyDBStorageInterface#resumeBrokenBusinessProcesses()
   */
  private void resumeBrokenBusinessProcesses() throws Exception {
    logger.info("resumeBrokenBusinessProcesses");
    run(new DatabaseTransaction<Void>() {
      @Override
      public Void run(Connection con) throws Exception {
        dialect.resumeBrokenBusinessProcesses(con);
        return null;
      }
    });
  }

  /* (non-Javadoc)
   * @see de.scoopgmbh.copper.persistent.ScottyDBStorageInterface#insert(de.scoopgmbh.copper.Workflow)
   */
  public void insert(final Workflow<?> wf, final Acknowledge ack) throws Exception {
    logger.trace("insert({})",wf);
    try {
      run(new DatabaseTransaction<Void>() {
        @Override
        public Void run(Connection con) throws Exception {
          dialect.insert(wf, con);
          return null;
        }
      });
      ack.onSuccess();
    } catch (Exception e) {
      ack.onException(e);
      throw e;
    }
  }

  /* (non-Javadoc)
   * @see de.scoopgmbh.copper.persistent.ScottyDBStorageInterface#insert(java.util.List)
   */
  public void insert(final List<Workflow<?>> wfs, final Acknowledge ack) throws Exception {
    logger.trace("insert(wfs.size={})",wfs.size());
    try {
      run(new DatabaseTransaction<Void>() {
        @Override
        public Void run(Connection con) throws Exception {
          dialect.insert(wfs, con);
          return null;
        }
      });
      ack.onSuccess();
    } catch (Exception e) {
      ack.onException(e);
      throw e;
    }
  }

  @Override
  public List<Workflow<?>> dequeue(final String ppoolId, final int max) throws Exception {
    while (true) {
      List<Workflow<?>> ret = run(new DatabaseTransaction<List<Workflow<?>>>() {
        @Override
        public List<Workflow<?>> run(Connection con) throws Exception {
          return dialect.dequeue(ppoolId, max, con);
        }
      });
      if (!ret.isEmpty()) {
        return ret;
      }
      waitForEnqueue();
    }
  }

  final int waitForEnqueueMSec = 500;
  Object enqueueSignal = new Object();
  private void waitForEnqueue()  throws InterruptedException {
    synchronized (enqueueSignal) {
      enqueueSignal.wait(waitForEnqueueMSec);
    }
  }
  private void signalEnqueue() {
    synchronized (enqueueSignal) {
      enqueueSignal.notify();
    }
  }

  Object queueStateSignal = new Object();
  private void waitForQueueState(int waitTimethrows InterruptedException {
    synchronized (queueStateSignal) {
      queueStateSignal.wait(waitTime);
    }
  }
  private void signalQueueState() {
    synchronized (queueStateSignal) {
      queueStateSignal.notify();
    }
  }

  protected List<List<String>> splitt(Collection<String> keySet, int n) {
    if (keySet.isEmpty())
      return Collections.emptyList();

    List<List<String>> r = new ArrayList<List<String>>(keySet.size()/n+1);
    List<String> l = new ArrayList<String>(n);
    for (String s : keySet) {
      l.add(s);
      if (l.size() == n) {
        r.add(l);
        l = new ArrayList<String>(n);
      }
    }
    if (l.size() > 0) {
      r.add(l);
    }
    return r;
  }

  /* (non-Javadoc)
   * @see de.scoopgmbh.copper.persistent.ScottyDBStorageInterface#notify(java.util.List)
   */
  public void notify(final List<Response<?>> response, Acknowledge ack) throws Exception {
    for (Response<?> r : response)
      notify(r,ack);
  }


  /* (non-Javadoc)
   * @see de.scoopgmbh.copper.persistent.ScottyDBStorageInterface#startup()
   */
  public synchronized void startup() {
    try {
            dialect.startup();

      checkDbConsistencyAtStartup();
      deleteStaleResponse();
      resumeBrokenBusinessProcesses();
     


      enqueueThread = new Thread("ENQUEUE") {
        @Override
        public void run() {
          updateQueueState();
        }
      };
      enqueueThread.start();

      scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

      scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
        @Override
        public void run() {
          try {
            deleteStaleResponse();
          }
          catch (Exception e) {
            logger.error("deleteStaleResponse failed",e);
          }
        }
      }, deleteStaleResponsesIntervalMsec, deleteStaleResponsesIntervalMsec, TimeUnit.MILLISECONDS);
    }
    catch(Exception e) {
      throw new Error("Unable to startup",e);
    }
  }

  private void checkDbConsistencyAtStartup() {
    if (!checkDbConsistencyAtStartup) {
      return;
    }
   
    logger.info("doing checkDbConsistencyAtStartup...");
    try {
      run(new DatabaseTransaction<Void>() {
        @Override
        public Void run(Connection con) throws Exception {
          dialect.checkDbConsistency(con);
          return null;
        }
      });
      logger.info("finished checkDbConsistencyAtStartup");
    }
    catch(Exception e) {
      logger.error("checkDbConsistencyAtStartup failed",e);
    }
  }

  private void deleteStaleResponse() throws Exception {
    if (logger.isTraceEnabled()) logger.trace("deleteStaleResponse()");

    int n = 0;
    final int MAX_ROWS = 20000;
    do {
      n = run(new DatabaseTransaction<Integer>() {
        @Override
        public Integer run(Connection con) throws Exception {
          return dialect.deleteStaleResponse(con, MAX_ROWS);
        }
      });
    }
    while(n == MAX_ROWS);
  }

  /* (non-Javadoc)
   * @see de.scoopgmbh.copper.persistent.ScottyDBStorageInterface#shutdown()
   */
  public synchronized void shutdown() {
    if (shutdown)
      return;

    shutdown = true;
   
    scheduledExecutorService.shutdown();

    shutdownEnqueueThread();
   
    dialect.shutdown();
  }

  private void shutdownEnqueueThread() {
    enqueueThread.interrupt();
    try {
      enqueueThreadTerminated.await(30, TimeUnit.SECONDS);
    }
    catch (InterruptedException e) {
      logger.warn("await interrupted",e);
    }
  }

  private void updateQueueState() {
    final int max = 5000;
    final int lowTraffic = 100;
    logger.info("started");
    int sleepTime = 0;
    int sleepTimeMaxIdle = 2000;
    int sleepTimeMaxLowTraffic = 500;
    while(!shutdown) {
      int x=0;
      try {
        x = run(new DatabaseTransaction<Integer>() {
          @Override
          public Integer run(Connection con) throws Exception {
            return dialect.updateQueueState(max,con);
          }
        });
      }
      catch(Exception e) {
        logger.error("updateQueueState failed",e);
      }
      if (x > 0) {
        signalEnqueue();
      }
      if (x == 0) {
        sleepTime = Math.max(10, Math.min(3*sleepTime/2,sleepTimeMaxIdle));
      }
      else if (x < lowTraffic) {
        sleepTime = Math.max(10, Math.min(11*sleepTime/10,sleepTimeMaxLowTraffic));
      } else {
        sleepTime = 0;
      }
      if (sleepTime > 0) {
        try {
          waitForQueueState(sleepTime);
        } catch (InterruptedException e) {
        }
      }
    }
    logger.info("finished");
    enqueueThreadTerminated.countDown();
  }

  @Override
  public void insert(Workflow<?> wf, Connection con) throws Exception {
    if (con == null)
      insert(wf, new Acknowledge.BestEffortAcknowledge());
    else
      dialect.insert(wf, con);
  }

  @Override
  public void insert(List<Workflow<?>> wfs, Connection con) throws Exception {
    if (con == null)
      insert(wfs, new Acknowledge.BestEffortAcknowledge());
    else
      dialect.insert(wfs, con);
  }

  public void restart(final String workflowInstanceId) throws Exception {
    run(new DatabaseTransaction<Void>() {
      @Override
      public Void run(Connection con) throws Exception {
        dialect.restart(workflowInstanceId, con);
        return null;
      }
    });
  }

  @Override
  public void restartAll() throws Exception {
    run(new DatabaseTransaction<Void>() {
      @Override
      public Void run(Connection con) throws Exception {
        dialect.restartAll(con);
        return null;
      }
    });   
  }

  @Override
  public void setRemoveWhenFinished(boolean removeWhenFinished) {
    dialect.setRemoveWhenFinished(removeWhenFinished);
  }

  @SuppressWarnings({"rawtypes", "unchecked"})
  private void runSingleBatchCommand(final BatchCommand cmd) throws Exception {
    try {
      run(new DatabaseTransaction<Void>() {
        @Override
        public Void run(Connection con) throws Exception {
          cmd.executor().doExec(Collections.singletonList(cmd), con);
          return null;
        }
      });
      cmd.callback().commandCompleted();
    } catch (Exception e) {
      cmd.callback().unhandledException(e);
      throw e;
    }
  }

  @Override
  public void notify(List<Response<?>> responses, Connection c) throws Exception {
    dialect.notify(responses, c);
  }   

  @Override
  public void error(Workflow<?> w, Throwable t, final Acknowledge callback) {
    if (logger.isTraceEnabled()) logger.trace("error("+w.getId()+","+t.toString()+")");
    try {
      executeBatchCommand(dialect.createBatchCommand4error(w, t, DBProcessingState.ERROR, callback));
    }
    catch (Exception e) {
      logger.error("error failed",e);
    }
  }

  @SuppressWarnings({"unchecked", "rawtypes"})
  private void executeBatchCommand(BatchCommand cmd) throws Exception {
    if (batcher != null) {
      batcher.submitBatchCommand(cmd);
    }
    else {
      runSingleBatchCommand(cmd);
    }
  } 

  /* (non-Javadoc)
   * @see de.scoopgmbh.copper.persistent.ScottyDBStorageInterface#registerCallback(de.scoopgmbh.copper.persistent.RegisterCall)
   */
  public void registerCallback(final RegisterCall rc, final Acknowledge callback) throws Exception {
    if (logger.isTraceEnabled()) logger.trace("registerCallback("+rc+")");
    if (rc == null)
      throw new NullPointerException();
    executeBatchCommand(dialect.createBatchCommand4registerCallback(rc, this, callback));
 

  /* (non-Javadoc)
   * @see de.scoopgmbh.copper.persistent.ScottyDBStorageInterface#notify(de.scoopgmbh.copper.Response, java.lang.Object)
   */
  public void notify(final Response<?> response, final Acknowledge callback) throws Exception {
    if (logger.isTraceEnabled()) logger.trace("notify("+response+")");
    if (response == null)
      throw new NullPointerException();
    Acknowledge notify = new Acknowledge() {
      @Override
      public void onSuccess() {
        signalQueueState();
        callback.onSuccess();
      }
      @Override
      public void onException(Throwable t) {
        callback.onException(t);
      }
    };
    executeBatchCommand(dialect.createBatchCommand4Notify(response, notify));
 

  /* (non-Javadoc)
   * @see de.scoopgmbh.copper.persistent.ScottyDBStorageInterface#finish(de.scoopgmbh.copper.Workflow)
   */
  public void finish(final Workflow<?> w, final Acknowledge callback) {
    if (logger.isTraceEnabled()) logger.trace("finish("+w.getId()+")");
    try {
      executeBatchCommand(dialect.createBatchCommand4Finish(w, callback));
    }
    catch (Exception e) {
      logger.error("finish failed",e);
      error(w,e,callback);
    }
  }

  @Override
  public BatcherMXBean getBatcherMXBean() {
    return (BatcherMXBean) (batcher instanceof BatcherMXBean ? batcher : null);
  }

  @Override
  public DatabaseDialectMXBean getDatabaseDialectMXBean() {
    return (DatabaseDialectMXBean) (dialect instanceof DatabaseDialectMXBean ? dialect : null);
  }

  @Override
  public String getDescription() {
    return "Default RDBMS storage";
  }

  @Override
  public Workflow<?> read(final String workflowInstanceId) throws Exception {
    return run(new DatabaseTransaction<Workflow<?>>() {
      @Override
      public Workflow<?> run(Connection con) throws Exception {
        return dialect.read(workflowInstanceId, con);
      }
    });
  }
 
}
TOP

Related Classes of de.scoopgmbh.copper.persistent.ScottyDBStorage

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.