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.
>>>>
>>>
>>>
>>
>

Reply via email to