changeset 1084:bf4f8d4e38a9

Merge
author Omair Majid <omajid@redhat.com>
date Mon, 13 May 2013 12:39:57 -0400
parents 066ee2d7317c (current diff) 0403e2a4e430 (diff)
children 6c9d61f870b3
files
diffstat 17 files changed, 409 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/host-cpu/distribution/plugin.xml	Tue May 07 18:27:02 2013 -0400
+++ b/host-cpu/distribution/plugin.xml	Mon May 13 12:39:57 2013 -0400
@@ -38,7 +38,7 @@
 -->
 <plugin xmlns="http://icedtea.classpath.org/thermostat/plugins/v1.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0">
+  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0 plugin.xsd">
   <extensions>
     <extension>
       <name>gui</name>
--- a/host-memory/distribution/plugin.xml	Tue May 07 18:27:02 2013 -0400
+++ b/host-memory/distribution/plugin.xml	Mon May 13 12:39:57 2013 -0400
@@ -38,7 +38,7 @@
 -->
 <plugin xmlns="http://icedtea.classpath.org/thermostat/plugins/v1.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0">
+  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0 plugin.xsd">
   <extensions>
     <extension>
       <name>gui</name>
--- a/host-overview/distribution/plugin.xml	Tue May 07 18:27:02 2013 -0400
+++ b/host-overview/distribution/plugin.xml	Mon May 13 12:39:57 2013 -0400
@@ -38,7 +38,7 @@
 -->
 <plugin xmlns="http://icedtea.classpath.org/thermostat/plugins/v1.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0">
+  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0 plugin.xsd">
   <extensions>
     <extension>
       <name>gui</name>
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginCommandInfoSource.java	Tue May 07 18:27:02 2013 -0400
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginCommandInfoSource.java	Mon May 13 12:39:57 2013 -0400
@@ -92,13 +92,18 @@
                 File configurationFile = new File(pluginDir, PLUGIN_CONFIG_FILE);
                 PluginConfiguration pluginConfig = parser.parse(configurationFile);
                 loadNewAndExtendedCommands(internalJarRoot, pluginDir, pluginConfig);
-            } catch (PluginConfigurationParseException | FileNotFoundException exception) {
+            } catch (PluginConfigurationParseException exception) {
                 logger.log(Level.WARNING, "unable to parse plugin configuration", exception);
+            } catch (PluginConfigurationValidatorException exception) {
+                logger.log(Level.WARNING, "unable to validate " + exception.getFilePath() + " file\n");
+            } catch (FileNotFoundException exception) {
+                logger.log(Level.WARNING, "file not found", exception);
             }
         }
-
         combineCommands();
     }
+    
+   
 
     private void loadNewAndExtendedCommands(File coreJarRoot, File pluginDir,
             PluginConfiguration pluginConfig) {
@@ -203,5 +208,6 @@
         }
         return result;
     }
+    
 
 }
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginConfigurationParser.java	Tue May 07 18:27:02 2013 -0400
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginConfigurationParser.java	Mon May 13 12:39:57 2013 -0400
@@ -36,11 +36,15 @@
 
 package com.redhat.thermostat.launcher.internal;
 
+
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -50,6 +54,11 @@
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
+import javax.xml.XMLConstants;
 
 import org.apache.commons.cli.Option;
 import org.apache.commons.cli.OptionGroup;
@@ -61,7 +70,6 @@
 import org.xml.sax.SAXException;
 import org.xml.sax.SAXParseException;
 
-import com.redhat.thermostat.common.Pair;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.launcher.internal.PluginConfiguration.CommandExtensions;
 import com.redhat.thermostat.launcher.internal.PluginConfiguration.NewCommand;
