Skip to content
Snippets Groups Projects
Commit ee848ab3 authored by buczekp's avatar buczekp
Browse files

[LMS-2148] first implementation of search for parents with tests

SVN: 20416
parent bb03368c
No related branches found
No related tags found
No related merge requests found
Showing
with 340 additions and 65 deletions
......@@ -38,6 +38,7 @@ import ch.systemsx.cisd.authentication.ISessionManager;
import ch.systemsx.cisd.authentication.Principal;
import ch.systemsx.cisd.common.exceptions.UserFailureException;
import ch.systemsx.cisd.common.spring.IInvocationLoggerContext;
import ch.systemsx.cisd.openbis.generic.server.business.DetailedSearchManager;
import ch.systemsx.cisd.openbis.generic.server.business.IPropertiesBatchManager;
import ch.systemsx.cisd.openbis.generic.server.business.bo.DataAccessExceptionTranslator;
import ch.systemsx.cisd.openbis.generic.server.business.bo.IAttachmentBO;
......@@ -475,68 +476,22 @@ public final class CommonServer extends AbstractCommonServer<ICommonServerForInt
return sampleLister.list(new ListOrSearchSampleCriteria(criteria));
}
public List<Sample> searchForSamples(String sessionToken, DetailedSearchCriteria criteria)
{
final Session session = getSession(sessionToken);
try
{
final Collection<Long> sampleIds =
findSampleIds(criteria, Collections.<DetailedSearchSubCriteria> emptyList());
final ISampleLister sampleLister = businessObjectFactory.createSampleLister(session);
return sampleLister.list(new ListOrSearchSampleCriteria(sampleIds));
} catch (final DataAccessException ex)
{
throw createUserFailureException(ex);
}
}
public List<Sample> searchForSamples(String sessionToken, DetailedSearchCriteria criteria,
List<DetailedSearchSubCriteria> subCriterias)
{
final Session session = getSession(sessionToken);
try
{
final Collection<Long> sampleIds = findSampleIds(criteria, subCriterias);
final ISampleLister sampleLister = businessObjectFactory.createSampleLister(session);
return sampleLister.list(new ListOrSearchSampleCriteria(sampleIds));
final IHibernateSearchDAO searchDAO = getDAOFactory().getHibernateSearchDAO();
return new DetailedSearchManager(searchDAO, sampleLister).searchForSamples(criteria,
subCriterias);
} catch (final DataAccessException ex)
{
throw createUserFailureException(ex);
}
}
private Collection<Long> findSampleIds(DetailedSearchCriteria criteria,
List<DetailedSearchSubCriteria> subCriterias)
{
final IHibernateSearchDAO searchDAO = getDAOFactory().getHibernateSearchDAO();
// for now we connect all sub criteria with logical AND
List<DetailedSearchAssociationCriteria> associations =
new ArrayList<DetailedSearchAssociationCriteria>();
for (DetailedSearchSubCriteria subCriteria : subCriterias)
{
associations.add(findAssociatedEntities(subCriteria));
}
final Collection<Long> sampleIds =
searchDAO.searchForEntityIds(criteria,
DtoConverters.convertEntityKind(EntityKind.SAMPLE), associations);
return sampleIds;
}
private DetailedSearchAssociationCriteria findAssociatedEntities(
DetailedSearchSubCriteria subCriteria)
{
final IHibernateSearchDAO searchDAO = getDAOFactory().getHibernateSearchDAO();
// for now we don't support sub criteria of sub criteria
List<DetailedSearchAssociationCriteria> associations = Collections.emptyList();
final Collection<Long> associatedIds =
searchDAO.searchForEntityIds(subCriteria.getCriteria(),
DtoConverters.convertEntityKind(subCriteria.getTargetEntityKind()),
associations);
return new DetailedSearchAssociationCriteria(subCriteria.getTargetEntityKind(),
associatedIds);
}
public final List<ExternalData> listSampleExternalData(final String sessionToken,
final TechId sampleId, final boolean showOnlyDirectlyConnected)
{
......
/*
* Copyright 2011 ETH Zuerich, CISD
*
* 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 ch.systemsx.cisd.openbis.generic.server.business;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.springframework.dao.DataAccessException;
import ch.systemsx.cisd.openbis.generic.server.business.bo.samplelister.ISampleLister;
import ch.systemsx.cisd.openbis.generic.server.dataaccess.IHibernateSearchDAO;
import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DetailedSearchAssociationCriteria;
import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DetailedSearchCriteria;
import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DetailedSearchCriterion;
import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DetailedSearchSubCriteria;
import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityKind;
import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListOrSearchSampleCriteria;
import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
import ch.systemsx.cisd.openbis.generic.shared.translator.DtoConverters;
/**
* Manages detailed search with complex search criteria.
*
* @author Piotr Buczek
*/
public class DetailedSearchManager
{
private final IHibernateSearchDAO searchDAO;
private final ISampleLister sampleLister;
public DetailedSearchManager(IHibernateSearchDAO searchDAO, ISampleLister sampleLister)
{
this.searchDAO = searchDAO;
this.sampleLister = sampleLister;
}
public List<Sample> searchForSamples(DetailedSearchCriteria criteria,
List<DetailedSearchSubCriteria> subCriterias) throws DataAccessException
{
final DetailedSearchCriteria parentCriteria = new DetailedSearchCriteria();
final List<DetailedSearchSubCriteria> otherSubCriterias =
new ArrayList<DetailedSearchSubCriteria>();
groupSampleSubCriteria(subCriterias, parentCriteria, otherSubCriterias);
final List<Long> mainSampleIds = findSampleIds(criteria, otherSubCriterias);
final Set<Long> filteredSampleIds;
if (parentCriteria.isEmpty())
{
filteredSampleIds = new HashSet<Long>(mainSampleIds);
} else
{
final List<Long> parentSampleIds =
findSampleIds(parentCriteria,
Collections.<DetailedSearchSubCriteria> emptyList());
filteredSampleIds = new HashSet<Long>();
if (mainSampleIds.size() > parentSampleIds.size())
{
// search for connections
Map<Long, Set<Long>> parentToChildIds =
sampleLister.listChildrenIds(parentSampleIds);
for (Set<Long> childrenIds : parentToChildIds.values())
{
filteredSampleIds.addAll(childrenIds);
}
// filter main parents
filteredSampleIds.retainAll(mainSampleIds);
} else
{
// search for connections
Map<Long, Set<Long>> childToParentIds = sampleLister.listParentIds(mainSampleIds);
// filter main parents
for (Entry<Long, Set<Long>> entry : childToParentIds.entrySet())
{
Long childId = entry.getKey();
Set<Long> parentIds = entry.getValue();
parentIds.retainAll(parentSampleIds);
if (parentIds.isEmpty() == false)
{
filteredSampleIds.add(childId);
}
}
}
}
return sampleLister.list(new ListOrSearchSampleCriteria(filteredSampleIds));
}
private void groupSampleSubCriteria(List<DetailedSearchSubCriteria> allSubCriterias,
DetailedSearchCriteria parentCriteria, List<DetailedSearchSubCriteria> otherSubCriterias)
{
parentCriteria.setCriteria(new ArrayList<DetailedSearchCriterion>());
for (DetailedSearchSubCriteria subCriteria : allSubCriterias)
{
if (subCriteria.getTargetEntityKind() == EntityKind.SAMPLE)
{
// merge all parent sub criteria into one
parentCriteria.getCriteria().addAll(subCriteria.getCriteria().getCriteria());
parentCriteria.setConnection(subCriteria.getCriteria().getConnection());
parentCriteria.setUseWildcardSearchMode(subCriteria.getCriteria()
.isUseWildcardSearchMode());
} else
{
otherSubCriterias.add(subCriteria);
}
}
}
private List<Long> findSampleIds(DetailedSearchCriteria criteria,
List<DetailedSearchSubCriteria> subCriterias)
{
// for now we connect all sub criteria with logical AND
List<DetailedSearchAssociationCriteria> associations =
new ArrayList<DetailedSearchAssociationCriteria>();
for (DetailedSearchSubCriteria subCriteria : subCriterias)
{
associations.add(findAssociatedEntities(subCriteria));
}
final List<Long> sampleIds =
searchDAO.searchForEntityIds(criteria,
DtoConverters.convertEntityKind(EntityKind.SAMPLE), associations);
return sampleIds;
}
private DetailedSearchAssociationCriteria findAssociatedEntities(
DetailedSearchSubCriteria subCriteria)
{
// for now we don't support sub criteria of sub criteria
List<DetailedSearchAssociationCriteria> associations = Collections.emptyList();
final Collection<Long> associatedIds =
searchDAO.searchForEntityIds(subCriteria.getCriteria(),
DtoConverters.convertEntityKind(subCriteria.getTargetEntityKind()),
associations);
return new DetailedSearchAssociationCriteria(subCriteria.getTargetEntityKind(),
associatedIds);
}
}
......@@ -16,7 +16,10 @@
package ch.systemsx.cisd.openbis.generic.server.business.bo.samplelister;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import ch.systemsx.cisd.common.collections.IValidator;
import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListOrSearchSampleCriteria;
......@@ -36,10 +39,10 @@ public interface ISampleLister
* Returns a sorted list of {@link Sample}s that match given criteria.
*/
public List<Sample> list(ListOrSearchSampleCriteria criteria);
/**
* Returns the id of the relationship type of specified code. If code starts with an '$'
* it is interpreted as an internally defined relationship type.
* Returns the id of the relationship type of specified code. If code starts with an '$' it is
* interpreted as an internally defined relationship type.
*
* @throws IllegalArgumentException if code not known.
*/
......@@ -50,11 +53,21 @@ public interface ISampleLister
* specified criteria.
*/
public List<SampleSkeleton> listSampleBy(IValidator<SampleSkeleton> criteria);
/**
* Returns all sample relation ships as skeletons (thats is, only primary and foreign keys) fulfilling
* specified criteria.
* Returns all sample relation ships as skeletons (thats is, only primary and foreign keys)
* fulfilling specified criteria.
*
* @deprecated This way of loading relationships is slow. There is no filtering on DB level. If
* the <code>criteria</code> use only a collection of parent/children ids than use
* listChildrenIds/listParentIds.
*/
public List<SampleRelationShipSkeleton> listSampleRelationShipsBy(IValidator<SampleRelationShipSkeleton> criteria);
@Deprecated
public List<SampleRelationShipSkeleton> listSampleRelationShipsBy(
IValidator<SampleRelationShipSkeleton> criteria);
public Map<Long, Set<Long>> listParentIds(Collection<Long> childrenIds);
public Map<Long, Set<Long>> listChildrenIds(Collection<Long> parentIds);
}
......@@ -101,6 +101,15 @@ public interface ISampleListingQuery extends TransactionQuery, IPropertyListingQ
public DataIterator<SampleRelationRecord> getParentRelations(long relationshipId,
LongSet childrenSampleIds);
/**
* Returns the child sample ids of the specified parent sample ids in specified relationship.
*/
@Select(sql = "SELECT * FROM sample_relationships "
+ " WHERE relationship_id=?{1} AND sample_id_parent = any(?{2})", parameterBindings =
{ TypeMapper.class/* default */, LongSetMapper.class }, fetchSize = FETCH_SIZE)
public DataIterator<SampleRelationRecord> getChildrenRelations(long relationshipId,
LongSet parentSampleIds);
@Select(sql = "select id, saty_id, space_id, dbin_id, expe_id from samples", fetchSize = FETCH_SIZE)
public DataIterator<SampleRecord> getSampleSkeletons();
......
......@@ -16,8 +16,15 @@
package ch.systemsx.cisd.openbis.generic.server.business.bo.samplelister;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.lemnik.eodsql.DataIterator;
......@@ -25,6 +32,7 @@ import ch.rinn.restrictions.Friend;
import ch.systemsx.cisd.common.collections.IValidator;
import ch.systemsx.cisd.openbis.generic.server.business.bo.common.entity.SecondaryEntityDAO;
import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
import ch.systemsx.cisd.openbis.generic.shared.basic.BasicConstant;
import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListOrSearchSampleCriteria;
import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
import ch.systemsx.cisd.openbis.generic.shared.dto.SampleRelationShipSkeleton;
......@@ -95,7 +103,8 @@ public class SampleLister implements ISampleLister
public List<SampleRelationShipSkeleton> listSampleRelationShipsBy(
IValidator<SampleRelationShipSkeleton> criteria)
{
DataIterator<SampleRelationRecord> records = dao.getQuery().getSampleRelationshipSkeletons();
DataIterator<SampleRelationRecord> records =
dao.getQuery().getSampleRelationshipSkeletons();
List<SampleRelationShipSkeleton> result = new ArrayList<SampleRelationShipSkeleton>();
for (SampleRelationRecord record : records)
{
......@@ -111,4 +120,67 @@ public class SampleLister implements ISampleLister
return result;
}
public Map<Long, Set<Long>> listParentIds(Collection<Long> childrenIds)
{
LongOpenHashSet ids = new LongOpenHashSet();
for (Long id : childrenIds)
{
ids.add(id);
}
final long relationshipTypeID =
getRelationshipTypeID(BasicConstant.PARENT_CHILD_INTERNAL_RELATIONSHIP);
DataIterator<SampleRelationRecord> relationships =
dao.getQuery().getParentRelations(relationshipTypeID, ids);
Map<Long, Set<Long>> map = new LinkedHashMap<Long, Set<Long>>();
for (SampleRelationRecord relationship : relationships)
{
Set<Long> parents = map.get(relationship.sample_id_child);
if (parents == null)
{
parents = new LinkedHashSet<Long>();
map.put(relationship.sample_id_child, parents);
}
parents.add(relationship.sample_id_parent);
}
return map;
}
public Map<Long, Set<Long>> listChildrenIds(Collection<Long> parentIds)
{
LongOpenHashSet ids = new LongOpenHashSet();
for (Long id : parentIds)
{
ids.add(id);
}
final long relationshipTypeID =
getRelationshipTypeID(BasicConstant.PARENT_CHILD_INTERNAL_RELATIONSHIP);
DataIterator<SampleRelationRecord> relationships =
dao.getQuery().getChildrenRelations(relationshipTypeID, ids);
Map<Long, Set<Long>> map = new LinkedHashMap<Long, Set<Long>>();
for (SampleRelationRecord relationship : relationships)
{
Set<Long> children = map.get(relationship.sample_id_parent);
if (children == null)
{
children = new LinkedHashSet<Long>();
map.put(relationship.sample_id_parent, children);
}
children.add(relationship.sample_id_child);
}
return map;
}
public Set<Long> listChildrenIdsSet(Collection<Long> parentIds)
{
LongOpenHashSet ids = new LongOpenHashSet();
for (Long id : parentIds)
{
ids.add(id);
}
final long relationshipTypeID =
getRelationshipTypeID(BasicConstant.PARENT_CHILD_INTERNAL_RELATIONSHIP);
DataIterator<Long> resultIterator = dao.getQuery().getChildrenIds(relationshipTypeID, ids);
return new LongOpenHashSet(resultIterator);
}
}
......@@ -7,5 +7,7 @@ package ch.systemsx.cisd.openbis.generic.shared.api.v1.dto;
*/
public enum SearchableEntityKind
{
SAMPLE, EXPERIMENT
SAMPLE, EXPERIMENT,
// sample subcriteria
SAMPLE_CONTAINER, SAMPLE_PARENT, // SAMPLE_CHILD
}
\ No newline at end of file
......@@ -240,6 +240,30 @@ public class GeneralInformationServiceTest extends AbstractServerTestCase
context.assertIsSatisfied();
}
@Test
public void testSearchForSamplesWithParent()
{
prepareGetSession();
final RecordingMatcher<DetailedSearchCriteria> detailedSearchCriteriaMatcher =
RecordingMatcher.create();
final RecordingMatcher<List<DetailedSearchSubCriteria>> detailedSearchSubCriteriaMatcher =
RecordingMatcher.create();
prepareSearchForSamples(detailedSearchCriteriaMatcher, detailedSearchSubCriteriaMatcher);
List<Sample> result =
service.searchForSamples(SESSION_TOKEN, createSearchCriteriaForSampleWithParent());
assertEquals(1, result.size());
Sample resultSample = result.get(0);
assertEquals("/space/code", resultSample.getIdentifier());
assertEquals("ATTRIBUTE CODE: a code AND "
+ "PROPERTY MY_PROPERTY2: a property value (with wildcards)",
detailedSearchCriteriaMatcher.recordedObject().toString());
// check experiment subcriteria
assertEquals("[SAMPLE: ATTRIBUTE CODE: parent code AND "
+ "PROPERTY PARENT_PROPERTY: parent property value (with wildcards)]",
detailedSearchSubCriteriaMatcher.recordedObject().toString());
context.assertIsSatisfied();
}
private void prepareSearchForSamples(
final RecordingMatcher<DetailedSearchCriteria> detailedSearchCriteriaMatcher,
final RecordingMatcher<List<DetailedSearchSubCriteria>> detailedSearchSubCriteriaMatcher)
......@@ -439,6 +463,16 @@ public class GeneralInformationServiceTest extends AbstractServerTestCase
return sc;
}
private SearchCriteria createSearchCriteriaForSampleParent()
{
SearchCriteria sc = new SearchCriteria();
sc.addMatchClause(MatchClause
.createAttributeMatch(MatchClauseAttribute.CODE, "parent code"));
sc.addMatchClause(MatchClause.createPropertyMatch("PARENT_PROPERTY",
"parent property value"));
return sc;
}
private SearchCriteria createSearchCriteriaForExperiment()
{
SearchCriteria sc = new SearchCriteria();
......@@ -457,6 +491,14 @@ public class GeneralInformationServiceTest extends AbstractServerTestCase
return mainCriteria;
}
private SearchCriteria createSearchCriteriaForSampleWithParent()
{
SearchCriteria mainCriteria = createSearchCriteriaForSample();
SearchCriteria parentCriteria = createSearchCriteriaForSampleParent();
mainCriteria.addSubCriteria(SearchSubCriteria.createSampleCriteria(parentCriteria));
return mainCriteria;
}
private void assertSpaceAndProjects(String expectedSpaceCode, String expectedProjects,
SpaceWithProjectsAndRoleAssignments space)
{
......
......@@ -135,6 +135,7 @@ public class GeneralInformationServiceTest extends SystemTestCase
{
// Search for Samples with only experiment's code limiting the results
SearchCriteria sc = new SearchCriteria();
sc.addMatchClause(MatchClause.createAttributeMatch(MatchClauseAttribute.CODE, "*"));
SearchCriteria ec = new SearchCriteria();
ec.addMatchClause(MatchClause.createAttributeMatch(MatchClauseAttribute.CODE, "EXP-TEST-1"));
sc.addSubCriteria(SearchSubCriteria.createExperimentCriteria(ec));
......@@ -148,6 +149,7 @@ public class GeneralInformationServiceTest extends SystemTestCase
{
// Search for Samples with only experiment's property limiting the results
SearchCriteria sc = new SearchCriteria();
sc.addMatchClause(MatchClause.createAttributeMatch(MatchClauseAttribute.CODE, "*"));
SearchCriteria ec = new SearchCriteria();
ec.addMatchClause(MatchClause.createPropertyMatch("DESCRIPTION", "A simple experiment"));
sc.addSubCriteria(SearchSubCriteria.createExperimentCriteria(ec));
......@@ -156,20 +158,38 @@ public class GeneralInformationServiceTest extends SystemTestCase
}
@Test
public void testSearchForSamplesByPropertyAndExperimentCode()
public void testSearchForSamplesByParentCode()
{
// Search for Samples with only parent's code limiting the results
SearchCriteria sc = new SearchCriteria();
sc.addMatchClause(MatchClause.createAttributeMatch(MatchClauseAttribute.CODE, "*"));
SearchCriteria pc = new SearchCriteria();
pc.addMatchClause(MatchClause.createAttributeMatch(MatchClauseAttribute.CODE, "MP002-1"));
sc.addSubCriteria(SearchSubCriteria.createSampleCriteria(pc));
List<Sample> result = generalInformationService.searchForSamples(sessionToken, sc);
assertEquals(2, result.size());
}
@Test
public void testSearchForSamplesByExperimentAndParentCode()
{
// Search for Samples first
SearchCriteria sc = new SearchCriteria();
sc.addMatchClause(MatchClause.createPropertyMatch("ORGANISM", "*"));
sc.addMatchClause(MatchClause.createAttributeMatch(MatchClauseAttribute.CODE, "*"));
List<Sample> result = generalInformationService.searchForSamples(sessionToken, sc);
assertEquals(4, result.size());
// Add experiment criteria limiting results to only 1
assertEquals(true, result.size() > 1000);
// Add experiment criteria limiting results to 7
SearchCriteria ec = new SearchCriteria();
ec.addMatchClause(MatchClause.createAttributeMatch(MatchClauseAttribute.CODE, "EXP-TEST-1"));
ec.addMatchClause(MatchClause.createAttributeMatch(MatchClauseAttribute.CODE, "EXP-REUSE"));
sc.addSubCriteria(SearchSubCriteria.createExperimentCriteria(ec));
List<Sample> result2 = generalInformationService.searchForSamples(sessionToken, sc);
assertEquals(1, result2.size());
assertEquals("/CISD/NEMO/EXP-TEST-1", result2.get(0).getExperimentIdentifierOrNull());
assertEquals(7, result2.size());
// Add parent criteria limiting results to only 2
SearchCriteria pc = new SearchCriteria();
pc.addMatchClause(MatchClause.createAttributeMatch(MatchClauseAttribute.CODE, "DP1-A"));
sc.addSubCriteria(SearchSubCriteria.createSampleCriteria(pc));
List<Sample> result3 = generalInformationService.searchForSamples(sessionToken, sc);
assertEquals(2, result3.size());
}
@Test
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment