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

io.continual.services.model.service.impl.cassandra.CassModel Maven / Gradle / Ivy

There is a newer version: 0.2.27
Show newest version
/*
 *	Copyright 2019, Continual.io
 *
 *	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 io.continual.services.model.service.impl.cassandra;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.json.JSONException;
import org.json.JSONObject;

import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;

import io.continual.iam.access.AccessControlList;
import io.continual.services.model.core.ModelObject;
import io.continual.services.model.core.ModelObjectPath;
import io.continual.services.model.core.ModelObjectUpdater;
import io.continual.services.model.core.ModelOperation;
import io.continual.services.model.core.ModelRequestContext;
import io.continual.services.model.core.exceptions.ModelItemDoesNotExistException;
import io.continual.services.model.core.exceptions.ModelServiceIoException;
import io.continual.services.model.core.exceptions.ModelServiceRequestException;
import io.continual.services.model.service.Model;
import io.continual.services.model.service.ModelObjectContainer;
import io.continual.services.model.service.ModelRelation;
import io.continual.util.data.json.CommentedJsonTokener;
import io.continual.util.data.json.JsonUtil;
import io.continual.util.naming.Name;
import io.continual.util.naming.Path;

public class CassModel extends CassObjectContainer implements Model
{
	public static String getKeyspaceNameFor ( String acctId, String modelName )
	{
		return "model_" + acctId + "_" + modelName;
	}

	public static String getObjectTableName ( String acctId, String modelName )
	{
		return getKeyspaceNameFor ( acctId, modelName ) + ".objects";
	}

	public static String getRelationTableName ( String acctId, String modelName )
	{
		return getKeyspaceNameFor ( acctId, modelName ) + ".relns";
	}

	public static CassModel fromJson ( JSONObject object, CassModelLoaderContext mlc ) throws ModelServiceIoException
	{
		return new CassModel ( mlc, object );
	}

	CassModel ( CassModelLoaderContext bc, JSONObject data )
	{
		super ( bc, data );
	}

	@Override
	public String getId ()
	{
		return Path.getRootPath ()
			.makeChildItem ( Name.fromString ( getAcctId() ) )
			.makeChildItem ( Name.fromString ( getModelName () ) )
			.toString ()
		;
	}

	@Override
	public String getResourceDescription ()
	{
		return "Model " + getId ();
	}


	@Override
	public boolean exists ( ModelRequestContext context, Path id ) throws ModelServiceIoException, ModelServiceRequestException
	{
		final ModelObjectPath mop = pathToFullPath ( id );
		if ( context.knownToNotExist ( mop ) ) return false;

		final String keyspace = getKeyspaceNameFor ( mop.getAcctId (), mop.getModelName () );

		final ResultSet rs = getBaseContext().getModelService ().runQuery ( "SELECT data FROM " + keyspace + ".objects WHERE path=?", id.toString () );
		final List rows = rs.all ();
		final boolean exists = !rows.isEmpty ();
		if ( !exists )
		{
			context.knownToNotExist ( mop );
		}
		return exists;
	}

	@Override
	public boolean exists ( ModelRequestContext context, Name itemName ) throws ModelServiceIoException, ModelServiceRequestException
	{
		return exists ( context, Path.getRootPath ().makeChildItem ( itemName ) );
	}

	@Override
	public CassElementList getElementsBelow ( ModelRequestContext context ) throws ModelServiceRequestException, ModelServiceIoException
	{
		return getElementsBelow ( context, Path.getRootPath () );
	}

	@Override
	public ModelObjectContainer load ( ModelRequestContext context, Path id ) throws ModelItemDoesNotExistException, ModelServiceRequestException, ModelServiceIoException
	{
		// requesting the root node? return this model.
		if ( id == null || id.getParentPath () == null )
		{
			return this;
		}

		// otherwise, load the parent node, then load the item from there
		return load ( context, id.getParentPath () )
			.load ( context, id.getItemName () )
		;
	}

	@Override
	public void store ( ModelRequestContext context, Path id, ModelObject o )
		throws ModelServiceRequestException, ModelServiceIoException
	{
		if ( id == null || id.getParentPath () == null )
		{
			throw new ModelServiceRequestException ( "You cannot store to the model node." );
		}

		final Path parentPath = id.getParentPath ();
		final ModelObjectContainer parent = load ( context, parentPath );
		parent.store ( context, id.getItemName (), o );
	}

	@Override
	public void store ( ModelRequestContext context, Path id, String json )
		throws ModelServiceRequestException, ModelServiceIoException
	{
		try
		{
			new JSONObject ( new CommentedJsonTokener ( json ) );
		}
		catch ( JSONException e )
		{
			throw new ModelServiceRequestException ( e );
		}

		final JSONObject objData = new JSONObject ( new CommentedJsonTokener (
			CassModelObject.createBasicObjectJson ( context.getOperator ().getId () )
		) );
		JsonUtil.copyInto ( new JSONObject ( new CommentedJsonTokener ( json ) ), objData );

		store ( context, id, new ModelObject () {

			@Override
			public AccessControlList getAccessControlList ()
			{
				// TODO Auto-generated method stub
				// FIXME
				return null;
			}

			@Override
			public String getId ()
			{
				// TODO Auto-generated method stub
				// FIXME
				return null;
			}

			@Override
			public String asJson () { return objData.toString (); }

			@Override
			public Set getTypes ()
			{
				return new TreeSet<> ();
			}

			@Override
			public JSONObject getData ()
			{
				// TODO Auto-generated method stub
				return null;
			}
		} );
	}

	@Override
	public void update ( ModelRequestContext context, Path id, ModelObjectUpdater updater )
		throws ModelServiceRequestException, ModelServiceIoException
	{
		final ModelObject toUpdate;
		if ( exists ( context, id ) )
		{
			toUpdate = load ( context, id );
		}
		else
		{
			final String json = CassModelObject.createBasicObjectJson ( context.getOperator ().toString () );

			toUpdate = new ModelObject ()
			{
				@Override
				public AccessControlList getAccessControlList ()
				{
					// TODO Auto-generated method stub
					// FIXME
					return null;
				}

				@Override
				public String getId ()
				{
					// TODO Auto-generated method stub
					// FIXME
					return null;
				}

				@Override
				public String asJson ()
				{
					return json;
				}

				@Override
				public Set getTypes ()
				{
					return new TreeSet<> ();
				}

				@Override
				public JSONObject getData ()
				{
					// TODO Auto-generated method stub
					return null;
				}
			};
		}
		final ModelObject resultingObject = updater.update ( toUpdate );
		store ( context, id, resultingObject );
	}

	@Override
	public boolean remove ( ModelRequestContext context, Path id )
		throws ModelServiceIoException, ModelServiceRequestException
	{
		// remove this item by asking its parent to remove it
		return load ( context, id.getParentPath () )
			.remove ( context, id.getItemName () )
		;
	}

	@Override
	public void relate ( ModelRequestContext context, ModelRelation reln ) throws ModelServiceIoException, ModelServiceRequestException
	{
		final LinkedList relns = new LinkedList<> ();
		relns.add ( reln );
		relate ( context, relns );
	}

	@Override
	public void relate ( ModelRequestContext context, Collection relns ) throws ModelServiceIoException, ModelServiceRequestException
	{
		// check validity of inbound relations
		for ( ModelRelation reln : relns )
		{
			if (
				!reln.getFrom ().getAcctId ().equals ( getAcctId () ) ||
				!reln.getTo ().getAcctId ().equals ( getAcctId () ) ||
				!reln.getFrom ().getModelName ().equals ( getModelName () ) ||
				!reln.getTo ().getModelName ().equals ( getModelName () )
			)
			{
				throw new ModelServiceRequestException ( "A relation may not span models." );
			}

			final ModelObjectContainer from = load ( context, reln.getFrom ().getObjectPath () );
			final ModelObjectContainer to = load ( context, reln.getTo ().getObjectPath () );

			from.checkUser ( context.getOperator (), ModelOperation.UPDATE );
			to.checkUser ( context.getOperator (), ModelOperation.UPDATE );
		}

		// now run the relation creation
		final String keyspace = getKeyspaceNameFor ( getAcctId (), getModelName () );
		final CassandraModelService svc = getBaseContext().getModelService ();
		for ( ModelRelation reln : relns )
		{
			final String rev = buildReverseEntry ( reln.getTo ().getObjectPath (), reln.getName () );
			svc.runQuery (
				"INSERT INTO " + keyspace + ".relations ( fromPath, relation, toPath, reversed ) VALUES ( ?, ?, ?, ? )",
				reln.getFrom ().getObjectPath ().toString (),
				reln.getName (),
				reln.getTo ().getObjectPath ().toString (),
				rev
			);
		}
	}

	@Override
	public boolean unrelate ( ModelRequestContext context, ModelRelation reln ) throws ModelServiceIoException, ModelServiceRequestException
	{
		final String keyspace = getKeyspaceNameFor ( getAcctId (), getModelName () );
		final CassandraModelService svc = getBaseContext().getModelService ();

		final ResultSet rs = svc.runQuery (
			"DELETE FROM " + keyspace + ".relations WHERE fromPath=?, relation=?, toPath=?",
			reln.getFrom ().getObjectPath ().toString (),
			reln.getName (),
			reln.getTo ().getObjectPath ().toString ()
		);

		return rs.wasApplied ();
	}

	@Override
	public List getRelations ( ModelRequestContext context, Path forObject ) throws ModelServiceIoException, ModelServiceRequestException
	{
		// this has to be run as two queries because Cassandra has no "OR" operator.
		final LinkedList result = new LinkedList<> ();
		result.addAll ( getInboundRelations ( context, forObject ) );
		result.addAll ( getOutboundRelations ( context, forObject ) );
		return result;
	}

	@Override
	public List getInboundRelations ( ModelRequestContext context, Path forObject ) throws ModelServiceIoException, ModelServiceRequestException
	{
		if ( !exists ( context, forObject ) )
		{
			throw new ModelItemDoesNotExistException ( pathToFullPath ( forObject ) );
		}
		
		final String keyspace = getKeyspaceNameFor ( getAcctId (), getModelName () );
		final CassandraModelService svc = getBaseContext().getModelService ();

		final ResultSet rs = svc.runQuery (
			"SELECT * FROM " + keyspace + ".relations WHERE toPath=?",
			forObject.toString ()
		);
		return rowSetToRelationList ( rs );
	}

	@Override
	public List getOutboundRelations ( ModelRequestContext context, final Path forObject ) throws ModelServiceIoException, ModelServiceRequestException
	{
		if ( !exists ( context, forObject ) )
		{
			throw new ModelItemDoesNotExistException ( pathToFullPath ( forObject ) );
		}

		final String keyspace = getKeyspaceNameFor ( getAcctId (), getModelName () );
		final CassandraModelService svc = getBaseContext().getModelService ();

		final ResultSet rs = svc.runQuery (
			"SELECT * FROM " + keyspace + ".relations WHERE fromPath=?",
			forObject.toString ()
		);
		return rowSetToRelationList ( rs );
	}

	@Override
	public List getInboundRelationsNamed ( ModelRequestContext context, Path forObject, String named ) throws ModelServiceIoException, ModelServiceRequestException
	{
		if ( !exists ( context, forObject ) )
		{
			throw new ModelItemDoesNotExistException ( pathToFullPath ( forObject ) );
		}

		final String keyspace = getKeyspaceNameFor ( getAcctId (), getModelName () );
		final CassandraModelService svc = getBaseContext().getModelService ();

		final ResultSet rs = svc.runQuery (
			"SELECT * FROM " + keyspace + ".relations WHERE reversed=?",
			buildReverseEntry ( forObject, named )
		);
		return rowSetToRelationList ( rs );
	}

	@Override
	public List getOutboundRelationsNamed ( ModelRequestContext context, Path forObject, String named ) throws ModelServiceIoException, ModelServiceRequestException
	{
		if ( !exists ( context, forObject ) )
		{
			throw new ModelItemDoesNotExistException ( pathToFullPath ( forObject ) );
		}

		final String keyspace = getKeyspaceNameFor ( getAcctId (), getModelName () );
		final CassandraModelService svc = getBaseContext().getModelService ();

		final ResultSet rs = svc.runQuery (
			"SELECT * FROM " + keyspace + ".relations WHERE fromPath=? AND relation=?",
			forObject.toString (),
			named
		);
		return rowSetToRelationList ( rs );
	}

	@Override
	JSONObject getLocalData ()
	{
		return new JSONObject ();
	}

	@Override
	public String asJson ()
	{
		return toJson().toString ();
	}

	private static String buildReverseEntry ( Path objectPath, String name )
	{
		return new StringBuilder ()
			.append ( objectPath.toString () )
			.append ( "::" )
			.append ( name )
			.toString ()
		;
	}

	private List rowSetToRelationList ( ResultSet rs )
	{
		final LinkedList result = new LinkedList<> ();
		for ( Row row : rs )
		{
			final ModelObjectPath from = new ModelObjectPath ( getAcctId(), getModelName(), Path.fromString ( row.getString ( "fromPath" ) ) );
			final ModelObjectPath to = new ModelObjectPath ( getAcctId(), getModelName(), Path.fromString ( row.getString ( "toPath" ) ) );
			final String reln = row.getString ( "relation" );
			
			result.add ( new ModelRelation ()
			{
				@Override
				public ModelObjectPath getFrom () { return from; }

				@Override
				public ModelObjectPath getTo () { return to; }

				@Override
				public String getName () { return reln; }
			} );
		}
		return result;
	}

	@Override
	public Set getTypes ()
	{
		return new TreeSet<> ();
	}

	@Override
	public JSONObject getData ()
	{
		// TODO Auto-generated method stub
		return null;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy