package gw.rating
uses gw.api.database.Query
uses gw.api.database.QuerySelectColumns
uses gw.api.domain.financials.PCFinancialsLogger
uses java.math.BigDecimal
uses gw.api.path.Paths
uses gw.util.LRUCache
uses java.util.ArrayList
uses java.util.Collections
uses java.lang.Double
uses java.util.Date
uses java.util.List
uses gw.api.database.IQueryBeanResult
uses gw.api.upgrade.Coercions
/**
* Search critera that can find rating factors in the rate_adj_factors system table.
* See match and matchInRange methods for the full details
*/
@Export
class RateAdjFactorSearchCriteria
{
static var _rangeCache = Collections.synchronizedMap(new LRUCache(100))
static var _lookupCache = Collections.synchronizedMap(new LRUCache>(100))
var _factorName : String
var _findDate : Date
// For testing
static internal function clearCaches()
{
_rangeCache.clear()
_lookupCache.clear()
}
construct( factorName : String, findDate : Date )
{
if ( factorName == null or findDate == null )
{
// Throw because, while the match code will work (typically returning 1.0) passing in nulls is probably indicative of a bug.
throw "RateAdjFactorSearchCriteria requires both factorName and findDate " + this
}
_factorName = factorName
_findDate = Coercions.makeDateFrom(findDate.clone())
}
/**
* Find a factor from the rating_adj_factors system table best matching this criteria such that
*
* factorName matches
* findDate falls within the [effDate, expDate) range.
* If either dates is null, then that side of the range is considered unbound.
* factorState matches
*
* If it is unable to find a factor matching all criteria, it then looks for a full default
* factor match.
* It returns null if it is unable match anything.
*/
function match( factorState : Jurisdiction ) : BigDecimal
{
return match( null, null, factorState )
}
/**
* Find a factor from the rating_adj_factors system table best matching this criteria such that
*
* factorName matches
* findDate falls within the [effDate, expDate) range.
* If either dates is null, then that side of the range is considered unbound.
* lookup matches the stringOption
* factorState matches
*
* If it is unable to find a factor matching all criteria, it then looks for a factor for all
* states, and finally, a full default factor.
* It returns null if it is unable match anything.
*/
function match( lookup : String, factorState : Jurisdiction ) : BigDecimal
{
return match( lookup, null, factorState )
}
/**
* Find a factor from the rating_adj_factors system table best matching this criteria such that
*
* factorName matches
* findDate falls within the [effDate, expDate) range.
* If either dates is null, then that side of the range is considered unbound.
* lookup matches the stringOption
* lookup2 matches the stringOption2
* factorState matches
*
* If it is unable to find a factor matching all criteria, it then looks for a factor for all
* states, a lookup2 default factor, and finally, a full default factor.
* It returns null if it is unable match anything.
*/
function match( lookup : String, lookup2 : String, factorState : Jurisdiction ) : BigDecimal
{
var factor = findByLookups( lookup, lookup2, factorState ) // find with all criteria
if ( factor == null )
{
factor = findByLookups( lookup, lookup2, null ) // find "all states" default
}
if ( factor == null )
{
factor = findByLookups( lookup, null, null ) // find lookup2 default
}
if ( factor == null )
{
factor = findByLookups( null, null, null ) // find default
logDefault( factor, "matchFactor(lookup=" + lookup + ", lookup2=" + lookup2 + ", factorState=" + factorState + ")" )
}
return safeFactor( factor, "match(lookup=" + lookup + ", lookup2=" + lookup2 + ", factorState=" + factorState + ")" )
}
/**
* Find a factor from the rating_adj_factors system table matching the criteria such that
*
* factorName matches
* factorState matches
* lookup matches the stringOption
* lookup2 matches the stringOption2
* findDate falls within the [effDate, expDate) range.
* If either dates is null, then that side of the range is considered unbound.
*
* It returns null if there are no matches. If there are multiple matches,
* then it returns the one with the latest effective date, and if those are equal,
* then the one with a PublicID later in the alphabet.
*/
function findByLookups( lookup : String, lookup2 : String, factorState : Jurisdiction ) : RateAdjFactorExt
{
var result = findAllByLookups( lookup, lookup2, factorState )
if (result.HasElements) {
return result.first()
} else {
return null
}
}
/**
* Return a query that finds all factors from the rating_adj_factors system table best matching this criteria such that
* the factorState matches. If it is unable to find any factors matching the state, it then looks for the
* factors for all states.
*/
function matchAll( factorState : Jurisdiction ) : List
{
var result = findAllByLookups( null, null, factorState )
if ( result.Empty ) // find "all states" defaults
{
result = findAllByLookups( null, null, null )
}
return result
}
function matchInRange( findNumber : Double, factorState : Jurisdiction ) : BigDecimal
{
return matchInRange( null, findNumber, factorState )
}
function matchInRange( lookup : String, findNumber : Double, factorState : Jurisdiction ) : BigDecimal
{
var extraDesc = "matchInRange(lookup=" + lookup + ", findNumber=" + findNumber + ", factorState=" + factorState + ")"
var factor = findInRange( lookup, findNumber, factorState ) // find with all criteria
if ( factor == null )
{
factor = findInRange( lookup, findNumber, null ) // find "all states" default
}
if ( factor == null )
{
factor = findInRange( null, findNumber, null ) // find default
logDefault( factor, extraDesc )
}
return safeFactor( factor, extraDesc )
}
private function logDefault( factor : RateAdjFactorExt, extraDesc : String )
{
if ( factor != null )
{
PCFinancialsLogger.logDebug( "Could only match default rating factor. Using "
+ factor.factor + " for " + this + extraDesc )
}
}
private function safeFactor( factor : RateAdjFactorExt, extraDesc : String ) : BigDecimal
{
if ( factor == null )
{
PCFinancialsLogger.logDebug( "Could not match any rating factor. Using 1.0 for " + this + extraDesc )
return 1.0
}
return factor.factor
}
/**
* Return all factors from the rating_adj_factors system table matching the criteria such that
*
* factorName matches
* factorState matches
* lookup matches the stringOption
* lookup2 matches the stringOption2
* findDate falls within the [effDate, expDate) range.
* If either dates is null, then that side of the range is considered unbound.
*
*/
private function findAllByLookups( lookup : String, lookup2 : String, factorState : Jurisdiction ) : List
{
var result : List
var queryElems = new Object[]{lookup, lookup2, factorState, _factorName, _findDate}.toList()
if (_lookupCache.containsKey( queryElems )) {
result = _lookupCache.get( queryElems )
} else {
var query = Query.make(RateAdjFactorExt)
.compare(RateAdjFactorExt#factorName, Equals, _factorName)
.compare(RateAdjFactorExt#factorState, Equals, factorState.Code)
.compare(RateAdjFactorExt#stringOption, Equals, lookup)
.compare(RateAdjFactorExt#stringOption2, Equals, lookup2)
.and(\ andRestriction -> andRestriction
.or(\orRestriction -> {
orRestriction.compare(RateAdjFactorExt#effDate, Equals, null)
orRestriction.compare(RateAdjFactorExt#effDate, LessThanOrEquals, _findDate)
})
.or(\orRestriction -> {
orRestriction.compare(RateAdjFactorExt#expDate, Equals, null)
orRestriction.compare(RateAdjFactorExt#expDate, GreaterThan, _findDate)
})
).select()
result = query.toList()
_lookupCache.put(queryElems, result)
}
return result
}
/**
* Find a factor from the rating_adj_factors system table matching the criteria such that
*
* factorName matches
* factorState matches
* lookup matches the stringOption
* findNumber falls within the [minNumber, maxNumber] range
* findDate falls within the [effDate, expDate) range. If either dates is null, then that side of the range is considered unbound.
*
* It returns null if there are no matches. If there are multiple matches,
* then it returns the one with the latest effective date, and if those are equal,
* then the one with a PublicID later in the alphabet.
*/
private function findInRange( lookup : String, findNumber : Double, factorState : Jurisdiction ) : RateAdjFactorExt
{
var result : RateAdjFactorExt
var queryElems = new Object[]{lookup, findNumber, factorState, _factorName, _findDate}.toList()
if (_rangeCache.containsKey( queryElems )) {
result = _rangeCache.get( queryElems )
} else {
var factorQuery = Query.make(RateAdjFactorExt)
.compare(RateAdjFactorExt#factorName, Equals, _factorName)
.compare(RateAdjFactorExt#factorState, Equals, factorState.Code)
.compare(RateAdjFactorExt#stringOption, Equals, lookup)
.compare(RateAdjFactorExt#minNumber, LessThanOrEquals, findNumber as BigDecimal)
.compare(RateAdjFactorExt#maxNumber, GreaterThanOrEquals, findNumber as BigDecimal)
.and(\andRestriction -> andRestriction
.or(\orRestriction -> {
orRestriction.compare(RateAdjFactorExt#effDate, Equals, null)
orRestriction.compare(RateAdjFactorExt#effDate, LessThanOrEquals, _findDate)
})
.or(\orRestriction -> {
orRestriction.compare(RateAdjFactorExt#expDate, Equals, null)
orRestriction.compare(RateAdjFactorExt#expDate, GreaterThan, _findDate)
})
).select()
result = getFirstResult( factorQuery )
_rangeCache.put(queryElems, result)
}
return result
}
private function getFirstResult( factorQuery : IQueryBeanResult ) : RateAdjFactorExt
{
if ( factorQuery != null )
{
// get first the latest one, with the earliest PublicID
factorQuery.orderByDescending(QuerySelectColumns.path(Paths.make(RateAdjFactorExt#effDate))).thenBy(QuerySelectColumns.path(Paths.make(RateAdjFactorExt#PublicID)))
factorQuery.setPageSize(2)
var factors = factorQuery.iterator()
if (factors.hasNext()) {
var factor = factors.next()
if (factors.hasNext()) {
PCFinancialsLogger.logDebug("Found more than one matching rating factor. Possible duplicates? " + this)
}
return factor
}
}
return null
}
override function toString() : String
{
return "{_factorName=" + _factorName + ", findDate=" + _findDate + "}"
}
}