/* * Copyright: (c) 2004-2011 Mayo Foundation for Medical Education and * Research (MFMER). All rights reserved. MAYO, MAYO CLINIC, and the * triple-shield Mayo logo are trademarks and service marks of MFMER. * * Except as contained in the copyright notice above, or as used to identify * MFMER as the author of this software, the trade names, trademarks, service * marks, or product names of the copyright holder shall not be used in * advertising, promotion or otherwise in connection with this software without * prior written authorization of the copyright holder. * * 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 edu.mayo.cts2.framework.filter.directory; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import edu.mayo.cts2.framework.filter.match.Matcher; import edu.mayo.cts2.framework.filter.match.ResolvableComponentReference; import edu.mayo.cts2.framework.filter.match.ResolvableMatchAlgorithmReference; import edu.mayo.cts2.framework.model.command.ResolvedFilter; import edu.mayo.cts2.framework.model.core.ComponentReference; import edu.mayo.cts2.framework.model.core.MatchAlgorithmReference; import edu.mayo.cts2.framework.model.directory.DirectoryResult; import edu.mayo.cts2.framework.model.exception.ExceptionFactory; /** * A {@link DirectoryBuilder} implementation based on knowing a priori all potential * results, and incrementally removing ones that don't match the filters. * * Use this {@link DirectoryBuilder} implementation when the result set is small enough * to be held entirely in memory. * * @param <F> the generic type * @param <T> the generic type * @author <a href="mailto:kevin.peterson@mayo.edu">Kevin Peterson</a> */ public abstract class AbstractRemovingDirectoryBuilder<F,T> extends AbstractNonLazyDirectoryBuilder<T> { private static float DEFAULT_SCORE_THRESHOLD = 0.5f; private List<Restriction<F>> restrictions = new ArrayList<Restriction<F>>(); /** The match algorithm references. */ private Set<ResolvableMatchAlgorithmReference> matchAlgorithmReferences = new HashSet<ResolvableMatchAlgorithmReference>(); private Set<ResolvableComponentReference<F>> resolvablePropertyReferences = new HashSet<ResolvableComponentReference<F>>(); private Set<F> allPossibleResults; private int allPossibleResultsCount; /** * Instantiates a new abstract removing directory builder. * * @param allPossibleResults the all possible results */ public AbstractRemovingDirectoryBuilder(List<F> allPossibleResults){ this(allPossibleResults, null, null); } /** * Instantiates a new abstract removing directory builder. * * @param allPossibleResults the all possible results * @param matchAlgorithmReferences the match algorithm references * @param resolvableModelAttributeReferences the resolvable model attribute references * @param resolvablePredicateReferences the resolvable predicate references */ public AbstractRemovingDirectoryBuilder( List<F> allPossibleResults, Set<ResolvableMatchAlgorithmReference> matchAlgorithmReferences, Set<ResolvableComponentReference<F>> resolvablePropertyReferences ){ super(); this.allPossibleResults = new HashSet<F>(allPossibleResults); this.allPossibleResultsCount = allPossibleResults.size(); if(matchAlgorithmReferences != null){ this.matchAlgorithmReferences = matchAlgorithmReferences; } if(resolvablePropertyReferences != null){ this.resolvablePropertyReferences = resolvablePropertyReferences; } } /** * Adds the supported model attribute reference. * * @param reference the reference * @return the directory builder */ public DirectoryBuilder<T> addSupportedPropertyReference( ResolvableComponentReference<F> reference){ this.resolvablePropertyReferences.add(reference); return this; } /** * Adds the supported model attribute reference. * * @param reference the reference * @return the directory builder */ public DirectoryBuilder<T> addSupportedMatchAlgorithmReference( ResolvableMatchAlgorithmReference reference){ this.matchAlgorithmReferences.add(reference); return this; } public DirectoryBuilder<T> addResolvablePropertyReference( ResolvableComponentReference<F> reference){ this.resolvablePropertyReferences.add(reference); return this; } /* (non-Javadoc) * @see org.cts2.internal.model.uri.restrict.ListBasedResolvingRestrictionHandler#restrict(java.util.List, org.cts2.core.Filter) */ public DirectoryResult<T> resolve() { this.verifyRange(); for(Restriction<F> restriction : this.getRestrictions()){ allPossibleResults.retainAll( doProcessRestriction(restriction)); } for(final ResolvedFilter filter : this.getFilterComponents()){ allPossibleResults.retainAll( doRestrict(filter, DEFAULT_SCORE_THRESHOLD)); } return this.createDirectoryResult(new ArrayList<F>(allPossibleResults)); } /* (non-Javadoc) * @see edu.mayo.cts2.framework.filter.directory.DirectoryBuilder#count() */ public int count() { for(final ResolvedFilter filter : this.getFilterComponents()){ allPossibleResults.retainAll( doRestrict(filter, DEFAULT_SCORE_THRESHOLD)); } return this.allPossibleResults.size(); } /** * Verify range. */ private void verifyRange() { if(this.getStart() != 0 && this.getStart() >= this.allPossibleResultsCount){ throw ExceptionFactory.createPageOutOfBoundsException(); } } /** * Creates the directory result. * * @param results the results * @return the directory result */ protected DirectoryResult<T> createDirectoryResult(List<F> results){ List<T> transformedResults = this.transformResults(results); int resultsMatchingFilters = transformedResults.size(); List<T> prunedResults = this.pruneResults(transformedResults); boolean atEnd = resultsMatchingFilters <= this.getEnd(); return new DirectoryResult<T>(prunedResults, atEnd); } /** * Transform results. * * @param results the results * @return the list */ protected abstract List<T> transformResults(List<F> results); /** * Prune results. * * @param results the results * @return the list */ protected List<T> pruneResults(List<T> results){ List<T> prunedList = new ArrayList<T>(); for(int i = this.getStart(); (i < this.getMaxToReturn() + this.getStart()) && i < results.size(); i++){ prunedList.add(results.get(i)); } return prunedList; } /** * Do process restriction. * * @param restriction the restriction * @return the sets the */ protected Set<F> doProcessRestriction(Restriction<F> restriction){ Set<F> passingResults = new HashSet<F>(); for(F candidate : this.allPossibleResults){ if(restriction.passRestriction(candidate)){ passingResults.add(candidate); } } return passingResults; } /** * Do restrict. * * @param filterComponent the filter component * @param minScore the min score * @return the sets the */ protected Set<F> doRestrict(ResolvedFilter filterComponent, float minScore){ Set<F> returnSet = new HashSet<F>(); Matcher algorithm = this.getMatchAlgorithm(filterComponent.getMatchAlgorithmReference()); String matchText = filterComponent.getMatchValue(); for(F candidate : this.allPossibleResults){ Iterable<String> candidates = this.getCandidateText(filterComponent.getComponentReference(), candidate); if(candidates != null){ for(String candidateText : candidates){ float score = algorithm.matchScore(matchText, candidateText); if(score != 0 && score >= minScore){ returnSet.add(candidate); break; } } } } return returnSet; } /** * Gets the candidate text. * * @param filterComponent the filter component * @param referenceType the reference type * @param candidate the candidate * @return the candidate text */ protected Iterable<String> getCandidateText( ComponentReference componentReference, F candidate) { ResolvableComponentReference<F> ref = this.getResolvableComponentReferences(componentReference); return ref.getModelAttributeValue(candidate); } /** * Gets the resolvable predicate references. * * @param nameOrUri the name or uri * @return the resolvable predicate references */ private ResolvableComponentReference<F> getResolvableComponentReferences(ComponentReference nameOrUri){ for(ResolvableComponentReference<F> predicate : this.resolvablePropertyReferences){ if(predicate.matches(nameOrUri)){ return predicate; } } throw ExceptionFactory.createUnsupportedPropertyReference( nameOrUri.toString(), this.resolvablePropertyReferences); } /** * Gets the match algorithm. * * @param reference the reference * @return the match algorithm */ protected ResolvableMatchAlgorithmReference getMatchAlgorithm(MatchAlgorithmReference reference){ for(ResolvableMatchAlgorithmReference matchAlgorithm : this.matchAlgorithmReferences){ if(matchAlgorithm.getContent().equals(reference.getContent())){ return matchAlgorithm; } } throw ExceptionFactory.createUnsupportedMatchAlgorithm( reference.getContent(), this.matchAlgorithmReferences); } /** * Adds the restriction. * * @param restriction the restriction */ public void addRestriction(Restriction<F> restriction) { this.restrictions.add(restriction); } protected List<Restriction<F>> getRestrictions() { return restrictions; } }