@@ -188,16 +196,32 @@
  * This class is thread-safe
  */
 public class PluginConfigurationParser {
-
+    
     private static final Logger logger = LoggingUtils.getLogger(PluginConfigurationParser.class);
-
     // thread safe because there is no state :)
-
-    public PluginConfiguration parse(File configurationFile) throws FileNotFoundException {
+    
+    public PluginConfiguration parse(File configurationFile) throws FileNotFoundException, PluginConfigurationValidatorException {
+        validate(new StreamSource(configurationFile));
         return parse(configurationFile.getParentFile().getName(), new FileInputStream(configurationFile));
     }
-
-    public PluginConfiguration parse(String pluginName, InputStream configurationStream) {
+           
+    void validate(StreamSource plugin) throws PluginConfigurationValidatorException {
+        URL schemaUrl = getClass().getResource("/plugin.xsd");
+        SchemaFactory schemaFactory = 
+                SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+        
+        try {
+            Schema schema = schemaFactory.newSchema(schemaUrl);
+            Validator validator = schema.newValidator();
+            validator.setErrorHandler(new ConfigurationValidatorErrorHandler());
+            validator.validate(plugin);
+        } catch (IOException | SAXException exception) {
+            throw new PluginConfigurationValidatorException
+                (plugin.getSystemId(), exception.getLocalizedMessage(), exception);
+        }
+    }
+    
+    PluginConfiguration parse(String pluginName, InputStream configurationStream) {
         try {
             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
             factory.setIgnoringComments(true);
@@ -208,12 +232,12 @@
             if (rootNode == null) {
                 throw new PluginConfigurationParseException("no configuration found");
             }
-            return parseRootElement(pluginName, rootNode);
+            return parseRootElement(pluginName, rootNode); 
         } catch (ParserConfigurationException | SAXException | IOException exception) {
             throw new PluginConfigurationParseException("failed to parse plugin configuration", exception);
         }
     }
-
+    
     private PluginConfiguration parseRootElement(String pluginName, Node root) {
         List<NewCommand> commands = Collections.emptyList();
         List<CommandExtensions> extensions = Collections.emptyList();
@@ -505,4 +529,85 @@
             throw exception;
         }
     }
+    
+    private static class ConfigurationValidatorErrorHandler implements ErrorHandler {
+        
+        private int warningsErrorsCounter = 0;
+
+        @Override
+        public void warning(SAXParseException exception) throws SAXException {
+            warningsErrorsCounter++;
+            printInfo(exception, "Warning");
+        }
+
+        @Override
+        public void error(SAXParseException exception) throws SAXParseException {
+            warningsErrorsCounter++;
+            printInfo(exception, "Error");
+        }
+        
+        @Override
+        public void fatalError(SAXParseException exception) throws SAXParseException {
+            if (warningsErrorsCounter == 0) {
+                printInfo(exception, "Fatal error");
+                logger.warning("XML not well formed");
+            }
+        }
+
+        private static void printInfo(SAXParseException e, String errorType) {
+            int columnNumber = e.getColumnNumber();
+            int lineNumber = e.getLineNumber();
+            
+            StringBuffer buffer = new StringBuffer();
+            
+            String firstLine = null;
+            String secondLine = null;
+            String thirdLine = null;
+            String errorLine = null;
+            String pointer = "";
+            String absolutePath = e.getSystemId();
+            absolutePath = absolutePath.substring(5);
+            
+            try {
+                BufferedReader br = new BufferedReader(new FileReader(absolutePath));
+                for (int i = 1; i < lineNumber-3; i++) {
+                    br.readLine();
+                }
+                firstLine = br.readLine();
+                secondLine = br.readLine();
+                thirdLine = br.readLine();
+                errorLine = br.readLine();
+                
+                for (int j = 1; j < columnNumber-1; j++) {
+                    pointer = pointer.concat(" ");
+                }
+                pointer = pointer.concat("^");
+                br.close();
+            } catch (IOException exception) {
+                System.out.println("File not found!");;
+            }
+            
+            buffer.append(errorType + " in file " + absolutePath + ":" + lineNumber + "." + columnNumber + "\n");
+            buffer.append(formatMessage(e.getLocalizedMessage()) + "\n\n");
+            buffer.append(firstLine + "\n");
+            buffer.append(secondLine + "\n");
+            buffer.append(thirdLine + "\n");
+            buffer.append(errorLine + "\n");
+            buffer.append(pointer  + "\n");
+            
+            logger.warning("\n" + buffer.toString());
+            
+        }
+        
+        private static String formatMessage(String message) {
+            String[] arguments = message.split("\"http://icedtea.classpath.org/thermostat/plugins/v1.0\":");
+            int size = arguments.length;
+            String output = "";
+            for (int i = 0; i < size; i++) {
+                output=output.concat(arguments[i]);
+            }
+            return output;
+        }
+    }
+    
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginConfigurationValidatorException.java	Mon May 13 12:39:57 2013 -0400
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.launcher.internal;
+
+
+public class PluginConfigurationValidatorException extends Exception {
+    
+    private static final long serialVersionUID = 1L;
+    private String filePath;
+    
+    /**
+     * Constructor of PluginConfigurationValidatorException
+     * @param filePath must include the protocol 
+     * @param message the detailed message
+     */
+    public PluginConfigurationValidatorException(String filePath, String message) {
+        super(message);
+        this.filePath = computeFilePath(filePath);
+    }
+    
+    /**
+     * Constructor of PluginConfigurationValidatorException
+     * @param filePath must include the protocol 
+     * @param message the detailed message
+     */
+    public PluginConfigurationValidatorException(String filePath, String message, Throwable cause) {
+        super(message, cause);
+        this.filePath = computeFilePath(filePath);
+    }
+    
+    public String getFilePath() {
+        return filePath;
+    }
+    
+    /**
+     * Computes the file path removing the protocol scheme
+     * @param filePath must include the protocol
+     * @return the path without the protocol scheme
+     */
+    private String computeFilePath(String filePath) {
+        // the substring starts from position 5, skipping "file:" filePath content 
+        return filePath.substring(5);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/main/resources/plugin.xsd	Mon May 13 12:39:57 2013 -0400
@@ -0,0 +1,1 @@
+../../../../distribution/docs/plugin.xsd
\ No newline at end of file
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/PluginCommandInfoSourceTest.java	Tue May 07 18:27:02 2013 -0400
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/PluginCommandInfoSourceTest.java	Mon May 13 12:39:57 2013 -0400
@@ -74,7 +74,7 @@
     private UsageStringBuilder usageBuilder;
 
     @Before
-    public void setUp() throws IOException {
+    public void setUp() throws IOException, PluginConfigurationValidatorException {
         parser = mock(PluginConfigurationParser.class);
         parserResult = mock(PluginConfiguration.class);
         when(parser.parse(isA(File.class))).thenReturn(parserResult);
@@ -108,7 +108,7 @@
     }
 
     @Test
-    public void verifyParserIsInvokedOnAllConfigurationFiles() throws IOException {
+    public void verifyParserIsInvokedOnAllConfigurationFiles() throws IOException, PluginConfigurationValidatorException {
         Path[] pluginDirs = new Path[] {
                 pluginRootDir.resolve("plugin1"),
                 pluginRootDir.resolve("plugin2"),
@@ -130,7 +130,7 @@
     }
 
     @Test
-    public void verifyMissingConfigurationFileIsHandledCorrectly() throws FileNotFoundException {
+    public void verifyMissingConfigurationFileIsHandledCorrectly() throws FileNotFoundException, PluginConfigurationValidatorException {
         when(parser.parse(isA(File.class))).thenThrow(new FileNotFoundException("test"));
 
         new PluginCommandInfoSource(jarRootDir.toFile(), pluginRootDir.toFile(), parser, usageBuilder);
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/PluginConfigurationParserTest.java	Tue May 07 18:27:02 2013 -0400
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/PluginConfigurationParserTest.java	Mon May 13 12:39:57 2013 -0400
@@ -44,11 +44,9 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import java.io.ByteArrayInputStream;
-import java.io.UnsupportedEncodingException;
-import java.util.Arrays;
-import java.util.List;
-
+import java.io.*;
+import java.util.*;
+import javax.xml.transform.stream.StreamSource;
 import org.apache.commons.cli.Option;
 import org.apache.commons.cli.OptionGroup;
 import org.apache.commons.cli.Options;
@@ -67,13 +65,30 @@
         parser.parse("test", new ByteArrayInputStream(config.getBytes("UTF-8")));
         fail("should not reach here");
     }
+    
+    @Test
+    public void validateEmptyConfiguration() throws IOException {
+        String config = "<?xml version=\"1.0\"?>\n";
+        PluginConfigurationParser parser = new PluginConfigurationParser();
+        File testFile = createFile("testSystemId", config);
+        try {
+            parser.validate(new StreamSource(testFile));
+            fail("should not come here");
+        } catch (PluginConfigurationValidatorException e) {
+            //pass
+        } finally {
+            testFile.delete();
+        }
+    }
 
     @Test
     public void testMinimalConfiguration() throws UnsupportedEncodingException {
         PluginConfigurationParser parser = new PluginConfigurationParser();
         String config = "" +
                 "<?xml version=\"1.0\"?>\n" +
-                "<plugin>\n" +
+                "<plugin xmlns=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\"\n" +
+                " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+                " xsi:schemaLocation=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\">\n" +
                 "</plugin>";
         PluginConfiguration result = parser.parse("test", new ByteArrayInputStream(config.getBytes("UTF-8")));
 
@@ -84,7 +99,9 @@
     @Test
     public void testConfigurationThatExtendsExistingCommand() throws UnsupportedEncodingException {
         String config = "<?xml version=\"1.0\"?>\n" +
-                "<plugin>\n" +
+                "<plugin xmlns=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\"\n" +
+                " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+                " xsi:schemaLocation=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\">\n" +
                 "  <extensions>\n" +
                 "    <extension>\n" +
                 "      <name>test</name>\n" +
@@ -113,11 +130,100 @@
         assertEquals(Arrays.asList("foo", "bar", "baz"), first.getPluginBundles());
         assertEquals(Arrays.asList("thermostat-foo"), first.getDepenedencyBundles());
     }
+    
+    @Test
+    public void canValidatePluginXMLMultipleTimes() throws Exception {
+        
+        try {
+            String config = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                    "<plugin xmlns=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\"\n" +
+                    " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+                    " xsi:schemaLocation=\"http://icedtea.classpath.org/thermostat/plugins/v1.0 plugin.xsd\">\n" +
+                    "  <extensions>\n" +
+                    "    <extension>\n" +
+                    "      <name>test</name>\n" +
+                    "      <bundles>\n" +
+                    "        <bundle>foo</bundle>\n" +
+                    "        <bundle>bar</bundle>\n" +
+                    "        <bundle>baz</bundle>\n" +
+                    "      </bundles>\n" +
+                    "      <dependencies>\n" +
+                    "        <dependency>thermostat-foo</dependency>\n" +
+                    "      </dependencies>\n" +
+                    "    </extension>\n" +
+                    "  </extensions>\n" +
+                    "</plugin>";
+            PluginConfigurationParser parser = new PluginConfigurationParser();
+            File testFile = createFile("testSystemId", config);
+            parser.validate(new StreamSource(testFile));
+            testFile.delete();
+        
+            config = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                    "<plugin xmlns=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\"\n" +
+                    " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+                    " xsi:schemaLocation=\"http://icedtea.classpath.org/thermostat/plugins/v1.0 plugin.xsd\">\n" +
+                    "  <extensions>\n" +
+                    "    <extension>\n" +
+                    "      <name>test</name>\n" +
+                    "      <bundles>\n" +
+                    "        <bundle>foo</bundle>\n" +
+                    "        <bundle>bar</bundle>\n" +
+                    "        <bundle>baz</bundle>\n" +
+                    "      </bundles>\n" +
+                    "      <dependencies>\n" +
+                    "        <dependency>thermostat-foo</dependency>\n" +
+                    "      </dependencies>\n" +
+                    "    </extension>\n" +
+                    "  </extensions>\n" +
+                    "</plugin>";
+            File testFile2 = createFile("testSystemId", config);
+            parser.validate(new StreamSource(testFile));
+            testFile2.delete();
+        } catch (PluginConfigurationValidatorException e) {
+           fail("should not reach here, plugin.xml should be validated according to schema");
+        }
+    }
+    
+    @Test
+    public void validationFailsOnInvalidPluginFile() throws Exception {
+        String config = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<plugin xmlns=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\"\n" +
+                " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+                " xsi:schemaLocation=\"http://icedtea.classpath.org/thermostat/plugins/v1.0 plugin.xsd\">\n" +
+                "  <extensions>\n" +
+                "    <something>\n" +
+                "    <extension>\n" +
+                "      <name>test</name>\n" +
+                "      <bundles>\n" +
+                "        <bundle>foo</bundle>\n" +
+                "        <bundle>bar</bundle>\n" +
+                "        <bundle>baz</bundle>\n" +
+                "      </bundles>\n" +
+                "      <dependencies>\n" +
+                "        <dependency>thermostat-foo</dependency>\n" +
+                "      </dependencies>\n" +
+                "    </extension>\n" +
+                "  </extensions>\n" +
+                "</plugin>";
+
+        PluginConfigurationParser parser = new PluginConfigurationParser();
+        File testFile = createFile("testSystemId", config);
+        try {
+            parser.validate(new StreamSource(testFile));
+            fail("plugin.xml should not validate according to schema");
+        } catch (PluginConfigurationValidatorException e) {
+            //pass
+        } finally {
+            testFile.delete();
+        }
+    }
 
     @Test
     public void testConfigurationThatAddsNewCommand() throws UnsupportedEncodingException {
         String config = "<?xml version=\"1.0\"?>\n" +
-                "<plugin>\n" +
+                "<plugin xmlns=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\"\n" +
+                " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+                " xsi:schemaLocation=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\">\n" +
                 "  <commands>\n" +
                 "    <command>\n" +
                 "      <name>test</name>\n" +
@@ -156,7 +262,9 @@
     @Test
     public void testSpacesAtStartAndEndAreTrimmed() throws UnsupportedEncodingException {
         String config = "<?xml version=\"1.0\"?>\n" +
-                "<plugin>\n" +
+                "<plugin xmlns=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\"\n" +
+                " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+                " xsi:schemaLocation=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\">\n" +
                 "  <extensions>" +
                 "    <extension>\n" +
                 "      <name>\ntest   \n</name>\n" +
@@ -222,9 +330,70 @@
     }
 
     @Test
+    public void canValidateCorrectFile() throws IOException {
+        String config = "<?xml version=\"1.0\"?>\n" +
+                "<plugin xmlns=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\"\n" +
+                " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+                " xsi:schemaLocation=\"http://icedtea.classpath.org/thermostat/plugins/v1.0 plugin.xsd\">\n" +
+                "  <commands>\n" +
+                "    <command>\n" +
+                "      <name>test</name>\n" +
+                "      <description>just a test</description>\n" +
+                "      <options>\n" +
+                "        <group>\n" +
+                "          <required>true</required>\n" +
+                "          <option>\n" +
+                "            <long>exclusive-a</long>\n" +
+                "            <short>a</short>\n" +
+                "            <argument>false</argument>\n" +
+                "            <required>false</required>\n" +
+                "            <description>exclusive option a</description>\n" +
+                "          </option>\n" +
+                "          <option>\n" +
+                "            <long>exclusive-b</long>\n" +
+                "            <short>b</short>\n" +
+                "            <argument>false</argument>\n" +
+                "            <required>false</required>\n" +
+                "            <description>exclusive option b</description>\n" +
+                "          </option>\n" +
+                "        </group>\n" +
+                "        <option>\n" +
+                "          <long>long</long>\n" +
+                "          <short>l</short>\n" +
+                "          <argument>true</argument>\n" +
+                "          <required>true</required>\n" +
+                "          <description>some required and long option</description>\n" +
+                "        </option>\n" +
+                "      </options>\n" +
+                "      <bundles>\n" +
+                "        <bundle>\n \t  \nfoo\t \n \n</bundle>\n" +
+                "        <bundle>\tbar  baz\n</bundle>\n" +
+                "        <bundle>buzz</bundle>\n" +
+                "      </bundles>\n" +
+                "      <dependencies>\n\t\n\t \t\t\n" +
+                "        <dependency>\t\t\t  thermostat-foo\n\t\t\n</dependency>\n" +
+                "      </dependencies>\n" +
+                "    </command>\n" +
+                "  </commands>\n" +
+                "</plugin>";
+
+        PluginConfigurationParser parser = new PluginConfigurationParser();
+        File testFile = createFile("testSystemId", config);
+        try {
+            parser.validate(new StreamSource(testFile));
+        } catch (PluginConfigurationValidatorException e) {
+           fail("should not reach here, plugin.xml should be validated according to schema");
+        } finally {
+            testFile.delete();
+        }
+        
+    }
+    @Test
     public void testOptionParsing() throws UnsupportedEncodingException {
         String config = "<?xml version=\"1.0\"?>\n" +
-                "<plugin>\n" +
+                "<plugin xmlns=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\"\n" +
+                " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+                " xsi:schemaLocation=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\">\n" +
                 "  <commands>\n" +
                 "    <command>\n" +
                 "      <name>test</name>\n" +
@@ -295,7 +464,9 @@
     @Test
     public void testCommonOptionParsing() throws UnsupportedEncodingException {
         String config = "<?xml version=\"1.0\"?>\n" +
-                "<plugin>\n" +
+                "<plugin xmlns=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\"\n" +
+                " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+                " xsi:schemaLocation=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\">\n" +
                 "  <commands>\n" +
                 "    <command>\n" +
                 "      <name>test</name>\n" +
@@ -339,7 +510,9 @@
     @Test
     public void testFakeCommonOptionIsIgnored() throws UnsupportedEncodingException {
         String config = "<?xml version=\"1.0\"?>\n" +
-                "<plugin>\n" +
+                "<plugin xmlns=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\"\n" +
+                " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+                " xsi:schemaLocation=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\">\n" +
                 "  <commands>\n" +
                 "    <command>\n" +
                 "      <name>test</name>\n" +
@@ -369,4 +542,12 @@
         Option dbUrlOption = opts.getOption("foobarbaz");
         assertNull(dbUrlOption);
     }
+    
+    private File createFile(String fileName, String contents) throws IOException {
+        FileWriter fstream = new FileWriter(fileName);
+        BufferedWriter out = new BufferedWriter(fstream);
+        out.write(contents);
+        out.close();
+        return new File(fileName);
+    }
 }
--- a/numa/distribution/plugin.xml	Tue May 07 18:27:02 2013 -0400
+++ b/numa/distribution/plugin.xml	Mon May 13 12:39:57 2013 -0400
@@ -38,7 +38,7 @@
 -->
 <plugin xmlns="http://icedtea.classpath.org/thermostat/plugins/v1.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0">
+  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0 plugin.xsd">
   <extensions>
     <extension>
       <name>gui</name>
--- a/thread/distribution/plugin.xml	Tue May 07 18:27:02 2013 -0400
+++ b/thread/distribution/plugin.xml	Mon May 13 12:39:57 2013 -0400
@@ -38,7 +38,7 @@
 -->
 <plugin xmlns="http://icedtea.classpath.org/thermostat/plugins/v1.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0">
+  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0 plugin.xsd">
   <extensions>
     <extension>
       <name>gui</name>
--- a/vm-classstat/distribution/plugin.xml	Tue May 07 18:27:02 2013 -0400
+++ b/vm-classstat/distribution/plugin.xml	Mon May 13 12:39:57 2013 -0400
@@ -38,7 +38,7 @@
 -->
 <plugin xmlns="http://icedtea.classpath.org/thermostat/plugins/v1.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0">
+  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0 plugin.xsd">
   <extensions>
     <extension>
       <name>gui</name>
--- a/vm-cpu/distribution/plugin.xml	Tue May 07 18:27:02 2013 -0400
+++ b/vm-cpu/distribution/plugin.xml	Mon May 13 12:39:57 2013 -0400
@@ -38,7 +38,7 @@
 -->
 <plugin xmlns="http://icedtea.classpath.org/thermostat/plugins/v1.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0">
+  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0 plugin.xsd">
   <extensions>
     <extension>
       <name>gui</name>
--- a/vm-gc/distribution/plugin.xml	Tue May 07 18:27:02 2013 -0400
+++ b/vm-gc/distribution/plugin.xml	Mon May 13 12:39:57 2013 -0400
@@ -38,7 +38,7 @@
 -->
 <plugin xmlns="http://icedtea.classpath.org/thermostat/plugins/v1.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0">
+  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0 plugin.xsd">
   <extensions>
     <extension>
       <name>gui</name>
--- a/vm-heap-analysis/distribution/plugin.xml	Tue May 07 18:27:02 2013 -0400
+++ b/vm-heap-analysis/distribution/plugin.xml	Mon May 13 12:39:57 2013 -0400
@@ -38,7 +38,7 @@
 -->
 <plugin xmlns="http://icedtea.classpath.org/thermostat/plugins/v1.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0">
+  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0 plugin.xsd">
   <commands>
     <command>
       <name>dump-heap</name>
--- a/vm-memory/distribution/plugin.xml	Tue May 07 18:27:02 2013 -0400
+++ b/vm-memory/distribution/plugin.xml	Mon May 13 12:39:57 2013 -0400
@@ -38,7 +38,7 @@
 -->
 <plugin xmlns="http://icedtea.classpath.org/thermostat/plugins/v1.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0">
+  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0 plugin.xsd">
   <extensions>
     <extension>
       <name>gui</name>
--- a/vm-overview/distribution/plugin.xml	Tue May 07 18:27:02 2013 -0400
+++ b/vm-overview/distribution/plugin.xml	Mon May 13 12:39:57 2013 -0400
@@ -38,7 +38,7 @@
 -->
 <plugin xmlns="http://icedtea.classpath.org/thermostat/plugins/v1.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0">
+  xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0 plugin.xsd">
   <extensions>
     <extension>
       <name>gui</name>