package org.priha.providers;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.logging.Logger;
import javax.jcr.*;
import me.prettyprint.cassandra.service.CassandraClient;
import me.prettyprint.cassandra.service.CassandraClientPool;
import me.prettyprint.cassandra.service.CassandraClientPoolFactory;
import me.prettyprint.cassandra.service.Keyspace;
import org.apache.cassandra.thrift.*;
import org.apache.thrift.TException;
import org.priha.core.ItemType;
import org.priha.core.RepositoryImpl;
import org.priha.core.WorkspaceImpl;
import org.priha.nodetype.QNodeDefinition;
import org.priha.path.Path;
import org.priha.path.PathFactory;
import org.priha.util.ConfigurationException;
import org.priha.util.QName;
/**
* Provides backing store using Apache Cassandra NoSQL solution.
* <p>
* Uses the following ColumnFamily configuration:
*
* ColumnFamily : UUIDMap
* name = uuid
* comparewith = BytesType
* "path" : path
*
* SuperColumnFamily : Content
* name = Workspace
* columntype = super
* comparewith = UTF8Type
* comparesubcolumnswith = UTF8Type
*
* key : value[] = Path : Properties[]
*
* special key "_childnodes" lists all names of childnodes in order
*
* ColumnFamily : Config
* name = <Workspaces>
* comparewith = UTF8Type
* key : value = workspace : name
*/
public class CassandraProvider implements RepositoryProvider
{
private static final String DEFAULT_KEYSPACE = "Priha";
private static final String PROP_WORKSPACES = "workspaces";
private static final String PROP_CONNECTIONS = "connections";
private Logger log = Logger.getLogger( getClass().getName() );
// These are just temporary and assume a single connection
private String m_connection;
private int m_port;
private CassandraClientPool m_clientPool = CassandraClientPoolFactory.INSTANCE.get();
private Keyspace getKeyspace() throws RepositoryException
{
try
{
CassandraClient c = m_clientPool.borrowClient(m_connection,m_port);
return c.getKeyspace(DEFAULT_KEYSPACE);
}
catch( Exception e )
{
throw new RepositoryException("Unable to get Cassandra client",e);
}
}
private void returnKeyspace( Keyspace c )
{
try
{
if( c != null ) m_clientPool.releaseClient(c.getClient());
}
catch( Exception e ){} // FIXME: Shouldn't.
}
public void addNode(StoreTransaction tx, Path path, QNodeDefinition definition) throws RepositoryException
{
Keyspace ks = ((CassandraTransaction)tx).keyspace;
// ColumnPath columnPath = ThriftGlue.createColumnPath("Content",
// superColumnName, columnName);
}
public void close(WorkspaceImpl ws)
{
// TODO Auto-generated method stub
}
public Path findByUUID(WorkspaceImpl ws, String uuid) throws ItemNotFoundException, RepositoryException
{
Keyspace ks = null;
try
{
ks = getKeyspace();
Column c = ks.getColumn("path", ThriftGlue.createColumnPath("UUIDMap", null, uuid.getBytes("UTF-8")) );
return PathFactory.getPath( new String(c.value,"UTF-8") );
}
catch( NotFoundException e )
{
throw new ItemNotFoundException("There is no such UUID "+uuid);
}
catch (Exception e)
{
throw new RepositoryException("Unable to get column ",e);
}
finally
{
returnKeyspace(ks);
}
}
public List<Path> findReferences(WorkspaceImpl ws, String uuid) throws RepositoryException
{
// TODO Auto-generated method stub
return null;
}
public ValueContainer getPropertyValue(WorkspaceImpl ws, Path path) throws PathNotFoundException, RepositoryException
{
// TODO Auto-generated method stub
return null;
}
public boolean itemExists(WorkspaceImpl ws, Path path, ItemType type) throws RepositoryException
{
// TODO Auto-generated method stub
return false;
}
public List<Path> listNodes(WorkspaceImpl ws, Path parentpath) throws RepositoryException
{
// TODO Auto-generated method stub
return null;
}
public List<QName> listProperties(WorkspaceImpl ws, Path path) throws PathNotFoundException, RepositoryException
{
// TODO Auto-generated method stub
return null;
}
public Collection<String> listWorkspaces() throws RepositoryException
{
Keyspace ks = null;
try
{
ks = getKeyspace();
//ks.getRangeSlice("path", ThriftGlue.createColumnParent( "Workspaces", null );
return null;
}
catch (Exception e)
{
throw new RepositoryException("Unable to get column ",e);
}
finally
{
returnKeyspace(ks);
}
}
public void open(RepositoryImpl rep, Credentials credentials, String workspaceName)
throws RepositoryException,
NoSuchWorkspaceException
{
// TODO Auto-generated method stub
}
public void putPropertyValue(StoreTransaction tx, Path path, ValueContainer property) throws RepositoryException
{
// TODO Auto-generated method stub
}
public void remove(StoreTransaction tx, Path path) throws RepositoryException
{
// TODO Auto-generated method stub
}
public void reorderNodes(StoreTransaction tx, Path path, List<Path> childOrder) throws RepositoryException
{
// TODO Auto-generated method stub
}
public void start(RepositoryImpl repository, Properties properties) throws ConfigurationException
{
String[] workspaces = properties.getProperty( PROP_WORKSPACES, "default" ).split( "\\s" );
String[] connections = properties.getProperty( PROP_CONNECTIONS, "localhost:9160" ).split("\\s");
// FIXME: Should make this run across multiple connections; now just one
if( connections.length > 1 )
{
throw new ConfigurationException("Currently only a single connection is allowed");
}
String[] host = connections[0].split(":");
m_connection = host[0];
if( host.length > 1 ) m_port = Integer.parseInt(host[1]);
log.info("Connecting to Cassandra service on host '"+m_connection+"', port "+m_port);
Keyspace ks = null;
//
// Write the workspaces into the config part.
//
try
{
ks = getKeyspace();
for( String ws : workspaces )
{
ks.insert(ws,
ThriftGlue.createColumnPath("Config", null, "Workspaces".getBytes("UTF-8")),
ws.getBytes("UTF-8"));
}
}
catch (Exception e)
{
throw new ConfigurationException("Failed to start Cassandra", e);
}
finally
{
returnKeyspace(ks);
}
}
public void stop(RepositoryImpl rep)
{
}
public void storeCancelled(StoreTransaction tx) throws RepositoryException
{
((CassandraTransaction)tx).close();
}
public void storeFinished(StoreTransaction tx) throws RepositoryException
{
((CassandraTransaction)tx).close();
}
public StoreTransaction storeStarted(WorkspaceImpl ws) throws RepositoryException
{
return new CassandraTransaction(ws);
}
private class CassandraTransaction extends BaseStoreTransaction
{
public Keyspace keyspace;
public CassandraTransaction(WorkspaceImpl ws) throws RepositoryException
{
super(ws);
keyspace = getKeyspace();
}
public void close()
{
returnKeyspace(keyspace);
}
}
}