package io.cattle.platform.iaas.api.auth.dynamic; import io.cattle.platform.core.dao.DynamicSchemaDao; import io.cattle.platform.core.model.DynamicSchema; import io.github.ibuildthecloud.gdapi.factory.SchemaFactory; import io.github.ibuildthecloud.gdapi.factory.impl.AbstractSchemaFactory; import io.github.ibuildthecloud.gdapi.json.JsonMapper; import io.github.ibuildthecloud.gdapi.model.Field; import io.github.ibuildthecloud.gdapi.model.Schema; import io.github.ibuildthecloud.gdapi.model.impl.FieldImpl; import io.github.ibuildthecloud.gdapi.model.impl.SchemaImpl; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.cache.Cache; public class DynamicSchemaFactory extends AbstractSchemaFactory implements SchemaFactory { private static final Logger log = LoggerFactory.getLogger(DynamicSchemaFactory.class); private static final Schema NULL = new SchemaImpl(); Cache<String, Schema> schemaCache; long accountId; SchemaFactory factory; DynamicSchemaDao dynamicSchemaDao; JsonMapper jsonMapper; String role; public DynamicSchemaFactory(long accountId, SchemaFactory factory, DynamicSchemaDao dynamicSchemaDao, JsonMapper jsonMapper, String role, Cache<String, Schema> schemaCache) { this.accountId = accountId; this.factory = factory; this.dynamicSchemaDao = dynamicSchemaDao; this.jsonMapper = jsonMapper; this.role = role; this.schemaCache = schemaCache; } @Override public String getId() { return factory.getId(); } @Override public List<Schema> listSchemas() { Map<String, Schema> schemas = new TreeMap<String, Schema>(); for (Schema s : factory.listSchemas()) { schemas.put(s.getId(), s); } List<? extends DynamicSchema> dynamic = dynamicSchemaDao.getSchemas(accountId, role); for (DynamicSchema dynamicSchema : dynamic) { Schema schema = safeConvert(dynamicSchema); if (schema != null) { schemas.put(schema.getId(), schema); } } List<Schema> result = new ArrayList<>(schemas.size()); result.addAll(schemas.values()); return result; } @Override public Schema getSchema(Class<?> clz) { return factory.getSchema(clz); } @Override public Schema getSchema(String type) { if (type == null) { return null; } if (type.contains("host") || type.contains("machine") || type.toLowerCase().contains("config")) { DynamicSchema dynamicSchema = dynamicSchemaDao.getSchema(type, accountId, role); if (dynamicSchema != null) { return safeConvert(dynamicSchema); } } return factory.getSchema(type); } protected Schema safeConvert(final DynamicSchema dynamicSchema) { try { Schema schema = schemaCache.get(factory.getId() + "/" + dynamicSchema.getId(), new Callable<Schema>() { @Override public Schema call() throws Exception { Schema schema = convert(dynamicSchema); return schema == null ? NULL : schema; } }); return schema == NULL ? null : schema; } catch (ExecutionException e) { log.error("Failed to construct dynamic schema for [{}]", dynamicSchema.getId(), e); throw new IllegalStateException(e); } } private Schema convert(DynamicSchema dynamicSchema) throws IOException { if (dynamicSchema == null || dynamicSchema.getDefinition() == null) { return null; } if (dynamicSchema.getParent() == null) { SchemaImpl newSchema = jsonMapper.readValue(dynamicSchema.getDefinition().getBytes("UTF-8"), SchemaImpl.class); newSchema.setId(dynamicSchema.getName()); return newSchema; } Schema parentSchema = factory.getSchema(dynamicSchema.getParent()); if (parentSchema == null || !(parentSchema instanceof SchemaImpl)) { return null; } SchemaImpl newSchema = jsonMapper.readValue(dynamicSchema.getDefinition().getBytes("UTF-8"), SchemaImpl.class); newSchema.setName(dynamicSchema.getName()); SchemaImpl mergedSchema = new SchemaImpl((SchemaImpl)parentSchema); mergedSchema.setPluralName(newSchema.getPluralName()); mergedSchema.setId(dynamicSchema.getName()); mergedSchema.setParent(dynamicSchema.getParent()); mergedSchema.setCollectionMethods(newSchema.getCollectionMethods()); mergedSchema.setResourceMethods(newSchema.getResourceMethods()); if (mergedSchema.getParent().equals(mergedSchema.getId())) { mergedSchema.setParent(parentSchema.getParent()); } Map<String, Field> existingFields = mergedSchema.getResourceFields(); for (Map.Entry<String, Field> entry : newSchema.getResourceFields().entrySet()) { Field oldField = existingFields.put(entry.getKey(), entry.getValue()); if (oldField instanceof FieldImpl) { ((FieldImpl)entry.getValue()).setReadMethod(((FieldImpl) oldField).getReadMethod()); ((FieldImpl)entry.getValue()).setAttributes(oldField.getAttributes()); } } return mergedSchema; } @Override public Class<?> getSchemaClass(String type) { Class<?> clz = factory.getSchemaClass(type); if (clz != null) { return clz; } String baseType = getBaseType(type); return factory.getSchemaClass(baseType); } @Override public Schema registerSchema(Object obj) { throw new UnsupportedOperationException(); } @Override public Schema parseSchema(String name) { throw new UnsupportedOperationException(); } }