package org.openslx.util.vm;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.log4j.Logger;
import org.apache.log4j.varia.StringMatchFilter;
import org.junit.runners.ParentRunner;
import org.openslx.util.vm.VmwareConfig.ConfigEntry;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.openslx.util.vm.VmMetaData.DriveBusType;
import org.openslx.util.vm.VmMetaData.HardDisk;
public class VboxConfig
{
private static final Logger LOGGER = Logger.getLogger( VboxConfig.class );
XPath xPath = XPathFactory.newInstance().newXPath();
private Document doc = null;
private String displayNameExpression = "/VirtualBox/Machine/@name";
private String displayName;
private String osTypeExpression = "/VirtualBox/Machine/@OSType";
private String osName = new String();;
private String hddsExpression = "/VirtualBox/Machine/MediaRegistry/HardDisks/*";
private ArrayList<HardDisk> hddsArray;
// a black list of sorts of tags that need to be removed from the .vbox file
private static String[] blackList = { "ExtraData", "Adapter", "GuestProperties", "LPT" };
/*
* constructor with input xml file
* used to set the doc variable of this class when creating vm
* @args: file as File - the input xml File
* @return: no return value
*/
public VboxConfig( File file ) throws IOException, UnsupportedVirtualizerFormatException
{
try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
doc = dBuilder.parse( file );
// TODO - does this test suffice??
if ( !doc.getDocumentElement().getNodeName().equals( "VirtualBox" ) ) {
throw new UnsupportedVirtualizerFormatException( "not VirtualBox image format" );
}
} catch ( ParserConfigurationException | SAXException | IOException e ) {
LOGGER.debug( "Could not parse .Vbox", e );
throw new UnsupportedVirtualizerFormatException( "Konnte VBoxConfig nicht erstellen!" );
}
displayName = new String();
hddsArray = new ArrayList<HardDisk>();
}
/*
* constructor with input string from server
* used to set the doc variable of this class when rebuilding the doc
* @args: filtered as String - sent from server
* @return: no return value
*/
public VboxConfig( String filtered, int length ) throws IOException
{
// TODO test this spoon
try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
//dbFactory.setValidating( true );
//dbFactory.setIgnoringElementContentWhitespace( true );
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
InputSource is = new InputSource( new StringReader( filtered ) );
doc = dBuilder.parse( is );
} catch ( ParserConfigurationException | SAXException e ) {
LOGGER.debug( "Could not recreate the dom", e );
}
}
// returns definition document
public Document getConfigDoc()
{
return doc;
}
// initialization function
public void init()
{
try {
setMachineName();
setOsType();
setHdds();
removeBlackListedTags();
addPlaceHolders();
//removeEmptyText();
} catch ( XPathExpressionException e ) {
LOGGER.debug( "Konnte VBoxConfig nicht initializieren", e );
return;
}
}
public void removeEmptyText()
{
try {
XPathFactory xpathFactory = XPathFactory.newInstance();
XPathExpression xpathExp = xpathFactory.newXPath().compile( "//text()[normalize-space(.) = '']" );
NodeList emptyTextNodes = (NodeList)xpathExp.evaluate( doc, XPathConstants.NODESET );
// Remove each empty text node from document.
for ( int i = 0; i < emptyTextNodes.getLength(); ++i ) {
Node emptyTextNode = emptyTextNodes.item( i );
emptyTextNode.getParentNode().removeChild( emptyTextNode );
}
} catch ( XPathExpressionException e ) {
LOGGER.debug( "Could not remove white spaces...", e );
}
}
public void setMachineName() throws XPathExpressionException
{
String name = xPath.compile( displayNameExpression ).evaluate( this.doc );
if ( !name.isEmpty() ) {
displayName = name;
}
}
public void setOsType() throws XPathExpressionException
{
String os = xPath.compile( osTypeExpression ).evaluate( this.doc );
if ( !os.isEmpty() ) {
osName = os;
}
}
public void setHdds() throws XPathExpressionException
{
XPathExpression hddsExpr = xPath.compile( hddsExpression );
Object result = hddsExpr.evaluate( this.doc, XPathConstants.NODESET );
// take all the hdd nodes
NodeList nodes = (NodeList)result;
// TODO find all HardDisk under HardDisks, give them to the hdds
// foreach hdd in the hddnodes do:
for ( int i = 0; i < nodes.getLength(); i++ ) {
// have the node
// take the uuid
// look under <AttachedDevice if found and do stuff with it
Element hddElement = (Element)nodes.item( i );
// read the filePath
String fileName = hddElement.getAttribute( "location" );
// take the uuid
String uuid = hddElement.getAttribute( "uuid" );
// do the loop the loop
// search in the xml thingy and give back the parent of the parent of the node that is called Image and has the given uuid
String pathToParent = givePathToStorageController( uuid );
XPathExpression attachedDevicesExpr = xPath.compile( pathToParent );
Object devicesResult = attachedDevicesExpr.evaluate( this.doc, XPathConstants.NODESET );
NodeList devicesNodes = (NodeList)devicesResult;
// TODO -- ehm...should only have 1 element...what do when there are more?
if ( devicesNodes.getLength() > 1 ) {
LOGGER.error( "There can not be more HDDs with the same UUID!" );
return;
}
Element deviceElement = (Element)devicesNodes.item( 0 );
String controllerDevice = deviceElement.getAttribute( "type" );
String bus = deviceElement.getAttribute( "name" );
DriveBusType busType = null;
if ( bus.equals( "IDE" ) ) {
busType = DriveBusType.IDE;
} else if ( bus.equals( "SCSI" ) ) {
busType = DriveBusType.SCSI;
} else if ( bus.equals( "SATA" ) ) {
busType = DriveBusType.SATA;
}
// add them together
hddsArray.add( new HardDisk( controllerDevice, busType, fileName ) );
}
}
public void addPlaceHolders()
{
// placeholder for the location of the virtual hdd
changeAttribute( "HardDisk", "location", "#some_fancy_HDD_place_holder" );
// placeholder for the memory
changeAttribute( "Memory", "RAMSize", "#some_fancy_MEMORY_place_holder" );
// placeholder for the CPU
changeAttribute( "CPU", "count", "#some_fancy_CPU_place_holder" );
}
public void changeAttribute( String targetTag, String attribute, String newValue )
{
changeAttribute( targetTag, attribute, newValue, null, null );
}
public NodeList findANode( String targetTag )
{
String path = ".//" + targetTag;
XPathExpression expr;
NodeList nodes = null;
try {
expr = xPath.compile( path );
Object nodesObject = expr.evaluate( this.doc, XPathConstants.NODESET );
nodes = (NodeList)nodesObject;
} catch ( XPathExpressionException e ) {
LOGGER.error( "Path konnte nicht gebaut werden", e );
}
return nodes;
}
public void changeAttribute( String targetTag, String targetAttr, String newValue, String refAttr, String refVal )
{
NodeList nodes = findANode( targetTag );
for ( int i = 0; i < nodes.getLength(); i++ ) {
Element element = (Element)nodes.item( i );
if ( refAttr != null && refVal != null ) {
if ( element.getAttribute( refAttr ).equals( refVal ) ) {
element.setAttribute( targetAttr, newValue );
break;
}
} else {
if ( nodes.getLength() > 1 ) {
LOGGER.error( "Aktion würde an mehreren Knotten die Value ändern; unterbrochen!" );
return;
}
element.setAttribute( targetAttr, newValue );
}
}
}
// cleanup part here
private void removeBlackListedTags() throws XPathExpressionException
{
// iterate over the blackList
for ( String blackedTag : blackList ) {
String blackedExpression = ".//" + blackedTag;
XPathExpression blackedExpr = xPath.compile( blackedExpression );
NodeList blackedNodes = (NodeList)blackedExpr.evaluate( this.doc, XPathConstants.NODESET );
//LOGGER.debug( blackedNodes.getLength() );
for ( int i = 0; i < blackedNodes.getLength(); i++ ) {
// get the child node
Element child = (Element)blackedNodes.item( i );
// get the parent node
Node parent = (Node)child.getParentNode();
// remove child
if ( child.getTagName().equals( "Adapter" ) && child.getAttribute( "enabled" ).equals( "true" ) ) {
continue;
}
// the structure of the xml Document is achieved through children nodes that are made up of just /n and spaces
// when a child node is deleted we get an empty line there the old child node used to be
Node prevChild = child.getPreviousSibling();
parent.removeChild( child );
// HACK remove empty lines
// format children have type 3
if ( prevChild.getNodeType() == 3 ) {
// the value of these Nodes are characters
String tmp = prevChild.getNodeValue();
boolean shouldDelete = true;
for ( int foo = 0; foo < tmp.length(); foo++ ) {
if ( !Character.isWhitespace( tmp.charAt( foo ) ) ) {
shouldDelete = false;
break;
}
}
if ( shouldDelete )
parent.removeChild( prevChild );
}
}
}
}
//private void removeTag
private String givePathToStorageController( String uuid )
{
// StorageController is the parent of the parent of node with given uuid
return "//Image[contains(@uuid, \'" + uuid + "\')]/../..";
}
public String getDisplayName()
{
return displayName;
}
public String getOsName()
{
return osName;
}
public ArrayList<HardDisk> getHdds()
{
return hddsArray;
}
public String toString()
{
try {
StringWriter sw = new StringWriter();
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "no" );
transformer.setOutputProperty( OutputKeys.METHOD, "xml" );
transformer.setOutputProperty( OutputKeys.INDENT, "yes" );
transformer.setOutputProperty( OutputKeys.ENCODING, "UTF-8" );
transformer.transform( new DOMSource( doc ), new StreamResult( sw ) );
return sw.toString();
} catch ( Exception ex ) {
throw new RuntimeException( "Error converting to String", ex );
}
}
}