コード例 #1
0
/**
 * Read-through write-through per-session repository cache.
 *
 * <p>TODO doc TODO logging perf measurements
 *
 * @author Radovan Semancik
 */
public class RepositoryCache implements RepositoryService {

  private static ThreadLocal<Cache> cacheInstance = new ThreadLocal<>();

  private RepositoryService repository;

  private static final Trace LOGGER = TraceManager.getTrace(RepositoryCache.class);
  private static final Trace PERFORMANCE_ADVISOR = TraceManager.getPerformanceAdvisorTrace();

  private PrismContext prismContext;

  public RepositoryCache() {}

  public void setRepository(RepositoryService service, PrismContext prismContext) {
    Validate.notNull(service, "Repository service must not be null.");
    Validate.notNull(prismContext, "Prism context service must not be null.");
    this.repository = service;
    this.prismContext = prismContext;
  }

  private static Cache getCache() {
    return cacheInstance.get();
  }

  public static void init() {}

  public static void destroy() {
    Cache.destroy(cacheInstance, LOGGER);
  }

  public static void enter() {
    Cache.enter(cacheInstance, Cache.class, LOGGER);
  }

  public static void exit() {
    Cache.exit(cacheInstance, LOGGER);
  }

  public static boolean exists() {
    return Cache.exists(cacheInstance);
  }

  public static String debugDump() {
    return Cache.debugDump(cacheInstance);
  }

  @Override
  public <T extends ObjectType> PrismObject<T> getObject(
      Class<T> type,
      String oid,
      Collection<SelectorOptions<GetOperationOptions>> options,
      OperationResult parentResult)
      throws ObjectNotFoundException, SchemaException {
    if (!isCacheable(type) || !nullOrHarmlessOptions(options)) {
      log("Cache: PASS {} ({})", oid, type.getSimpleName());
      return repository.getObject(type, oid, options, parentResult);
    }
    Cache cache = getCache();
    if (cache == null) {
      log("Cache: NULL {} ({})", oid, type.getSimpleName());
    } else {
      PrismObject<T> object = (PrismObject) cache.getObject(oid);
      if (object != null) {
        // TODO: result?
        log("Cache: HIT {} ({})", oid, type.getSimpleName());
        return object.clone();
      }
      log("Cache: MISS {} ({})", oid, type.getSimpleName());
    }
    PrismObject<T> object = repository.getObject(type, oid, null, parentResult);
    cacheObject(cache, object);
    return object;
  }

  private boolean isCacheable(Class<?> type) {
    if (type.equals(TaskType.class)) {
      return false;
    }
    //		if (ShadowType.class.isAssignableFrom(type)) {
    //			return false;
    //		}
    return true;
  }

  @Override
  public <T extends ObjectType> String addObject(
      PrismObject<T> object, RepoAddOptions options, OperationResult parentResult)
      throws ObjectAlreadyExistsException, SchemaException {
    String oid = repository.addObject(object, options, parentResult);
    Cache cache = getCache();
    // DON't cache the object here. The object may not have proper "JAXB" form, e.g. some pieces may
    // be
    // DOM element instead of JAXB elements. Not to cache it is safer and the performance loss
    // is acceptable.
    if (cache != null) {
      // Invalidate the cache entry if it happens to be there
      cache.removeObject(oid);
      cache.clearQueryResults(object.getCompileTimeClass());
    }
    return oid;
  }

  @Override
  public <T extends ObjectType> SearchResultList<PrismObject<T>> searchObjects(
      Class<T> type,
      ObjectQuery query,
      Collection<SelectorOptions<GetOperationOptions>> options,
      OperationResult parentResult)
      throws SchemaException {
    if (!isCacheable(type) || !nullOrHarmlessOptions(options)) {
      log("Cache: PASS ({})", type.getSimpleName());
      return repository.searchObjects(type, query, options, parentResult);
    }
    Cache cache = getCache();
    if (cache == null) {
      log("Cache: NULL ({})", type.getSimpleName());
    } else {
      SearchResultList queryResult = cache.getQueryResult(type, query, prismContext);
      if (queryResult != null) {
        log("Cache: HIT {} ({})", query, type.getSimpleName());
        return queryResult.clone();
      }
      log("Cache: MISS {} ({})", query, type.getSimpleName());
    }

    // Cannot satisfy from cache, pass down to repository
    SearchResultList<PrismObject<T>> objects =
        repository.searchObjects(type, query, options, parentResult);
    if (cache != null && options == null) {
      for (PrismObject<T> object : objects) {
        cacheObject(cache, object);
      }
      cache.putQueryResult(type, query, objects, prismContext);
    }
    return objects;
  }

  /* (non-Javadoc)
   * @see com.evolveum.midpoint.repo.api.RepositoryService#searchObjectsIterative(java.lang.Class, com.evolveum.midpoint.prism.query.ObjectQuery, com.evolveum.midpoint.schema.ResultHandler, com.evolveum.midpoint.schema.result.OperationResult)
   */
  @Override
  public <T extends ObjectType> SearchResultMetadata searchObjectsIterative(
      Class<T> type,
      ObjectQuery query,
      final ResultHandler<T> handler,
      Collection<SelectorOptions<GetOperationOptions>> options,
      OperationResult parentResult)
      throws SchemaException {
    // TODO use cached query result if applicable
    log("Cache: PASS searchObjectsIterative ({})", type.getSimpleName());
    final Cache cache = getCache();
    ResultHandler<T> myHandler =
        new ResultHandler<T>() {
          @Override
          public boolean handle(PrismObject<T> object, OperationResult parentResult) {
            cacheObject(cache, object);
            return handler.handle(object, parentResult);
          }
        };
    return repository.searchObjectsIterative(type, query, myHandler, options, parentResult);
  }

