package io.cattle.platform.schema.processor;
import io.cattle.platform.json.JsonMapper;
import io.cattle.platform.util.resource.ResourceLoader;
import io.cattle.platform.util.type.Priority;
import io.github.ibuildthecloud.gdapi.factory.SchemaFactory;
import io.github.ibuildthecloud.gdapi.factory.impl.AbstractSchemaPostProcessor;
import io.github.ibuildthecloud.gdapi.factory.impl.SchemaPostProcessor;
import io.github.ibuildthecloud.gdapi.model.impl.SchemaImpl;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JsonFileOverlayPostProcessor extends AbstractSchemaPostProcessor implements SchemaPostProcessor, Priority {
private static final Logger log = LoggerFactory.getLogger(JsonFileOverlayPostProcessor.class);
public static final String REMOVE = "-";
JsonMapper jsonMapper;
io.github.ibuildthecloud.gdapi.json.JsonMapper schemaMashaller;
boolean explicitByDefault = false;
boolean whiteList = false;
Set<String> ignoreTypes = new HashSet<String>();
Map<String, List<URL>> resources = new HashMap<String, List<URL>>();
String path;
ResourceLoader resourceLoader;
public JsonFileOverlayPostProcessor() {
ignoreTypes.add("schema");
ignoreTypes.add("error");
}
@Override
public SchemaImpl postProcessRegister(SchemaImpl schema, SchemaFactory factory) {
if (ignoreTypes.contains(schema.getId())) {
return schema;
}
try {
List<URL> resources = lookUpResource(schema.getId());
if (whiteList && resources.size() == 0) {
return null;
}
} catch (IOException e) {
throw new IllegalStateException("Failed to lookup schema for [" + schema.getId() + "] at [" + path + "]");
}
return super.postProcessRegister(schema, factory);
}
protected List<URL> lookUpResource(String id) throws IOException {
List<URL> result = new ArrayList<URL>();
String base = String.format("%s/%s.json", path, id);
String override = String.format("%s/%s.json.d/**/*.json", path, id);
URL url = getClass().getClassLoader().getResource(base);
if (url != null) {
log.info("Loading JSON schema overlay for type [{}] from [{}]", id, url);
result.add(url);
}
for (URL overrideUrl : resourceLoader.getResources(override)) {
log.info("Loading JSON schema overlay for type [{}] from [{}]", id, overrideUrl);
result.add(overrideUrl);
}
resources.put(id, result);
return result;
}
@Override
public SchemaImpl postProcess(SchemaImpl schema, SchemaFactory factory) {
if (ignoreTypes.contains(schema.getId())) {
return schema;
}
List<URL> resources = this.resources.get(schema.getId());
if (resources == null || resources.size() == 0) {
return schema;
}
for (URL resource : resources) {
InputStream is = null;
try {
is = resource.openStream();
if (is == null) {
continue;
}
byte[] bytes = IOUtils.toByteArray(is);
Map<String, Object> mapData = jsonMapper.readValue(bytes);
SchemaOverlayImpl data = null;
if (explicitByDefault) {
data = schemaMashaller.readValue(bytes, ExplicitByDefaultSchemaOverlayImpl.class);
} else {
data = schemaMashaller.readValue(bytes, SchemaOverlayImpl.class);
}
processSchema(schema, data, mapData);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | IOException e) {
throw new IllegalStateException("Error processing " + resource, e);
} finally {
IOUtils.closeQuietly(is);
}
}
return schema;
}
protected void processSchema(SchemaImpl schema, SchemaOverlayImpl data, Map<String, Object> mapData) throws IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
for (PropertyDescriptor prop : PropertyUtils.getPropertyDescriptors(schema)) {
String name = prop.getName();
Method writeMethod = prop.getWriteMethod();
if (writeMethod == null || prop.getReadMethod() == null) {
continue;
}
Class<?> type = prop.getPropertyType();
if (Map.class.isAssignableFrom(type)) {
processMapData(schema, data, mapData, name);
} else {
Object newValue = PropertyUtils.getProperty(data, name);
if (mapData.containsKey(name)) {
PropertyUtils.setProperty(schema, name, newValue);
}
}
}
}
@SuppressWarnings("unchecked")
protected void processMapData(SchemaImpl schema, SchemaOverlayImpl data, Map<String, Object> mapData, String name) throws IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
Map<String, Object> oldValues = (Map<String, Object>) PropertyUtils.getProperty(schema, name);
Map<String, Object> newValues = (Map<String, Object>) PropertyUtils.getProperty(data, name);
Object value = null;
try {
value = PropertyUtils.getProperty(data, name + "Explicit");
} catch (NoSuchMethodException e) {
return;
}
if (Boolean.TRUE.equals(value)) {
for (String key : new HashSet<String>(oldValues.keySet())) {
if (newValues == null || !newValues.containsKey(key)) {
oldValues.remove(key);
}
}
}
if (newValues == null || newValues.size() == 0) {
return;
}
for (String key : newValues.keySet()) {
if (key.startsWith(REMOVE)) {
oldValues.remove(StringUtils.removeStart(key, REMOVE));
continue;
}
Object oldValue = oldValues.get(key);
Object newValue = newValues.get(key);
if (newValue == null) {
continue;
}
Map<String, Object> mapProperty = (Map<String, Object>) mapData.get(name);
if (oldValue == null) {
BeanUtils.copyProperties(newValue, mapProperty.get(key));
oldValues.put(key, newValue);
continue;
}
BeanUtils.copyProperties(oldValue, mapProperty.get(key));
}
}
@Override
public int getPriority() {
return Priority.DEFAULT;
}
public JsonMapper getJsonMapper() {
return jsonMapper;
}
@Inject
public void setJsonMapper(JsonMapper jsonMapper) {
this.jsonMapper = jsonMapper;
}
public boolean isExplicitByDefault() {
return explicitByDefault;
}
public void setExplicitByDefault(boolean explicitByDefault) {
this.explicitByDefault = explicitByDefault;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public io.github.ibuildthecloud.gdapi.json.JsonMapper getSchemaMashaller() {
return schemaMashaller;
}
@Inject
public void setSchemaMashaller(io.github.ibuildthecloud.gdapi.json.JsonMapper schemaMashaller) {
this.schemaMashaller = schemaMashaller;
}
public boolean isWhiteList() {
return whiteList;
}
public void setWhiteList(boolean whiteList) {
this.whiteList = whiteList;
}
public Set<String> getIgnoreTypes() {
return ignoreTypes;
}
public void setIgnoreTypes(Set<String> ignoreTypes) {
this.ignoreTypes = ignoreTypes;
}
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
@Inject
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}