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

net.sf.hajdbc.state.bdb.BerkeleyDBStateManager Maven / Gradle / Ivy

There is a newer version: 3.6.61
Show newest version
/*
 * HA-JDBC: High-Availability JDBC
 * Copyright (C) 2012  Paul Ferraro
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 */
package net.sf.hajdbc.state.bdb;

import java.io.File;
import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import net.sf.hajdbc.DatabaseCluster;
import net.sf.hajdbc.ExceptionType;
import net.sf.hajdbc.durability.Durability;
import net.sf.hajdbc.durability.DurabilityListener;
import net.sf.hajdbc.durability.InvocationEvent;
import net.sf.hajdbc.durability.InvocationEventImpl;
import net.sf.hajdbc.durability.InvokerEvent;
import net.sf.hajdbc.durability.InvokerEventImpl;
import net.sf.hajdbc.durability.InvokerResult;
import net.sf.hajdbc.pool.CloseablePoolProvider;
import net.sf.hajdbc.pool.Pool;
import net.sf.hajdbc.pool.PoolFactory;
import net.sf.hajdbc.state.DatabaseEvent;
import net.sf.hajdbc.state.DurabilityListenerAdapter;
import net.sf.hajdbc.state.SerializedDurabilityListener;
import net.sf.hajdbc.state.StateManager;
import net.sf.hajdbc.tx.TransactionIdentifierFactory;
import net.sf.hajdbc.util.Objects;

import com.sleepycat.bind.ByteArrayBinding;
import com.sleepycat.bind.EntryBinding;
import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.collections.StoredKeySet;
import com.sleepycat.collections.StoredMap;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.Transaction;

/**
 * @author paul
 */
public class BerkeleyDBStateManager extends CloseablePoolProvider implements StateManager, SerializedDurabilityListener
{
	private static final String STATE = "state";
	private static final String INVOCATION = "invocation";
	private static final String INVOKER = "invoker";
	private static final EntryBinding INVOCATION_KEY_BINDING = new KeyBinding();
	private static final EntryBinding INVOKER_KEY_BINDING = new KeyBinding();
	private static final EntryBinding BLOB_BINDING = new ByteArrayBinding();
	static final byte[] NULL = new byte[0];
	
	private final DatabaseCluster cluster;
	private final File file;
	private final PoolFactory poolFactory;
	private final EnvironmentConfig config;
	private final DurabilityListener durabilityListener;
	
	private volatile Pool pool;

	public BerkeleyDBStateManager(DatabaseCluster cluster, File file, EnvironmentConfig config, PoolFactory poolFactory)
	{
		super(Environment.class, DatabaseException.class);
		this.cluster = cluster;
		this.file = file;
		this.poolFactory = poolFactory;
		this.config = config;
		this.durabilityListener = new DurabilityListenerAdapter(this, cluster.getTransactionIdentifierFactory());
	}

	@Override
	public void start()
	{
		this.file.mkdirs();
		this.pool = this.poolFactory.createPool(this);
		Environment env = this.pool.take();
		try
		{
			env.openDatabase(null, STATE, new DatabaseConfig().setAllowCreate(true)).close();
			env.openDatabase(null, INVOCATION, new DatabaseConfig().setAllowCreate(true)).close();
			env.openDatabase(null, INVOKER, new DatabaseConfig().setAllowCreate(true)).close();
		}
		finally
		{
			this.pool.release(env);
		}
	}

	@Override
	public void stop()
	{
		this.pool.close();
	}

	@Override
	public Set getActiveDatabases()
	{
		DatabaseQuery> query = new DatabaseQuery>(STATE)
		{
			@Override
			Set execute(Database database)
			{
				return new TreeSet(createStateSet(database, true));
			}
		};
		return this.execute(query);
	}

	@Override
	public void setActiveDatabases(final Set databases)
	{
		DatabaseOperation operation = new DatabaseOperation(STATE)
		{
			@Override
			void execute(Database database)
			{
				createStateSet(database, false).retainAll(databases);
			}
		};
		this.execute(operation);
	}

	@Override
	public void activated(final DatabaseEvent event)
	{
		DatabaseOperation operation = new DatabaseOperation(STATE)
		{
			@Override
			void execute(Database database)
			{
				createStateSet(database, false).add(event.getSource());
			}
		};
		this.execute(operation);
	}

	@Override
	public void deactivated(final DatabaseEvent event)
	{
		DatabaseOperation operation = new DatabaseOperation(STATE)
		{
			@Override
			void execute(Database database)
			{
				createStateSet(database, false).remove(event.getSource());
			}
		};
		this.execute(operation);
	}