  @Override
  public <T extends ObjectType> int countObjects(
      Class<T> type, ObjectQuery query, OperationResult parentResult) throws SchemaException {
    // TODO use cached query result if applicable
    log("Cache: PASS countObjects ({})", type.getSimpleName());
    return repository.countObjects(type, query, parentResult);
  }

  @Override
  public <T extends ObjectType> void modifyObject(
      Class<T> type,
      String oid,
      Collection<? extends ItemDelta> modifications,
      OperationResult parentResult)
      throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException {
    repository.modifyObject(type, oid, modifications, parentResult);
    // this changes the object. We are too lazy to apply changes ourselves, so just invalidate
    // the object in cache
    Cache cache = getCache();
    if (cache != null) {
      cache.removeObject(oid);
      cache.clearQueryResults(type);
    }
  }

  @Override
  public <T extends ObjectType> void deleteObject(
      Class<T> type, String oid, OperationResult parentResult) throws ObjectNotFoundException {
    repository.deleteObject(type, oid, parentResult);
    Cache cache = getCache();
    if (cache != null) {
      cache.removeObject(oid);
      cache.clearQueryResults(type);
    }
  }

  @Override
  public <F extends FocusType> PrismObject<F> searchShadowOwner(
      String shadowOid,
      Collection<SelectorOptions<GetOperationOptions>> options,
      OperationResult parentResult)
      throws ObjectNotFoundException {
    // TODO cache the search operation?
    PrismObject<F> ownerObject = repository.searchShadowOwner(shadowOid, options, parentResult);
    if (ownerObject != null && nullOrHarmlessOptions(options)) {
      cacheObject(getCache(), ownerObject);
    }
    return ownerObject;
  }

  private boolean nullOrHarmlessOptions(Collection<SelectorOptions<GetOperationOptions>> options) {
    if (options == null || options.isEmpty()) {
      return true;
    }
    if (options.size() > 1) {
      return false;
    }
    SelectorOptions<GetOperationOptions> selectorOptions = options.iterator().next();
    if (!selectorOptions.isRoot()) {
      return false;
    }
    GetOperationOptions options1 = selectorOptions.getOptions();
    if (options1 == null
        || options1.equals(new GetOperationOptions())
        || options1.equals(GetOperationOptions.createAllowNotFound())) {
      return true;
    }
    return false;
  }

  @Override
  @Deprecated
  public PrismObject<UserType> listAccountShadowOwner(
      String accountOid, OperationResult parentResult) throws ObjectNotFoundException {
    return repository.listAccountShadowOwner(accountOid, parentResult);
  }

  @Override
  public <T extends ShadowType> List<PrismObject<T>> listResourceObjectShadows(
      String resourceOid, Class<T> resourceObjectShadowType, OperationResult parentResult)
      throws ObjectNotFoundException, SchemaException {
    return repository.listResourceObjectShadows(
        resourceOid, resourceObjectShadowType, parentResult);
  }

  /* (non-Javadoc)
   * @see com.evolveum.midpoint.repo.api.RepositoryService#getVersion(java.lang.Class, java.lang.String, com.evolveum.midpoint.schema.result.OperationResult)
   */
  @Override
  public <T extends ObjectType> String getVersion(
      Class<T> type, String oid, OperationResult parentResult)
      throws ObjectNotFoundException, SchemaException {
    if (!isCacheable(type)) {
      log("Cache: PASS {} ({})", oid, type.getSimpleName());
      return repository.getVersion(type, oid, parentResult);
    }
    Cache cache = getCache();
    if (cache == null) {
      log("Cache: NULL {} ({})", oid, type.getSimpleName());
    } else {
      String version = cache.getObjectVersion(oid);
      if (version != null) {
        log("Cache: HIT {} ({})", oid, type.getSimpleName());
        return version;
      }
      log("Cache: MISS {} ({})", oid, type.getSimpleName());
    }
    String version = repository.getVersion(type, oid, parentResult);
    cacheObjectVersion(cache, oid, version);
    return version;
  }

  /* (non-Javadoc)
   * @see com.evolveum.midpoint.repo.api.RepositoryService#getRepositoryDiag()
   */
  @Override
  public RepositoryDiag getRepositoryDiag() {
    return repository.getRepositoryDiag();
  }

  /* (non-Javadoc)
   * @see com.evolveum.midpoint.repo.api.RepositoryService#repositorySelfTest(com.evolveum.midpoint.schema.result.OperationResult)
   */
  @Override
  public void repositorySelfTest(OperationResult parentResult) {
    repository.repositorySelfTest(parentResult);
  }

  @Override
  public void testOrgClosureConsistency(boolean repairIfNecessary, OperationResult testResult) {
    repository.testOrgClosureConsistency(repairIfNecessary, testResult);
  }

  private <T extends ObjectType> void cacheObject(Cache cache, PrismObject<T> object) {
    if (cache != null) {
      cache.putObject(object.getOid(), (PrismObject<ObjectType>) object.clone());
    }
  }

  private <T extends ObjectType> void cacheObjectVersion(Cache cache, String oid, String version) {
    if (cache != null) {
      cache.putObjectVersion(oid, version);
    }
  }

  @Override
  public boolean isAnySubordinate(String upperOrgOid, Collection<String> lowerObjectOids)
      throws SchemaException {
    return repository.isAnySubordinate(upperOrgOid, lowerObjectOids);
  }

  private void log(String message, Object... params) {
    if (LOGGER.isTraceEnabled()) {
      LOGGER.trace(message, params);
    }
    if (PERFORMANCE_ADVISOR.isTraceEnabled()) {
      PERFORMANCE_ADVISOR.trace(message, params);
    }
  }
}