Custom Function embedded mode

From Blazegraph
Jump to: navigation, search

Introduction

A sample Java application demonstrates using custom functions with Blazegraph in an embedded mode. See Custom Function tutorial for more details.
We will create a new function that checks solutions against an internal security validator. This type of function is useful if you want to limit the visibility of results based on the current user's credentials.
For example, if you have the following dataset:

PREFIX rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX ex: <http://www.example.com/>

ex:document1 a ex:Document ;
		ex:grantedTo ex:John , ex:Mary .
ex:document2 a ex:Document ;
		ex:grantedTo ex:Mary .

We can register a custom function with uri:

<http://www.example.com/validate>

Then we can use this function in a SPARQL query:

SELECT ?doc { 
 ?doc rdf:type <http://www.example.com/Document> .
 filter(<http://www.example.com/validate>(<http://www.example.com/John>, ?doc)) .
}

Download a sample application

You can download sample-customFunction-embedded application on Github here.

Comments

GlobalSecurityValidator

GlobalSecurityValidator at first call caсhes security information in a Map that for each user stores a list of documents granted to him. Validate function uses it to check availability of a document for a user.

public class GlobalSecurityValidator {
	
	protected static final Logger log = Logger.getLogger(GlobalSecurityValidator.class);
	
	private static final String GRANTED_DOCUMENTS = "select ?user ?doc {" + //
						"?doc <http://www.example.com/grantedTo> ?user" + //
						"}";
	
	private final Map<Value, List<Value>> securityInfo = new HashMap<Value, List<Value>>();
	
	
	public GlobalSecurityValidator(final Repository repo) {
		
			final TupleQueryResult result;
			try {
				
				result = Utils.executeSelectQuery(repo, GRANTED_DOCUMENTS, QueryLanguage.SPARQL);
						
				while(result.hasNext()){
					
					 BindingSet bs = result.next();
					
					Binding user = bs.getBinding("user");
					Binding document = bs.getBinding("doc");
										
					if(securityInfo.containsKey(user)){
												
						securityInfo.get(user).add(document.getValue());
						
					}else{
						
						List<Value> docs = new LinkedList<Value>();
						docs.add(document.getValue());
						
						securityInfo.put(user.getValue(), docs);
						
					}
					
				}
			
			} catch (OpenRDFException e) {
				log.error("Security info was not collected", e);
			}
			
			
		} 
	
	
	public boolean validate(final Value user, final Value document){
		
		if(securityInfo.containsKey(user)){
			
			if(securityInfo.get(user).contains(document)){				
				return true;				
			} else {				
				return false;				
			}	
			
		} else {			
			return false;			
		}
		
	}

}

SecurityFilter

A filter function will always evaluate to TRUE (keep the solution) or FALSE (drop the solution). If you are writing a simple boolean filter, you can extends XSDBooleanIVValueExpression.

public class SecurityFilter extends XSDBooleanIVValueExpression 
			implements INeedsMaterialization {

	private GlobalSecurityValidator validator;

	/**
	* Required deep copy constructor.
	* 
	* @param op
	*/
	public SecurityFilter(final SecurityFilter op) {
		super(op);
	}
	
	/**
	* Required shallow copy constructor.
	* 
	* @param args
	*            The function arguments.
	* @param anns
	*            The function annotations.
	*/
	public SecurityFilter(final BOp[] args, final Map<String, Object> anns) {
		super(args, anns);
	}
	
	/**
	* The function needs two pieces of information to operate - the document to check
	* and the user to check against.
	 * @param validator 
	*/
	public SecurityFilter(
	    final IValueExpression<? extends IV> user,
	    final IValueExpression<? extends IV> document,
	    final GlobalAnnotations globals, GlobalSecurityValidator validator) {
	
		this(new BOp[] { user, document }, XSDBooleanIVValueExpression.anns(globals));
		this.validator = validator;
	
	}
	
	@Override
	protected boolean accept(final IBindingSet bset) {
	
		// get the bound term for the ?user var
		final Value user = asValue(getAndCheckBound(0, bset));
		
		// get the bound term for the ?document var
		final Value document = asValue(getAndCheckBound(1, bset));
		
		return validator.validate(user, document);
	
	}

	public Requirement getRequirement() {
		return Requirement.SOMETIMES;
	}
}

Register function in an Embedded Blazegraph Instance

public static void registerCustomFunction(final Repository repo){
		
		final URI myFunctionURI = new URIImpl("http://www.example.com/validate");
		
 		final FunctionRegistry.Factory securityFactory = new FunctionRegistry.Factory() {

 			    public IValueExpression<? extends IV> create(
					BOpContextBase context, GlobalAnnotations globals,
 					Map<String, Object> scalarValues,
					ValueExpressionNode... args) {
				
				
			      // Validate your argument(s)
			      FunctionRegistry.checkArgs(args, ValueExpressionNode.class, ValueExpressionNode.class);

			      // Turn them into physical (executable) bops
			      final IValueExpression<? extends IV> user =  AST2BOpUtility.toVE(context, globals, args[0]);
			      final IValueExpression<? extends IV> document = AST2BOpUtility.toVE(context, globals, args[1]);
						      
			     
			      final GlobalSecurityValidator securityValidator = new GlobalSecurityValidator(repo);			      
			     			      
			      // Return your custom function.
			      return new SecurityFilter(user, document, globals, securityValidator);

			}

		};		
		
		FunctionRegistry.add(myFunctionURI, securityFactory);
		
	}

Test your custom function

public class SampleBlazegraphCustomFunctionEmbedded {
	
	protected static final Logger log = Logger.getLogger(SampleBlazegraphCustomFunctionEmbedded.class);
	
	/*
	 * Select all documents available to <http://www.example.com/John> 
	 */
	public static final String QUERY = "SELECT ?doc " + // 
			"{ ?doc rdf:type <http://www.example.com/Document> . " + //
			" filter(<http://www.example.com/validate>(<http://www.example.com/John>, ?doc)) . }";

	public static void main(String[] args) throws OpenRDFException, IOException {
		
		final Repository repo = createRepository();
		
		registerCustomFunction(repo);
			
		try{
			repo.initialize();
			
			/*
			 * Load data from resources 
			 * src/main/resources/data.n3
			 */
	
			Utils.loadDataFromResources(repo, "data.n3", "");
											
			final TupleQueryResult result = Utils.executeSelectQuery(repo, QUERY, QueryLanguage.SPARQL);
			
			try {
				while(result.hasNext()){
					
					BindingSet bs = result.next();
					log.info(bs);
					
				}
			} finally {
				result.close();
			}
		} finally {
			repo.shutDown();
		}
	}
	
	public static Repository createRepository(){
		
		final Properties props = new Properties();
		props.put(Options.BUFFER_MODE, BufferMode.DiskRW); 
		props.put(Options.FILE, "/tmp/blazegraph/test.jnl"); 
		final BigdataSail sail = new BigdataSail(props);
		final Repository repo = new BigdataSailRepository(sail);
		return repo;
		
	}
	
	public static void registerCustomFunction(final Repository repo){
		
		final URI myFunctionURI = new URIImpl("http://www.example.com/validate");
		
		final FunctionRegistry.Factory securityFactory = new FunctionRegistry.Factory() {

			public IValueExpression<? extends IV> create(
					BOpContextBase context, GlobalAnnotations globals,
					Map<String, Object> scalarValues,
					ValueExpressionNode... args) {
				
				
			      // Validate your argument(s)
			      FunctionRegistry.checkArgs(args, ValueExpressionNode.class, ValueExpressionNode.class);

			      // Turn them into physical (executable) bops
			      final IValueExpression<? extends IV> user =  AST2BOpUtility.toVE(context, globals, args[0]);
			      final IValueExpression<? extends IV> document = AST2BOpUtility.toVE(context, globals, args[1]);
						      
			     
			      final GlobalSecurityValidator securityValidator = new GlobalSecurityValidator(repo);			      
			     			      
			      // Return your custom function.
			      return new SecurityFilter(user, document, globals, securityValidator);

			}

		};		
		
		FunctionRegistry.add(myFunctionURI, securityFactory);
		
	}
}