private <NewSqlDao extends EntitySqlDao<NewEntityModelDao, NewEntity>, NewEntityModelDao extends EntityModelDao<NewEntity>, NewEntity extends Entity> NewSqlDao create(final Class<NewSqlDao> newSqlDaoClass, final NewSqlDao newSqlDao) { final ClassLoader classLoader = newSqlDao.getClass().getClassLoader(); final Class[] interfacesToImplement = {newSqlDaoClass}; final EntitySqlDaoWrapperInvocationHandler<NewSqlDao, NewEntityModelDao, NewEntity> wrapperInvocationHandler = new EntitySqlDaoWrapperInvocationHandler<NewSqlDao, NewEntityModelDao, NewEntity>(newSqlDaoClass, newSqlDao, handle, clock, cacheControllerDispatcher, internalCallContextFactory); final Object newSqlDaoObject = Proxy.newProxyInstance(classLoader, interfacesToImplement, wrapperInvocationHandler); return newSqlDaoClass.cast(newSqlDaoObject); } }
private Object invokeWithCaching(final Cachable cachableAnnotation, final Method method, final Object[] args) throws Throwable { final ObjectType objectType = getObjectType(); final CacheType cacheType = cachableAnnotation.value(); final CacheController<Object, Object> cache = cacheControllerDispatcher.getCacheController(cacheType); final Annotation[][] annotations = getAnnotations(method); for (int i = 0; i < annotations.length; i++) { for (int j = 0; j < annotations[i].length; j++) { final String cacheKey = buildCacheKey(keyPieces); return cache.get(cacheKey, cacheLoaderArgument); } else { return invokeRaw(method, args);
@Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { try { return prof.executeWithProfiling(ProfilingFeatureType.DAO, getProfilingId(null, method), new WithProfilingCallback<Object, Throwable>() { @Override public Object execute() throws Throwable { if (statement != null) { errorDuringTransaction(t.getCause().getCause(), method, statement.toString() + "\n" + binding.toString()); } else { errorDuringTransaction(t.getCause().getCause(), method, binding.toString()); errorDuringTransaction(t.getCause().getCause(), method); } else if (t.getCause() != null) { errorDuringTransaction(t.getCause(), method); } else { errorDuringTransaction(t, method);
private Object invokeSafely(final Object proxy, final Method method, final Object[] args) throws Throwable { final Audited auditedAnnotation = method.getAnnotation(Audited.class); final Cachable cachableAnnotation = method.getAnnotation(Cachable.class); // This can't be AUDIT'ed and CACHABLE'd at the same time as we only cache 'get' if (auditedAnnotation != null) { return invokeWithAuditAndHistory(auditedAnnotation, method, args); } else if (cachableAnnotation != null && cacheControllerDispatcher != null) { return invokeWithCaching(cachableAnnotation, method, args); } else { return invokeRaw(method, args); } }
private Object invokeWithAuditAndHistory(final Audited auditedAnnotation, final Method method, final Object[] args) throws Throwable { final InternalCallContext context = retrieveContextFromArguments(args); final List<String> entityIds = retrieveEntityIdsFromArguments(method, args); for (final String entityId : entityIds) { deletedEntities.put(entityId, sqlDao.getById(entityId, context)); printSQLWarnings(); final Object obj = prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, getProfilingId("raw", method), new WithProfilingCallback<Object, Throwable>() { @Override public Object execute() throws Throwable { m = updateHistoryAndAudit(entityId, deletedEntities.get(entityId), changeType, context);
private void insertAudits(final TableName tableName, final M entityModelDao, final Long entityRecordId, final Long historyRecordId, final ChangeType changeType, final InternalCallContext contextMaybeWithoutAccountRecordId) { final TableName destinationTableName = MoreObjects.firstNonNull(tableName.getHistoryTableName(), tableName); final EntityAudit audit = new EntityAudit(destinationTableName, historyRecordId, changeType, contextMaybeWithoutAccountRecordId.getCreatedDate()); final InternalCallContext context; // Populate the account record id when creating the account record if (TableName.ACCOUNT.equals(tableName) && ChangeType.INSERT.equals(changeType)) { // AccountModelDao in practice final TimeZoneAwareEntity accountModelDao = (TimeZoneAwareEntity) entityModelDao; context = internalCallContextFactory.createInternalCallContext(accountModelDao, entityRecordId, contextMaybeWithoutAccountRecordId); } else { context = contextMaybeWithoutAccountRecordId; } sqlDao.insertAuditFromTransaction(audit, context); printSQLWarnings(); // We need to invalidate the caches. There is a small window of doom here where caches will be stale. // TODO Knowledge on how the key is constructed is also in AuditSqlDao if (tableName.getHistoryTableName() != null) { final CacheController<String, List> cacheController = cacheControllerDispatcher.getCacheController(CacheType.AUDIT_LOG_VIA_HISTORY); if (cacheController != null) { final String key = buildCacheKey(ImmutableMap.<Integer, Object>of(0, tableName.getHistoryTableName(), 1, tableName.getHistoryTableName(), 2, entityRecordId)); cacheController.remove(key); } } else { final CacheController<String, List> cacheController = cacheControllerDispatcher.getCacheController(CacheType.AUDIT_LOG); if (cacheController != null) { final String key = buildCacheKey(ImmutableMap.<Integer, Object>of(0, tableName, 1, entityRecordId)); cacheController.remove(key); } } }
private List<String> retrieveEntityIdsFromArguments(final Method method, final Object[] args) { final Annotation[][] parameterAnnotations = getAnnotations(method); int i = -1; for (final Object arg : args) { i++; // Assume the first argument of type Entity is our type of Entity (type U here) // This is true for e.g. create calls if (arg instanceof Entity) { return ImmutableList.<String>of(((Entity) arg).getId().toString()); } // For Batch calls, the first argument will be of type List<Entity> if (arg instanceof Iterable) { final Builder<String> entityIds = extractEntityIdsFromBatchArgument((Iterable) arg); if (entityIds != null) { return entityIds.build(); } } for (final Annotation annotation : parameterAnnotations[i]) { if (arg instanceof String && Bind.class.equals(annotation.annotationType()) && ("id").equals(((Bind) annotation).value())) { return ImmutableList.<String>of((String) arg); } else if (arg instanceof Collection && BindIn.class.equals(annotation.annotationType()) && ("ids").equals(((BindIn) annotation).value())) { return ImmutableList.<String>copyOf((Collection) arg); } } } return ImmutableList.<String>of(); }
private Object invokeRaw(final Method method, final Object[] args) throws Throwable { return prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, getProfilingId("raw", method), new WithProfilingCallback<Object, Throwable>() { @Override public Object execute() throws Throwable { // Real jdbc call final Object result = executeJDBCCall(method, args); // This is *almost* the default invocation except that we want to intercept getById calls to populate the caches; the pattern is to always fetch // the object after it was created, which means this method is (by pattern) first called right after object creation and contains all the goodies we care // about (record_id, account_record_id, object_id, tenant_record_id) // if (result != null && method.getName().equals("getById")) { populateCacheOnGetByIdInvocation((M) result); } return result; } }); }
private void errorDuringTransaction(final Throwable t, final Method method) throws Throwable { errorDuringTransaction(t, method, null); }
public static void populateCaches(final CacheControllerDispatcher cacheControllerDispatcher, final EntityModelDao model) { final CacheController<String, Long> cacheRecordId = cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID); cacheRecordId.putIfAbsent(getKey(model.getId().toString(), CacheType.RECORD_ID, model.getTableName()), model.getRecordId()); final CacheController<String, UUID> cacheObjectId = cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID); cacheObjectId.putIfAbsent(getKey(model.getRecordId().toString(), CacheType.OBJECT_ID, model.getTableName()), model.getId()); if (model.getTenantRecordId() != null) { final CacheController<String, Long> cacheTenantRecordId = cacheControllerDispatcher.getCacheController(CacheType.TENANT_RECORD_ID); cacheTenantRecordId.putIfAbsent(getKey(model.getId().toString(), CacheType.TENANT_RECORD_ID, model.getTableName()), model.getTenantRecordId()); } if (model.getAccountRecordId() != null) { final CacheController<String, Long> cacheAccountRecordId = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID); cacheAccountRecordId.putIfAbsent(getKey(model.getId().toString(), CacheType.ACCOUNT_RECORD_ID, model.getTableName()), model.getAccountRecordId()); } }
private Object invokeWithAuditAndHistory(final Audited auditedAnnotation, final Method method, final Object[] args) throws Throwable { final InternalCallContext context = retrieveContextFromArguments(args); final List<String> entityIds = retrieveEntityIdsFromArguments(method, args); for (final String entityId : entityIds) { deletedEntities.put(entityId, sqlDao.getById(entityId, context)); printSQLWarnings(); final Object obj = prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, getProfilingId("raw", method), new WithProfilingCallback<Object, Throwable>() { @Override public Object execute() throws Throwable { m = updateHistoryAndAudit(entityId, deletedEntities.get(entityId), changeType, context);
private Object invokeSafely(final Object proxy, final Method method, final Object[] args) throws Throwable { final Audited auditedAnnotation = method.getAnnotation(Audited.class); final Cachable cachableAnnotation = method.getAnnotation(Cachable.class); // This can't be AUDIT'ed and CACHABLE'd at the same time as we only cache 'get' if (auditedAnnotation != null) { return invokeWithAuditAndHistory(auditedAnnotation, method, args); } else if (cachableAnnotation != null && cacheControllerDispatcher != null) { return invokeWithCaching(cachableAnnotation, method, args); } else { return invokeRaw(method, args); } }
private void insertAudits(final TableName tableName, final M entityModelDao, final Long entityRecordId, final Long historyRecordId, final ChangeType changeType, final InternalCallContext contextMaybeWithoutAccountRecordId) { final TableName destinationTableName = MoreObjects.firstNonNull(tableName.getHistoryTableName(), tableName); final EntityAudit audit = new EntityAudit(destinationTableName, historyRecordId, changeType, contextMaybeWithoutAccountRecordId.getCreatedDate()); final InternalCallContext context; // Populate the account record id when creating the account record if (TableName.ACCOUNT.equals(tableName) && ChangeType.INSERT.equals(changeType)) { // AccountModelDao in practice final TimeZoneAwareEntity accountModelDao = (TimeZoneAwareEntity) entityModelDao; context = internalCallContextFactory.createInternalCallContext(accountModelDao, entityRecordId, contextMaybeWithoutAccountRecordId); } else { context = contextMaybeWithoutAccountRecordId; } sqlDao.insertAuditFromTransaction(audit, context); printSQLWarnings(); // We need to invalidate the caches. There is a small window of doom here where caches will be stale. // TODO Knowledge on how the key is constructed is also in AuditSqlDao if (tableName.getHistoryTableName() != null) { final CacheController<String, List> cacheController = cacheControllerDispatcher.getCacheController(CacheType.AUDIT_LOG_VIA_HISTORY); if (cacheController != null) { final String key = buildCacheKey(ImmutableMap.<Integer, Object>of(0, tableName.getHistoryTableName(), 1, tableName.getHistoryTableName(), 2, entityRecordId)); cacheController.remove(key); } } else { final CacheController<String, List> cacheController = cacheControllerDispatcher.getCacheController(CacheType.AUDIT_LOG); if (cacheController != null) { final String key = buildCacheKey(ImmutableMap.<Integer, Object>of(0, tableName, 1, entityRecordId)); cacheController.remove(key); } } }
private List<String> retrieveEntityIdsFromArguments(final Method method, final Object[] args) { final Annotation[][] parameterAnnotations = getAnnotations(method); int i = -1; for (final Object arg : args) { i++; // Assume the first argument of type Entity is our type of Entity (type U here) // This is true for e.g. create calls if (arg instanceof Entity) { return ImmutableList.<String>of(((Entity) arg).getId().toString()); } // For Batch calls, the first argument will be of type List<Entity> if (arg instanceof Iterable) { final Builder<String> entityIds = extractEntityIdsFromBatchArgument((Iterable) arg); if (entityIds != null) { return entityIds.build(); } } for (final Annotation annotation : parameterAnnotations[i]) { if (arg instanceof String && Bind.class.equals(annotation.annotationType()) && ("id").equals(((Bind) annotation).value())) { return ImmutableList.<String>of((String) arg); } else if (arg instanceof Collection && BindIn.class.equals(annotation.annotationType()) && ("ids").equals(((BindIn) annotation).value())) { return ImmutableList.<String>copyOf((Collection) arg); } } } return ImmutableList.<String>of(); }
private M updateHistoryAndAudit(final String entityId, @Nullable final M deletedEntity, final ChangeType changeType, final InternalCallContext context) throws Throwable { final Object reHydratedEntity = prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, getProfilingId("history/audit", null), new WithProfilingCallback<Object, Throwable>() { @Override public M execute() throws Throwable { final M reHydratedEntity; if (changeType == ChangeType.DELETE) { reHydratedEntity = deletedEntity; } else { // See note above regarding "markAsInactive" operations reHydratedEntity = MoreObjects.firstNonNull(sqlDao.getById(entityId, context), deletedEntity); printSQLWarnings(); } Preconditions.checkNotNull(reHydratedEntity, "reHydratedEntity cannot be null"); final Long entityRecordId = reHydratedEntity.getRecordId(); final TableName tableName = reHydratedEntity.getTableName(); // Note: audit entries point to the history record id final Long historyRecordId; if (tableName.getHistoryTableName() != null) { historyRecordId = insertHistory(entityRecordId, reHydratedEntity, changeType, context); } else { historyRecordId = entityRecordId; } // Make sure to re-hydrate the object (especially needed for create calls) insertAudits(tableName, reHydratedEntity, entityRecordId, historyRecordId, changeType, context); return reHydratedEntity; } }); //noinspection unchecked return (M) reHydratedEntity; }
private void errorDuringTransaction(final Throwable t, final Method method) throws Throwable { errorDuringTransaction(t, method, null); }
public static void populateCaches(final CacheControllerDispatcher cacheControllerDispatcher, final EntityModelDao model) { final CacheController<String, Long> cacheRecordId = cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID); cacheRecordId.putIfAbsent(getKey(model.getId().toString(), CacheType.RECORD_ID, model.getTableName()), model.getRecordId()); final CacheController<String, UUID> cacheObjectId = cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID); cacheObjectId.putIfAbsent(getKey(model.getRecordId().toString(), CacheType.OBJECT_ID, model.getTableName()), model.getId()); if (model.getTenantRecordId() != null) { final CacheController<String, Long> cacheTenantRecordId = cacheControllerDispatcher.getCacheController(CacheType.TENANT_RECORD_ID); cacheTenantRecordId.putIfAbsent(getKey(model.getId().toString(), CacheType.TENANT_RECORD_ID, model.getTableName()), model.getTenantRecordId()); } if (model.getAccountRecordId() != null) { final CacheController<String, Long> cacheAccountRecordId = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID); cacheAccountRecordId.putIfAbsent(getKey(model.getId().toString(), CacheType.ACCOUNT_RECORD_ID, model.getTableName()), model.getAccountRecordId()); } }
private Object invokeWithCaching(final Cachable cachableAnnotation, final Method method, final Object[] args) throws Throwable { final ObjectType objectType = getObjectType(); final CacheType cacheType = cachableAnnotation.value(); final CacheController<Object, Object> cache = cacheControllerDispatcher.getCacheController(cacheType); final Annotation[][] annotations = getAnnotations(method); for (int i = 0; i < annotations.length; i++) { for (int j = 0; j < annotations[i].length; j++) { final String cacheKey = buildCacheKey(keyPieces); return cache.get(cacheKey, cacheLoaderArgument); } else { return invokeRaw(method, args);
@Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { try { return prof.executeWithProfiling(ProfilingFeatureType.DAO, getProfilingId(null, method), new WithProfilingCallback<Object, Throwable>() { @Override public Object execute() throws Throwable { if (statement != null) { errorDuringTransaction(t.getCause().getCause(), method, statement.toString() + "\n" + binding.toString()); } else { errorDuringTransaction(t.getCause().getCause(), method, binding.toString()); errorDuringTransaction(t.getCause().getCause(), method); } else if (t.getCause() != null) { errorDuringTransaction(t.getCause(), method); } else { errorDuringTransaction(t, method);
private Object invokeRaw(final Method method, final Object[] args) throws Throwable { return prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, getProfilingId("raw", method), new WithProfilingCallback<Object, Throwable>() { @Override public Object execute() throws Throwable { // Real jdbc call final Object result = executeJDBCCall(method, args); // This is *almost* the default invocation except that we want to intercept getById calls to populate the caches; the pattern is to always fetch // the object after it was created, which means this method is (by pattern) first called right after object creation and contains all the goodies we care // about (record_id, account_record_id, object_id, tenant_record_id) // if (result != null && method.getName().equals("getById")) { populateCacheOnGetByIdInvocation((M) result); } return result; } }); }