event notification for RDF API and change to model setup for SimpleReasoner
This commit is contained in:
parent
964cb1bdb4
commit
f55916b8d8
12 changed files with 245 additions and 14 deletions
|
@ -0,0 +1,136 @@
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.dao.jena;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import com.hp.hpl.jena.graph.BulkUpdateHandler;
|
||||||
|
import com.hp.hpl.jena.graph.Capabilities;
|
||||||
|
import com.hp.hpl.jena.graph.Graph;
|
||||||
|
import com.hp.hpl.jena.graph.GraphEventManager;
|
||||||
|
import com.hp.hpl.jena.graph.GraphStatisticsHandler;
|
||||||
|
import com.hp.hpl.jena.graph.Node;
|
||||||
|
import com.hp.hpl.jena.graph.Reifier;
|
||||||
|
import com.hp.hpl.jena.graph.TransactionHandler;
|
||||||
|
import com.hp.hpl.jena.graph.Triple;
|
||||||
|
import com.hp.hpl.jena.graph.TripleMatch;
|
||||||
|
import com.hp.hpl.jena.graph.query.QueryHandler;
|
||||||
|
import com.hp.hpl.jena.shared.AddDeniedException;
|
||||||
|
import com.hp.hpl.jena.shared.DeleteDeniedException;
|
||||||
|
import com.hp.hpl.jena.shared.PrefixMapping;
|
||||||
|
import com.hp.hpl.jena.util.iterator.ExtendedIterator;
|
||||||
|
import com.hp.hpl.jena.util.iterator.WrappedIterator;
|
||||||
|
|
||||||
|
public class DifferenceGraph implements Graph {
|
||||||
|
|
||||||
|
private Graph g;
|
||||||
|
private Graph subtract;
|
||||||
|
|
||||||
|
public DifferenceGraph(Graph g, Graph subtract) {
|
||||||
|
this.g = g;
|
||||||
|
this.subtract = subtract;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
// not clear what the best behavior here is
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Triple arg0) {
|
||||||
|
return g.contains(arg0) && !subtract.contains(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Node arg0, Node arg1, Node arg2) {
|
||||||
|
return g.contains(arg0, arg1, arg2) && !subtract.contains(arg0, arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(Triple arg0) throws DeleteDeniedException {
|
||||||
|
g.delete(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean dependsOn(Graph arg0) {
|
||||||
|
return g.dependsOn(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExtendedIterator<Triple> find(TripleMatch arg0) {
|
||||||
|
Set<Triple> tripSet = g.find(arg0).toSet();
|
||||||
|
tripSet.removeAll(subtract.find(arg0).toSet());
|
||||||
|
return WrappedIterator.create(tripSet.iterator());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExtendedIterator<Triple> find(Node arg0, Node arg1, Node arg2) {
|
||||||
|
Set<Triple> tripSet = g.find(arg0, arg1, arg2).toSet();
|
||||||
|
tripSet.removeAll(subtract.find(arg0, arg1, arg2).toSet());
|
||||||
|
return WrappedIterator.create(tripSet.iterator());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BulkUpdateHandler getBulkUpdateHandler() {
|
||||||
|
return g.getBulkUpdateHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Capabilities getCapabilities() {
|
||||||
|
return g.getCapabilities();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GraphEventManager getEventManager() {
|
||||||
|
return g.getEventManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrefixMapping getPrefixMapping() {
|
||||||
|
return g.getPrefixMapping();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reifier getReifier() {
|
||||||
|
return g.getReifier();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GraphStatisticsHandler getStatisticsHandler() {
|
||||||
|
return g.getStatisticsHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransactionHandler getTransactionHandler() {
|
||||||
|
return g.getTransactionHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() {
|
||||||
|
return g.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return g.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isIsomorphicWith(Graph arg0) {
|
||||||
|
return g.isIsomorphicWith(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryHandler queryHandler() {
|
||||||
|
return g.queryHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return g.size() - subtract.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(Triple arg0) throws AddDeniedException {
|
||||||
|
g.add(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -109,7 +109,8 @@ public class IndividualDaoJena extends JenaBaseDao implements IndividualDao {
|
||||||
public void removeVClass(String individualURI, String vclassURI) {
|
public void removeVClass(String individualURI, String vclassURI) {
|
||||||
OntModel ontModel = getOntModelSelector().getABoxModel();
|
OntModel ontModel = getOntModelSelector().getABoxModel();
|
||||||
ontModel.enterCriticalSection(Lock.WRITE);
|
ontModel.enterCriticalSection(Lock.WRITE);
|
||||||
ontModel.getBaseModel().notifyEvent(new IndividualUpdateEvent(getWebappDaoFactory().getUserURI(),true,individualURI));
|
Object event = new IndividualUpdateEvent(getWebappDaoFactory().getUserURI(),true,individualURI);
|
||||||
|
ontModel.getBaseModel().notifyEvent(event);
|
||||||
try {
|
try {
|
||||||
Resource indRes = ontModel.getResource(individualURI);
|
Resource indRes = ontModel.getResource(individualURI);
|
||||||
getOntModel().remove(indRes, RDF.type, ontModel.getResource(vclassURI));
|
getOntModel().remove(indRes, RDF.type, ontModel.getResource(vclassURI));
|
||||||
|
|
|
@ -42,6 +42,7 @@ public class JenaChangeListener implements ChangeListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void notifyEvent(String graphURI, Object event) {
|
public void notifyEvent(String graphURI, Object event) {
|
||||||
|
log.debug("event: " + event.getClass());
|
||||||
listener.notifyEvent(m, event);
|
listener.notifyEvent(m, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ public class RDFServiceDataset implements Dataset {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Model getDefaultModel() {
|
public Model getDefaultModel() {
|
||||||
return ModelFactory.createModelForGraph(g.getDefaultGraph());
|
return RDFServiceGraph.createRDFServiceModel(g.getDefaultGraph());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -51,7 +51,7 @@ public class RDFServiceDataset implements Dataset {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Model getNamedModel(String arg0) {
|
public Model getNamedModel(String arg0) {
|
||||||
return ModelFactory.createModelForGraph(g.getGraph(Node.createURI(arg0)));
|
return RDFServiceGraph.createRDFServiceModel(g.getGraph(Node.createURI(arg0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -161,12 +161,12 @@ public class RDFServiceDatasetGraph implements DatasetGraph {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Graph getDefaultGraph() {
|
public RDFServiceGraph getDefaultGraph() {
|
||||||
return new RDFServiceGraph(rdfService);
|
return new RDFServiceGraph(rdfService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Graph getGraph(Node arg0) {
|
public RDFServiceGraph getGraph(Node arg0) {
|
||||||
return new RDFServiceGraph(rdfService, arg0.getURI());
|
return new RDFServiceGraph(rdfService, arg0.getURI());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,9 @@ import com.hp.hpl.jena.graph.query.QueryHandler;
|
||||||
import com.hp.hpl.jena.graph.query.SimpleQueryHandler;
|
import com.hp.hpl.jena.graph.query.SimpleQueryHandler;
|
||||||
import com.hp.hpl.jena.query.QuerySolution;
|
import com.hp.hpl.jena.query.QuerySolution;
|
||||||
import com.hp.hpl.jena.query.ResultSet;
|
import com.hp.hpl.jena.query.ResultSet;
|
||||||
|
import com.hp.hpl.jena.rdf.listeners.StatementListener;
|
||||||
|
import com.hp.hpl.jena.rdf.model.Model;
|
||||||
|
import com.hp.hpl.jena.rdf.model.ModelFactory;
|
||||||
import com.hp.hpl.jena.shared.AddDeniedException;
|
import com.hp.hpl.jena.shared.AddDeniedException;
|
||||||
import com.hp.hpl.jena.shared.DeleteDeniedException;
|
import com.hp.hpl.jena.shared.DeleteDeniedException;
|
||||||
import com.hp.hpl.jena.shared.PrefixMapping;
|
import com.hp.hpl.jena.shared.PrefixMapping;
|
||||||
|
@ -434,4 +437,22 @@ public class RDFServiceGraph implements GraphWithPerform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Model createRDFServiceModel(final RDFServiceGraph g) {
|
||||||
|
Model m = ModelFactory.createModelForGraph(g);
|
||||||
|
m.register(new StatementListener() {
|
||||||
|
@Override
|
||||||
|
public void notifyEvent(Model m, Object event) {
|
||||||
|
ChangeSet changeSet = g.getRDFService().manufactureChangeSet();
|
||||||
|
changeSet.addPreChangeEvent(event);
|
||||||
|
try {
|
||||||
|
g.getRDFService().changeSetUpdate(changeSet);
|
||||||
|
} catch (RDFServiceException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,4 +86,34 @@ public interface ChangeSet {
|
||||||
RDFService.ModelSerializationFormat serializationFormat,
|
RDFService.ModelSerializationFormat serializationFormat,
|
||||||
ModelChange.Operation operation,
|
ModelChange.Operation operation,
|
||||||
String graphURI);
|
String graphURI);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an event that will be be passed to any change listeners in advance of
|
||||||
|
* the change set additions and retractions being performed. The event
|
||||||
|
* will only be fired if the precondition (if any) is met.
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
public void addPreChangeEvent(Object event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an event that will be be passed to any change listeners after all of
|
||||||
|
* the change set additions and retractions are performed.
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
public void addPostChangeEvent(Object event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a list of events to pass to any change listeners in
|
||||||
|
* advance of the change set additions and retractions being performed.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<Object> getPreChangeEvents();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a list of events to pass to any change listeners after
|
||||||
|
* the change set additions and retractions are performed.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<Object> getPostChangeEvents();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,10 @@ public class ChangeSetImpl implements ChangeSet {
|
||||||
|
|
||||||
private String preconditionQuery;
|
private String preconditionQuery;
|
||||||
private RDFService.SPARQLQueryType queryType;
|
private RDFService.SPARQLQueryType queryType;
|
||||||
private ArrayList<ModelChange> modelChanges;
|
private ArrayList<ModelChange> modelChanges = new ArrayList<ModelChange>();
|
||||||
|
private ArrayList<Object> preChangeEvents = new ArrayList<Object>();
|
||||||
|
private ArrayList<Object> postChangeEvents = new ArrayList<Object>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Getter for the precondition query
|
* Getter for the precondition query
|
||||||
*
|
*
|
||||||
|
@ -122,4 +124,25 @@ public class ChangeSetImpl implements ChangeSet {
|
||||||
String graphURI) {
|
String graphURI) {
|
||||||
return new ModelChangeImpl(serializedModel, serializationFormat, operation, graphURI);
|
return new ModelChangeImpl(serializedModel, serializationFormat, operation, graphURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addPreChangeEvent(Object o) {
|
||||||
|
this.preChangeEvents.add(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addPostChangeEvent(Object o) {
|
||||||
|
this.postChangeEvents.add(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Object> getPreChangeEvents() {
|
||||||
|
return this.preChangeEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Object> getPostChangeEvents() {
|
||||||
|
return this.postChangeEvents;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,6 +143,17 @@ public abstract class RDFServiceImpl implements RDFService {
|
||||||
listener.removedStatement(sparqlTriple(triple).toString(), graphURI);
|
listener.removedStatement(sparqlTriple(triple).toString(), graphURI);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void notifyListenersOfEvent(Object event) {
|
||||||
|
|
||||||
|
Iterator<ChangeListener> iter = registeredListeners.iterator();
|
||||||
|
|
||||||
|
while (iter.hasNext()) {
|
||||||
|
ChangeListener listener = iter.next();
|
||||||
|
// TODO what is the graphURI parameter for?
|
||||||
|
listener.notifyEvent(null, event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isPreconditionSatisfied(String query,
|
protected boolean isPreconditionSatisfied(String query,
|
||||||
|
|
|
@ -124,9 +124,15 @@ public class RDFServiceSDB extends RDFServiceImpl implements RDFService {
|
||||||
dataset.getLock().leaveCriticalSection();
|
dataset.getLock().leaveCriticalSection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (Object o : changeSet.getPreChangeEvents()) {
|
||||||
|
this.notifyListenersOfEvent(o);
|
||||||
|
}
|
||||||
if (transaction) {
|
if (transaction) {
|
||||||
conn.getTransactionHandler().commit();
|
conn.getTransactionHandler().commit();
|
||||||
}
|
}
|
||||||
|
for (Object o : changeSet.getPostChangeEvents()) {
|
||||||
|
this.notifyListenersOfEvent(o);
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(e, e);
|
log.error(e, e);
|
||||||
if (transaction) {
|
if (transaction) {
|
||||||
|
@ -404,10 +410,12 @@ public class RDFServiceSDB extends RDFServiceImpl implements RDFService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addedStatement(Statement stmt) {
|
public void addedStatement(Statement stmt) {
|
||||||
|
log.debug("adding " + stmt + " to " + graphURI);
|
||||||
s.notifyListeners(stmt.asTriple(), ModelChange.Operation.ADD, graphURI);
|
s.notifyListeners(stmt.asTriple(), ModelChange.Operation.ADD, graphURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removedStatement(Statement stmt) {
|
public void removedStatement(Statement stmt) {
|
||||||
|
log.debug("removing " + stmt + " from " + graphURI);
|
||||||
s.notifyListeners(stmt.asTriple(), ModelChange.Operation.REMOVE, graphURI);
|
s.notifyListeners(stmt.asTriple(), ModelChange.Operation.REMOVE, graphURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ import com.hp.hpl.jena.vocabulary.RDFS;
|
||||||
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.dao.jena.ABoxJenaChangeListener;
|
import edu.cornell.mannlib.vitro.webapp.dao.jena.ABoxJenaChangeListener;
|
||||||
import edu.cornell.mannlib.vitro.webapp.dao.jena.CumulativeDeltaModeler;
|
import edu.cornell.mannlib.vitro.webapp.dao.jena.CumulativeDeltaModeler;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.dao.jena.DifferenceGraph;
|
||||||
import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceGraph;
|
import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceGraph;
|
||||||
import edu.cornell.mannlib.vitro.webapp.dao.jena.event.BulkUpdateEvent;
|
import edu.cornell.mannlib.vitro.webapp.dao.jena.event.BulkUpdateEvent;
|
||||||
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
|
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
|
||||||
|
@ -89,7 +90,7 @@ public class SimpleReasoner extends StatementListener {
|
||||||
this.tboxModel = tboxModel;
|
this.tboxModel = tboxModel;
|
||||||
this.aboxModel = ModelFactory.createOntologyModel(
|
this.aboxModel = ModelFactory.createOntologyModel(
|
||||||
OntModelSpec.OWL_MEM, ModelFactory.createModelForGraph(
|
OntModelSpec.OWL_MEM, ModelFactory.createModelForGraph(
|
||||||
new RDFServiceGraph(rdfService)));
|
new DifferenceGraph(new RDFServiceGraph(rdfService), inferenceModel.getGraph())));
|
||||||
this.inferenceModel = inferenceModel;
|
this.inferenceModel = inferenceModel;
|
||||||
this.inferenceRebuildModel = inferenceRebuildModel;
|
this.inferenceRebuildModel = inferenceRebuildModel;
|
||||||
this.scratchpadModel = scratchpadModel;
|
this.scratchpadModel = scratchpadModel;
|
||||||
|
@ -187,8 +188,7 @@ public class SimpleReasoner extends StatementListener {
|
||||||
* Synchronized part of removedStatement. Interacts
|
* Synchronized part of removedStatement. Interacts
|
||||||
* with DeltaComputer.
|
* with DeltaComputer.
|
||||||
*/
|
*/
|
||||||
protected synchronized void handleRemovedStatement(Statement stmt) {
|
protected synchronized void handleRemovedStatement(Statement stmt) {
|
||||||
|
|
||||||
if (batchMode1) {
|
if (batchMode1) {
|
||||||
aBoxDeltaModeler1.removedStatement(stmt);
|
aBoxDeltaModeler1.removedStatement(stmt);
|
||||||
} else if (batchMode2) {
|
} else if (batchMode2) {
|
||||||
|
@ -753,7 +753,7 @@ public class SimpleReasoner extends StatementListener {
|
||||||
Iterator<OntClass> parentIt = parents.iterator();
|
Iterator<OntClass> parentIt = parents.iterator();
|
||||||
|
|
||||||
while (parentIt.hasNext()) {
|
while (parentIt.hasNext()) {
|
||||||
OntClass parentClass = parentIt.next();
|
OntClass parentClass = parentIt.next();
|
||||||
|
|
||||||
// VIVO doesn't materialize statements that assert anonymous types
|
// VIVO doesn't materialize statements that assert anonymous types
|
||||||
// for individuals. Also, sharing an identical anonymous node is
|
// for individuals. Also, sharing an identical anonymous node is
|
||||||
|
@ -770,7 +770,7 @@ public class SimpleReasoner extends StatementListener {
|
||||||
inferenceModel.enterCriticalSection(Lock.WRITE);
|
inferenceModel.enterCriticalSection(Lock.WRITE);
|
||||||
try {
|
try {
|
||||||
if (inferenceModel.contains(infStmt)) {
|
if (inferenceModel.contains(infStmt)) {
|
||||||
inferenceModel.remove(infStmt);
|
inferenceModel.remove(infStmt);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
inferenceModel.leaveCriticalSection();
|
inferenceModel.leaveCriticalSection();
|
||||||
|
@ -1745,7 +1745,7 @@ public class SimpleReasoner extends StatementListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void notifyEvent(Model model, Object event) {
|
public synchronized void notifyEvent(Model model, Object event) {
|
||||||
|
|
||||||
if (event instanceof BulkUpdateEvent) {
|
if (event instanceof BulkUpdateEvent) {
|
||||||
if (((BulkUpdateEvent) event).getBegin()) {
|
if (((BulkUpdateEvent) event).getBegin()) {
|
||||||
|
|
||||||
|
|
|
@ -135,7 +135,7 @@ public class RDFServiceSetup extends JenaDataSourceSetupBase
|
||||||
RDFServiceFactory rdfServiceFactory = new RDFServiceFactorySingle(rdfService);
|
RDFServiceFactory rdfServiceFactory = new RDFServiceFactorySingle(rdfService);
|
||||||
RDFServiceUtils.setRDFServiceFactory(ctx, rdfServiceFactory);
|
RDFServiceUtils.setRDFServiceFactory(ctx, rdfServiceFactory);
|
||||||
|
|
||||||
Graph g = new RDFServiceGraph(rdfService);
|
// Graph g = new RDFServiceGraph(rdfService);
|
||||||
|
|
||||||
Dataset dataset = new RDFServiceDataset(rdfService);
|
Dataset dataset = new RDFServiceDataset(rdfService);
|
||||||
setStartupDataset(dataset, ctx);
|
setStartupDataset(dataset, ctx);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue