diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/VitroVocabulary.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/VitroVocabulary.java index a24f26c3e..d06f6ccab 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/VitroVocabulary.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/VitroVocabulary.java @@ -12,6 +12,8 @@ public class VitroVocabulary { public static final String VITRO_AUTH = "http://vitro.mannlib.cornell.edu/ns/vitro/authorization#"; public static final String VITRO_PUBLIC = "http://vitro.mannlib.cornell.edu/ns/vitro/public#"; public static final String VITRO_PUBLIC_ONTOLOGY = "http://vitro.mannlib.cornell.edu/ns/vitro/public"; + // TODO change the following before 1.6 release + public static final String PROPERTY_CONFIG_DATA = "http://example.org/appConfig/"; /** BJL23 2008-02-25: diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyDaoJena.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyDaoJena.java index 4ca5fe172..487d2a116 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyDaoJena.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyDaoJena.java @@ -949,7 +949,6 @@ public class ObjectPropertyDaoJena extends PropertyDaoJena implements ObjectProp " { ?property display:listViewConfigFile ?filename \n" + " } UNION { \n" + " ?configuration config:listViewConfigFile ?filename . \n " + -// " ?configuration config:hasListView ?lv . " + " ?context config:hasConfiguration ?configuration . \n" + " ?context config:configContextFor ?property . \n" + " ?context config:qualifiedBy ?range . \n" + diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/ABoxUpdater.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/ABoxUpdater.java index da1af4423..7e59ad681 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/ABoxUpdater.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/ABoxUpdater.java @@ -44,6 +44,7 @@ public class ABoxUpdater { private Dataset dataset; private RDFService rdfService; private OntModel newTBoxAnnotationsModel; + private TBoxUpdater tboxUpdater; private ChangeLogger logger; private ChangeRecord record; private OntClass OWL_THING = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createClass(OWL.Thing.getURI()); @@ -61,20 +62,19 @@ public class ABoxUpdater { * and the retractions model. * */ - public ABoxUpdater(OntModel oldTboxModel, - OntModel newTboxModel, - RDFService rdfService, - OntModel newAnnotationsModel, + public ABoxUpdater(UpdateSettings settings, ChangeLogger logger, ChangeRecord record) { - this.oldTboxModel = oldTboxModel; - this.newTboxModel = newTboxModel; + this.oldTboxModel = settings.getOldTBoxModel(); + this.newTboxModel = settings.getNewTBoxModel(); + RDFService rdfService = settings.getRDFService(); this.dataset = new RDFServiceDataset(rdfService); this.rdfService = rdfService; - this.newTBoxAnnotationsModel = newAnnotationsModel; + this.newTBoxAnnotationsModel = settings.getNewTBoxAnnotationsModel(); this.logger = logger; this.record = record; + this.tboxUpdater = new TBoxUpdater(settings, logger, record); } /** @@ -632,6 +632,8 @@ public class ABoxUpdater { propObj.getDestinationURI() + " instead"); } } + + tboxUpdater.renameProperty(propObj); } public void logChanges(Statement oldStatement, Statement newStatement) throws IOException { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/KnowledgeBaseUpdater.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/KnowledgeBaseUpdater.java index 573f03298..ac5cda224 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/KnowledgeBaseUpdater.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/KnowledgeBaseUpdater.java @@ -64,7 +64,6 @@ public class KnowledgeBaseUpdater { } long startTime = System.currentTimeMillis(); - System.out.println("Migrating the knowledge base"); log.info("Migrating the knowledge base"); logger.log("Started knowledge base migration"); @@ -84,7 +83,6 @@ public class KnowledgeBaseUpdater { logger.closeLogs(); long elapsedSecs = (System.currentTimeMillis() - startTime)/1000; - System.out.println("Finished knowledge base migration in " + elapsedSecs + " second" + (elapsedSecs != 1 ? "s" : "")); log.info("Finished knowledge base migration in " + elapsedSecs + " second" + (elapsedSecs != 1 ? "s" : "")); return; @@ -110,20 +108,15 @@ public class KnowledgeBaseUpdater { } catch (Exception e) { log.error("unable to migrate migration metadata " + e.getMessage()); } - - log.warn("KnowledgeBaseUpdater needs to be modified to work on all graphs!"); - OntModel readModel = settings.getUnionOntModelSelector().getABoxModel(); - OntModel writeModel = settings.getAssertionOntModelSelector().getABoxModel(); - // TODO make sure the ABox update applies to all graphs - - log.info("\tupdating the abox"); - updateABox(changes); - + log.info("performing SPARQL CONSTRUCT additions"); performSparqlConstructs(settings.getSparqlConstructAdditionsDir(), settings.getRDFService(), ADD); log.info("performing SPARQL CONSTRUCT retractions"); performSparqlConstructs(settings.getSparqlConstructDeletionsDir(), settings.getRDFService(), RETRACT); + + log.info("\tupdating the abox"); + updateABox(changes); } @@ -249,10 +242,8 @@ public class KnowledgeBaseUpdater { private void updateABox(AtomicOntologyChangeLists changes) throws IOException { - OntModel oldTBoxModel = settings.getOldTBoxModel(); - OntModel newTBoxModel = settings.getNewTBoxModel(); - RDFService rdfService = settings.getRDFService(); - ABoxUpdater aboxUpdater = new ABoxUpdater(oldTBoxModel, newTBoxModel, rdfService, settings.getNewTBoxAnnotationsModel(), logger, record); + + ABoxUpdater aboxUpdater = new ABoxUpdater(settings, logger, record); aboxUpdater.processPropertyChanges(changes.getAtomicPropertyChanges()); aboxUpdater.processClassChanges(changes.getAtomicClassChanges()); } @@ -290,14 +281,18 @@ public class KnowledgeBaseUpdater { rdfService.changeSetUpdate(removeChangeSet); } - private void updateTBoxAnnotations() throws IOException { - - TBoxUpdater tboxUpdater = new TBoxUpdater(settings.getOldTBoxAnnotationsModel(), - settings.getNewTBoxAnnotationsModel(), - settings.getAssertionOntModelSelector().getTBoxModel(), logger, record); - - tboxUpdater.updateDefaultAnnotationValues(); - //tboxUpdater.updateAnnotationModel(); + private void updateTBoxAnnotations() { + TBoxUpdater tboxUpdater = new TBoxUpdater(settings, logger, record); + try { + tboxUpdater.modifyPropertyQualifications(); + } catch (Exception e) { + log.error("Unable to modify qualified property config file ", e); + } + try { + tboxUpdater.updateDefaultAnnotationValues(); + } catch (Exception e) { + log.error("Unable to update default annotation values ", e); + } } /** diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/TBoxUpdater.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/TBoxUpdater.java index bf5c27817..9f3930d65 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/TBoxUpdater.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/TBoxUpdater.java @@ -2,11 +2,22 @@ package edu.cornell.mannlib.vitro.webapp.ontology.update; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.util.List; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import com.hp.hpl.jena.ontology.OntModel; import com.hp.hpl.jena.ontology.OntModelSpec; +import com.hp.hpl.jena.query.Dataset; +import com.hp.hpl.jena.query.Query; +import com.hp.hpl.jena.query.QueryExecution; +import com.hp.hpl.jena.query.QueryExecutionFactory; +import com.hp.hpl.jena.query.QueryFactory; import com.hp.hpl.jena.rdf.model.Literal; import com.hp.hpl.jena.rdf.model.Model; import com.hp.hpl.jena.rdf.model.ModelFactory; @@ -14,6 +25,7 @@ import com.hp.hpl.jena.rdf.model.NodeIterator; import com.hp.hpl.jena.rdf.model.Property; import com.hp.hpl.jena.rdf.model.RDFNode; import com.hp.hpl.jena.rdf.model.Resource; +import com.hp.hpl.jena.rdf.model.ResourceFactory; import com.hp.hpl.jena.rdf.model.Statement; import com.hp.hpl.jena.rdf.model.StmtIterator; import com.hp.hpl.jena.shared.Lock; @@ -21,403 +33,539 @@ import com.hp.hpl.jena.vocabulary.RDF; import com.hp.hpl.jena.vocabulary.RDFS; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; +import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceDataset; +import edu.cornell.mannlib.vitro.webapp.servlet.setup.JenaDataSourceSetupBase; /** -* Performs knowledge base updates to the tbox to align with a new ontology version -* -*/ + * Performs knowledge base updates to the tbox to align with a new ontology version + * + */ public class TBoxUpdater { - private OntModel oldTboxAnnotationsModel; - private OntModel newTboxAnnotationsModel; - private OntModel siteModel; - private ChangeLogger logger; - private ChangeRecord record; - private boolean detailLogs = false; - - private static final String classGroupURI = "http://vitro.mannlib.cornell.edu/ns/vitro/0.7#ClassGroup"; - private Resource classGroupClass = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createResource(classGroupURI); - private static final String inClassGroupURI = "http://vitro.mannlib.cornell.edu/ns/vitro/0.7#inClassGroup"; - private Property inClassGroupProp = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createProperty(inClassGroupURI); + private static final Log log = LogFactory.getLog(TBoxUpdater.class); - /** - * - * Constructor - * - * @param oldTboxAnnotationsModel - previous version of the annotations in the ontology - * @param newTboxAnnotationsModel - new version of the annotations in the ontology - * @param siteModel - the knowledge base to be updated - * @param logger - for writing to the change log - * and the error log. - * @param record - for writing to the additions model - * and the retractions model. - * - */ - public TBoxUpdater(OntModel oldTboxAnnotationsModel, - OntModel newTboxAnnotationsModel, - OntModel siteModel, - ChangeLogger logger, - ChangeRecord record) { - - this.oldTboxAnnotationsModel = oldTboxAnnotationsModel; - this.newTboxAnnotationsModel = newTboxAnnotationsModel; - this.siteModel = siteModel; - this.logger = logger; - this.record = record; - } - - /** - * - * Update a knowledge base to align with changes to vitro annotation property default - * values in a new version of the ontology. The two versions of the ontology and the - * knowledge base to be updated are provided in the class constructor and are - * referenced via class level variables. - * - * If the default value (i.e. the value that is provided in the vivo-core - * annotations files) of a vitro annotation property has been changed for a vivo - * core class, and that default value has not been changed in the site knowledge - * base, then update the value in the site knowledge base to be the new default. - * Also, if a new vitro annotation property setting (i.e. either an existing - * setting applied to an existing class where it wasn't applied before, or - * an existing setting applied to a new class) has been applied to a vivo - * core class then copy that new property statement into the site model. - * If a property setting for a class exists in the old ontology but - * not in the new one, then that statement will be removed from the - * site knowledge base. - * - * Writes to the change log file, the error log file, and the incremental change - * knowledge base. - * - * Note: as specified, this method for now assumes that no new vitro annotation - * properties have been introduced. This should be updated for future versions. - */ - public void updateDefaultAnnotationValues() throws IOException { - - siteModel.enterCriticalSection(Lock.WRITE); - - try { - - Model additions = ModelFactory.createDefaultModel(); - Model retractions = ModelFactory.createDefaultModel(); - - // Update defaults values for vitro annotation properties in the site model - // if the default has changed in the new version of the ontology AND if - // the site hasn't overidden the previous default in their knowledge base. - - if(oldTboxAnnotationsModel == null) { - logger.log("oldTboxAnnotationModel is null; aborting update of annotation values"); - return; - } - - StmtIterator iter = oldTboxAnnotationsModel.listStatements(); - - while (iter.hasNext()) { - - Statement stmt = iter.next(); - Resource subject = stmt.getSubject(); - Property predicate = stmt.getPredicate(); - RDFNode oldObject = stmt.getObject(); - - if (! ( (RDFS.getURI().equals(predicate.getNameSpace())) || - (VitroVocabulary.vitroURI.equals(predicate.getNameSpace())) - ) ) { - // this annotation updater is only concerned with properties - // such as rdfs:comment and properties in the vitro application - // namespace - continue; - } - - NodeIterator newObjects = newTboxAnnotationsModel.listObjectsOfProperty(subject, predicate); - - if ((newObjects == null) || (!newObjects.hasNext()) ) { - // first check to see if the site has a local value change - // that should override the deletion - List siteObjects = siteModel.listObjectsOfProperty(subject, predicate).toList(); - - if (siteObjects.size() > 1) { - /* + private UpdateSettings settings; + private OntModel oldTboxAnnotationsModel; + private OntModel newTboxAnnotationsModel; + private OntModel siteModel; + private ChangeLogger logger; + private ChangeRecord record; + private boolean detailLogs = false; + + private static final String classGroupURI = "http://vitro.mannlib.cornell.edu/ns/vitro/0.7#ClassGroup"; + private Resource classGroupClass = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createResource(classGroupURI); + private static final String inClassGroupURI = "http://vitro.mannlib.cornell.edu/ns/vitro/0.7#inClassGroup"; + private Property inClassGroupProp = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createProperty(inClassGroupURI); + + /** + * + * Constructor + * + * @param oldTboxAnnotationsModel - previous version of the annotations in the ontology + * @param newTboxAnnotationsModel - new version of the annotations in the ontology + * @param siteModel - the knowledge base to be updated + * @param logger - for writing to the change log + * and the error log. + * @param record - for writing to the additions model + * and the retractions model. + * + */ + public TBoxUpdater(UpdateSettings settings, + ChangeLogger logger, + ChangeRecord record) { + this.settings = settings; + this.oldTboxAnnotationsModel = settings.getOldTBoxAnnotationsModel(); + this.newTboxAnnotationsModel = settings.getNewTBoxAnnotationsModel(); + this.siteModel = settings.getAssertionOntModelSelector().getTBoxModel(); + this.logger = logger; + this.record = record; + } + + /** + * Update application ontology data for domain and range-qualified properties + * to use any applicable settings from obsolete subproperties + */ + public void modifyPropertyQualifications() throws IOException { + + } + + private Model mergeConfigurations(Model oldConfig, Model newConfig) { + return null; + } + + public void updateDefaultAnnotationValues() throws IOException { + updateDefaultAnnotationValues(null); + } + + /** + * + * Update a knowledge base to align with changes to vitro annotation property default + * values in a new version of the ontology. The two versions of the ontology and the + * knowledge base to be updated are provided in the class constructor and are + * referenced via class level variables. + * + * If the default value (i.e. the value that is provided in the vivo-core + * annotations files) of a vitro annotation property has been changed for a vivo + * core class, and that default value has not been changed in the site knowledge + * base, then update the value in the site knowledge base to be the new default. + * Also, if a new vitro annotation property setting (i.e. either an existing + * setting applied to an existing class where it wasn't applied before, or + * an existing setting applied to a new class) has been applied to a vivo + * core class then copy that new property statement into the site model. + * If a property setting for a class exists in the old ontology but + * not in the new one, then that statement will be removed from the + * site knowledge base. + * + * Writes to the change log file, the error log file, and the incremental change + * knowledge base. + * + * Note: as specified, this method for now assumes that no new vitro annotation + * properties have been introduced. This should be updated for future versions. + */ + public void updateDefaultAnnotationValues(String subjectURI) throws IOException { + + siteModel.enterCriticalSection(Lock.WRITE); + + try { + + Model additions = ModelFactory.createDefaultModel(); + Model retractions = ModelFactory.createDefaultModel(); + + // Update defaults values for vitro annotation properties in the site model + // if the default has changed in the new version of the ontology AND if + // the site hasn't overidden the previous default in their knowledge base. + + if(oldTboxAnnotationsModel == null) { + logger.log("oldTboxAnnotationModel is null; aborting update of annotation values"); + return; + } + + Resource subj = (subjectURI == null) ? null : ResourceFactory.createResource(subjectURI); + + StmtIterator iter = oldTboxAnnotationsModel.listStatements(subj, null, (RDFNode) null); + + while (iter.hasNext()) { + + Statement stmt = iter.next(); + Resource subject = stmt.getSubject(); + Property predicate = stmt.getPredicate(); + RDFNode oldObject = stmt.getObject(); + + if (! ( (RDFS.getURI().equals(predicate.getNameSpace())) || + (VitroVocabulary.vitroURI.equals(predicate.getNameSpace())) + ) ) { + // this annotation updater is only concerned with properties + // such as rdfs:comment and properties in the vitro application + // namespace + continue; + } + + NodeIterator newObjects = newTboxAnnotationsModel.listObjectsOfProperty(subject, predicate); + + if ((newObjects == null) || (!newObjects.hasNext()) ) { + // first check to see if the site has a local value change + // that should override the deletion + List siteObjects = siteModel.listObjectsOfProperty(subject, predicate).toList(); + + if (siteObjects.size() > 1) { + /* logger.log("WARNING: found " + siteObjects.size() + " statements with subject = " + subject.getURI() + " and property = " + predicate.getURI() + " in the site database (maximum of one is expected)"); - */ - } - - if (siteObjects.size() > 0) { - RDFNode siteNode = siteObjects.get(0); - if (siteNode.equals(oldObject)) { - retractions.add(siteModel.listStatements(subject, predicate, (RDFNode) null)); - } - } - - continue; - } - - RDFNode newObject = newObjects.next(); - - int i = 1; - while (newObjects.hasNext()) { - i++; - newObjects.next(); - } - - if (i > 1) { - /* + */ + } + + if (siteObjects.size() > 0) { + RDFNode siteNode = siteObjects.get(0); + if (siteNode.equals(oldObject)) { + retractions.add(siteModel.listStatements(subject, predicate, (RDFNode) null)); + } + } + + continue; + } + + RDFNode newObject = newObjects.next(); + + int i = 1; + while (newObjects.hasNext()) { + i++; + newObjects.next(); + } + + if (i > 1) { + /* logger.log("WARNING: found " + i + " statements with subject = " + subject.getURI() + " and property = " + predicate.getURI() + " in the new version of the annotations ontology (maximum of one is expected)"); - */ - continue; - } - - // If a subject-property pair occurs in the old annotation TBox and the new annotations - // TBox, but not in the site model, then it is considered an erroneous deletion and - // the value from the new TBox is added into the site model. - // sjm: 7-16-2010. We want this here now to add back in annotations mistakenly dropped - // in the .9 to 1.0 migration, but I'm not sure we would want this here permanently. - // Shouldn't a site be allowed to delete annotations if they want to? - - NodeIterator siteObjects = siteModel.listObjectsOfProperty(subject,predicate); - - if (siteObjects == null || !siteObjects.hasNext()) { - try { - additions.add(subject, predicate, newObject); - - if (detailLogs) { - logger.log( "adding Statement: subject = " + subject.getURI() + - " property = " + predicate.getURI() + - " object = " + (newObject.isLiteral() ? ((Literal)newObject).getLexicalForm() - : ((Resource)newObject).getURI())); - } - } catch (Exception e) { - logger.logError("Error trying to add statement with property " + predicate.getURI() + - " of class = " + subject.getURI() + " in the knowledge base:\n" + e.getMessage()); - } - - continue; - } - - - if (!newObject.equals(oldObject)) { + */ + continue; + } - RDFNode siteObject = siteObjects.next(); + // If a subject-property pair occurs in the old annotation TBox and the new annotations + // TBox, but not in the site model, then it is considered an erroneous deletion and + // the value from the new TBox is added into the site model. + // sjm: 7-16-2010. We want this here now to add back in annotations mistakenly dropped + // in the .9 to 1.0 migration, but I'm not sure we would want this here permanently. + // Shouldn't a site be allowed to delete annotations if they want to? - i = 1; - while (siteObjects.hasNext()) { - i++; - siteObjects.next(); - } + NodeIterator siteObjects = siteModel.listObjectsOfProperty(subject,predicate); - if (i > 1) { - /* + if (siteObjects == null || !siteObjects.hasNext()) { + try { + additions.add(subject, predicate, newObject); + + if (detailLogs) { + logger.log( "adding Statement: subject = " + subject.getURI() + + " property = " + predicate.getURI() + + " object = " + (newObject.isLiteral() ? ((Literal)newObject).getLexicalForm() + : ((Resource)newObject).getURI())); + } + } catch (Exception e) { + logger.logError("Error trying to add statement with property " + predicate.getURI() + + " of class = " + subject.getURI() + " in the knowledge base:\n" + e.getMessage()); + } + + continue; + } + + + if (!newObject.equals(oldObject)) { + + RDFNode siteObject = siteObjects.next(); + + i = 1; + while (siteObjects.hasNext()) { + i++; + siteObjects.next(); + } + + if (i > 1) { + /* logger.log("WARNING: found " + i + " statements with subject = " + subject.getURI() + " and property = " + predicate.getURI() + " in the site annotations model (maximum of one is expected) "); - */ - continue; - } - - if (siteObject.equals(oldObject)) { - try { - StmtIterator it = siteModel.listStatements(subject, predicate, (RDFNode)null); - while (it.hasNext()) { - retractions.add(it.next()); - } - } catch (Exception e) { - logger.logError("Error removing statement for subject = " + subject.getURI() + - "and property = " + predicate.getURI() + - "from the knowledge base:\n" + e.getMessage()); - } + */ + continue; + } - try { - additions.add(subject, predicate, newObject); - - if (detailLogs) { - logger.log("Changed the value of property " + predicate.getURI() + - " of subject = " + subject.getURI() + - " from " + - (oldObject.isResource() ? ((Resource)oldObject).getURI() : ((Literal)oldObject).getLexicalForm()) + - " to " + - (newObject.isResource() ? ((Resource)newObject).getURI() : ((Literal)newObject).getLexicalForm()) + - " in the knowledge base:\n"); - } - } catch (Exception e) { - logger.logError("Error trying to change the value of property " + predicate.getURI() + - " of class = " + subject.getURI() + " in the knowledge base:\n" + e.getMessage()); - } - } - } - } - - Model actualAdditions = additions.difference(retractions); - siteModel.add(actualAdditions); - record.recordAdditions(actualAdditions); - Model actualRetractions = retractions.difference(additions); - siteModel.remove(actualRetractions); - record.recordRetractions(actualRetractions); - - long numAdded = actualAdditions.size(); - long numRemoved = actualRetractions.size(); - - // log summary of changes - if (numAdded > 0) { - logger.log("Updated the default vitro annotation value for " + - numAdded + " statements in the knowledge base"); - } - - if (numRemoved > 0) { - logger.log("Removed " + numRemoved + - " outdated vitro annotation property setting" + ((numRemoved > 1) ? "s" : "") + " from the knowledge base"); - } - - // Copy annotation property settings that were introduced in the new ontology - // into the site model. - // + if (siteObject.equals(oldObject)) { + try { + StmtIterator it = siteModel.listStatements(subject, predicate, (RDFNode)null); + while (it.hasNext()) { + retractions.add(it.next()); + } + } catch (Exception e) { + logger.logError("Error removing statement for subject = " + subject.getURI() + + "and property = " + predicate.getURI() + + "from the knowledge base:\n" + e.getMessage()); + } - Model newAnnotationSettings = newTboxAnnotationsModel.difference(oldTboxAnnotationsModel); - Model newAnnotationSettingsToAdd = ModelFactory.createDefaultModel(); - StmtIterator newStmtIt = newAnnotationSettings.listStatements(); - while (newStmtIt.hasNext()) { - Statement stmt = newStmtIt.next(); - if (!siteModel.contains(stmt)) { - newAnnotationSettingsToAdd.add(stmt); - - if (detailLogs) { - logger.log( "adding Statement: subject = " + stmt.getSubject().getURI() + - " property = " + stmt.getPredicate().getURI() + - " object = " + (stmt.getObject().isLiteral() ? ((Literal)stmt.getObject()).getLexicalForm() - : ((Resource)stmt.getObject()).getURI())); - } - } - } - - siteModel.add(newAnnotationSettingsToAdd); - record.recordAdditions(newAnnotationSettingsToAdd); - - // log the additions - summary - if (newAnnotationSettingsToAdd.size() > 0) { - boolean plural = (newAnnotationSettingsToAdd.size() > 1); - logger.log("Added " + newAnnotationSettingsToAdd.size() + " new annotation property setting" + (plural ? "s" : "") + " to the knowledge base. This includes only " + - "existing annotation properties applied to existing classes where they weren't applied before, or existing " + - "properties applied to new classes."); - } - - } finally { - siteModel.leaveCriticalSection(); - } -} - -/** - * - * Update a knowledge base to align with changes to the vitro annotation model - * in a new version of the ontology. The two versions of the ontology and the - * knowledge base to be updated are provided in the class constructor and are - * referenced via class level variables. - * - * Currently, this method only handles deletions of a ClassGroup - * - * Writes to the change log file, the error log file, and the incremental change - * knowledge base. - * - */ -public void updateAnnotationModel() throws IOException { - - // for each ClassGroup in the old vitro annotations model: if it is not in - // the new vitro annotations model and the site has no classes asserted to - // be in that class group then delete it. - - removeObsoleteAnnotations(); - - siteModel.enterCriticalSection(Lock.WRITE); - - try { - Model retractions = ModelFactory.createDefaultModel(); - - StmtIterator iter = oldTboxAnnotationsModel.listStatements((Resource) null, RDF.type, classGroupClass); - - while (iter.hasNext()) { - Statement stmt = iter.next(); - - if (!newTboxAnnotationsModel.contains(stmt) && !usesGroup(siteModel, stmt.getSubject())) { - long pre = retractions.size(); - retractions.add(siteModel.listStatements(stmt.getSubject(),(Property) null,(RDFNode)null)); - long post = retractions.size(); - if ((post - pre) > 0) { - logger.log("Removed the " + stmt.getSubject().getURI() + " ClassGroup from the annotations model"); - } - } - } - - if (retractions.size() > 0) { - siteModel.remove(retractions); - record.recordRetractions(retractions); - } - - } finally { - siteModel.leaveCriticalSection(); - } + try { + additions.add(subject, predicate, newObject); - // If we were going to handle add, this is the logic: - // for each ClassGroup in new old vitro annotations model: if it is not in - // the old vitro annotations and it is not in the site model, then - // add it. - -} + if (detailLogs) { + logger.log("Changed the value of property " + predicate.getURI() + + " of subject = " + subject.getURI() + + " from " + + (oldObject.isResource() ? ((Resource)oldObject).getURI() : ((Literal)oldObject).getLexicalForm()) + + " to " + + (newObject.isResource() ? ((Resource)newObject).getURI() : ((Literal)newObject).getLexicalForm()) + + " in the knowledge base:\n"); + } + } catch (Exception e) { + logger.logError("Error trying to change the value of property " + predicate.getURI() + + " of class = " + subject.getURI() + " in the knowledge base:\n" + e.getMessage()); + } + } + } + } -public boolean usesGroup(Model model, Resource theClassGroup) throws IOException { - - model.enterCriticalSection(Lock.READ); - - try { - return (model.contains((Resource) null, inClassGroupProp, theClassGroup) ? true : false); - } finally { - model.leaveCriticalSection(); - } -} + Model actualAdditions = additions.difference(retractions); + siteModel.add(actualAdditions); + record.recordAdditions(actualAdditions); + Model actualRetractions = retractions.difference(additions); + siteModel.remove(actualRetractions); + record.recordRetractions(actualRetractions); -public void removeObsoleteAnnotations() throws IOException { - - Resource subj1 = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createResource("http://vivoweb.org/ontology/florida#StatewideGoalAndFocusArea"); - Resource obj1 = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createResource("http://vivoweb.org/ontology#vitroClassGrouptopics"); - - Property subj2 = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createProperty("http://vivoweb.org/ontology/florida#divisionOfSponsoredResearchNumber"); - Resource obj2 = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createResource("http://vivoweb.org/ontology#vitroPropertyGroupidentifiers"); - - Property subj3 = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createProperty("http://vivoweb.org/ontology/florida#statewideGoalAndFocusArea"); - Resource obj3 = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createResource("http://vivoweb.org/ontology#vitroPropertyGroupoutreach"); - - Property inPropertyGroupProp = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createProperty("http://vitro.mannlib.cornell.edu/ns/vitro/0.7#inPropertyGroup"); - - siteModel.enterCriticalSection(Lock.WRITE); - - try { - Model retractions = ModelFactory.createDefaultModel(); - - if (siteModel.contains(subj1, inClassGroupProp, obj1) ) { - retractions.add(subj1, inClassGroupProp, obj1); - logger.log("Removed statement " + ABoxUpdater.stmtString(subj1, inClassGroupProp, obj1) + " from the knowledge base (assumed to be obsolete)"); + long numAdded = actualAdditions.size(); + long numRemoved = actualRetractions.size(); + + // log summary of changes + if (numAdded > 0) { + logger.log("Updated the default vitro annotation value for " + + numAdded + " statements in the knowledge base"); + } + + if (numRemoved > 0) { + logger.log("Removed " + numRemoved + + " outdated vitro annotation property setting" + ((numRemoved > 1) ? "s" : "") + " from the knowledge base"); + } + + // Copy annotation property settings that were introduced in the new ontology + // into the site model. + // + + Model newAnnotationSettings = newTboxAnnotationsModel.difference(oldTboxAnnotationsModel); + Model newAnnotationSettingsToAdd = ModelFactory.createDefaultModel(); + StmtIterator newStmtIt = newAnnotationSettings.listStatements(); + while (newStmtIt.hasNext()) { + Statement stmt = newStmtIt.next(); + if (!siteModel.contains(stmt)) { + newAnnotationSettingsToAdd.add(stmt); + + if (detailLogs) { + logger.log( "adding Statement: subject = " + stmt.getSubject().getURI() + + " property = " + stmt.getPredicate().getURI() + + " object = " + (stmt.getObject().isLiteral() ? ((Literal)stmt.getObject()).getLexicalForm() + : ((Resource)stmt.getObject()).getURI())); + } + } + } + + siteModel.add(newAnnotationSettingsToAdd); + record.recordAdditions(newAnnotationSettingsToAdd); + + // log the additions - summary + if (newAnnotationSettingsToAdd.size() > 0) { + boolean plural = (newAnnotationSettingsToAdd.size() > 1); + logger.log("Added " + newAnnotationSettingsToAdd.size() + " new annotation property setting" + (plural ? "s" : "") + " to the knowledge base. This includes only " + + "existing annotation properties applied to existing classes where they weren't applied before, or existing " + + "properties applied to new classes."); + } + + } finally { + siteModel.leaveCriticalSection(); + } + } + + /** + * + * Update a knowledge base to align with changes to the vitro annotation model + * in a new version of the ontology. The two versions of the ontology and the + * knowledge base to be updated are provided in the class constructor and are + * referenced via class level variables. + * + * Currently, this method only handles deletions of a ClassGroup + * + * Writes to the change log file, the error log file, and the incremental change + * knowledge base. + * + */ + public void updateAnnotationModel() throws IOException { + + // for each ClassGroup in the old vitro annotations model: if it is not in + // the new vitro annotations model and the site has no classes asserted to + // be in that class group then delete it. + + removeObsoleteAnnotations(); + + siteModel.enterCriticalSection(Lock.WRITE); + + try { + Model retractions = ModelFactory.createDefaultModel(); + + StmtIterator iter = oldTboxAnnotationsModel.listStatements((Resource) null, RDF.type, classGroupClass); + + while (iter.hasNext()) { + Statement stmt = iter.next(); + + if (!newTboxAnnotationsModel.contains(stmt) && !usesGroup(siteModel, stmt.getSubject())) { + long pre = retractions.size(); + retractions.add(siteModel.listStatements(stmt.getSubject(),(Property) null,(RDFNode)null)); + long post = retractions.size(); + if ((post - pre) > 0) { + logger.log("Removed the " + stmt.getSubject().getURI() + " ClassGroup from the annotations model"); + } + } + } + + if (retractions.size() > 0) { + siteModel.remove(retractions); + record.recordRetractions(retractions); + } + + } finally { + siteModel.leaveCriticalSection(); } - if (siteModel.contains(subj2, inPropertyGroupProp, obj2) ) { - retractions.add(subj2, inPropertyGroupProp, obj2); - logger.log("Removed statement " + ABoxUpdater.stmtString(subj2, inPropertyGroupProp, obj2) + " from the knowledge base (assumed to be obsolete)"); - } + // If we were going to handle add, this is the logic: + // for each ClassGroup in new old vitro annotations model: if it is not in + // the old vitro annotations and it is not in the site model, then + // add it. - if (siteModel.contains(subj3, inPropertyGroupProp, obj3) ) { - retractions.add(subj3, inPropertyGroupProp, obj3); - logger.log("Removed statement " + ABoxUpdater.stmtString(subj3, inPropertyGroupProp, obj3) + " from the knowledge base (assumed to be obsolete)"); + } + + public boolean usesGroup(Model model, Resource theClassGroup) throws IOException { + + model.enterCriticalSection(Lock.READ); + + try { + return (model.contains((Resource) null, inClassGroupProp, theClassGroup) ? true : false); + } finally { + model.leaveCriticalSection(); } - - if (retractions.size() > 0) { - siteModel.remove(retractions); - record.recordRetractions(retractions); - } - - } finally { - siteModel.leaveCriticalSection(); - } -} + } + + public void removeObsoleteAnnotations() throws IOException { + + Resource subj1 = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createResource("http://vivoweb.org/ontology/florida#StatewideGoalAndFocusArea"); + Resource obj1 = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createResource("http://vivoweb.org/ontology#vitroClassGrouptopics"); + + Property subj2 = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createProperty("http://vivoweb.org/ontology/florida#divisionOfSponsoredResearchNumber"); + Resource obj2 = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createResource("http://vivoweb.org/ontology#vitroPropertyGroupidentifiers"); + + Property subj3 = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createProperty("http://vivoweb.org/ontology/florida#statewideGoalAndFocusArea"); + Resource obj3 = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createResource("http://vivoweb.org/ontology#vitroPropertyGroupoutreach"); + + Property inPropertyGroupProp = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createProperty("http://vitro.mannlib.cornell.edu/ns/vitro/0.7#inPropertyGroup"); + + siteModel.enterCriticalSection(Lock.WRITE); + + try { + Model retractions = ModelFactory.createDefaultModel(); + + if (siteModel.contains(subj1, inClassGroupProp, obj1) ) { + retractions.add(subj1, inClassGroupProp, obj1); + logger.log("Removed statement " + ABoxUpdater.stmtString(subj1, inClassGroupProp, obj1) + " from the knowledge base (assumed to be obsolete)"); + } + + if (siteModel.contains(subj2, inPropertyGroupProp, obj2) ) { + retractions.add(subj2, inPropertyGroupProp, obj2); + logger.log("Removed statement " + ABoxUpdater.stmtString(subj2, inPropertyGroupProp, obj2) + " from the knowledge base (assumed to be obsolete)"); + } + + if (siteModel.contains(subj3, inPropertyGroupProp, obj3) ) { + retractions.add(subj3, inPropertyGroupProp, obj3); + logger.log("Removed statement " + ABoxUpdater.stmtString(subj3, inPropertyGroupProp, obj3) + " from the knowledge base (assumed to be obsolete)"); + } + + if (retractions.size() > 0) { + siteModel.remove(retractions); + record.recordRetractions(retractions); + } + + } finally { + siteModel.leaveCriticalSection(); + } + } + + public void renameProperty(AtomicOntologyChange changeObj) throws IOException { + if(changeObj.getNotes() != null && changeObj.getNotes().startsWith("cc:")) { + mergePropertyAnnotationsToPropertyConfig(changeObj); + } + + } + + private void mergePropertyAnnotationsToPropertyConfig(AtomicOntologyChange changeObj) throws IOException { + String contextURI = VitroVocabulary.PROPERTY_CONFIG_DATA + changeObj.getNotes().substring(3); + String oldPropertyURI = changeObj.getSourceURI(); + + Model oldAnnotationsModel = settings.getOldTBoxAnnotationsModel(); + Dataset dataset = new RDFServiceDataset(settings.getRDFService()); + Model userAnnotationsModel = dataset.getNamedModel( + JenaDataSourceSetupBase.JENA_TBOX_ASSERTIONS_MODEL); + + String propertyAnnotationsQuery = + "PREFIX config: <" + VitroVocabulary.configURI + "> \n" + + "PREFIX vitro: <" + VitroVocabulary.vitroURI + "> \n" + + "CONSTRUCT { \n" + + " <" + oldPropertyURI + "> vitro:inPropertyGroupAnnot ?group . \n" + + " <" + oldPropertyURI + "> <" + RDFS.label.getURI() + "> ?label . \n" + + " <" + oldPropertyURI + "> vitro:displayRankAnnot ?displayRank . \n" + + " <" + oldPropertyURI + "> vitro:customEntryFormAnnot ?customForm . \n" + + " <" + oldPropertyURI + "> vitro:hiddenFromDisplayBelowRoleLevelAnnot ?displayLevel . \n" + + " <" + oldPropertyURI + "> vitro:prohibitedFromUpdateBelowRoleLevelAnnot ?updateLevel . \n " + + "} WHERE { \n" + + " { <" + oldPropertyURI + "> vitro:inPropertyGroupAnnot ?group } \n" + + " UNION { <" + oldPropertyURI + "> <" + RDFS.label.getURI() + "> ?label } \n" + + " UNION { <" + oldPropertyURI + "> vitro:displayRankAnnot ?displayRank } \n" + + " UNION { <" + oldPropertyURI + "> vitro:customEntryFormAnnot ?customForm } \n" + + " UNION { <" + oldPropertyURI + "> vitro:hiddenFromDisplayBelowRoleLevelAnnot ?displayLevel } \n" + + " UNION { <" + oldPropertyURI + "> vitro:prohibitedFromUpdateBelowRoleLevelAnnot ?updateLevel } \n " + + "} \n" ; + + Model userChangesModel = construct( + propertyAnnotationsQuery, userAnnotationsModel).difference( + construct(propertyAnnotationsQuery, oldAnnotationsModel)); + + String addQuery = "PREFIX config: <" + VitroVocabulary.configURI + "> \n" + + "PREFIX vitro: <" + VitroVocabulary.vitroURI + "> \n" + + "CONSTRUCT { \n" + + " ?configuration config:propertyGroup ?group . \n" + + " ?configuration config:displayName ?label . \n" + + " ?configuration vitro:displayRankAnnot ?displayRank . \n" + + " ?configuration vitro:customEntryFormAnnot ?customForm . \n" + + " ?configuration vitro:hiddenFromDisplayBelowRoleLevelAnnot ?displayLevel . \n" + + " ?configuration vitro:prohibitedFromUpdateBelowRoleLevelAnnot ?updateLevel . \n " + + "} WHERE { \n" + + " <" + contextURI + "> config:hasConfiguration ?configuration . \n" + + " OPTIONAL { <" + oldPropertyURI + "> vitro:inPropertyGroupAnnot ?group } \n" + + " OPTIONAL { <" + oldPropertyURI + "> <" + RDFS.label.getURI() + "> ?label } \n" + + " OPTIONAL { <" + oldPropertyURI + "> vitro:displayRankAnnot ?displayRank } \n" + + " OPTIONAL { <" + oldPropertyURI + "> vitro:customEntryFormAnnot ?customForm } \n" + + " OPTIONAL { <" + oldPropertyURI + "> vitro:hiddenFromDisplayBelowRoleLevelAnnot ?displayLevel } \n" + + " OPTIONAL { <" + oldPropertyURI + "> vitro:prohibitedFromUpdateBelowRoleLevelAnnot ?updateLevel } \n " + + "} \n" ; + + String retractQuery = "PREFIX config: <" + VitroVocabulary.configURI + "> \n" + + "PREFIX vitro: <" + VitroVocabulary.vitroURI + "> \n" + + "CONSTRUCT { \n" + + " <" + oldPropertyURI + "> config:propertyGroup ?rgroup . \n" + + " ?configuration config:displayName ?rlabel . \n" + + " ?configuration vitro:displayRankAnnot ?rdisplayRank . \n" + + " ?configuration vitro:customEntryFormAnnot ?rcustomForm . \n" + + " ?configuration vitro:hiddenFromDisplayBelowRoleLevelAnnot ?rdisplayLevel . \n" + + " ?configuration vitro:prohibitedFromUpdateBelowRoleLevelAnnot ?rupdateLevel . \n " + + "} WHERE { \n" + + " <" + contextURI + "> config:hasConfiguration ?configuration . \n" + + " OPTIONAL { <" + oldPropertyURI + "> vitro:inPropertyGroupAnnot ?group . \n" + + " ?configuration config:propertyGroup ?rgroup } \n" + + " OPTIONAL { <" + oldPropertyURI + "> <" + RDFS.label.getURI() + "> ?label . \n" + + " ?configuration config:displayName ?rlabel } \n " + + " OPTIONAL { <" + oldPropertyURI + "> vitro:displayRankAnnot ?displayRank . \n" + + " ?configuration vitro:displayRantAnnot ?rdisplayRank } \n " + + " OPTIONAL { <" + oldPropertyURI + "> vitro:customEntryFormAnnot ?customForm . \n" + + " ?configuration vitro:customEntryFormAnnot ?rcustomForm } \n" + + " OPTIONAL { <" + oldPropertyURI + "> vitro:hiddenFromDisplayBelowRoleLevelAnnot ?displayLevel . \n" + + " ?configuration vitro:hiddenFromDisplayBelowRoleLevelAnnot ?rdisplayLevel } \n" + + " OPTIONAL { <" + oldPropertyURI + "> vitro:prohibitedFromUpdateBelowRoleLevelAnnot ?updateLevel . \n " + + " ?configuration vitro:prohibitedFromUpdateBelowRoleLevelAnnot ?updateLevel } " + + "} \n" ; + + Model configModel = ModelFactory.createDefaultModel(); + String configFileName = settings.getQualifiedPropertyConfigFile(); + File file = new File(configFileName); + FileInputStream fis = new FileInputStream(file); + configModel.read(fis, null, "N3"); + + Model union = ModelFactory.createUnion(configModel, + userChangesModel); + + Model additions = construct(addQuery, union); + Model retractions = construct(retractQuery, union); + + if (additions.size() > 0 || retractions.size() > 0) { + configModel.remove(retractions); + log.info("Removing " + retractions.size() + " statements from " + contextURI); + configModel.add(additions); + log.info("Adding " + additions.size() + " statements from " + contextURI); + FileOutputStream fos = new FileOutputStream(file); + configModel.write(fos, "N3"); + } + } + + private Model construct(String queryStr, Model model) { + Query query = QueryFactory.create(queryStr); + QueryExecution qe = QueryExecutionFactory.create(query, model); + try { + return qe.execConstruct(); + } finally { + qe.close(); + } + } } \ No newline at end of file diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/UpdateSettings.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/UpdateSettings.java index 309198e2c..1e37ded8f 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/UpdateSettings.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/UpdateSettings.java @@ -21,6 +21,7 @@ public class UpdateSettings { private String errorLogFile; private String addedDataFile; private String removedDataFile; + private String qualifiedPropertyConfigFile; private String defaultNamespace; private OntModelSelector assertionOntModelSelector; private OntModelSelector inferenceOntModelSelector; @@ -121,7 +122,13 @@ public class UpdateSettings { public void setRemovedDataFile(String removedDataFile) { this.removedDataFile = removedDataFile; } - public String getDefaultNamespace() { + public String getQualifiedPropertyConfigFile() { + return qualifiedPropertyConfigFile; + } + public void setQualifiedPropertyConfigFile(String qualifiedPropertyConfigFile) { + this.qualifiedPropertyConfigFile = qualifiedPropertyConfigFile; + } + public String getDefaultNamespace() { return defaultNamespace; } public void setDefaultNamespace(String defaultNamespace) { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/UpdateKnowledgeBase.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/UpdateKnowledgeBase.java index cf53c8f1f..569e4dbf4 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/UpdateKnowledgeBase.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/UpdateKnowledgeBase.java @@ -96,7 +96,7 @@ public class UpdateKnowledgeBase implements ServletContextListener { settings.setNewTBoxModel(newTBoxModel); OntModel oldTBoxAnnotationsModel = loadModelFromDirectory(ctx.getRealPath(OLD_TBOX_ANNOTATIONS_DIR)); settings.setOldTBoxAnnotationsModel(oldTBoxAnnotationsModel); - OntModel newTBoxAnnotationsModel = loadModelFromDirectory(createDirectory(homeDir, "rdf", "tbox", "everytime").toString()); + OntModel newTBoxAnnotationsModel = loadModelFromDirectory(createDirectory(homeDir, "rdf", "tbox", "firsttime").toString()); settings.setNewTBoxAnnotationsModel(newTBoxAnnotationsModel); settings.setRDFService(RDFServiceUtils.getRDFServiceFactory(ctx).getRDFService()); @@ -145,6 +145,9 @@ public class UpdateKnowledgeBase implements ServletContextListener { log.warn("unable to successfully update display model: " + e.getMessage()); } } + // reload the display model since the TBoxUpdater may have + // modified it + new ApplicationModelSetup().contextInitialized(sce); } } catch (Exception ioe) { ss.fatal(this, "Exception updating knowledge base for ontology changes: ", ioe); @@ -189,8 +192,19 @@ public class UpdateKnowledgeBase implements ServletContextListener { Path logDir = createDirectory(dataDir, "logs"); settings.setLogFile(logDir.resolve(timestampedFileName("knowledgeBaseUpdate", "log")).toString()); settings.setErrorLogFile(logDir.resolve(timestampedFileName("knowledgeBaseUpdate.error", "log")).toString()); + + Path qualifiedPropertyConfigFile = getFilePath(homeDir, "rdf", "display", "everytime", "PropertyConfig.n3"); + settings.setQualifiedPropertyConfigFile(qualifiedPropertyConfigFile.toString()); } + private Path getFilePath(Path parent, String... children) throws IOException { + Path path = parent; + for (String child : children) { + path = path.resolve(child); + } + return path; + } + private Path createDirectory(Path parent, String... children) throws IOException { Path dir = parent; for (String child : children) {