/******************************************************************************* * Copyright 2013 * Ubiquitous Knowledge Processing (UKP) Lab * 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.csniper.webapp.evaluation; import static java.util.Collections.singletonList; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.ListIterator; import java.util.Set; import javax.persistence.EntityExistsException; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.persistence.PersistenceContext; import javax.persistence.TypedQuery; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.ScrollableResults; import org.hibernate.ejb.HibernateQuery; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.LinkedMultiValueMap; import bak.pcj.map.LongKeyOpenHashMap; import de.tudarmstadt.ukp.csniper.webapp.evaluation.SortableEvaluationResultDataProvider.ResultFilter; import de.tudarmstadt.ukp.csniper.webapp.evaluation.model.CachedParse; import de.tudarmstadt.ukp.csniper.webapp.evaluation.model.EvaluationItem; import de.tudarmstadt.ukp.csniper.webapp.evaluation.model.EvaluationResult; import de.tudarmstadt.ukp.csniper.webapp.evaluation.model.Query; import de.tudarmstadt.ukp.csniper.webapp.evaluation.model.SampleSet; import de.tudarmstadt.ukp.csniper.webapp.project.model.AnnotationType; import de.tudarmstadt.ukp.csniper.webapp.statistics.model.AggregatedEvaluationResult; import de.tudarmstadt.ukp.dkpro.core.api.resources.ResourceUtils; public class EvaluationRepository { private Log log = LogFactory.getLog(getClass()); @PersistenceContext private EntityManager entityManager; /** * Write the query to the database if it does not exist. */ @Transactional public Query recordQuery(String aEngine, String aQuery, String aCollectionId, String aType, String aComment, String aUser) { try { // try to fetch the query from db return getQuery(aEngine, aQuery, aCollectionId, aType, aUser); } catch (NoResultException e) { // if the query is not in db, write it Query q = new Query(aEngine, aQuery, aCollectionId, aType, aUser); q.setComment(aComment); writeQuery(q); return q; } } @Transactional public Query getQuery(String aEngine, String aQuery, String aCollectionId, String aType, String aUser) { return entityManager .createQuery( "FROM Query WHERE engine = :engine AND query = :query AND collectionId = :collectionId AND type = :type AND userId = :userId", Query.class).setParameter("engine", aEngine).setParameter("query", aQuery) .setParameter("collectionId", aCollectionId).setParameter("type", aType) .setParameter("userId", aUser).getSingleResult(); } @Transactional public void writeQuery(Query aQuery) { entityManager.persist(aQuery); } @Transactional public List<Query> listQueries() { return entityManager.createQuery("FROM Query ORDER BY type", Query.class).getResultList(); } @Transactional public List<Query> listUniqueQueries() { return entityManager .createQuery( "SELECT DISTINCT new Query(engine, query, collectionId, type) FROM Query ORDER BY type", Query.class).getResultList(); } @Transactional public List<Query> listQueries(String aEngine, String aCollectionId, AnnotationType aType, String aUser) { return entityManager .createQuery( "FROM Query WHERE engine = :engine AND collectionId = :collectionId AND type = :type AND userId = :userId ORDER BY type", Query.class).setParameter("engine", aEngine) .setParameter("collectionId", aCollectionId).setParameter("type", aType.getName()) .setParameter("userId", aUser).getResultList(); } @Transactional public SampleSet recordSampleSet(String aName, String aCollectionId, String aType, String aComment, String aUser, List<EvaluationItem> aItems) { try { // try to fetch the sampleset from db return getSampleSet(aName, aCollectionId, aType, aUser); } catch (NoResultException e) { // if the sampleset is not in db, write it SampleSet s = new SampleSet(aName, aCollectionId, aType, aUser); s.setComment(aComment); s.setItems(aItems); writeSampleSet(s); return s; } } @Transactional public SampleSet getSampleSet(String aName, String aCollectionId, String aType, String aUser) { return entityManager .createQuery( "FROM SampleSet WHERE name = :name AND collectionId = :collectionId AND type = :type AND userId = :userId", SampleSet.class).setParameter("name", aName) .setParameter("collectionId", aCollectionId).setParameter("type", aType) .setParameter("userId", aUser).getSingleResult(); } @Transactional public CachedParse getCachedParse(EvaluationItem aItem) { return getCachedParse(aItem.getCollectionId(), aItem.getDocumentId(), aItem.getBeginOffset(), aItem.getEndOffset()); } @Transactional public CachedParse getCachedParse(String aCollectionId, String aDocumentId, long aBeginOffset, long aEndOffset) { try { return entityManager .createQuery( "FROM CachedParse WHERE collectionId = :collectionId AND " + "documentId = :documentId AND beginOffset = :beginOffset " + "AND endOffset = :endOffset", CachedParse.class) .setParameter("collectionId", aCollectionId) .setParameter("documentId", aDocumentId) .setParameter("beginOffset", aBeginOffset) .setParameter("endOffset", aEndOffset).getSingleResult(); } catch (NoResultException e) { return null; } } @Transactional public int[][] listCachedParsesPages(String aCollectionId, int aPageSize) { List<int[]> pages = new ArrayList<int[]>(); ScrollableResults results = null; try { String queryString = "SELECT id FROM CachedParse WHERE collectionId = :collectionId"; org.hibernate.Query query = ((HibernateQuery) entityManager.createQuery(queryString)).getHibernateQuery(); query.setParameter("collectionId", aCollectionId); results = query.scroll(); results.beforeFirst(); int row = 0; int[] curPage = new int[] {-1, -1}; boolean hasNext = results.next(); while (hasNext) { int id = results.getLong(0).intValue(); // Record start of page if ((row % aPageSize) == 0) { curPage[0] = id; } // Step ahead hasNext = results.next(); row++; // Record end of page when end of page or end of results is reached if (((row % aPageSize) == (aPageSize - 1)) || !hasNext) { curPage[1] = id; pages.add(curPage); curPage = new int[] {-1, -1}; } } } finally { if (results != null) { results.close(); } } return pages.toArray(new int[pages.size()][2]); } @Transactional public List<CachedParse> listCachedParses(String aCollectionId, int aStartId, int aEndId) { return entityManager .createQuery( "FROM CachedParse WHERE collectionId = :collectionId AND id >= :startId AND id < :endId", CachedParse.class).setParameter("collectionId", aCollectionId) .setParameter("startId", (long) aStartId).setParameter("endId", (long) aEndId) .getResultList(); } @Transactional public long getCachedParsesCount(String aCollectionId) { return entityManager .createQuery("SELECT COUNT(*) FROM CachedParse WHERE collectionId = :collectionId", Long.class).setParameter("collectionId", aCollectionId).getSingleResult(); } @Transactional public EvaluationItem getEvaluationItem(long aItemId) { try { return entityManager .createQuery("FROM EvaluationItem WHERE id = :itemId", EvaluationItem.class) .setParameter("itemId", aItemId).getSingleResult(); } catch (NoResultException e) { return null; } } @Transactional public EvaluationResult getEvaluationResult(long aItemId, String aUserId) { try { return entityManager .createQuery( "FROM EvaluationResult WHERE item.id = :itemId AND userId = :userId", EvaluationResult.class).setParameter("itemId", aItemId) .setParameter("userId", aUserId).getSingleResult(); } catch (NoResultException e) { return null; } } @Transactional public void writeSampleSet(SampleSet aSampleSet) { entityManager.persist(aSampleSet); } @Transactional public void updateSampleSet(SampleSet aSampleset, List<EvaluationItem> aItems) { aSampleset.addItems(aItems); entityManager.merge(aSampleset); } @Transactional public List<SampleSet> listSampleSets() { return entityManager.createQuery("FROM SampleSet ORDER BY type", SampleSet.class) .getResultList(); } @Transactional public List<SampleSet> listSampleSets(String aCollectionId, String aType, String aUser) { return entityManager .createQuery( "FROM SampleSet WHERE collectionId = :collectionId AND type = :type AND userId = :userId ORDER BY type", SampleSet.class).setParameter("collectionId", aCollectionId) .setParameter("type", aType).setParameter("userId", aUser).getResultList(); } /** * Persist the given items. If they already exist in the database, replace the item in the list * with the item from the database. Transient data (e.g. match offsets) is preserved. */ @Transactional public List<EvaluationItem> writeEvaluationItems(List<EvaluationItem> aItems) { return writeEvaluationItems(aItems, true); } /** * Persist the given items. If they already exist in the database, replace the item in the list * with the item from the database. Transient data (e.g. match offsets) is preserved. * * @param aCreate * true = missing evaluation items are created and returned; false = missing * evaluation items are not created and returned */ @Transactional public List<EvaluationItem> writeEvaluationItems(List<EvaluationItem> aItems, boolean aCreate) { long start = System.currentTimeMillis(); log.info("Building index on in-memory items"); List<EvaluationItem> result = new ArrayList<EvaluationItem>(aItems.size()); LinkedMultiValueMap<String, EvaluationItem> idx = new LinkedMultiValueMap<String, EvaluationItem>(); for (EvaluationItem i : aItems) { idx.add(i.getCollectionId() + "-" + i.getDocumentId() + "-" + i.getType(), i); } TypedQuery<EvaluationItem> query = entityManager.createQuery( "FROM EvaluationItem WHERE collectionId = :collectionId AND documentId = " + ":documentId AND type = :type", EvaluationItem.class); log.info("Merging with in-database items in " + idx.size() + " chunks"); ProgressMeter progress = new ProgressMeter(idx.size()); for (List<EvaluationItem> items : idx.values()) { progress.next(); EvaluationItem ref = items.get(0); List<EvaluationItem> pItems = query.setParameter("collectionId", ref.getCollectionId()) .setParameter("documentId", ref.getDocumentId()) .setParameter("type", ref.getType()).getResultList(); Comparator<EvaluationItem> cmp = new Comparator<EvaluationItem>() { @Override public int compare(EvaluationItem aO1, EvaluationItem aO2) { if (aO1.getBeginOffset() > aO2.getBeginOffset()) { return 1; } else if (aO1.getBeginOffset() < aO2.getBeginOffset()) { return -1; } else if (aO1.getEndOffset() > aO2.getEndOffset()) { return 1; } else if (aO1.getEndOffset() < aO2.getEndOffset()) { return -1; } else { return 0; } } }; Collections.sort(pItems, cmp); for (EvaluationItem item : items) { int i = Collections.binarySearch(pItems, item, cmp); if (i < 0) { if (aCreate) { entityManager.persist(item); result.add(item); } } else { EvaluationItem pItem = pItems.get(i); pItem.copyTransientData(item); result.add(pItem); } } log.info(progress); } log.info("writeEvaluationItems for " + aItems.size() + " items completed in " + (System.currentTimeMillis() - start) + " ms"); return result; // String query = "FROM EvaluationItem WHERE collectionId = :collectionId AND documentId = " // + // ":documentId AND type = :type AND beginOffset = :beginOffset AND endOffset = :endOffset"; // for (ListIterator<EvaluationItem> li = aItems.listIterator(); li.hasNext();) { // EvaluationItem item = li.next(); // try { // EvaluationItem pItem = entityManager.createQuery(query, EvaluationItem.class) // .setParameter("collectionId", item.getCollectionId()) // .setParameter("documentId", item.getDocumentId()) // .setParameter("type", item.getType()) // .setParameter("beginOffset", item.getBeginOffset()) // .setParameter("endOffset", item.getEndOffset()).getSingleResult(); // // // if item already exists, use that instead of persisting the new // pItem.copyTransientData(item); // li.set(pItem); // } // catch (NoResultException e) { // // persist item if not exists // if (aCreate) { // entityManager.persist(item); // } // } // } } @Transactional public List<EvaluationItem> listEvaluationItems(String aCollectionId, String aType) { return entityManager .createQuery( "FROM EvaluationItem WHERE collectionId = :collectionId AND type = :type", EvaluationItem.class).setParameter("collectionId", aCollectionId) .setParameter("type", aType).getResultList(); } @Transactional public List<EvaluationItem> listEvaluationItems(List<String> aCollectionIds, List<AnnotationType> aTypes) { List<String> types = new ArrayList<String>(); for (AnnotationType at : aTypes) { types.add(at.getName()); } return entityManager .createQuery( "FROM EvaluationItem WHERE collectionId IN :collectionIds AND type IN :types", EvaluationItem.class).setParameter("collectionIds", aCollectionIds) .setParameter("types", types).getResultList(); } /** * Persist the given results. If they already exist in the database, replace the result in the * list with the result from the database. Transient data (e.g. match offsets) is preserved. */ @Transactional public void writeEvaluationResults(List<EvaluationResult> aResults) { long start = System.currentTimeMillis(); Set<String> users = new HashSet<String>(); for (EvaluationResult r : aResults) { users.add(r.getUserId()); } for (String user : users) { // Build index on in-memory results log.info("Building index on in-memory results (" + aResults.size() + " results)"); List<Long> itemIds = new ArrayList<Long>(aResults.size()); for (EvaluationResult r : aResults) { if (user.equals(r.getUserId())) { itemIds.add(r.getItem().getId()); } } // Build index on persisted results TypedQuery<EvaluationResult> query = entityManager.createQuery( "FROM EvaluationResult WHERE userId = :userId AND item_id in (:itemIds)", EvaluationResult.class); query.setParameter("userId", user); LongKeyOpenHashMap pResults = new LongKeyOpenHashMap(aResults.size()); if (itemIds.size() > 0) { // Big Performance problem with setParameterList() // https://hibernate.onjira.com/browse/HHH-766 int chunkSize = 500; log.info("Fetching in-database results in " + ((aResults.size() / chunkSize) + (aResults.size() % chunkSize == 0 ? 0 : 1)) + " chunks"); for (int i = 0; i < itemIds.size(); i += chunkSize) { query.setParameter("itemIds", itemIds.subList(i, Math.min(i + chunkSize, itemIds.size()))); List<EvaluationResult> pr = query.getResultList(); for (EvaluationResult r : pr) { pResults.put(r.getItem().getId(), r); } } } // Merge information from the database log.info("Merging"); for (ListIterator<EvaluationResult> li = aResults.listIterator(); li.hasNext();) { EvaluationResult mResult = li.next(); // only replace for current user if (!mResult.getUserId().equals(user)) { continue; } EvaluationResult pResult = (EvaluationResult) pResults.get(mResult.getItem() .getId()); if (pResult != null) { // if result already exists, use that instead of persisting the new pResult.getItem().copyTransientData(mResult.getItem()); li.set(pResult); } else if (mResult.getId() != 0) { li.set(entityManager.merge(mResult)); } else { // if results does not exist, persist it entityManager.persist(mResult); } } } log.info("writeEvaluationResults for " + aResults.size() + " items completed in " + (System.currentTimeMillis() - start) + " ms"); } @Transactional public void writeCachedParse(CachedParse aParses) { try { entityManager.persist(aParses); } catch (EntityExistsException e) { // well, if it exists, don't persist... } } @Transactional public List<EvaluationResult> listEvaluationResults(final String aUserId, final String aType, final int start, final int limit, ResultFilter aFilter) { final StringBuffer query = new StringBuffer(); query.append("FROM EvaluationResult WHERE userId = ?1 AND item.type = ?2 "); if (aFilter == ResultFilter.TODO) { query.append("AND length(result) = 0 "); } else if (aFilter == ResultFilter.ANNOTATED) { query.append("AND length(result) > 0 "); } /* * query.append("ORDER BY "); query.append(orderBy); query.append(isAscending ? " ASC " : * " DESC "); */ return entityManager.createQuery(query.toString(), EvaluationResult.class) .setParameter(1, aUserId).setParameter(2, aType).setFirstResult(start) .setMaxResults(limit).getResultList(); } @Transactional public List<EvaluationResult> listEvaluationResults(String aUserId, String aType, ResultFilter aFilter) { StringBuffer query = new StringBuffer(); query.append("FROM EvaluationResult "); query.append("WHERE userId = :userId "); query.append("AND item.type = :type "); if (aFilter == ResultFilter.TODO) { query.append("AND length(result) = 0 "); } else if (aFilter == ResultFilter.ANNOTATED) { query.append("AND length(result) > 0 "); } return entityManager.createQuery(query.toString(), EvaluationResult.class) .setParameter("userId", aUserId).setParameter("type", aType).getResultList(); } private String loadQuery(String aLocation) { InputStream is = null; try { is = ResourceUtils.resolveLocation(aLocation, null, null).openStream(); return IOUtils.toString(is); } catch (IOException e) { throw new DataAccessResourceFailureException("Unable to load query from [" + aLocation + "]", e); } finally { IOUtils.closeQuietly(is); } } @Transactional public List<EvaluationResult> listDisputedEvaluationResults(String aCollectionId, AnnotationType aType, String aUserId) { String query = loadQuery("classpath:/AggregatedQuery.sql"); Collection<String> aUsers = listUsers(); javax.persistence.Query q = entityManager.createNativeQuery(query); q.setParameter("collectionIds", singletonList(aCollectionId)); q.setParameter("types", singletonList(aType.getName())); q.setParameter("users", aUsers); q.setParameter("usersL", aUsers.size()); q.setParameter("userThreshold", 0); q.setParameter("confidenceThreshold", 0); @SuppressWarnings("unchecked") List<Object[]> list = q.getResultList(); List<EvaluationResult> results = new ArrayList<EvaluationResult>(); for (Object[] obj : list) { long itemId = ((Number) obj[0]).longValue(); int correct = ((Number) obj[1]).intValue(); int wrong = ((Number) obj[2]).intValue(); // Check if there is any dispute. No thresholds here, simple dispute is enough. if (!(correct > 0 && wrong > 0)) { continue; } EvaluationResult result = getEvaluationResult(itemId, aUserId); if (result != null) { results.add(result); } } log.info("Found [" + list.size() + "] results of which [" + results.size() + "] are disputed and apply to [" + aUserId + "]"); return results; } @Transactional public List<EvaluationResult> listEvaluationResults(String aCollectionId, AnnotationType aType, String aUserId) { StringBuffer query = new StringBuffer(); query.append("FROM EvaluationResult "); query.append("WHERE item.collectionId = :collectionId "); query.append("AND item.type = :type "); query.append("AND userId = :userId "); query.append("AND length(result) > 0"); // all non-empty results return entityManager.createQuery(query.toString(), EvaluationResult.class) .setParameter("collectionId", aCollectionId).setParameter("type", aType.getName()) .setParameter("userId", aUserId).getResultList(); } @Transactional public List<EvaluationResult> listEvaluationResults(String aUserId, SampleSet aSampleSet) { final StringBuffer query = new StringBuffer(); query.append("FROM EvaluationResult "); query.append("WHERE userId = :userId "); query.append("AND item "); query.append("IN (SELECT i FROM SampleSet s JOIN s.items i WHERE s.id = :samplesetId)"); return entityManager.createQuery(query.toString(), EvaluationResult.class) .setParameter("userId", aUserId).setParameter("samplesetId", aSampleSet.getId()) .getResultList(); } @Transactional public EvaluationItem updateEvaluationItem(EvaluationItem aItem) { return entityManager.merge(aItem); } @Transactional public EvaluationResult updateEvaluationResult(EvaluationResult aResult) { return entityManager.merge(aResult); } public List<String> listCollections() { return entityManager.createQuery( "SELECT DISTINCT collectionId FROM EvaluationItem ORDER BY collectionId", String.class).getResultList(); } public List<String> listTypes() { return entityManager.createQuery("SELECT DISTINCT type FROM EvaluationItem ORDER BY type", String.class).getResultList(); } public List<String> listUsers() { return entityManager.createQuery( "SELECT username FROM User ORDER BY username", String.class) .getResultList(); } @SuppressWarnings("unchecked") @Transactional public List<AggregatedEvaluationResult> listAggregatedResults(List<EvaluationItem> aItems, Collection<String> aUsers, double aUserThreshold, double aConfidenceThreshold) { if (aItems.isEmpty() || aUsers.isEmpty()) { return new ArrayList<AggregatedEvaluationResult>(); } String query = loadQuery("classpath:/AggregatedQueryByItem.sql"); javax.persistence.Query q = entityManager.createNativeQuery(query); List<String> itemIds = new ArrayList<String>(); for (EvaluationItem item : aItems) { itemIds.add(Long.toString(item.getId())); } // q.setParameter("itemIds", itemIds); q.setParameter("users", aUsers); q.setParameter("usersL", aUsers.size()); q.setParameter("userThreshold", aUserThreshold); q.setParameter("confidenceThreshold", aConfidenceThreshold); List<AggregatedEvaluationResult> aers = new ArrayList<AggregatedEvaluationResult>(); // Big Performance problem with setParameterList() // https://hibernate.onjira.com/browse/HHH-766 int chunkSize = 500; log.info("Fetching in-database results in " + ((aItems.size() / chunkSize) + (aItems.size() % chunkSize == 0 ? 0 : 1)) + " chunks"); ProgressMeter progress = new ProgressMeter(aItems.size()); for (int i = 0; i < aItems.size(); i += chunkSize) { q.setParameter("itemIds", itemIds.subList(i, Math.min(i + chunkSize, itemIds.size()))); // System.out.println("IDS: " + itemIds.subList(i, Math.min(i + chunkSize, // itemIds.size()))); //System.out.println("USERS: " + aUsers); List<Object[]> list = q.getResultList(); for (Object[] obj : list) { obj[0] = getEvaluationItem(Long.parseLong(obj[0].toString())); aers.add(new AggregatedEvaluationResult(obj, aUsers)); } progress.setDone(i); log.info(progress); } return aers; } @Transactional public List<AggregatedEvaluationResult> listAggregatedResults( Collection<String> aCollectionIds, Collection<AnnotationType> aTypes, Collection<String> aUsers, double aUserThreshold, double aConfidenceThreshold) { if (aCollectionIds.isEmpty() || aTypes.isEmpty() || aUsers.isEmpty()) { return new ArrayList<AggregatedEvaluationResult>(); } String query = loadQuery("classpath:/AggregatedQuery.sql"); // long start = System.currentTimeMillis(); javax.persistence.Query q = entityManager.createNativeQuery(query); q.setParameter("collectionIds", aCollectionIds); List<String> types = new ArrayList<String>(); for (AnnotationType at : aTypes) { types.add(at.getName()); } q.setParameter("types", types); q.setParameter("users", aUsers); q.setParameter("usersL", aUsers.size()); q.setParameter("userThreshold", aUserThreshold); q.setParameter("confidenceThreshold", aConfidenceThreshold); @SuppressWarnings("unchecked") List<Object[]> list = q.getResultList(); List<AggregatedEvaluationResult> aers = new ArrayList<AggregatedEvaluationResult>(); for (Object[] obj : list) { obj[0] = getEvaluationItem(Long.parseLong(obj[0].toString())); aers.add(new AggregatedEvaluationResult(obj, aUsers)); } // long stop = System.currentTimeMillis(); // System.out.println("Time1 " + (stop - start)); return aers; } public String test() { return entityManager.createNativeQuery("SELECT (2-0.00) / 3").getSingleResult().toString(); } /** * Fetch all the results that the current annotator has not yet evaluated but at least one of * the other specified annotators already did evaluate. */ public List<EvaluationItem> listEvaluationResultsMissing(final String aCollectionId, final String aType, final String aMyUser, final Collection<String> aOtherUsers) { // String q = "select * from EvaluationResult where userId = :myUser and result = '' " + // "and item_id in (select distinct item_id from EvaluationResult where " + // "userId != :myUser and result != '' and userId in (:otherUsers))"; String q = "from EvaluationItem where collectionId = :collectionId and type = :type and id " + "in (select distinct item from " + "EvaluationResult where userId != :myUser and result != '' and userId in " + "(:otherUsers)) and not (id in (select distinct item from EvaluationResult " + "where userId = :myUser and result != ''))"; TypedQuery<EvaluationItem> query = entityManager.createQuery(q, EvaluationItem.class); query.setParameter("type", aType); query.setParameter("myUser", aMyUser); query.setParameter("otherUsers", aOtherUsers); query.setParameter("collectionId", aCollectionId); return query.getResultList(); } public AnnotationType getType(String aTypeName) { return entityManager .createQuery("FROM AnnotationType WHERE name=:name", AnnotationType.class) .setParameter("name", aTypeName).getSingleResult(); } }