package io.cattle.platform.core.dao.impl;
import static io.cattle.platform.core.model.tables.DynamicSchemaRoleTable.*;
import static io.cattle.platform.core.model.tables.DynamicSchemaTable.*;
import io.cattle.platform.core.addon.DynamicSchemaWithRole;
import io.cattle.platform.core.cache.DBCacheManager;
import io.cattle.platform.core.constants.CommonStatesConstants;
import io.cattle.platform.core.dao.DynamicSchemaDao;
import io.cattle.platform.core.model.DynamicSchema;
import io.cattle.platform.core.model.tables.records.DynamicSchemaRecord;
import io.cattle.platform.core.model.tables.records.DynamicSchemaRoleRecord;
import io.cattle.platform.db.jooq.dao.impl.AbstractJooqDao;
import io.cattle.platform.util.type.CollectionUtils;
import io.github.ibuildthecloud.gdapi.util.TypeUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.jooq.InsertValuesStep2;
import org.jooq.Record;
import org.jooq.SelectConditionStep;
import org.jooq.exception.InvalidResultException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
@Named
public class DynamicSchemaDaoImpl extends AbstractJooqDao implements DynamicSchemaDao {
private static final Logger log = LoggerFactory.getLogger(DynamicSchemaDaoImpl.class);
private static final DynamicSchemaRecord NULL = new DynamicSchemaRecord();
@Inject
DBCacheManager dbCacheManager;
LoadingCache<Pair<Long, String>, List<? extends DynamicSchema>> schemasListCache;
LoadingCache<CacheKey, DynamicSchema> schemaCache;
@PostConstruct
public void init() {
schemasListCache = CacheBuilder.newBuilder()
.expireAfterWrite(15, TimeUnit.MINUTES)
.build(new CacheLoader<Pair<Long, String>, List<? extends DynamicSchema>>() {
@Override
public List<? extends DynamicSchema> load(Pair<Long, String> key) throws Exception {
return getSchemasFromDb(key.getLeft(), key.getRight());
}
});
schemaCache = CacheBuilder.newBuilder()
.expireAfterWrite(15, TimeUnit.MINUTES)
.build(new CacheLoader<CacheKey, DynamicSchema>() {
@Override
public DynamicSchema load(CacheKey key) throws Exception {
return getSchemaInternal(key.name, key.accountId, key.role);
}
});
dbCacheManager.register(schemasListCache);
dbCacheManager.register(schemaCache);
}
@Override
public List<? extends DynamicSchema> getSchemas(long accountId, String role) {
return schemasListCache.getUnchecked(Pair.of(accountId, role));
}
protected List<? extends DynamicSchema> getSchemasFromDb(long accountId, String role) {
List<Record> records = schemaQuery(accountId, role)
.orderBy(DYNAMIC_SCHEMA.CREATED.asc())
.fetch();
Map<String, List<Record>> nameSchemas = new HashMap<>();
for (Record record: records) {
if (nameSchemas.get(record.getValue(DYNAMIC_SCHEMA.NAME)) == null) {
nameSchemas.put(record.getValue(DYNAMIC_SCHEMA.NAME), new ArrayList<Record>());
}
nameSchemas.get(record.getValue(DYNAMIC_SCHEMA.NAME)).add(record);
}
List<DynamicSchema> recordsToReturn = new ArrayList<>();
for (List<Record> list: nameSchemas.values()) {
DynamicSchema schema = null;
try {
schema = pickRecordOnPriority(list, accountId, role);
} catch (InvalidResultException e){
log.error("Failed to get a schema record.", e);
}
if (schema != null && schema != NULL) {
recordsToReturn.add(schema);
}
}
return recordsToReturn;
}
private SelectConditionStep<Record> schemaQuery(long accountId, String role) {
return create()
.select()
.from(DYNAMIC_SCHEMA).leftOuterJoin(DYNAMIC_SCHEMA_ROLE)
.on(DYNAMIC_SCHEMA_ROLE.DYNAMIC_SCHEMA_ID.eq(DYNAMIC_SCHEMA.ID))
.where(DYNAMIC_SCHEMA.ACCOUNT_ID.eq(accountId)
.and(DYNAMIC_SCHEMA_ROLE.ROLE.eq(role))
.and(DYNAMIC_SCHEMA.STATE.ne(CommonStatesConstants.PURGED)))
.or(DYNAMIC_SCHEMA.ACCOUNT_ID.eq(accountId)
.and(DYNAMIC_SCHEMA_ROLE.ROLE.isNull())
.and(DYNAMIC_SCHEMA.STATE.ne(CommonStatesConstants.PURGED)))
.or(DYNAMIC_SCHEMA_ROLE.ROLE.eq(role)
.and(DYNAMIC_SCHEMA.ACCOUNT_ID.isNull())
.and(DYNAMIC_SCHEMA.STATE.ne(CommonStatesConstants.PURGED)));
}
@Override
public DynamicSchema getSchema(String name, long accountId, String role) {
if (name == null) {
return null;
}
DynamicSchema result = schemaCache.getUnchecked(new CacheKey(name, accountId, role));
return result == NULL ? null : result;
}
private DynamicSchema getSchemaInternal(String name, long accountId, String role) {
List<Record> records = schemaQuery(accountId, role)
.and(DYNAMIC_SCHEMA.NAME.eq(name))
.orderBy(DYNAMIC_SCHEMA.CREATED.asc())
.fetch();
if (records.size() == 0 && name != null && name.endsWith("s")) {
return getSchemaInternal(TypeUtils.guessSingularName(name), accountId, role);
}
if (records.size() == 1) {
return records.get(0).into(DynamicSchema.class);
} else if (records.size() == 0) {
return NULL;
} else {
return pickRecordOnPriority(records, accountId, role);
}
}
private DynamicSchema pickRecordOnPriority(List<Record> records, long accountId, String role) {
if (records.size() == 1) {
return records.get(0).into(DynamicSchema.class);
}
Record record = null;
int lastPriority = 0;
for (Record r: records) {
DynamicSchemaWithRole withRole = r.into(DynamicSchemaWithRole.class);
int priority = 0;
if (withRole.getAccountId() != null &&
withRole.getAccountId().equals(accountId) &&
StringUtils.equals(withRole.getRole(), role)) {
priority = 3;
} else if (withRole.getAccountId() != null &&
withRole.getAccountId().equals(accountId) &&
withRole.getRole() == null) {
priority = 2;
} else if (withRole.getAccountId() == null && role != null && withRole.getRole().equals(role)) {
priority = 1;
}
if (priority > lastPriority) {
lastPriority = priority;
record = r;
}
}
return record == null ? NULL : record.into(DynamicSchema.class);
}
@SuppressWarnings("unchecked")
@Override
public void createRoles(DynamicSchema dynamicSchema) {
List<String> roles = (List<String>) CollectionUtils.toList(CollectionUtils.getNestedValue(dynamicSchema.getData(),
"fields", "roles"));
InsertValuesStep2<DynamicSchemaRoleRecord, Long, String> insertStart = create()
.insertInto(DYNAMIC_SCHEMA_ROLE, DYNAMIC_SCHEMA_ROLE.DYNAMIC_SCHEMA_ID, DYNAMIC_SCHEMA_ROLE.ROLE);
for (String role: roles) {
insertStart = insertStart.values(dynamicSchema.getId(), role);
}
insertStart.execute();
}
@Override
public void removeRoles(DynamicSchema dynamicSchema) {
create().delete(DYNAMIC_SCHEMA_ROLE)
.where(DYNAMIC_SCHEMA_ROLE.DYNAMIC_SCHEMA_ID.eq(dynamicSchema.getId()))
.execute();
}
@Override
public boolean isUnique(String name, List<String> roles, Long accountId) {
List<DynamicSchemaWithRole> schemas;
if (accountId == null) {
schemas = create()
.select()
.from(DYNAMIC_SCHEMA).leftOuterJoin(DYNAMIC_SCHEMA_ROLE)
.on(DYNAMIC_SCHEMA_ROLE.DYNAMIC_SCHEMA_ID.eq(DYNAMIC_SCHEMA.ID))
.where(DYNAMIC_SCHEMA_ROLE.ROLE.in(roles))
.and(DYNAMIC_SCHEMA.NAME.eq(name))
.and(DYNAMIC_SCHEMA.STATE.in(CommonStatesConstants.CREATING, CommonStatesConstants.ACTIVE))
.and(DYNAMIC_SCHEMA.ACCOUNT_ID.isNull())
.fetch().into(DynamicSchemaWithRole.class);
} else if (roles == null || roles.isEmpty()) {
return null == create().select().from(DYNAMIC_SCHEMA)
.leftOuterJoin(DYNAMIC_SCHEMA_ROLE)
.on(DYNAMIC_SCHEMA_ROLE.DYNAMIC_SCHEMA_ID.eq(DYNAMIC_SCHEMA.ID))
.where(DYNAMIC_SCHEMA.ACCOUNT_ID.eq(accountId))
.and(DYNAMIC_SCHEMA.NAME.eq(name))
.and(DYNAMIC_SCHEMA_ROLE.ROLE.isNull())
.fetchAny();
} else {
schemas = create()
.select()
.from(DYNAMIC_SCHEMA).leftOuterJoin(DYNAMIC_SCHEMA_ROLE)
.on(DYNAMIC_SCHEMA_ROLE.DYNAMIC_SCHEMA_ID.eq(DYNAMIC_SCHEMA.ID))
.where(DYNAMIC_SCHEMA_ROLE.ROLE.in(roles))
.and(DYNAMIC_SCHEMA.NAME.eq(name))
.and(DYNAMIC_SCHEMA.ACCOUNT_ID.eq(accountId))
.and(DYNAMIC_SCHEMA.STATE.in(CommonStatesConstants.CREATING, CommonStatesConstants.ACTIVE))
.fetch().into(DynamicSchemaWithRole.class);
}
if (schemas.isEmpty()) {
return true;
}
for (DynamicSchemaWithRole schema: schemas) {
if (roles.contains(schema.getRole())) {
return false;
}
}
return true;
}
class CacheKey {
String name;
long accountId;
String role;
public CacheKey(String name, long accountId, String role) {
super();
this.name = name;
this.accountId = accountId;
this.role = role;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result + (int) (accountId ^ (accountId >>> 32));
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((role == null) ? 0 : role.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CacheKey other = (CacheKey) obj;
if (!getOuterType().equals(other.getOuterType()))
return false;
if (accountId != other.accountId)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (role == null) {
if (other.role != null)
return false;
} else if (!role.equals(other.role))
return false;
return true;
}
private DynamicSchemaDaoImpl getOuterType() {
return DynamicSchemaDaoImpl.this;
}
}
}