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 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.openslx.util.vm.VmMetaData.DriveBusType; import org.openslx.util.vm.VmMetaData.HardDisk; 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; /** * @author victorm * */ 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 = new String(); private String osTypeExpression = "/VirtualBox/Machine/@OSType"; private String osName = new String(); private String hddsExpression = "/VirtualBox/Machine/MediaRegistry/HardDisks/*"; private ArrayList hddsArray = new ArrayList(); // a black list of sorts of tags that need to be removed from the .vbox file private static String[] blackList = { "HID", "USB", "ExtraData", "Adapter", "GuestProperties", "LPT", "StorageController", "FloppyImages", "DVDImages", "AttachedDevice" }; /** * constructor with input xml file * used to set the doc variable of this class when creating vm * * @param file as File - the input xml File * @throws IOException * @throws UnsupportedVirtualizerFormatException */ 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.warn( "Could not parse .Vbox", e ); throw new UnsupportedVirtualizerFormatException( "Could not create VBoxConfig!" ); } } /** * constructor with input string from server * used to set the doc variable of this class when rebuilding the doc * * @param filtered as String - sent from server * @param length * @throws IOException */ public VboxConfig( byte[] filtered, int length ) throws IOException { // TODO test this spoon try { String filteredString = new String( filtered ); DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); //dbFactory.setValidating( true ); //dbFactory.setIgnoringElementContentWhitespace( true ); DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); InputSource is = new InputSource( new StringReader( filteredString ) ); doc = dBuilder.parse( is ); } catch ( ParserConfigurationException | SAXException e ) { LOGGER.warn( "Could not recreate the dom", e ); } } /** * getter for the xmldoc * * @return definition document */ public Document getConfigDoc() { return doc; } /** * initialization function * reads the doc, sets Machine name, os type, sets the hdds, adds placeholders, removes unwanted/ * unneeded nodes */ public void init() { if ( doc.getChildNodes().item( 0 ).getNodeType() == 8 ) { doc.removeChild( doc.getChildNodes().item( 0 ) ); } try { setMachineName(); setOsType(); if ( checkForPlaceholders() ) { return; } setHdds(); removeBlackListedTags(); addPlaceHolders(); } catch ( XPathExpressionException e ) { LOGGER.debug( "Could not initialize VBoxConfig", e ); return; } } /** * Function checks if the placeholders are present * * @return true if the placeholders are present, false otherwise */ private boolean checkForPlaceholders() { NodeList hdds = findANode( "HardDisk" ); for ( int i = 0; i < hdds.getLength(); i++ ) { Element hdd = (Element)hdds.item( i ); if ( hdd.getAttribute( "location" ).equals( "#OpenSLX_HDD_place_holder" ) ) { return true; } } return false; } /** * Function finds and saves the name of the machine * * @throws XPathExpressionException */ public void setMachineName() throws XPathExpressionException { String name = xPath.compile( displayNameExpression ).evaluate( this.doc ); if ( !name.isEmpty() ) { displayName = name; } } /** * Function finds and saves the name of the os * * @throws XPathExpressionException */ 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; // foreach hdd in the hddnodes do: for ( int i = 0; i < nodes.getLength(); i++ ) { // have the node // take the uuid // look under 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 MACAddress changeAttribute( "Adapter", "MACAddress", "#OpenSLX_Networkcard_MACAddress_place_holder" ); // placeholder for the machine uuid changeAttribute( "Machine", "uuid", "#OpenSLX_MUUID_place_holder" ); // placeholder for the location of the virtual hdd changeAttribute( "HardDisk", "location", "#OpenSLX_HDD_place_holder" ); // placeholder for the memory changeAttribute( "Memory", "RAMSize", "#OpenSLX_MEMORY_place_holder" ); // placeholder for the CPU changeAttribute( "CPU", "count", "#OpenSLX_CPU_place_holder" ); // add placeholder for the uuid of the virtual harddrive. // must be added on 2 positions...in the HardDisk tag and the attachedDevice tag // first find the uuid NodeList hdds = findANode( "HardDisk" ); for ( int i = 0; i < hdds.getLength(); i++ ) { Element hdd = (Element)findANode( "HardDisk" ).item( i ); String uuid = hdd.getAttribute( "uuid" ); hdd.setAttribute( "uuid", "#OpenSLX_HDDUUID_" + i + "_placeholder" ); NodeList images = findANode( "Image" ); Element image; for ( int j = 0; j < images.getLength(); j++ ) { if ( ( (Element)images.item( j ) ).getAttribute( "uuid" ).equals( uuid ) ) { image = (Element)images.item( j ); image.setAttribute( "uuid", "#OpenSLX_HDDUUID_" + i + "_placeholder" ); break; } } } } /** * Function used to find a node in the document * Function returnes a NodeList of Nodes...not just a Node...even when the wanted Node a single * Node is you get a NodeList with just one element * * @param targetTag as String * @return nodes as NodeList */ 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( "Could not build path", e ); } return nodes; } /** * Function uses the findANode function to narrow down the wanted node using 1 attribute and * its value * * @param targetTag * @param targetAttr0 * @param value0 * @param targetAttr1 * @param value1 * @return */ public Node findANode( String targetTag, String targetAttr0, String value0 ) { Node returnNode = null; NodeList foundNodes = findANode( targetTag ); for ( int i = 0; i < foundNodes.getLength(); i++ ) { Element node = (Element)foundNodes.item( i ); if ( node.hasAttribute( targetAttr0 ) && node.getAttribute( targetAttr0 ).equals( value0 ) ) { returnNode = foundNodes.item( i ); } } return returnNode; } /** * Function used to change the value of an attribute * Use this function if you know the targetNode is unique * * @param targetTag * @param attribute * @param newValue */ public void changeAttribute( String targetTag, String attribute, String newValue ) { changeAttribute( targetTag, attribute, newValue, null, null ); } /** * Function used to change the value of an attribute * Use this function if you are not sure if the targetNode is unique * Use refAttr and refVal to address the right node * * @param targetTag * @param targetAttr * @param newValue * @param refAttr * @param refVal */ 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( "Action would change values of more than one node; stopped!" ); return; } element.setAttribute( targetAttr, newValue ); } } } public void addAttributeToNode( Node targetNode, String attrName, String value ) { if ( targetNode == null ) { LOGGER.warn( "Node is null; stopped!" ); return; } ( (Element)targetNode ).setAttribute( attrName, value ); } public Node addNewNode( String nameOfParent, String nameOfnewNode, boolean oneLiner ) { return addNewNode( nameOfParent, nameOfnewNode, oneLiner, null, null ); } public Node addNewNode( String nameOfParent, String nameOfnewNode, boolean oneLiner, String refAttr, String refVal ) { Node parent = null; NodeList posibleParents = findANode( nameOfParent ); if ( posibleParents.getLength() > 1 ) { // if we have more then 1 parent we need to have an sanityArg s.t. we insert our new attribute in the right tag if ( refAttr == null ) { LOGGER.warn( "Action would change values of more than one node; stopped!" ); return null; } for ( int i = 1; i < posibleParents.getLength(); i++ ) { if ( ( (Element)posibleParents.item( i ) ).getAttribute( refAttr ).equals( refVal ) ) { parent = posibleParents.item( i ); break; } } } else { parent = posibleParents.item( 0 ); } if ( parent == null ) { LOGGER.warn( "Node: '" + nameOfParent + "' could not be found" ); return null; } Element newNode = doc.createElement( nameOfnewNode ); if ( !oneLiner ) { org.w3c.dom.Text a = doc.createTextNode( "\n" ); newNode.appendChild( a ); } parent.appendChild( newNode ); return newNode; } /** * USB will be enabled */ public void enableUsb() { addNewNode( "Hardware", "USB", false ); addNewNode( "USB", "Controllers", false ); // for USB 1. the next 3 lines are needed, for USB 2. all six are needed Node controller1 = addNewNode( "Controllers", "Controller", true ); addAttributeToNode( controller1, "name", "OHCI" ); addAttributeToNode( controller1, "type", "OHCI" ); /* // remove comment for USB 2. Node controller2 = addNewNode( "Controllers", "Controller", true ); addAttributeToNode( controller2, "name", "EHCI" ); addAttributeToNode( controller2, "type", "EHCI" ); */ } /** * disable usb by removing the USB tag */ public void disableUsb() { Node usb = findANode( "USB" ).item( 0 ); removeNode( usb ); } // function removes a given child and the format childNode private void removeNode( Node node ) { Node parent = node.getParentNode(); // this node here is usually a type3 Node used only for the formating of the vbox file Node previousSibling = node.getPreviousSibling(); parent.removeChild( node ); // HACK remove empty lines // format children (\n or \t) have type 3 if ( previousSibling.getNodeType() == 3 ) { // the value of these Nodes are characters String tmp = previousSibling.getNodeValue(); boolean shouldDelete = true; for ( int i = 0; i < tmp.length(); i++ ) { if ( !Character.isWhitespace( tmp.charAt( i ) ) ) { shouldDelete = false; break; } } if ( shouldDelete ) parent.removeChild( previousSibling ); } } // 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 ); for ( int i = 0; i < blackedNodes.getLength(); i++ ) { // get the child node Element child = (Element)blackedNodes.item( i ); // remove child if ( child.getTagName().equals( "Adapter" ) && child.getAttribute( "enabled" ).equals( "true" ) ) { // we need to remove the children of the active network adapter // these are the mode of network connection and disabled modes...they go together -> see wiki Node firstChild = child.getChildNodes().item( 0 ); Node secondChild = child.getChildNodes().item( 1 ); if ( firstChild != null && secondChild != null ) { if ( firstChild.getNodeName().equals( "#text" ) && !secondChild.getNodeName().equals( "#text" ) ) { removeNode( child.getChildNodes().item( 1 ) ); } } LOGGER.warn( "possible problem while removing formating node" ); continue; } if ( ( child.getTagName().equals( "StorageController" ) && !child.getAttribute( "name" ).equals( "Floppy" ) ) || ( child.getTagName().equals( "AttachedDevice" ) && child.getAttribute( "type" ).equals( "HardDisk" ) ) ) { 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 removeNode( child ); } } } 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 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 ); } } }