package org.molgenis.data.annotation.web;
import org.molgenis.data.DataService;
import org.molgenis.data.DatabaseAction;
import org.molgenis.data.Entity;
import org.molgenis.data.Repository;
import org.molgenis.data.annotation.core.EffectCreatingAnnotator;
import org.molgenis.data.annotation.core.RepositoryAnnotator;
import org.molgenis.data.annotation.core.exception.AnnotationException;
import org.molgenis.data.annotation.core.exception.UiAnnotationException;
import org.molgenis.data.meta.model.AttributeFactory;
import org.molgenis.data.meta.model.EntityType;
import org.molgenis.security.permission.PermissionSystemService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import static java.util.Spliterator.ORDERED;
import static java.util.Spliterators.spliteratorUnknownSize;
import static java.util.stream.StreamSupport.stream;
import static org.molgenis.data.DatabaseAction.UPDATE;
import static org.molgenis.data.RepositoryCapability.WRITABLE;
import static org.molgenis.data.annotation.core.utils.AnnotatorUtils.addAnnotatorMetaDataToRepositories;
import static org.molgenis.security.core.runas.RunAsSystemProxy.runAsSystem;
@Component
public class CrudRepositoryAnnotator
{
private static final Logger LOG = LoggerFactory.getLogger(CrudRepositoryAnnotator.class);
private final DataService dataService;
private final PermissionSystemService permissionSystemService;
private EntityType targetMetaData;
private AttributeFactory attributeFactory;
@Autowired
public CrudRepositoryAnnotator(DataService dataService, PermissionSystemService permissionSystemService,
AttributeFactory attributeFactory)
{
this.dataService = dataService;
this.permissionSystemService = permissionSystemService;
this.attributeFactory = attributeFactory;
}
/**
* @param annotator
* @param repository
*/
public void annotate(RepositoryAnnotator annotator, Repository<Entity> repository) throws IOException
{
annotate(annotator, repository, UPDATE);
}
/**
* @param annotator
* @param repository
* @param action
*/
private void annotate(RepositoryAnnotator annotator, Repository<Entity> repository, DatabaseAction action)
{
if (!repository.getCapabilities().contains(WRITABLE))
{
throw new UnsupportedOperationException("Currently only writable repositories can be annotated");
}
try
{
EntityType entityType = dataService.getMeta().getEntityType(repository.getName());
if (annotator instanceof EffectCreatingAnnotator)
{
targetMetaData = ((EffectCreatingAnnotator) annotator).getTargetEntityType(entityType);
if (!dataService.hasRepository(targetMetaData.getName()))
{
// add new entities to new repo
Repository externalRepository = dataService.getMeta().createRepository(targetMetaData);
permissionSystemService.giveUserEntityPermissions(SecurityContextHolder.getContext(),
Collections.singletonList(externalRepository.getName()));
runAsSystem(() -> dataService.getMeta().updateEntityType(externalRepository.getEntityType()));
iterateOverEntitiesAndAnnotate(repository, annotator, DatabaseAction.ADD);
}
else
{
throw new UnsupportedOperationException(
"This entity has already been annotated with " + annotator.getSimpleName());
}
}
else
{
runAsSystem(() -> dataService.getMeta()
.updateEntityType(addAnnotatorMetaDataToRepositories(entityType, attributeFactory, annotator)));
iterateOverEntitiesAndAnnotate(dataService.getRepository(repository.getName()), annotator, action);
}
}
catch (AnnotationException ae)
{
deleteResultEntity(annotator, targetMetaData);
throw new UiAnnotationException(ae);
}
catch (Exception e)
{
deleteResultEntity(annotator, targetMetaData);
throw new RuntimeException(e);
}
}
private void deleteResultEntity(RepositoryAnnotator annotator, EntityType targetMetaData)
{
try
{
if (annotator instanceof EffectCreatingAnnotator && targetMetaData != null)
{
runAsSystem(() ->
{
dataService.deleteAll(targetMetaData.getName());
dataService.getMeta().deleteEntityType(targetMetaData.getName());
});
}
}
catch (Exception ex)
{
// log the problem but throw the original exception
LOG.error("Failed to remove result entity: %s", targetMetaData.getName());
}
}
/**
* Iterates over all the entities within a repository and annotates.
*/
private void iterateOverEntitiesAndAnnotate(Repository<Entity> repository, RepositoryAnnotator annotator,
DatabaseAction action)
{
Iterator<Entity> it = annotator.annotate(repository);
String entityName;
if (annotator instanceof EffectCreatingAnnotator)
{
entityName = ((EffectCreatingAnnotator) annotator).getTargetEntityType(repository.getEntityType()).getName();
}
else
{
entityName = repository.getName();
}
switch (action)
{
case UPDATE:
dataService.update(entityName, stream(spliteratorUnknownSize(it, ORDERED), false));
break;
case ADD:
dataService.add(entityName, stream(spliteratorUnknownSize(it, ORDERED), false));
break;
default:
throw new UnsupportedOperationException();
}
}
}