edu.stanford.protege.webprotege.project.chg.ChangeManager Maven / Gradle / Ivy
The newest version!
package edu.stanford.protege.webprotege.project.chg;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import edu.stanford.protege.webprotege.DataFactory;
import edu.stanford.protege.webprotege.access.AccessManager;
import edu.stanford.protege.webprotege.authorization.ProjectResource;
import edu.stanford.protege.webprotege.change.HasApplyChanges;
import edu.stanford.protege.webprotege.change.*;
import edu.stanford.protege.webprotege.common.ChangeRequestId;
import edu.stanford.protege.webprotege.common.EventId;
import edu.stanford.protege.webprotege.common.ProjectId;
import edu.stanford.protege.webprotege.crud.*;
import edu.stanford.protege.webprotege.crud.gen.GeneratedAnnotationsGenerator;
import edu.stanford.protege.webprotege.entity.FreshEntityIri;
import edu.stanford.protege.webprotege.events.EventTranslatorManager;
import edu.stanford.protege.webprotege.events.HighLevelProjectEventProxy;
import edu.stanford.protege.webprotege.hierarchy.AnnotationPropertyHierarchyProvider;
import edu.stanford.protege.webprotege.hierarchy.ClassHierarchyProvider;
import edu.stanford.protege.webprotege.hierarchy.DataPropertyHierarchyProvider;
import edu.stanford.protege.webprotege.hierarchy.ObjectPropertyHierarchyProvider;
import edu.stanford.protege.webprotege.index.RootIndex;
import edu.stanford.protege.webprotege.index.impl.IndexUpdater;
import edu.stanford.protege.webprotege.inject.ProjectSingleton;
import edu.stanford.protege.webprotege.ipc.EventDispatcher;
import edu.stanford.protege.webprotege.lang.ActiveLanguagesManager;
import edu.stanford.protege.webprotege.owlapi.OWLEntityCreator;
import edu.stanford.protege.webprotege.owlapi.RenameMap;
import edu.stanford.protege.webprotege.owlapi.RenameMapFactory;
import edu.stanford.protege.webprotege.permissions.PermissionDeniedException;
import edu.stanford.protege.webprotege.project.*;
import edu.stanford.protege.webprotege.revision.Revision;
import edu.stanford.protege.webprotege.revision.RevisionManager;
import edu.stanford.protege.webprotege.shortform.DictionaryManager;
import edu.stanford.protege.webprotege.shortform.DictionaryUpdatesProcessor;
import edu.stanford.protege.webprotege.common.UserId;
import edu.stanford.protege.webprotege.util.IriReplacer;
import edu.stanford.protege.webprotege.util.IriReplacerFactory;
import edu.stanford.protege.webprotege.webhook.ProjectChangedWebhookInvoker;
import org.semanticweb.owlapi.model.*;
import javax.annotation.Nonnull;
import javax.inject.Inject;
import javax.inject.Provider;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static com.google.common.base.Preconditions.checkNotNull;
import static edu.stanford.protege.webprotege.access.BuiltInAction.*;
import static edu.stanford.protege.webprotege.authorization.Subject.forUser;
/**
* Matthew Horridge
* Stanford Center for Biomedical Informatics Research
* 20 Jun 2017
*/
@ProjectSingleton
public class ChangeManager implements HasApplyChanges {
@Nonnull
private final ProjectId projectId;
@Nonnull
private final OWLDataFactory dataFactory;
@Nonnull
private final DictionaryUpdatesProcessor dictionaryUpdatesProcessor;
@Nonnull
private final ActiveLanguagesManager activeLanguagesManager;
@Nonnull
private final AccessManager accessManager;
@Nonnull
private final PrefixDeclarationsStore prefixDeclarationsStore;
@Nonnull
private final ProjectDetailsRepository projectDetailsRepository;
@Nonnull
private final ProjectChangedWebhookInvoker projectChangedWebhookInvoker;
@Nonnull
private final EventDispatcher eventDispatcher;
@Nonnull
private final Provider eventTranslatorManagerProvider;
@Nonnull
private final ProjectEntityCrudKitHandlerCache entityCrudKitHandlerCache;
@Nonnull
private final RevisionManager changeManager;
@Nonnull
private final RootIndex rootIndex;
@Nonnull
private final DictionaryManager dictionaryManager;
@Nonnull
private final ClassHierarchyProvider classHierarchyProvider;
@Nonnull
private final ObjectPropertyHierarchyProvider objectPropertyHierarchyProvider;
@Nonnull
private final DataPropertyHierarchyProvider dataPropertyHierarchyProvider;
@Nonnull
private final AnnotationPropertyHierarchyProvider annotationPropertyHierarchyProvider;
@Nonnull
private final EntityCrudContextFactory entityCrudContextFactory;
@Nonnull
private final ReadWriteLock projectChangeLock = new ReentrantReadWriteLock();
@Nonnull
private final Lock projectChangeWriteLock = projectChangeLock.writeLock();
@Nonnull
private final Lock changeProcesssingLock = new ReentrantLock();
@Nonnull
private final RenameMapFactory renameMapFactory;
@Nonnull
private final BuiltInPrefixDeclarations builtInPrefixDeclarations;
@Nonnull
private final IndexUpdater indexUpdater;
@Nonnull
private final DefaultOntologyIdManager defaultOntologyIdManager;
@Nonnull
private final IriReplacerFactory iriReplacerFactory;
@Nonnull
private final GeneratedAnnotationsGenerator generatedAnnotationsGenerator;
private final OntologyChangeIriReplacer ontologyChangeIriReplacer = new OntologyChangeIriReplacer();
@Inject
public ChangeManager(@Nonnull ProjectId projectId,
@Nonnull OWLDataFactory dataFactory,
@Nonnull DictionaryUpdatesProcessor dictionaryUpdatesProcessor,
@Nonnull ActiveLanguagesManager activeLanguagesManager,
@Nonnull AccessManager accessManager,
@Nonnull PrefixDeclarationsStore prefixDeclarationsStore,
@Nonnull ProjectDetailsRepository projectDetailsRepository,
@Nonnull ProjectChangedWebhookInvoker projectChangedWebhookInvoker,
@Nonnull Provider eventTranslatorManagerProvider,
@Nonnull ProjectEntityCrudKitHandlerCache entityCrudKitHandlerCache,
@Nonnull RevisionManager changeManager,
@Nonnull RootIndex rootIndex,
@Nonnull DictionaryManager dictionaryManager,
@Nonnull ClassHierarchyProvider classHierarchyProvider,
@Nonnull ObjectPropertyHierarchyProvider objectPropertyHierarchyProvider,
@Nonnull DataPropertyHierarchyProvider dataPropertyHierarchyProvider,
@Nonnull AnnotationPropertyHierarchyProvider annotationPropertyHierarchyProvider,
@Nonnull EntityCrudContextFactory entityCrudContextFactory,
@Nonnull RenameMapFactory renameMapFactory,
@Nonnull BuiltInPrefixDeclarations builtInPrefixDeclarations,
@Nonnull IndexUpdater indexUpdater,
@Nonnull DefaultOntologyIdManager defaultOntologyIdManager,
@Nonnull IriReplacerFactory iriReplacerFactory,
@Nonnull GeneratedAnnotationsGenerator generatedAnnotationsGenerator,
@Nonnull EventDispatcher eventDispatcher) {
this.projectId = projectId;
this.dataFactory = dataFactory;
this.dictionaryUpdatesProcessor = dictionaryUpdatesProcessor;
this.activeLanguagesManager = activeLanguagesManager;
this.accessManager = accessManager;
this.prefixDeclarationsStore = prefixDeclarationsStore;
this.projectDetailsRepository = projectDetailsRepository;
this.projectChangedWebhookInvoker = projectChangedWebhookInvoker;
this.eventTranslatorManagerProvider = eventTranslatorManagerProvider;
this.entityCrudKitHandlerCache = entityCrudKitHandlerCache;
this.eventDispatcher = eventDispatcher;
this.changeManager = changeManager;
this.rootIndex = rootIndex;
this.dictionaryManager = dictionaryManager;
this.classHierarchyProvider = classHierarchyProvider;
this.objectPropertyHierarchyProvider = objectPropertyHierarchyProvider;
this.dataPropertyHierarchyProvider = dataPropertyHierarchyProvider;
this.annotationPropertyHierarchyProvider = annotationPropertyHierarchyProvider;
this.entityCrudContextFactory = entityCrudContextFactory;
this.renameMapFactory = renameMapFactory;
this.builtInPrefixDeclarations = builtInPrefixDeclarations;
this.indexUpdater = indexUpdater;
this.defaultOntologyIdManager = defaultOntologyIdManager;
this.iriReplacerFactory = iriReplacerFactory;
this.generatedAnnotationsGenerator = generatedAnnotationsGenerator;
}
/**
* Applies ontology changes to the ontologies contained within a project.
*
*
* @param userId The userId of the user applying the changes. Not {@code null}.
* @param changeListGenerator A generator which creates a list of changes (based on the state of the project at
* the time of change application). The idea behind passing in a change generator is
* that the list of changes to be applied can be created based on the state of the
* project immediately before they are applied. This is necessary where the changes
* depend on the structure/state of the ontology. This method guarantees that no third
* party ontology changes will take place between the
* {@link ChangeListGenerator#generateChanges(ChangeGenerationContext)}
* method being called and the changes being applied.
* @return A {@link ChangeApplicationResult} that describes the changes which took place an any renaminings.
* @throws NullPointerException if any parameters are {@code null}.
* @throws PermissionDeniedException if the user identified by {@code userId} does not have permssion to write to
* ontologies in this project.
*/
@Override
public ChangeApplicationResult applyChanges(@Nonnull final UserId userId,
@Nonnull final ChangeListGenerator changeListGenerator) throws PermissionDeniedException {
checkNotNull(userId);
checkNotNull(changeListGenerator);
// Final check of whether the user can actually edit the project
throwEditPermissionDeniedIfNecessary(userId);
final ChangeApplicationResult changeApplicationResult;
var crudContext = getEntityCrudContext(userId);
// The following must take into consideration fresh entity IRIs. Entity IRIs are minted on the webprotege, so
// ontology changes may contain fresh entity IRIs as place holders. We need to make sure these get replaced
// with true entity IRIs
try {
// Compute the changes that need to take place. We don't allow any other writes here because the
// generation of the changes may depend upon the state of the project
changeProcesssingLock.lock();
var changeList = changeListGenerator.generateChanges(new ChangeGenerationContext(userId));
// We have our changes
var changes = changeList.getChanges();
// We coin fresh IRIs for entities that have IRIs that follow the temp IRI pattern
// See DataFactory#isFreshEntity
var tempIri2MintedIri = new HashMap();
var changeSession = getEntityCrudKitHandler().createChangeSetSession();
// Changes that refer to entities that have temp IRIs
var changesToBeRenamed = new HashSet();
// Changes required to create fresh entities
var changesToCreateFreshEntities = new ArrayList();
for(var change : changes) {
change.getSignature()
.forEach(entityInSignature -> {
if(isFreshEntity(entityInSignature)) {
throwCreatePermissionDeniedIfNecessary(entityInSignature, userId);
changesToBeRenamed.add(change);
var tempIri = entityInSignature.getIRI();
if(!tempIri2MintedIri.containsKey(tempIri)) {
var freshEntityIri = FreshEntityIri.parse(tempIri.toString());
var shortName = freshEntityIri.getSuppliedName();
var langTag = Optional.empty();
if(!shortName.isEmpty()) {
langTag = Optional.of(freshEntityIri.getLangTag());
}
var entityType = entityInSignature.getEntityType();
var discriminator = freshEntityIri.getDiscriminator();
var parents = freshEntityIri.getParentEntities(dataFactory, entityType);
var creator = getEntityCreator(changeSession,
crudContext,
shortName,
discriminator,
langTag,
parents,
entityType);
changesToCreateFreshEntities.addAll(creator.getChanges());
var mintedIri = creator.getEntity()
.getIRI();
tempIri2MintedIri.put(tempIri, mintedIri);
}
}
});
if(isChangeForAnnotationAssertionWithFreshIris(change)) {
changesToBeRenamed.add(change);
}
}
var allChangesIncludingRenames = new ArrayList();
var changeRenamer = iriReplacerFactory.create(ImmutableMap.copyOf(tempIri2MintedIri));
for(var change : changes) {
if(changesToBeRenamed.contains(change)) {
var replacementChange = getRenamedChange(change, changeRenamer);
allChangesIncludingRenames.add(replacementChange);
}
else {
allChangesIncludingRenames.add(change);
}
}
allChangesIncludingRenames.addAll(changesToCreateFreshEntities);
final var eventTranslatorManager = eventTranslatorManagerProvider.get();
// Now we do the actual changing, so we lock the project here. No writes or reads can take place whilst
// we apply the changes
projectChangeWriteLock.lock();
final Optional revision;
try {
var effectiveChanges = rootIndex.getEffectiveChanges(allChangesIncludingRenames);
eventTranslatorManager.prepareForOntologyChanges(effectiveChanges);
var renameMap = renameMapFactory.create(tempIri2MintedIri);
var renamedResult = getRenamedResult(changeListGenerator, changeList.getResult(), renameMap);
changeApplicationResult = new ChangeApplicationResult<>(renamedResult, effectiveChanges, renameMap);
if(!effectiveChanges.isEmpty()) {
var rev = logAndProcessAppliedChanges(userId, changeListGenerator, changeApplicationResult);
revision = Optional.of(rev);
projectDetailsRepository.setModified(projectId, rev.getTimestamp(), userId);
}
else {
revision = Optional.empty();
}
} finally {
// Release for reads
projectChangeWriteLock.unlock();
}
var changeRequestId = changeListGenerator.getChangeRequestId();
generateAndDispatchHighLevelEvents(changeRequestId, userId,
changeListGenerator,
changeApplicationResult,
eventTranslatorManager,
revision);
} finally {
changeProcesssingLock.unlock();
}
return changeApplicationResult;
}
private void throwEditPermissionDeniedIfNecessary(UserId userId) {
var subject = forUser(userId);
var projectResource = new ProjectResource(projectId);
if(!accessManager.hasPermission(subject, projectResource, EDIT_ONTOLOGY)) {
throw new PermissionDeniedException("You do not have permission to edit this project");
}
}
private EntityCrudContext getEntityCrudContext(UserId userId) {
var prefixNameExpanderBuilder = PrefixedNameExpander.builder();
prefixDeclarationsStore.find(projectId)
.getPrefixes()
.forEach(prefixNameExpanderBuilder::withPrefixNamePrefix);
builtInPrefixDeclarations.getPrefixDeclarations()
.forEach(decl -> prefixNameExpanderBuilder.withPrefixNamePrefix(decl.getPrefixName(),
decl.getPrefix()));
var prefixNameExpander = prefixNameExpanderBuilder.build();
var defaultOntologyId = defaultOntologyIdManager.getDefaultOntologyId();
return entityCrudContextFactory.create(userId,
prefixNameExpander,
defaultOntologyId);
}
@SuppressWarnings("unchecked")
private EntityCrudKitHandler getEntityCrudKitHandler() {
return (EntityCrudKitHandler) entityCrudKitHandlerCache.getHandler();
}
private static boolean isFreshEntity(OWLEntity entity) {
return FreshEntityIri.isFreshEntityIri(entity.getIRI());
}
private void throwCreatePermissionDeniedIfNecessary(OWLEntity entity,
UserId userId) {
var subject = forUser(userId);
var projectResource = new ProjectResource(projectId);
if(entity.isOWLClass()) {
if(!accessManager.hasPermission(subject, projectResource, CREATE_CLASS)) {
throw new PermissionDeniedException("You do not have permission to create new classes");
}
}
else if(entity.isOWLObjectProperty() || entity.isOWLDataProperty() || entity.isOWLAnnotationProperty()) {
if(!accessManager.hasPermission(subject, projectResource, CREATE_PROPERTY)) {
throw new PermissionDeniedException("You do not have permission to create new properties");
}
}
else if(entity.isOWLNamedIndividual()) {
if(!accessManager.hasPermission(subject, projectResource, CREATE_INDIVIDUAL)) {
throw new PermissionDeniedException("You do not have permission to create new individuals");
}
}
else if(entity.isOWLDatatype()) {
if(!accessManager.hasPermission(subject, projectResource, CREATE_DATATYPE)) {
throw new PermissionDeniedException("You do not have permission to create new datatypes");
}
}
}
private OWLEntityCreator getEntityCreator(ChangeSetEntityCrudSession session,
EntityCrudContext context,
String shortName,
String discriminator,
Optional langTag,
ImmutableList parents,
EntityType entityType) {
if (discriminator.isEmpty() && !shortName.isEmpty()) {
Optional entity = getEntityOfTypeIfPresent(entityType, shortName);
if(entity.isPresent()) {
return new OWLEntityCreator<>(entity.get(), Collections.emptyList());
}
}
OntologyChangeList.Builder builder = OntologyChangeList.builder();
EntityCrudKitHandler handler = getEntityCrudKitHandler();
handler.createChangeSetSession();
E ent = handler.create(session, entityType, EntityShortForm.get(shortName), langTag, parents, context, builder);
// Generate changes to apply annotations
generatedAnnotationsGenerator.generateAnnotations(ent,
parents,
getEntityCrudKitHandler().getSettings(),
builder);
return new OWLEntityCreator<>(ent,
builder.build(ent)
.getChanges());
}
private boolean isChangeForAnnotationAssertionWithFreshIris(OntologyChange change) {
if(!change.isAxiomChange()) {
return false;
}
var axiom = change.getAxiomOrThrow();
if(!(axiom instanceof OWLAnnotationAssertionAxiom)) {
return false;
}
var assertion = (OWLAnnotationAssertionAxiom) axiom;
var subject = assertion.getSubject();
if(subject instanceof IRI) {
if(DataFactory.isFreshIri((IRI) subject)) {
return true;
}
}
var object = assertion.getValue();
if(object instanceof IRI) {
return DataFactory.isFreshIri((IRI) object);
}
return false;
}
/**
* Gets an ontology change which is a copy of an existing ontology change except for IRIs that are renamed.
* Renamings
* are specified by a rename map.
*
* @param change The change to copy.
* @param iriReplacer An IRI replacer used to rename IRIs in OWL objects
* @return The ontology change with the renamings.
*/
private OntologyChange getRenamedChange(OntologyChange change,
IriReplacer iriReplacer) {
return ontologyChangeIriReplacer.replaceIris(change, iriReplacer);
}
/**
* Renames a result if it is present.
*
* @param result The result to process.
* @param renameMap The rename map.
* @param The type of result.
* @return The renamed (or untouched if no rename was necessary) result.
*/
private R getRenamedResult(ChangeListGenerator changeListGenerator,
R result,
RenameMap renameMap) {
return changeListGenerator.getRenamedResult(result, renameMap);
}
private Revision logAndProcessAppliedChanges(UserId userId,
ChangeListGenerator changeList,
ChangeApplicationResult finalResult) {
var changes = finalResult.getChangeList();
// Update indexes in response to the changes
indexUpdater.updateIndexes(ImmutableList.copyOf(changes));
// Update the rendering first so that a proper change message is generated
activeLanguagesManager.handleChanges(changes);
dictionaryUpdatesProcessor.handleChanges(changes);
// Generate a description for the changes that were actually applied
var changeDescription = changeList.getMessage(finalResult);
// Log the changes
var revision = changeManager.addRevision(userId, changes, changeDescription);
classHierarchyProvider.handleChanges(changes);
objectPropertyHierarchyProvider.handleChanges(changes);
dataPropertyHierarchyProvider.handleChanges(changes);
annotationPropertyHierarchyProvider.handleChanges(changes);
return revision;
}
private void generateAndDispatchHighLevelEvents(ChangeRequestId changeRequestId, UserId userId,
ChangeListGenerator changeListGenerator,
ChangeApplicationResult finalResult,
EventTranslatorManager eventTranslatorManager,
Optional revision) {
var changes = finalResult.getChangeList();
// Fire low-level ontology changed events. There's an event for every change
// that was applied
for(var change : changes) {
var event = new OntologyChangedEvent(EventId.generate(), projectId, userId, change);
eventDispatcher.dispatchEvent(event);
}
if(changeListGenerator instanceof SilentChangeListGenerator) {
return;
}
revision.ifPresent(rev -> {
var highLevelEvents = new ArrayList();
eventTranslatorManager.translateOntologyChanges(changeRequestId, rev, finalResult, highLevelEvents);
if(changeListGenerator instanceof HasHighLevelEvents) {
highLevelEvents.addAll(((HasHighLevelEvents) changeListGenerator).getHighLevelEvents());
}
highLevelEvents.stream().map(HighLevelProjectEventProxy::asProjectEvent)
.forEach(eventDispatcher::dispatchEvent);
projectChangedWebhookInvoker.invoke(userId, rev.getRevisionNumber(), rev.getTimestamp());
});
}
@SuppressWarnings("unchecked")
private Optional getEntityOfTypeIfPresent(EntityType entityType,
String shortName) {
return dictionaryManager
.getEntities(shortName)
.filter(entity -> entity.getEntityType()
.equals(entityType))
.map(entity -> (E) entity)
.findFirst();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy