Thank you Rob/Andy.
Rob your code outline worked!
Andy I'd considered the part commit and the final code will have the option
to wrap the SPARUL update operations in a transaction. If wrapped I'll
create a custom UpdateWorker where the visit() is effectively a stub which
checks the operation for permit/deny. Effectively I'll call the execute
twice with the first pass atomically permitting or denying the operations.
For anyone visiting this thread I've included my r&d class which includes a
TDB quad filter (based on regex patterns) and SPARUL update operation
filter (based on operation type).
public class SPARUL {
private static Logger logger = Logger.getLogger(SPARUL.class);
final static Boolean PERMIT = true;
final static Boolean DENY = false;
/**
* @param args
*/
public static void main(String[] args) {
BasicConfigurator.configure();
PropertyConfigurator.configure("./log4j.properties");
new SPARUL().go();
}
// Instance.
static final String graphToHide = "http://example/g2";
public void go() {
UpdateEngineRegistry.get().add(new CustomUpdateEngineFactory());
Dataset ds = TDBFactory.createDataset() ;
DatasetGraph dsg = ds.asDatasetGraph() ;
Quad q1 = SSE.parseQuad("(<http://example/g1> <http://example/s> <
http://example/p> <http://example/o1>)") ;
Quad q2 = SSE.parseQuad("(<http://example/g2> <http://example/s> <
http://example/p> <http://example/o2>)") ;
dsg.add(q1) ;
dsg.add(q2) ;
final Node graphAsNode = Node.createURI(graphToHide);
final NodeId graphAsNodeId = TDBInternal.getNodeId(ds, graphAsNode);
TDBInternal.getNode(ds, graphAsNodeId);
// graph.
// Filter<Tuple<NodeId>> filter = new CustomFilter(graph);
List<ACLEntry> acl = new ArrayList<ACLEntry>();
acl.add(new ACLEntry(PERMIT, "^http://example/g1$"));
acl.add(new ACLEntry(DENY, "^http://example/g2$"));
Filter<Tuple<NodeId>> filter = new ACLFilter(dsg, acl);
String qs = "select * { graph ?g { ?s ?p ?o } }";
Query query = QueryFactory.create(qs) ;
QueryExecution qExec = QueryExecutionFactory.create(query, ds) ;
qExec.getContext().set(SystemTDB.symTupleFilter, filter) ;
ResultSetFormatter.out(qExec.execSelect()) ;
qExec.close() ;
GraphStore gs = GraphStoreFactory.create(ds);
UpdateRequest ur = UpdateFactory.create();
ur.add("drop all");
ur.add("create graph <http://example/g2>");
// ur.add("load <file:etc/update-data.ttl> into <http://example/g2>")
;
UpdateAction.execute(ur, gs);
}
class ACLEntry {
Boolean permit;
Pattern graph, subject, predicate, object;
public ACLEntry(Boolean permit, String graph) {
this.permit = permit;
this.graph = Pattern.compile(graph);
}
}
class ACLFilter implements Filter<Tuple<NodeId>> {
DatasetGraph dsg;
List<ACLEntry> acl;
public ACLFilter(DatasetGraph dsg, List<ACLEntry> acl) {
this.dsg = dsg;
this.acl = acl;
}
@Override
public boolean accept(Tuple<NodeId> item) {
Boolean result = DENY;
if ( item.size() == 4) {
final NodeId nodeId = item.get(0);
final Node node = TDBInternal.getNode(dsg, nodeId);
for (ACLEntry e : acl) {
if (e.graph.matcher(node.toString()).matches()) {
result = e.permit;
break;
}
}
}
return result;
}
}
class CustomFilter implements Filter<Tuple<NodeId>> {
NodeId target;
public CustomFilter(NodeId target) {
this.target = target;
}
@Override
public boolean accept(Tuple<NodeId> item) {
if ( item.size() == 4 && item.get(0).equals(target) )
{
return false ;
}
return true ;
}
}
class CustomUpdateEngineFactory implements UpdateEngineFactory {
@Override
public boolean accept(UpdateRequest request, GraphStore graphStore,
Context context) {
return true;
}
@Override
public UpdateEngine create(UpdateRequest request, GraphStore
graphStore, Binding inputBinding, Context context) {
return new CustomUpdateEngine(graphStore, request,
inputBinding, context);
}
}
class CustomUpdateEngine extends UpdateEngineBase {
public CustomUpdateEngine(GraphStore graphStore, UpdateRequest
request, Binding input, Context context) {
super(graphStore, request, input, context);
}
@Override
public void execute() {
graphStore.startRequest(request);
CustomUpdateEngineWorker worker = new
CustomUpdateEngineWorker(graphStore, startBinding, context) ;
for ( Update up : request ) {
up.visit(worker) ;
}
graphStore.finishRequest(request) ;
}
}
class CustomUpdateEngineWorker extends UpdateEngineWorker {
public CustomUpdateEngineWorker(GraphStore graphStore, Binding
initialBinding, Context context) {
super(graphStore, initialBinding, context);
}
@Override
public void visit(UpdateDrop update) {
logger.info(String.format("visit(%s)", update));
super.visit(update);
}
}
}
On 29 October 2012 21:02, Andy Seaborne <[email protected]> wrote:
> There is a slightly tricky point here - if you deny an operations, then
> partial operation get done. That's OK on a transactional system - it simply
> aborts - but not if it isn't transaction storage. It might be better to
> asses the update before dispatching it to the execution engine.
>
> That can be done with Rob's suggestion - do the whole of the request
> before any execution.
>
> Andy
>
>
>
>
> On 29/10/12 18:02, Rob Vesse wrote:
>
>> Re Step 2 - I just made a commit to trunk so that with the latest code you
>> can extend UpdateEngineMain and simply override the protected
>> prepareWorker() method to return your custom UpdateVisitor rather than the
>> default UpdateEngineWorker I.e. this avoids the need to implement the
>> execute() method yourself
>>
>>
>> Also in Step 3 you should be extending from UpdateEngineWorker rather than
>> the non-existent UpdateWorker
>>
>> Hope this helps,
>>
>> Rob
>>
>> On 10/29/12 9:53 AM, "Rob Vesse" <[email protected]> wrote:
>>
>> Hey Dick
>>>
>>> Yes we do this in our product in a production environment to replace the
>>> standard update handling with our own completely custom one
>>>
>>> Your desired extension is actually even easier than ours, extending
>>> Update
>>> evaluation basically requires you to do three things as follows.
>>>
>>> 1 - Create and register a custom UpdateEngineFactory
>>>
>>> Create a class that implements the UpdateEngineFactory interface, this
>>> has
>>> two methods for you to implement. Simply return true for the accept()
>>> method to indicate you wish to handle all updates and then for the
>>> create() method return a new instance of the class you create in Step 2
>>>
>>> Your code will need to ensure that this factory gets registered by
>>> calling
>>> UpdateEngineRegistry.add(new CustomUpdateEngineFactory()); in order for
>>> your code to intercept updates.
>>>
>>> 2 - Create a custom UpdateEngine
>>>
>>> Create a class that extends from UpdateEngineBase and implements the
>>> abstract execute() method, you can simply modify the default
>>> implementation found in UpdateEngineMain like so:
>>>
>>> @Override
>>> public void execute()
>>> {
>>> graphStore.startRequest(**request) ;
>>> CustomUpdateEngineWorker worker = new
>>> CustomUpdateEngineWorker(**graphStore, startBinding, context) ;
>>> for ( Update up : request ) {
>>> up.visit(worker) ;
>>> }
>>> graphStore.finishRequest(**request) ;
>>> }
>>>
>>>
>>> 3 - Create your custom UpdateVisitor
>>>
>>> Create a class that extends from UpdateWorker, this is the class you are
>>> referencing as CustomUpdateEngineWorker from Step 2, I assume you pick a
>>> more appropriate name. Then you simply override the methods that you
>>> want
>>> to add access control functionality to like so:
>>>
>>> @Override
>>> public void visit(UpdateCreate update)
>>> {
>>> if (deny(args)) {
>>> //Handle the error case
>>> } else {
>>> //Otherwise defer to normal logic
>>> super.visit(update);
>>> }
>>> }
>>>
>>> Hope this helps,
>>>
>>> Rob
>>>
>>>
>>>
>>> On 10/29/12 6:43 AM, "Dick Murray" <[email protected]> wrote:
>>>
>>> Hi all
>>>>
>>>> I need to permit/deny certain SPARUL update operations e.g. deny create|
>>>> drop graph.
>>>>
>>>> I've looked at the UpdateEngineMain and UpdateVisitor classes and was
>>>> wondering if anyone has extended or encapsulated these before? Ideally
>>>> I'd
>>>> like to capture the "visit" just prior to the actual visit.
>>>>
>>>> i.e the UpdateEngineWorker has...
>>>>
>>>> @Override
>>>> public void visit(UpdateCreate update)
>>>> {
>>>> Node g = update.getGraph() ;
>>>> if ( g == null )
>>>> return ;
>>>> if ( graphStore.containsGraph(g) )
>>>> {
>>>> if ( ! alwaysSilent && ! update.isSilent() )
>>>> error("Graph store already contains graph : "+g) ;
>>>> return ;
>>>> }
>>>> // In-memory specific
>>>> graphStore.addGraph(g, GraphFactory.**createDefaultGraph()) ;
>>>> }
>>>>
>>>> ...and I need a...
>>>>
>>>> if (deny(...)) {
>>>> error("update create denied");
>>>> return;
>>>>
>>>> Also need it too work whether the graphStore was from a Dataset or
>>>> TDB...
>>>> ideally... :-)
>>>>
>>>> Regards Dick.
>>>>
>>>
>>>
>>
>