package gw.rating.flow.domain uses com.google.common.collect.ImmutableList uses gw.api.logicalmatch.AnonymousBeanKey uses gw.api.system.PLDependenciesGateway uses gw.api.tools.ProductModelToolsUtil uses gw.entity.IEntityPropertyInfo uses gw.lang.reflect.ITypeLoaderListener uses gw.lang.reflect.RefreshRequest uses gw.lang.reflect.TypeSystem uses gw.rating.RateFlowLogger uses java.util.concurrent.ConcurrentHashMap /** * Caches CalcRoutine objects, mostly to avoid the repeated expense of * parsing the definitions into statements and converting them to Gosu classes. * * The minimal lookup key for the routine is Code + Version (although extension fields * can also be included). After fetching the cached entry, the cache validates that it is current based * on the UpdateTime of the CalcRoutineDefinition, so that if someone changes the definition, * subsequent runs of the routine will always use the new version. (This should only happen * in a development environment.) */ internal final class CalcRoutineCache { private final var keyFields: ImmutableList private final var cacheMap = new ConcurrentHashMap() private final var create: block(def: CalcRoutineDefinition): CalcRoutine private final var logger = RateFlowLogger.Logger // DO NOT INLINE IN THE CALL TO AddTypeloaderListenerAsWeakRef. // At GC time, if the only paths that reach an object are through WeakRefs, the object will be collected. // So we must hold onto the listener object ourselves. private var listener: ITypeLoaderListener /** * Construct an instance of the cache (only one should be needed, but this isn't enforced). * * @param routineCreator called to create a CalcRoutine from a CalcRoutineDefinition if the entry needs to be refreshed * @param extensionFields list of extension fields on the CalcRoutineDefinition that should be considered part of its unique key */ construct(routineCreator: block(def: CalcRoutineDefinition): CalcRoutine, extensionFields: IEntityPropertyInfo[]) { create = routineCreator keyFields = ImmutableList.builder() .add(CalcRoutineDefinition.CODE_PROP.get()) .add(CalcRoutineDefinition.VERSION_PROP.get()) .add(extensionFields) .build() if (extensionFields.HasElements) { logger.info("CalcRoutineCache created with extension fields: {${extensionFields.map(\e -> e.Name).join(",")}}") } else { logger.info("CalcRoutineCache created with no extension fields.") } // noinspection HiddenPackageReference if (PLDependenciesGateway.ServerMode.isDev()) { // In development mode, we want to be able to clear the cache on a refresh. listener = new ITypeLoaderListener() { @SuppressWarnings("unused") override function refreshed() { // If types were reloaded, we want to re-parse the Gosu to make sure it's still valid. logger.info("typesystem refreshed...clearing CalcRoutineCache") clear() } @SuppressWarnings("unused") override function refreshedTypes(types: RefreshRequest) { // If a type is refreshed, we want to re-parse the Gosu to make sure it's still valid. logger.info("refreshed types ${types}...clearing CalcRoutineCache") clear() } } TypeSystem.addTypeLoaderListenerAsWeakRef(listener) // In development mode, we want to be able to clear the cache on a refresh. ProductModelToolsUtil.addProductModelUpgradeListener(\-> { // If product model was reloaded, we want to re-parse the Gosu to make sure it's still valid. logger.info("product model reloaded...clearing CalcRoutineCache") clear() }) } } property get Size(): int { return cacheMap.size() } /** * Get a CalcRoutine (probably cached) that was built from the given CalcRoutineDefinition. This method * is thread-safe. In production nothing should invalidate the cache entries because the * CalcRoutineDefinitions in an active ratebook cannot be edited. * * @param calcRoutineDefinition A CalcRoutineDefinition for which we need the corresponding CalcRoutine * @return a CalcRoutine instance, newly-created or refreshed if necessary * @throws Exception whatever exceptions are thrown during attempted CalcRoutine creation */ function getCalcRoutine(calcRoutineDefinition: CalcRoutineDefinition): CalcRoutine { return getCalcRoutine(calcRoutineDefinition, \ -> create(calcRoutineDefinition)) } internal function getCalcRoutine(definition : CalcRoutineDefinition, creator : block() : CalcRoutine) : CalcRoutine { var key = new AnonymousBeanKey(definition, keyFields) return cacheMap.compute(key, \_, existingCalcRoutine -> existingCalcRoutine == null or definition.UpdateTime != existingCalcRoutine.UpdateTime ? creator() : existingCalcRoutine) } /** * Empty the cache. */ function clear() { cacheMap.clear() } internal property get CacheValues(): Collection { return cacheMap.values() } @SuppressWarnings("unused") internal property get CacheKeys(): Set { return cacheMap.keySet() } }