	Set createStateSet(Database database, boolean readOnly)
	{
		return new StoredKeySet(database, TupleBinding.getPrimitiveBinding(String.class), !readOnly);
	}
	
	@Override
	public void beforeInvocation(InvocationEvent event)
	{
		this.durabilityListener.beforeInvocation(event);
	}

	@Override
	public void afterInvocation(InvocationEvent event)
	{
		this.durabilityListener.afterInvocation(event);
	}

	@Override
	public void beforeInvoker(InvokerEvent event)
	{
		this.durabilityListener.beforeInvoker(event);
	}

	@Override
	public void afterInvoker(InvokerEvent event)
	{
		this.durabilityListener.afterInvoker(event);
	}

	@Override
	public void beforeInvocation(final byte[] transactionId, final byte phase, final byte exceptionType)
	{
		DatabaseOperation operation = new DatabaseOperation(INVOCATION)
		{
			@Override
			void execute(Database database)
			{
				createInvocationMap(database, false).put(new InvocationKey(transactionId, phase), exceptionType);
			}
		};
		this.execute(operation);
	}

	@Override
	public void afterInvocation(final byte[] transactionId, final byte phase)
	{
		DatabaseOperation invokerperation = new DatabaseOperation(INVOKER)
		{
			@Override
			void execute(Database database)
			{
				Iterator keys = createInvokerMap(database, false).keySet().iterator();
				while (keys.hasNext())
				{
					InvokerKey key = keys.next();
					if ((key.getPhase() == phase) && Arrays.equals(key.getTransactionId(), transactionId))
					{
						keys.remove();
					}
				}
			}
		};
		DatabaseOperation invocationOperation = new DatabaseOperation(INVOCATION)
		{
			@Override
			void execute(Database database)
			{
				createInvocationMap(database, false).remove(new InvocationKey(transactionId, phase));
			}
		};
		this.execute(invokerperation, invocationOperation);
	}

	@Override
	public void beforeInvoker(final byte[] transactionId, final byte phase, final String databaseId)
	{
		DatabaseOperation operation = new DatabaseOperation(INVOKER)
		{
			@Override
			void execute(Database database)
			{
				createInvokerMap(database, false).put(new InvokerKey(transactionId, phase, databaseId), NULL);
			}
		};
		this.execute(operation);
	}

	@Override
	public void afterInvoker(final byte[] transactionId, final byte phase, final String databaseId, final byte[] result)
	{
		DatabaseOperation operation = new DatabaseOperation(INVOKER)
		{
			@Override
			void execute(Database database)
			{
				createInvokerMap(database, false).put(new InvokerKey(transactionId, phase, databaseId), result);
			}
		};
		this.execute(operation);
	}

	@Override
	public Map> recover()
	{
		final Map> result = new HashMap>();
		final TransactionIdentifierFactory txIdFactory = this.cluster.getTransactionIdentifierFactory();
		DatabaseQuery query = new DatabaseQuery(INVOCATION)
		{
			@Override
			Void execute(Database database)
			{
				for (Map.Entry entry: createInvocationMap(database, true).entrySet())
				{
					InvocationKey key = entry.getKey();
					result.put(new InvocationEventImpl(txIdFactory.deserialize(key.getTransactionId()), Durability.Phase.values()[key.getPhase()], ExceptionType.values()[entry.getValue()]), new HashMap());
				}
				return null;
			}
		};
		this.execute(query);
		query = new DatabaseQuery(INVOKER)
		{
			@Override
			Void execute(Database database)
			{
				for (Map.Entry entry: createInvokerMap(database, true).entrySet())
				{
					InvokerKey key = entry.getKey();
					Map invokers = result.get(new InvocationEventImpl(txIdFactory.deserialize(key.getTransactionId()), Durability.Phase.values()[key.getPhase()], null));
					if (invokers != null)
					{
						InvokerEvent invoker = new InvokerEventImpl(txIdFactory.deserialize(key.getTransactionId()), Durability.Phase.values()[key.getPhase()], key.getDatabaseId());
						byte[] value = entry.getValue();
						if (value.length > 0)
						{
							invoker.setResult(Objects.deserialize(value));
						}
						invokers.put(key.getDatabaseId(), invoker);
					}
				}
				return null;
			}
		};
		this.execute(query);
		return result;
	}
	
	private static class InvocationKey implements Serializable
	{
		private static final long serialVersionUID = -9033714764207519351L;
		private final byte[] transactionId;
		private final byte phase;
		
