/*
* Copyright 2012
* Ubiquitous Knowledge Processing (UKP) Lab and FG Language Technology
* Technische Universität Darmstadt
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.tudarmstadt.ukp.clarin.webanno.api.dao;
import static de.tudarmstadt.ukp.clarin.webanno.api.ProjectService.ANNOTATION;
import static de.tudarmstadt.ukp.clarin.webanno.api.ProjectService.DOCUMENT;
import static de.tudarmstadt.ukp.clarin.webanno.api.ProjectService.PROJECT;
import static de.tudarmstadt.ukp.clarin.webanno.api.ProjectService.SOURCE;
import static de.tudarmstadt.ukp.clarin.webanno.api.WebAnnoConst.INITIAL_CAS_PSEUDO_USER;
import static org.apache.commons.io.IOUtils.closeQuietly;
import static org.apache.commons.io.IOUtils.copyLarge;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.zip.ZipFile;
import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import org.apache.commons.io.FileUtils;
import org.apache.uima.UIMAException;
import org.apache.uima.cas.CAS;
import org.apache.uima.cas.CASException;
import org.apache.uima.jcas.JCas;
import org.apache.uima.resource.ResourceInitializationException;
import org.apache.uima.resource.metadata.TypeSystemDescription;
import org.apache.uima.util.CasCreationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import de.tudarmstadt.ukp.clarin.webanno.api.AnnotationSchemaService;
import de.tudarmstadt.ukp.clarin.webanno.api.CasStorageService;
import de.tudarmstadt.ukp.clarin.webanno.api.DocumentLifecycleAware;
import de.tudarmstadt.ukp.clarin.webanno.api.DocumentLifecycleAwareRegistry;
import de.tudarmstadt.ukp.clarin.webanno.api.DocumentService;
import de.tudarmstadt.ukp.clarin.webanno.api.ImportExportService;
import de.tudarmstadt.ukp.clarin.webanno.api.ProjectLifecycleAware;
import de.tudarmstadt.ukp.clarin.webanno.api.WebAnnoConst;
import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocument;
import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocumentState;
import de.tudarmstadt.ukp.clarin.webanno.model.Mode;
import de.tudarmstadt.ukp.clarin.webanno.model.PermissionLevel;
import de.tudarmstadt.ukp.clarin.webanno.model.Project;
import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument;
import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocumentStateTransition;
import de.tudarmstadt.ukp.clarin.webanno.security.UserDao;
import de.tudarmstadt.ukp.clarin.webanno.security.model.User;
import de.tudarmstadt.ukp.clarin.webanno.support.logging.Logging;
@Component(DocumentService.SERVICE_NAME)
public class DocumentServiceImpl
implements DocumentService, InitializingBean, ProjectLifecycleAware
{
private final Logger log = LoggerFactory.getLogger(getClass());
@PersistenceContext
private EntityManager entityManager;
@Resource(name = "annotationService")
private AnnotationSchemaService annotationService;
@Resource(name = "userRepository")
private UserDao userRepository;
@Resource(name = "casStorageService")
private CasStorageService casStorageService;
@Resource(name = "importExportService")
private ImportExportService importExportService;
@Resource
private DocumentLifecycleAwareRegistry documentLifecycleAwareRegistry;
@Value(value = "${repository.path}")
private File dir;
@Override
public void afterPropertiesSet()
throws Exception
{
log.info("Repository: " + dir);
}
@Override
public File getDir()
{
return dir;
}
@Override
public File getDocumentFolder(SourceDocument aDocument)
throws IOException
{
File sourceDocFolder = new File(dir, PROJECT + aDocument.getProject().getId() + DOCUMENT
+ aDocument.getId() + SOURCE);
FileUtils.forceMkdir(sourceDocFolder);
return sourceDocFolder;
}
@Override
@Transactional
public void createSourceDocument(SourceDocument aDocument)
throws IOException
{
if (aDocument.getId() == 0) {
entityManager.persist(aDocument);
}
else {
entityManager.merge(aDocument);
}
// Notify all relevant service so that they can initialize themselves for the given document
for (DocumentLifecycleAware bean : documentLifecycleAwareRegistry
.getBeans()) {
try {
bean.afterDocumentCreate(aDocument);
}
catch (IOException e) {
throw e;
}
catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
@Override
@Transactional
public boolean existsAnnotationDocument(SourceDocument aDocument, User aUser)
{
try {
entityManager
.createQuery(
"FROM AnnotationDocument WHERE project = :project "
+ " AND document = :document AND user = :user",
AnnotationDocument.class)
.setParameter("project", aDocument.getProject())
.setParameter("document", aDocument).setParameter("user", aUser.getUsername())
.getSingleResult();
return true;
}
catch (NoResultException ex) {
return false;
}
}
@Override
@Transactional
public void createAnnotationDocument(AnnotationDocument aAnnotationDocument)
{
if (aAnnotationDocument.getId() == 0) {
entityManager.persist(aAnnotationDocument);
}
else {
entityManager.merge(aAnnotationDocument);
}
try (MDC.MDCCloseable closable = MDC.putCloseable(Logging.KEY_PROJECT_ID,
String.valueOf(aAnnotationDocument.getProject().getId()))) {
log.info(
"Created annotation document [{}] for user [{}] for source document [{}]({}) "
+ "in project [{}]({})",
aAnnotationDocument.getId(), aAnnotationDocument.getUser(),
aAnnotationDocument.getDocument().getName(),
aAnnotationDocument.getDocument().getId(),
aAnnotationDocument.getProject().getName(),
aAnnotationDocument.getProject().getId());
}
}
@Override
@Transactional
public boolean existsCas(SourceDocument aSourceDocument, String aUsername)
throws IOException
{
return new File(casStorageService.getAnnotationFolder(aSourceDocument), aUsername + ".ser")
.exists();
}
@Override
@Transactional
public boolean existsSourceDocument(Project aProject, String aFileName)
{
try {
entityManager
.createQuery(
"FROM SourceDocument WHERE project = :project AND " + "name =:name ",
SourceDocument.class).setParameter("project", aProject)
.setParameter("name", aFileName).getSingleResult();
return true;
}
catch (NoResultException ex) {
return false;
}
}
@Override
public File getSourceDocumentFile(SourceDocument aDocument)
{
File documentUri = new File(dir.getAbsolutePath() + PROJECT
+ aDocument.getProject().getId() + DOCUMENT + aDocument.getId() + SOURCE);
return new File(documentUri, aDocument.getName());
}
@Override
public File getCasFile(SourceDocument aDocument, String aUser)
{
File documentUri = new File(dir.getAbsolutePath() + PROJECT
+ aDocument.getProject().getId() + DOCUMENT + aDocument.getId() + ANNOTATION);
return new File(documentUri, aUser + ".ser");
}
@Override
@Transactional(noRollbackFor = NoResultException.class)
public AnnotationDocument createOrGetAnnotationDocument(SourceDocument aDocument, User aUser)
{
// Check if there is an annotation document entry in the database. If there is none,
// create one.
AnnotationDocument annotationDocument = null;
if (!existsAnnotationDocument(aDocument, aUser)) {
annotationDocument = new AnnotationDocument();
annotationDocument.setDocument(aDocument);
annotationDocument.setName(aDocument.getName());
annotationDocument.setUser(aUser.getUsername());
annotationDocument.setProject(aDocument.getProject());
createAnnotationDocument(annotationDocument);
}
else {
annotationDocument = getAnnotationDocument(aDocument, aUser);
}
return annotationDocument;
}
@Override
@Transactional(noRollbackFor = NoResultException.class)
public AnnotationDocument getAnnotationDocument(SourceDocument aDocument, User aUser)
{
return entityManager
.createQuery(
"FROM AnnotationDocument WHERE document = :document AND " + "user =:user"
+ " AND project = :project", AnnotationDocument.class)
.setParameter("document", aDocument).setParameter("user", aUser.getUsername())
.setParameter("project", aDocument.getProject()).getSingleResult();
}
@Override
@Transactional(noRollbackFor = NoResultException.class)
public SourceDocument getSourceDocument(Project aProject, String aDocumentName)
{
return entityManager
.createQuery("FROM SourceDocument WHERE name = :name AND project =:project",
SourceDocument.class).setParameter("name", aDocumentName)
.setParameter("project", aProject).getSingleResult();
}
@Override
@Transactional(noRollbackFor = NoResultException.class)
public SourceDocument getSourceDocument(long aProjectId, long aSourceDocId)
{
return entityManager.createQuery("FROM SourceDocument WHERE id = :docid AND project.id =:pid", SourceDocument.class)
.setParameter("docid", aSourceDocId)
.setParameter("pid", aProjectId).getSingleResult();
}
@Override
@Transactional(noRollbackFor = NoResultException.class)
public boolean existsFinishedAnnotation(SourceDocument aDocument)
{
List<AnnotationDocument> annotationDocuments = entityManager
.createQuery("FROM AnnotationDocument WHERE document = :document",
AnnotationDocument.class).setParameter("document", aDocument)
.getResultList();
for (AnnotationDocument annotationDocument : annotationDocuments) {
if (annotationDocument.getState().equals(AnnotationDocumentState.FINISHED)) {
return true;
}
}
return false;
}
@Override
@Transactional(noRollbackFor = NoResultException.class)
public boolean existsFinishedAnnotation(Project aProject)
{
for (SourceDocument document : listSourceDocuments(aProject)) {
List<AnnotationDocument> annotationDocuments = entityManager
.createQuery("FROM AnnotationDocument WHERE document = :document",
AnnotationDocument.class).setParameter("document", document)
.getResultList();
for (AnnotationDocument annotationDocument : annotationDocuments) {
if (annotationDocument.getState().equals(AnnotationDocumentState.FINISHED)) {
return true;
}
}
}
return false;
}
@Override
public List<AnnotationDocument> listFinishedAnnotationDocuments(Project aProject)
{
// Get all annotators in the project
List<String> users = getAllAnnotators(aProject);
// Bail out already. HQL doesn't seem to like queries with an empty
// parameter right of "in"
if (users.isEmpty()) {
return new ArrayList<AnnotationDocument>();
}
return entityManager
.createQuery(
"FROM AnnotationDocument WHERE project = :project AND state = :state"
+ " AND user in (:users)", AnnotationDocument.class)
.setParameter("project", aProject).setParameter("users", users)
.setParameter("state", AnnotationDocumentState.FINISHED).getResultList();
}
@Override
@Transactional(noRollbackFor = NoResultException.class)
public List<AnnotationDocument> listAllAnnotationDocuments(SourceDocument aSourceDocument)
{
return entityManager
.createQuery(
"FROM AnnotationDocument WHERE project = :project AND document = :document",
AnnotationDocument.class)
.setParameter("project", aSourceDocument.getProject())
.setParameter("document", aSourceDocument).getResultList();
}
@Override
@Transactional(noRollbackFor = NoResultException.class)
public List<SourceDocument> listSourceDocuments(Project aProject)
{
List<SourceDocument> sourceDocuments = entityManager
.createQuery("FROM SourceDocument where project =:project ORDER BY name ASC", SourceDocument.class)
.setParameter("project", aProject).getResultList();
List<SourceDocument> tabSepDocuments = new ArrayList<SourceDocument>();
for (SourceDocument sourceDocument : sourceDocuments) {
if (sourceDocument.getFormat().equals(WebAnnoConst.TAB_SEP)) {
tabSepDocuments.add(sourceDocument);
}
}
sourceDocuments.removeAll(tabSepDocuments);
return sourceDocuments;
}
@Override
@Transactional
public void removeSourceDocument(SourceDocument aDocument)
throws IOException
{
for (AnnotationDocument annotationDocument : listAllAnnotationDocuments(aDocument)) {
removeAnnotationDocument(annotationDocument);
}
// Notify all relevant service so that they can clean up themselves before we remove the
// document - notification happens in reverse order
List<DocumentLifecycleAware> beans = new ArrayList<>(
documentLifecycleAwareRegistry.getBeans());
Collections.reverse(beans);
for (DocumentLifecycleAware bean : beans) {
try {
bean.beforeDocumentRemove(aDocument);
}
catch (IOException e) {
throw e;
}
catch (Exception e) {
throw new IllegalStateException(e);
}
}
entityManager.remove(aDocument);
String path = dir.getAbsolutePath() + PROJECT + aDocument.getProject().getId() + DOCUMENT
+ aDocument.getId();
// remove from file both source and related annotation file
if (new File(path).exists()) {
FileUtils.forceDelete(new File(path));
}
try (MDC.MDCCloseable closable = MDC.putCloseable(Logging.KEY_PROJECT_ID,
String.valueOf(aDocument.getProject().getId()))) {
Project project = aDocument.getProject();
log.info("Removed source document [{}]({}) from project [{}]({})", aDocument.getName(),
aDocument.getId(), project.getName(), project.getId());
}
}
@Override
@Transactional
public void removeAnnotationDocument(AnnotationDocument aAnnotationDocument)
{
entityManager.remove(aAnnotationDocument);
}
@Override
@Transactional
public void uploadSourceDocument(File aFile, SourceDocument aDocument)
throws IOException
{
// Create the metadata record - this also assigns the ID to the document
createSourceDocument(aDocument);
// Copy the original file into the repository
File targetFile = getSourceDocumentFile(aDocument);
FileUtils.forceMkdir(targetFile.getParentFile());
FileUtils.copyFile(aFile, targetFile);
// Check if the file has a valid format / can be converted without error
// This requires that the document ID has already been assigned
try {
createInitialCas(aDocument);
}
catch (Exception e) {
FileUtils.forceDelete(targetFile);
removeSourceDocument(aDocument);
throw new IOException(e.getMessage(), e);
}
try (MDC.MDCCloseable closable = MDC.putCloseable(Logging.KEY_PROJECT_ID,
String.valueOf(aDocument.getProject().getId()))) {
Project project = aDocument.getProject();
log.info("Imported source document [{}]({}) to project [{}]({})",
aDocument.getName(), aDocument.getId(), project.getName(), project.getId());
}
}
@Override
@Transactional
@Deprecated
public void uploadSourceDocument(InputStream aIs, SourceDocument aDocument)
throws IOException
{
File targetFile = getSourceDocumentFile(aDocument);
FileUtils.forceMkdir(targetFile.getParentFile());
OutputStream os = null;
try {
os = new FileOutputStream(targetFile);
copyLarge(aIs, os);
}
finally {
closeQuietly(os);
closeQuietly(aIs);
}
try (MDC.MDCCloseable closable = MDC.putCloseable(Logging.KEY_PROJECT_ID,
String.valueOf(aDocument.getProject().getId()))) {
Project project = aDocument.getProject();
log.info("Imported source document [{}]({}) to project [{}]({})",
aDocument.getName(), aDocument.getId(), project.getName(), project.getId());
}
}
@Override
public boolean existsInitialCas(SourceDocument aDocument)
throws IOException
{
return existsCas(aDocument, INITIAL_CAS_PSEUDO_USER);
}
@Override
public JCas createInitialCas(SourceDocument aDocument)
throws UIMAException, IOException, ClassNotFoundException
{
// Normally, the initial CAS should be created on document import, but after
// adding this feature, the existing projects do not yet have initial CASes, so
// we create them here lazily
JCas jcas = importExportService.importCasFromFile(getSourceDocumentFile(aDocument),
aDocument.getProject(), aDocument.getFormat());
casStorageService.analyzeAndRepair(aDocument, INITIAL_CAS_PSEUDO_USER, jcas.getCas());
CasPersistenceUtils.writeSerializedCas(jcas,
getCasFile(aDocument, INITIAL_CAS_PSEUDO_USER));
return jcas;
}
@Override
public JCas readInitialCas(SourceDocument aDocument)
throws CASException, ResourceInitializationException, IOException
{
JCas jcas = CasCreationUtils.createCas((TypeSystemDescription) null, null, null).getJCas();
CasPersistenceUtils.readSerializedCas(jcas, getCasFile(aDocument, INITIAL_CAS_PSEUDO_USER));
casStorageService.analyzeAndRepair(aDocument, INITIAL_CAS_PSEUDO_USER, jcas.getCas());
return jcas;
}
@Override
public JCas createOrReadInitialCas(SourceDocument aDocument)
throws IOException, UIMAException, ClassNotFoundException
{
if (existsInitialCas(aDocument)) {
return readInitialCas(aDocument);
}
else {
return createInitialCas(aDocument);
}
}
@Override
@Transactional
@Deprecated
public JCas readAnnotationCas(SourceDocument aDocument, User aUser)
throws IOException
{
// Change the state of the source document to in progress
aDocument.setState(SourceDocumentStateTransition
.transition(SourceDocumentStateTransition.NEW_TO_ANNOTATION_IN_PROGRESS));
// Check if there is an annotation document entry in the database. If there is none,
// create one.
AnnotationDocument annotationDocument = createOrGetAnnotationDocument(aDocument, aUser);
return readAnnotationCas(annotationDocument);
}
@Override
@Transactional
public JCas readAnnotationCas(AnnotationDocument aAnnotationDocument)
throws IOException
{
// If there is no CAS yet for the annotation document, create one.
JCas jcas = null;
SourceDocument aDocument = aAnnotationDocument.getDocument();
String user = aAnnotationDocument.getUser();
if (!existsCas(aAnnotationDocument.getDocument(), user)) {
// Convert the source file into an annotation CAS
try {
if (!existsInitialCas(aDocument)) {
jcas = createInitialCas(aDocument);
}
// Ok, so at this point, we either have the lazily converted CAS already loaded
// or we know that we can load the existing initial CAS.
if (jcas == null) {
jcas = readInitialCas(aDocument);
}
}
catch (Exception e) {
log.error("The reader for format [" + aDocument.getFormat()
+ "] is unable to digest data", e);
throw new IOException("The reader for format [" + aDocument.getFormat()
+ "] is unable to digest data" + e.getMessage());
}
casStorageService.writeCas(aDocument, jcas, user);
}
else {
// Read existing CAS
// We intentionally do not upgrade the CAS here because in general the IDs
// must remain stable. If an upgrade is required the caller should do it
jcas = casStorageService.readCas(aDocument, user);
}
return jcas;
}
@Override
@Transactional
public void writeAnnotationCas(JCas aJcas, SourceDocument aDocument, User aUser,
boolean aUpdateTimestamp)
throws IOException
{
casStorageService.writeCas(aDocument, aJcas, aUser.getUsername());
AnnotationDocument annotationDocument = getAnnotationDocument(aDocument, aUser);
if (aUpdateTimestamp) {
annotationDocument.setSentenceAccessed(aDocument.getSentenceAccessed());
annotationDocument.setTimestamp(new Timestamp(new Date().getTime()));
annotationDocument.setState(AnnotationDocumentState.IN_PROGRESS);
entityManager.merge(annotationDocument);
}
// Notify all relevant service so that they can update themselves for the given document
for (DocumentLifecycleAware bean : documentLifecycleAwareRegistry
.getBeans()) {
try {
bean.afterAnnotationUpdate(annotationDocument, aJcas);
}
catch (IOException e) {
throw e;
}
catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
@Override
@Deprecated
public void upgradeCasAndSave(SourceDocument aDocument, Mode aMode, String aUsername)
throws IOException
{
User user = userRepository.get(aUsername);
if (existsAnnotationDocument(aDocument, user)) {
log.debug("Upgrading annotation document [" + aDocument.getName() + "] " + "with ID ["
+ aDocument.getId() + "] in project ID [" + aDocument.getProject().getId()
+ "] for user [" + aUsername + "] in mode [" + aMode + "]");
// DebugUtils.smallStack();
AnnotationDocument annotationDocument = getAnnotationDocument(aDocument, user);
try {
CAS cas = readAnnotationCas(annotationDocument).getCas();
upgradeCas(cas, annotationDocument);
writeAnnotationCas(cas.getJCas(), annotationDocument.getDocument(), user, false);
// This is no longer needed because it is handled on the respective pages.
// if (aMode.equals(Mode.ANNOTATION)) {
// // In this case we only need to upgrade to annotation document
// }
// else if (aMode.equals(Mode.AUTOMATION) || aMode.equals(Mode.CORRECTION)) {
// CAS corrCas = readCorrectionCas(aDocument).getCas();
// upgradeCas(corrCas, annotationDocument);
// writeCorrectionCas(corrCas.getJCas(), aDocument, user, false);
// }
// else {
// CAS curCas = readCurationCas(aDocument).getCas();
// upgradeCas(curCas, annotationDocument);
// writeCurationCas(curCas.getJCas(), aDocument, user);
// }
}
catch (Exception e) {
// no need to catch, it is acceptable that no curation document
// exists to be upgraded while there are annotation documents
}
try (MDC.MDCCloseable closable = MDC.putCloseable(
Logging.KEY_PROJECT_ID,
String.valueOf(aDocument.getProject().getId()))) {
Project project = aDocument.getProject();
log.info(
"Upgraded annotations of user [{}] for "
+ "document [{}]({}) in project [{}]({}) in mode [{}]",
user.getUsername(), aDocument.getName(), aDocument.getId(),
project.getName(), project.getId(), aMode);
}
}
}
@Override
public void upgradeCas(CAS aCas, AnnotationDocument aAnnotationDocument)
throws UIMAException, IOException
{
annotationService.upgradeCas(aCas, aAnnotationDocument.getDocument(),
aAnnotationDocument.getUser());
}
/**
* Return true if there exist at least one annotation document FINISHED for annotation for this
* {@link SourceDocument}
*
* @param aSourceDocument
* the source document.
* @param aProject
* the project.
* @return if a finished document exists.
*/
@Override
public boolean existFinishedDocument(SourceDocument aSourceDocument, Project aProject)
{
List<de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocument> annotationDocuments = listAnnotationDocuments(
aSourceDocument);
boolean finishedAnnotationDocumentExist = false;
for (de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocument annotationDocument : annotationDocuments) {
if (annotationDocument.getState().equals(AnnotationDocumentState.FINISHED)) {
finishedAnnotationDocumentExist = true;
break;
}
}
return finishedAnnotationDocumentExist;
}
@Override
public Map<SourceDocument, AnnotationDocument> listAnnotatableDocuments(Project aProject,
User aUser)
{
// First get the source documents
List<SourceDocument> sourceDocuments = entityManager
.createQuery(
"FROM SourceDocument " +
"WHERE project = (:project) AND trainingDocument = false",
SourceDocument.class)
.setParameter("project", aProject)
.getResultList();
// Next we get all the annotation document records. We can use these to filter out
// documents which are IGNOREed for given users.
List<AnnotationDocument> annotationDocuments = entityManager
.createQuery(
"FROM AnnotationDocument " +
"WHERE user = (:username) AND project = (:project)",
AnnotationDocument.class)
.setParameter("username", aUser.getUsername())
.setParameter("project", aProject)
.getResultList();
// First we add all the source documents
Map<SourceDocument, AnnotationDocument> map = new TreeMap<>(SourceDocument.NAME_COMPARATOR);
for (SourceDocument doc : sourceDocuments) {
map.put(doc, null);
}
// Now we link the source documents to the annotation documents and remove IGNOREed
// documents
for (AnnotationDocument adoc : annotationDocuments) {
switch (adoc.getState()) {
case IGNORE:
map.remove(adoc.getDocument());
break;
default:
map.put(adoc.getDocument(), adoc);
break;
}
}
return map;
}
@Override
@Transactional(noRollbackFor = NoResultException.class)
public boolean isAnnotationFinished(SourceDocument aDocument, User aUser)
{
try {
AnnotationDocument annotationDocument = entityManager
.createQuery(
"FROM AnnotationDocument WHERE document = :document AND "
+ "user =:user", AnnotationDocument.class)
.setParameter("document", aDocument).setParameter("user", aUser.getUsername())
.getSingleResult();
if (annotationDocument.getState().equals(AnnotationDocumentState.FINISHED)) {
return true;
}
else {
return false;
}
}
// User even didn't start annotating
catch (NoResultException e) {
return false;
}
}
@Override
@Transactional(noRollbackFor = NoResultException.class)
public List<AnnotationDocument> listAnnotationDocuments(SourceDocument aDocument)
{
// Get all annotators in the project
List<String> users = getAllAnnotators(aDocument.getProject());
// Bail out already. HQL doesn't seem to like queries with an empty
// parameter right of "in"
if (users.isEmpty()) {
return new ArrayList<AnnotationDocument>();
}
return entityManager
.createQuery(
"FROM AnnotationDocument WHERE project = :project AND document = :document "
+ "AND user in (:users)", AnnotationDocument.class)
.setParameter("project", aDocument.getProject()).setParameter("users", users)
.setParameter("document", aDocument).getResultList();
}
@Override
public List<AnnotationDocument> listAnnotationDocuments(Project aProject, User aUser)
{
return entityManager
.createQuery("FROM AnnotationDocument WHERE project = :project AND user = :user",
AnnotationDocument.class)
.setParameter("project", aProject).setParameter("user", aUser.getUsername())
.getResultList();
}
@Override
public int numberOfExpectedAnnotationDocuments(Project aProject)
{
// Get all annotators in the project
List<String> users = getAllAnnotators(aProject);
// Bail out already. HQL doesn't seem to like queries with an empty
// parameter right of "in"
if (users.isEmpty()) {
return 0;
}
int ignored = 0;
List<AnnotationDocument> annotationDocuments = entityManager
.createQuery(
"FROM AnnotationDocument WHERE project = :project AND user in (:users)",
AnnotationDocument.class).setParameter("project", aProject)
.setParameter("users", users).getResultList();
for (AnnotationDocument annotationDocument : annotationDocuments) {
if (annotationDocument.getState().equals(AnnotationDocumentState.IGNORE)) {
ignored++;
}
}
return listSourceDocuments(aProject).size() * users.size() - ignored;
}
private List<String> getAllAnnotators(Project aProject)
{
// Get all annotators in the project
List<String> users = entityManager
.createQuery(
"SELECT DISTINCT user FROM ProjectPermission WHERE project = :project "
+ "AND level = :level", String.class)
.setParameter("project", aProject).setParameter("level", PermissionLevel.USER)
.getResultList();
// check if the username is in the Users database (imported projects
// might have username
// in the ProjectPermission entry while it is not in the Users database
List<String> notInUsers = new ArrayList<String>();
for (String user : users) {
if (!userRepository.exists(user)) {
notInUsers.add(user);
}
}
users.removeAll(notInUsers);
return users;
}
@Override
public void afterProjectCreate(Project aProject)
{
// Nothing to do
}
@Override
public void beforeProjectRemove(Project aProject)
throws IOException
{
for (SourceDocument document : listSourceDocuments(aProject)) {
removeSourceDocument(document);
}
}
@Override
@Transactional
public void onProjectImport(ZipFile aZip,
de.tudarmstadt.ukp.clarin.webanno.export.model.Project aExportedProject,
Project aProject)
throws Exception
{
// Nothing at the moment
}
}