Are there any more thoughts on this discussion? If not then I am going to assume this is basically correct and document it on the wiki, build a contract test for this bit, and start on the bits of the Graph interface we have not agreed on yet.
Claude On Sun, Aug 17, 2014 at 11:43 AM, Claude Warren <cla...@xenei.com> wrote: > I think the contract has to cover multi-threaded possibilities. However, > for the most part the document I originally proposed is the view from > within a single thread. > > I agree that graphAdd serves no purpose and go as far as saying it should > be removed in Jena 3. > > Think that defining the add with the listener will clarify the contract, > but we need clarification of the Listener contract later. > > I think that the current process is: > > 1. triple added to or deleted from graph > 2. listeners notified > > I think that this is correct but that we need to add that exceptions in > the listeners may not raise and add denied exception. I believe that the > contract with listeners is: > > 1. they are notified after the event they are listening for has been > completed. That they are not notified if an Exception is thrown in the > add. > 2. if a listener throws an exception it will not undo the add or > delete. > 3. I believe that: #1 means that the listeners would be notified at > the commit of a transaction, so listeners are guaranteed to have messages > queued by the end of the commit (if present) or at the end of add (if no > transaction is present). > > This does lead to the possibility that a graph implementation may need to > notify other components within the transaction that the add or delete was > completed -- I am not certain that this is needed but raise the point here > for further discussion if necessary. > > So the full process for an add is > > 1. begin add( triple ) > 2. if adding is not allowed (Capabilities.addAllowed() returns false) > throw AddDeniedException. > 3. add to the underlying storage system, may throw an exception. > 1. If a checked exception is thrown wrap it in an > AddDeniedException. > 4. if not in a transaction notify listeners of add > 5. end add(triple) > 6. begin commit if in transaction > 7. commit the change so that it is visible to outside of the > transaction. > 8. notify listeners of add. > 9. end commit. > > If that it the case then the full process for a delete is > > 1. begin delete( triple ) > 2. if deleting is not allowed (Capabilities.deleteAllowed() returns > false) throw DeleteDeniedException. > 3. delete from the underlying storage system, may throw an exception. > 1. If a checked exception is thrown wrap it in a > DeleteDeniedException. > 4. if not in a transaction notify listeners of delete > 5. end delete(triple) > 6. begin commit if in transaction > 7. commit the change so that it is visible to outside of the > transaction. > 8. notify listeners of delete. > 9. end commit. > > As for the find process > > 1. returns an ExtendedIterator of triples that match the specified > triple. > 2. If inside a transaction all uncommited triples are candidates for > matching. > > The iterator may throw a ConcurrentModificationException in conditions > outlined by > http://docs.oracle.com/javase/7/docs/api/java/util/ConcurrentModificationException.html > with the following caveat: > > - If the find is taking place within a transaction and the current > thread has not modified the underlying data the > ConcurrentModificationException may not be thrown. > > > Thoughs? > Claude > > > > > > On Mon, Aug 11, 2014 at 6:19 PM, Andy Seaborne <a...@apache.org> wrote: > >> On 08/08/14 22:13, Claude Warren wrote: >> >>> This is a message stack for Graph SPI Contract testing. It covers only >>> the >>> Jena 2 Graph Contract. This an attempt to document the current Graph >>> contract. Any correction should specify the bullet point number. >>> >> >> Overall: >> >> Getting the exact contract is hard and I'm assuming this is only for >> single-threaded code. >> >> Maybe start with a subset of Graph >> >> .add >> .delete >> .find >> >> then add listeners into the picture >> then define other operations in terms of the primitives: >> >> .contains >> .remove >> .clear >> >> Transactions: >> >> The text around transactions does not distinguish being inside or outside >> a transaction. >> >> There are 2 base kinds of graphs - ones in datasets (views) and >> standalone ones, then things like InfGraph and other added functionality. >> Transactions on view graphs need to be defined in the context of the >> dataset because transactions are connected. >> >> >> 1. add() -- technically from GraphAdd >>> >> >> IMO The "GraphAdd" interface serves no purpose. >> >> 1. when a triple is added to a graph all registered listeners must >>> >>> receive an (add graph triple) message >>> >> >> It's hard to define listeners: >> >> Does a listener see the graph before or after the triple is added? >> Is a listener called if AddDeniedException is raised? >> Can a listener cause AddDeniedException to be raised? >> Is the listener guaranted to have been called by the >> time add() returns? >> >> hence the suggestion of starting with just the basic operations. >> >> 2. subsequent graph.contains( triple ) must return true. >>> 3. If add is performed within a transaction the listeners are not >>> >>> notified until after the commit. >>> 4. If graph is read only (Capabilities.addAllowed() returns false) >>> must throw AddDeniedException >>> >> >> 1.1 and 1.2 have "must" text >> >> Surely it's: >> >> Either >> the triple is added >> or >> an AddDeniedException exception is thrown. >> >> 2. clear() >>> >> >> This is like remove(Node.ANY, Node.ANY, Node.ANY) except for the listener >> contract? >> >> 1. If the graph can be empty (Capabilities.canBeEmpty()) there >>> should >>> >>> be no triples returned from find( Triple.ANY ) >>> >> >> Nothing except tests uses Capabilities.canBeEmpty. >> >> 2. If the graph can not be empty there should only be the elements >>> >>> that were present when the graph was created. >>> >> >> This implies part of the contract for create in that create does not take >> initial contents. >> >> Graph g2 = view of g1 >> g1 can not be empty >> >> 3. if delete is not allowed (Capabilities.canDelete() is >>> >>> false) clear() must throw DeleteDeniedException >>> >> >> An alternative is that if clear() causes a change, DeleteDeniedException >> is raised. >> >> Example - if the empty, read-only graph is cleared, why should >> DeleteDeniedException be raised? >> >> There is a relationship to remove(ANY,ANY,ANY) >> >> 3. close() >>> 1. after close isClosed() should return true >>> 2. calling close on closed graph should not throw an exception. >>> 3. calling any Graph method other than close() on a closed graph >>> should throw a ClosedException >>> >> >> Is there a need for close() long term, if not, then the deatiled contract >> is moot. >> >> This form of Graph.close() might work for a basic, storage graph but >> there are other cases. >> >> A graph may be a view of another - close is meaningless and is more >> usefully a no-op. >> >> If the graph is from a system wide cache, close() might be a no-op so as >> to protect the cache. >> >> 4. contains() >>> >> >> Defined as "find(S,P,O).hasNext()" >> >> 1. returns true if the graph contains the specified triple. >>> 1. Node.ANY will match any node in the position. >>> 2. if the graph supports transactions and a transaction is in >>> >>> progress the graph will only not show any triples that only exist >>> within >>> the transaction. >>> >> >> If an app goes: >> >> begin >> add(triple) >> contains(triple) -> false >> >> it's going to be a bit confusing! >> >> 5. delete() >>> 1. if delete is not allowed (Capabilities.canDelete() is false) >>> delete() must throw DeleteDeniedException >>> 2. when a triple is deleted from a graph all registered listeners >>> >>> must receive an (delete graph triple) message >>> 3. subsequent graph.contains( triple ) must return false. >>> 4. If add is performed within a transaction the listeners are not >>> >>> notified until after the commit. >>> >> >> Same listener issues as add() >> >> 6. dependsOn() >>> >> >> What is this used for nowadays? >> >> 1. true if this graph's content depends on the other graph. May be >>> >>> pessimistic (ie return true if it's not sure). Typically true >>> when a graph >>> is a composition of other graphs, eg union. >>> 7. find() >>> 1. returns an iterator of triples that match the specified triple. >>> >> >> And the iterator? >> >> Specifically, there are ConcurrentModificationException issues even in >> single threaded code. >> >> 8. getBulkUpdateHandler() -- deprecated / removed -- no tests >>> 9. getCapabilities() >>> >> >> Aside: Capabilities need clearing up. It's too black-and-white. it can't >> express the totality of possibilities. >> >> Big question: what use does application code make of capabilities? I >> suspect none, or noe except to flag errors. I can't envisage getting a >> graph that says"addAllowed=false" and doign anything but signalling the >> user that they can't do what ever the task is. Yet it's going to have >> ("should have") error handling code anyway. >> >> Maybe it reduces to >> >> Graph.isReadOnly >> >> I'm unconvinced the add/delete distinction matters. I can think of graph >> where there is a difference (append-only) but not of an application that >> adapts based on this other than to say "no, can't". >> >> e.g. >> addAllowed( boolean everyTriple ); >> >> Capabilities.handlesLiteralTyping -- can't say "some, not others" >> >> 1. must not return null. >>> >> >> If we retain the current Capabilities, then we need a way to say "don't >> know". Some of the capabilities are definite yes/no. >> >> e.g addAllowed -- presumably "yes" on most graphs but what if there is a >> security wrapper? Or system resources are >> >> 2. capabilities must match other results. >>> 1. if not addAllowed() , add must throw exception >>> 2. if not deleteAllowed(), >>> 1. delete must throw exception >>> 2. clear must throw exception >>> >> >> clear() of an already empty graph? >> >> 3. if iteratorRemoveAllowed(), iterator from find must allow >>> remove() >>> 4. if canBeEmpty() >>> 1. initial construction must be empty() >>> 2. clear() must be empty. >>> 3. must pass Capabilities contract tests. >>> 10. getEventManager() >>> 1. May not return null >>> 2. Listeners registered with event manager must be notified of >>> changes. >>> 3. EventManager must pass GraphEventManager contract test. >>> 11. getPrefixMapping() >>> 1. May not be null >>> 2. changes to the prefixes managed by the PrefixMapping returned >>> >>> getPrefixMapping() must be reflected in all other PrefixMapping >>> classes >>> from the same graph. >>> >> >> I disagree with the defined contract in javadoc! The "same object" is >> horrible!! >> >> 3. Changes made to a prefix mapping within a transaction are >>> visible >>> >>> outside of the transaction and are not rolled back by the >>> transaction. >>> >> >> !! >> >> 4. PrefixMapping must pass the PrefixMapping contract test >>> 12. getStatisticsHandler() >>> >> >> No longer used. >> >> 1. may be null >>> 2. if not null must pass the GraphStatisticsHandler contract test. >>> 3. all GraphStatisticsHandlers returned must pass handler.equals( >>> handler2 ) >>> 13. getTransactionHandler() >>> 1. may not be null >>> 2. must pass the TransactionHandler contract test. >>> 14. isClosed() >>> 1. must return false when the graph is created. >>> 2. must return true after the close() has been called. >>> 15. isEmpty() >>> 1. must return true when graph is created if >>> Capabilities.canBeEmpty() is true >>> >> >> I don't understand this - a graph may be a view of another soit's not >> empty at the start. >> >> 2. must not return true after triples are added >>> 3. must return true after all triples are deleted if >>> Capabilities.canBeEmpty() is true. >>> 4. must return true after clear() if Capabilities.canBeEmpty() is >>> true. >>> 16. isIsomorphicWith() -- from ( >>> >>> http://www.w3.org/TR/2014/REC-rdf11-concepts-20140225/# >>> section-graph-equality): >>> Two RDF graphs G and G' are isomorphic (that is, they have an >>> identical >>> form) if there is a bijection M between the sets of nodes of the two >>> graphs, such that: >>> 1. M maps blank nodes to blank nodes. >>> 2. M(lit)=lit for all RDF literals lit which are nodes of G. >>> 3. M(iri)=iri for all IRIs iri which are nodes of G. >>> 4. The triple ( s, p, o ) is in G if and only if the triple ( >>> M(s), >>> >>> p, M(o) ) is in G' >>> 17. remove() >>> 1. when a triple is removed from a graph all registered listeners >>> >>> must receive an (remove graph triple) message >>> >> >> remove() removes by pattern >> >> After remove(S,P,O), contains(S,P,O) is false (S/P/O can be Node.ANY) >> >> 2. subsequent graph.contains( triple ) must return false, unless >>> the >>> >>> triple was is in the newly constructed graph and >>> Capabilities.canBeEmpty() >>> is false. >>> 3. If removed is performed within a transaction the listeners are >>> not >>> >>> notified until after the commit. >>> 4. If delete is denied (Capabilities.deleteAllowed() returns >>> false) >>> must throw DeleteDeniedException >>> 18. size() >>> 1. if Capabilities.sizeAccurate() is true >>> 1. if transactions are supported >>> (TransactionHandler.transactionsSupported() is true) >>> 1. the size from within the transaction must function >>> 1. adding a triple must increment the size of the graph. >>> 2. removing a triple must decrement the size of the >>> graph. >>> 2. the size from outside the transaction must not change >>> 2. if transactions are not in >>> supported (TransactionHandler.transactionsSupported() is >>> false) >>> 1. adding a triple must increment the size of the graph. >>> 2. removing a triple must decrement the size of the graph. >>> 2. if Capabilities.sizeAccurate() is false >>> 1. if transactions are supported >>> (TransactionHandler.transactionsSupported() is true) >>> 1. the size from within the transaction must function >>> 1. adding a triple may increment the size of the graph. >>> 2. adding a triple may not decrement the size of the >>> graph. >>> 3. removing a triple may decrement the size of the graph. >>> 4. removing a triple may not increment the size of the >>> graph. >>> 2. the size from outside the transaction must not change >>> 1. adding a triple may not decrement the size of the >>> graph. >>> 2. removing a triple may not increment the size of the >>> graph. >>> 2. if transactions are not in >>> supported (TransactionHandler.transactionsSupported() is >>> false) >>> 1. adding a triple may increment the size of the graph. >>> 2. adding a triple may not decrement the size of the graph. >>> 3. removing a triple may decrement the size of the graph. >>> 4. removing a triple may not increment the size of the >>> graph. >>> >>> >>> >>> Please comment as appropriate. >>> Claude >>> >>> >> > > > -- > I like: Like Like - The likeliest place on the web > <http://like-like.xenei.com> > LinkedIn: http://www.linkedin.com/in/claudewarren > -- I like: Like Like - The likeliest place on the web <http://like-like.xenei.com> LinkedIn: http://www.linkedin.com/in/claudewarren