		InvocationKey(byte[] transactionId, byte phase)
		{
			this.transactionId = transactionId;
			this.phase = phase;
		}
		
		byte[] getTransactionId()
		{
			return this.transactionId;
		}
		
		byte getPhase()
		{
			return this.phase;
		}
	}

	private static class InvokerKey extends InvocationKey
	{
		private static final long serialVersionUID = 400751577923581135L;
		private final String databaseId;
		
		InvokerKey(byte[] transactionId, byte phase, String databaseId)
		{
			super(transactionId, phase);
			this.databaseId = databaseId;
		}
		
		String getDatabaseId()
		{
			return this.databaseId;
		}
	}
	
	Map createInvocationMap(Database database, boolean readOnly)
	{
		return new StoredMap(database, INVOCATION_KEY_BINDING, TupleBinding.getPrimitiveBinding(Byte.class), !readOnly);
	}
	
	Map createInvokerMap(Database database, boolean readOnly)
	{
		return new StoredMap(database, INVOKER_KEY_BINDING, BLOB_BINDING, !readOnly);
	}

	@Override
	public boolean isEnabled()
	{
		return true;
	}

	@Override
	public boolean isValid(net.sf.hajdbc.Database database) {
		return this.getActiveDatabases().contains(database.getId());
	}


	/**
	 * {@inheritDoc}
	 * @see net.sf.hajdbc.pool.PoolProvider#create()
	 */
	@Override
	public Environment create() throws DatabaseException
	{
		return new Environment(this.file, this.config);
	}

	/**
	 * {@inheritDoc}
	 * @see net.sf.hajdbc.pool.PoolProvider#isValid(java.lang.Object)
	 */
	@Override
	public boolean isValid(Environment environment)
	{
		try
		{
			environment.checkHandleIsValid();
			
			return true;
		}
		catch (DatabaseException e)
		{
			return false;
		}
	}

	private static abstract class DatabaseOperation
	{
		private final String databaseName;

		DatabaseOperation(String databaseName)
		{
			this.databaseName = databaseName;
		}

		String getDatabaseName()
		{
			return this.databaseName;
		}

		abstract void execute(Database database);
	}
	
	private void execute(DatabaseOperation... dbOperations)
	{
		Operation[] operations = new Operation[dbOperations.length];
		for (int i = 0; i < dbOperations.length; ++i)
		{
			final DatabaseOperation operation = dbOperations[i];
			operations[i] = new Operation()
			{
				@Override
				public void execute(Environment env, Transaction transaction)
				{
					Database database = env.openDatabase(transaction, operation.getDatabaseName(), new DatabaseConfig().setTransactional(true));
					try
					{
						operation.execute(database);
					}
					finally
					{
						database.close();
					}
				}
			};
		}
		this.execute(operations);
	}

	private abstract static class DatabaseQuery
	{
		private final String databaseName;
		
		DatabaseQuery(String databaseName)
		{
			this.databaseName = databaseName;
		}
		
		String getDatabaseName()
		{
			return this.databaseName;
		}
		
		abstract T execute(Database database);
	}
	
	private  T execute(final DatabaseQuery dbQuery)
	{
		Query query = new Query()
		{
			@Override
			public T execute(Environment env)
			{
				Database database = env.openDatabase(null, dbQuery.getDatabaseName(), new DatabaseConfig().setReadOnly(true));
				try
				{
					return dbQuery.execute(database);
				}
				finally
				{
					database.close();
				}
			}
		};
		return this.execute(query);
	}
	
	private static interface Operation
	{
		void execute(Environment env, Transaction transaction);
	}
	
	private void execute(Operation... operations)
	{
		Environment env = this.pool.take();
		try
		{
			Transaction transaction = env.beginTransaction(null, null);
			try
			{
				for (Operation operation: operations)
				{
					operation.execute(env, transaction);
				}
				transaction.commit();
			}
			catch (RuntimeException e)
			{
				transaction.abort();
				throw e;
			}
		}
		finally
		{
			this.pool.release(env);
		}
	}
	
	private static interface Query
	{
		T execute(Environment env);
	}
	
	private  T execute(Query query)
	{
		Environment env = this.pool.take();
		try
		{
			return query.execute(env);
		}
		finally
		{
			this.pool.release(env);
		}
	}
	
	static class KeyBinding implements EntryBinding
	{
		@Override
		public T entryToObject(DatabaseEntry entry)
		{
			return Objects.deserialize(entry.getData());
		}

		@Override
		public void objectToEntry(T object, DatabaseEntry entry)
		{
			entry.setData(Objects.serialize(object));
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy