package org.arquillian.cube.docker.impl.util;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.arquillian.cube.docker.impl.client.config.CubeContainer;
import org.arquillian.cube.docker.impl.client.config.DockerCompositions;
import org.arquillian.cube.docker.impl.client.config.ExposedPort;
import org.arquillian.cube.docker.impl.client.config.Image;
import org.arquillian.cube.docker.impl.client.config.Link;
import org.arquillian.cube.docker.impl.client.config.Network;
import org.arquillian.cube.docker.impl.client.config.PortBinding;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.introspector.Property;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeId;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.ScalarNode;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.representer.Represent;
import org.yaml.snakeyaml.representer.Representer;
public final class ConfigUtil {
private static final String NETWORKS = "networks";
private static final String CONTAINERS = "containers";
private ConfigUtil() {
}
public static String[] trim(String[] data) {
List<String> result = new ArrayList<String>();
for (String val : data) {
String trimmed = val.trim();
if (!trimmed.isEmpty()) {
result.add(trimmed);
}
}
return result.toArray(new String[] {});
}
public static String[] reverse(String[] cubeIds) {
String[] result = new String[cubeIds.length];
int n = cubeIds.length - 1;
for (int i = 0; i < cubeIds.length; i++) {
result[n--] = cubeIds[i];
}
return result;
}
public static String dump(DockerCompositions containers) {
Yaml yaml = new Yaml(new CubeRepresenter());
return yaml.dump(containers);
}
public static DockerCompositions load(String content) {
return load(new ByteArrayInputStream(content.getBytes()));
}
@SuppressWarnings("unchecked")
public static DockerCompositions load(InputStream inputStream) {
// TODO: Figure out how to map root Map<String, Type> objects. Workaround by mapping it to Map structure then dumping it into individual objects
Yaml yaml = new Yaml(new CubeConstructor());
Map<String, Object> rawLoad = (Map<String, Object>) yaml.load(inputStream);
DockerCompositions containers = new DockerCompositions();
for (Map.Entry<String, Object> rawLoadEntry : rawLoad.entrySet()) {
if (NETWORKS.equals(rawLoadEntry.getKey())) {
Map<String, Object> rawNetworks = (Map<String, Object>) rawLoadEntry.getValue();
for (Map.Entry<String, Object> rawNetworkEntry : rawNetworks.entrySet()) {
Network network = yaml.loadAs(yaml.dump(rawNetworkEntry.getValue()), Network.class);
containers.add(rawNetworkEntry.getKey(), network);
}
} else if (CONTAINERS.equals(rawLoadEntry.getKey())) {
Map<String, Object> rawContainers = (Map<String, Object>) rawLoadEntry.getValue();
for (Map.Entry<String, Object> rawContainerEntry : rawContainers.entrySet()) {
CubeContainer cubeContainer =
yaml.loadAs(yaml.dump(rawContainerEntry.getValue()), CubeContainer.class);
containers.add(rawContainerEntry.getKey(), cubeContainer);
}
} else {
CubeContainer container = yaml.loadAs(yaml.dump(rawLoadEntry.getValue()), CubeContainer.class);
containers.add(rawLoadEntry.getKey(), container);
}
}
return applyExtendsRules(containers);
}
private static DockerCompositions applyExtendsRules(DockerCompositions dockerCompositions) {
for (Map.Entry<String, CubeContainer> containerEntry : dockerCompositions.getContainers().entrySet()) {
CubeContainer container = containerEntry.getValue();
if (container.getExtends() != null) {
String extendsContainer = container.getExtends();
if (dockerCompositions.get(extendsContainer) == null) {
throw new IllegalArgumentException(
containerEntry.getKey() + " extends a non existing container definition " + extendsContainer);
}
CubeContainer extendedContainer = dockerCompositions.get(extendsContainer);
container.merge(extendedContainer);
}
}
return dockerCompositions;
}
private static class CubeRepresenter extends Representer {
public CubeRepresenter() {
this.representers.put(PortBinding.class, new ToStringRepresent());
this.representers.put(ExposedPort.class, new ToStringRepresent());
this.representers.put(Image.class, new ToStringRepresent());
this.representers.put(Link.class, new ToStringRepresent());
addClassTag(DockerCompositions.class, Tag.MAP);
}
@Override
protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue,
Tag customTag) {
if (propertyValue == null) {
return null;
}
return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag);
}
public class ToStringRepresent implements Represent {
@Override
public Node representData(Object data) {
return representScalar(Tag.STR, data.toString());
}
}
}
public static class CubeConstructor extends Constructor {
public CubeConstructor() {
this.yamlClassConstructors.put(NodeId.scalar, new CubeMapping());
}
private class CubeMapping extends Constructor.ConstructScalar {
@Override
public Object construct(Node node) {
if (node.getType() == PortBinding.class) {
String value = constructScalar((ScalarNode) node).toString();
return PortBinding.valueOf(value);
} else if (node.getType() == ExposedPort.class) {
String value = constructScalar((ScalarNode) node).toString();
return ExposedPort.valueOf(value);
} else if (node.getType() == Image.class) {
String value = constructScalar((ScalarNode) node).toString();
return Image.valueOf(value);
} else if (node.getType() == Link.class) {
String value = constructScalar((ScalarNode) node).toString();
return Link.valueOf(value);
}
return super.construct(node);
}
}
}
}