Issue #42686: consistent validation of project metadata according to schema. 1. XMLUtil.validate utility method added. 2. XML catalog for project metadata now read from ProjectXMLCatalog in the system filesystem, not the web. Register http://www.netbeans.org/ns/foo/1 as ProjectXMLCatalog/foo/1.xsd to be found. 3. Validate project.xml/private.xml according to schemas found in ProjectXMLCatalog: a. When loading a project, if project.xml is invalid then make the load fail. (Effectively preventing older IDEs from trying to load newer IDEs' projects, if schemas changed.) b. Validate before saving project.xml/private.xml, to guard against buggy module code. c. Validate before loading project.xml/private.xml, to guard against e.g. bad user edits or VCS conflict markers. Note that e.g. uses lax subelement processing, so children with no matching schemas will be skipped. 4. Remove logic to display validation errors of freeform project.xml in Output Window. Users can anyway use A-S-F9 to use the regular Validate action before saving. 5. ant.freeform.ProjectNature.getSchemas removed (natures need no longer implement it). diff --git a/ant.freeform/nbproject/project.xml b/ant.freeform/nbproject/project.xml --- a/ant.freeform/nbproject/project.xml +++ b/ant.freeform/nbproject/project.xml @@ -123,27 +123,11 @@ - org.openide.explorer - - - - 6.8 - - - org.openide.filesystems 6.2 - - - - org.openide.io - - - - 1.2 @@ -158,14 +142,6 @@ 6.2 - - - - org.openide.text - - - - 6.16 diff --git a/ant.freeform/src/org/netbeans/modules/ant/freeform/FreeformProject.java b/ant.freeform/src/org/netbeans/modules/ant/freeform/FreeformProject.java --- a/ant.freeform/src/org/netbeans/modules/ant/freeform/FreeformProject.java +++ b/ant.freeform/src/org/netbeans/modules/ant/freeform/FreeformProject.java @@ -86,7 +86,6 @@ eval = new FreeformEvaluator(this); lookup = initLookup(); Logger.getLogger(FreeformProject.class.getName()).log(Level.FINER, "Initializing project in {0} with {1}", new Object[] {helper, lookup}); - new ProjectXmlValidator(helper.resolveFileObject(AntProjectHelper.PROJECT_XML_PATH)); } public AntProjectHelper helper() { diff --git a/ant.freeform/src/org/netbeans/modules/ant/freeform/ProjectXmlValidator.java b/ant.freeform/src/org/netbeans/modules/ant/freeform/ProjectXmlValidator.java deleted file mode 100644 --- a/ant.freeform/src/org/netbeans/modules/ant/freeform/ProjectXmlValidator.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common - * Development and Distribution License("CDDL") (collectively, the - * "License"). You may not use this file except in compliance with the - * License. You can obtain a copy of the License at - * http://www.netbeans.org/cddl-gplv2.html - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the - * specific language governing permissions and limitations under the - * License. When distributing the software, include this License Header - * Notice in each file and include the License file at - * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this - * particular file as subject to the "Classpath" exception as provided - * by Sun in the GPL Version 2 section of the License file that - * accompanied this code. If applicable, add the following below the - * License Header, with the fields enclosed by brackets [] replaced by - * your own identifying information: - * "Portions Copyrighted [year] [name of copyright owner]" - * - * Contributor(s): - * - * The Original Software is NetBeans. The Initial Developer of the Original - * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun - * Microsystems, Inc. All Rights Reserved. - * - * If you wish your version of this file to be governed by only the CDDL - * or only the GPL Version 2, indicate your decision by adding - * "[Contributor] elects to include this software in this distribution - * under the [CDDL or GPL Version 2] license." If you do not indicate a - * single choice of license, a recipient has the option to distribute - * your version of this file under either the CDDL, the GPL Version 2 or - * to extend the choice of license to its licensees as provided above. - * However, if you add GPL Version 2 code and therefore, elected the GPL - * Version 2 license, then the option applies only if the new code is - * made subject to such option by the copyright holder. - */ - -package org.netbeans.modules.ant.freeform; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Iterator; -import java.util.Set; -import java.util.TreeSet; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; -import org.netbeans.modules.ant.freeform.spi.ProjectNature; -import org.openide.ErrorManager; -import org.openide.cookies.EditorCookie; -import org.openide.filesystems.FileAttributeEvent; -import org.openide.filesystems.FileChangeListener; -import org.openide.filesystems.FileEvent; -import org.openide.filesystems.FileObject; -import org.openide.filesystems.FileRenameEvent; -import org.openide.filesystems.FileUtil; -import org.openide.filesystems.URLMapper; -import org.openide.loaders.DataObject; -import org.openide.loaders.DataObjectNotFoundException; -import org.openide.text.Line; -import org.openide.util.Lookup; -import org.openide.util.NbBundle; -import org.openide.windows.IOProvider; -import org.openide.windows.InputOutput; -import org.openide.windows.OutputEvent; -import org.openide.windows.OutputListener; -import org.xml.sax.SAXException; -import org.xml.sax.SAXNotRecognizedException; -import org.xml.sax.SAXParseException; -import org.xml.sax.helpers.DefaultHandler; - -/** - * Checks validity of freeform project.xml files. - * @see "#47288" - * @author Jesse Glick - */ -final class ProjectXmlValidator extends DefaultHandler implements FileChangeListener { - - private final FileObject projectXml; - private InputOutput io; - - public ProjectXmlValidator(FileObject projectXml) { - this.projectXml = projectXml; - projectXml.addFileChangeListener(this); - validateProjectXml(); - } - - private void validateProjectXml() { - if (System.getProperty("netbeans.user") == null) { // NOI18N - // Probably in a unit test; skip it. - return; - } - open(); - try { - // XXX may want to preinitialize the desired SAXParserFactory and keep it statically, for speed - SAXParserFactory f = SAXParserFactory.newInstance(); - f.setNamespaceAware(true); - f.setValidating(true); - SAXParser p = f.newSAXParser(); - p.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", // NOI18N - "http://www.w3.org/2001/XMLSchema"); // NOI18N - p.setProperty("http://java.sun.com/xml/jaxp/properties/schemaSource", getSchemas()); // NOI18N - p.parse(projectXml.getURL().toString(), this); - } catch (SAXParseException e) { - log(e); - } catch (Exception e) { - ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); - } finally { - close(); - } - } - - /** - * Compute a list of XML schema locations to be used for validating project.xml files. - */ - private static String[] getSchemas() { - Set schemas = new TreeSet(); - // XXX should not refer to schema in another module; wait for #42686 to solve properly - schemas.add("nbres:/org/netbeans/modules/project/ant/project.xsd"); // NOI18N - schemas.add("nbres:/org/netbeans/modules/ant/freeform/resources/freeform-project-general.xsd"); // NOI18N - schemas.add("nbres:/org/netbeans/modules/ant/freeform/resources/freeform-project-general-2.xsd"); // NOI18N - for (ProjectNature nature : FreeformProject.PROJECT_NATURES.allInstances()) { - schemas.addAll(nature.getSchemas()); - } - return schemas.toArray(new String[schemas.size()]); - } - - public void fileChanged(FileEvent fe) { - validateProjectXml(); - } - - public void fileRenamed(FileRenameEvent fe) {} - - public void fileAttributeChanged(FileAttributeEvent fe) {} - - public void fileFolderCreated(FileEvent fe) {} - - public void fileDeleted(FileEvent fe) {} - - public void fileDataCreated(FileEvent fe) {} - - public void warning(SAXParseException e) throws SAXException { - log(e); - } - - public void error(SAXParseException e) throws SAXException { - log(e); - } - - public void fatalError(SAXParseException e) throws SAXException { - throw e; - } - - /** Close any old error tab. */ - private void open() { - if (io != null) { - io.closeInputOutput(); - io = null; - } - } - - /** Log a parse error, opening error tab as needed. */ - private void log(SAXParseException e) { - if (io == null) { - String title = NbBundle.getMessage(ProjectXmlValidator.class, "LBL_project.xml_errors", FileUtil.getFileDisplayName(projectXml)); - io = IOProvider.getDefault().getIO(title, true); - io.select(); - } - try { - io.getErr().println(e.getLocalizedMessage(), new Hyperlink(e.getSystemId(), e.getLineNumber(), e.getColumnNumber())); - } catch (IOException x) { - ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, x); - } - } - - /** Close the stream for the error tab, if one is open, but leave it visible. */ - private void close() { - if (io != null) { - io.getErr().close(); - io.getOut().close(); // XXX why is this necessary? - } - } - - private static final class Hyperlink implements OutputListener { - - private final String uri; - private final int line, column; - - public Hyperlink(String uri, int line, int column) { - this.uri = uri; - this.line = line; - this.column = column; - } - - public void outputLineAction(OutputEvent ev) { - FileObject fo; - try { - fo = URLMapper.findFileObject(new URL(uri)); - } catch (MalformedURLException e) { - assert false : e; - return; - } - if (fo == null) { - return; - } - DataObject d; - try { - d = DataObject.find(fo); - } catch (DataObjectNotFoundException e) { - assert false : e; - return; - } - EditorCookie ec = d.getCookie(EditorCookie.class); - if (ec == null) { - return; - } - if (line != -1) { - try { - // XXX do we need to call ec.openDocument as in org.apache.tools.ant.module.run.Hyperlink? - Line l = ec.getLineSet().getOriginal(line - 1); - if (column != -1) { - l.show(Line.SHOW_GOTO, column - 1); - } else { - l.show(Line.SHOW_GOTO); - } - } catch (IndexOutOfBoundsException e) { - // forget it - ec.open(); - } - } else { - ec.open(); - } - } - - public void outputLineSelected(OutputEvent ev) {} - - public void outputLineCleared(OutputEvent ev) {} - - } - -} diff --git a/ant.freeform/src/org/netbeans/modules/ant/freeform/resources/layer.xml b/ant.freeform/src/org/netbeans/modules/ant/freeform/resources/layer.xml --- a/ant.freeform/src/org/netbeans/modules/ant/freeform/resources/layer.xml +++ b/ant.freeform/src/org/netbeans/modules/ant/freeform/resources/layer.xml @@ -59,5 +59,10 @@ - + + + + + + diff --git a/ant.freeform/src/org/netbeans/modules/ant/freeform/spi/ProjectNature.java b/ant.freeform/src/org/netbeans/modules/ant/freeform/spi/ProjectNature.java --- a/ant.freeform/src/org/netbeans/modules/ant/freeform/spi/ProjectNature.java +++ b/ant.freeform/src/org/netbeans/modules/ant/freeform/spi/ProjectNature.java @@ -49,7 +49,6 @@ import org.netbeans.spi.project.support.ant.PropertyEvaluator; import org.openide.filesystems.FileObject; import org.openide.nodes.Node; -import org.openide.util.Lookup; /** * Description of base freeform project extension. Instances should be @@ -69,12 +68,6 @@ * @return a list of {@link TargetDescriptor}s (can be empty but not null) */ List getExtraTargets(Project project, AntProjectHelper projectHelper, PropertyEvaluator projectEvaluator, AuxiliaryConfiguration aux); - - /** - * Returns set of XML schemas describing syntax of project.xml defined by this project extension. - * @return set of Strings whose value is URL of XML schema file - */ - Set getSchemas(); /** * Get a set of view styles supported by the nature for displaying source folders in the logical view. diff --git a/ant.freeform/src/org/netbeans/modules/ant/freeform/spi/support/Util.java b/ant.freeform/src/org/netbeans/modules/ant/freeform/spi/support/Util.java --- a/ant.freeform/src/org/netbeans/modules/ant/freeform/spi/support/Util.java +++ b/ant.freeform/src/org/netbeans/modules/ant/freeform/spi/support/Util.java @@ -45,15 +45,11 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Map; import javax.xml.XMLConstants; -import javax.xml.transform.dom.DOMSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; -import javax.xml.validation.Validator; import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectManager; import org.netbeans.api.queries.CollocationQuery; @@ -70,7 +66,7 @@ import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.util.Mutex; -import org.w3c.dom.Attr; +import org.openide.xml.XMLUtil; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -80,9 +76,7 @@ import org.w3c.dom.Text; import org.w3c.dom.ls.DOMImplementationLS; import org.w3c.dom.ls.LSSerializer; -import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; -import org.xml.sax.SAXParseException; /** * Miscellaneous helper methods. @@ -360,11 +354,11 @@ public Void run() { Element dataAs1 = translateXML(data, FreeformProjectType.NS_GENERAL_1); try { - validate(dataAs1, SCHEMA_1); + XMLUtil.validate(dataAs1, SCHEMA_1); putPrimaryConfigurationDataAs1(helper, dataAs1); } catch (SAXException x1) { try { - validate(data, SCHEMA_2); + XMLUtil.validate(data, SCHEMA_2); putPrimaryConfigurationDataAs2(helper, data); } catch (SAXException x2) { assert false : x2.getMessage() + "; rejected content: " + format(data); @@ -398,51 +392,6 @@ throw new ExceptionInInitializerError(e); } } - private static void validate(Element data, Schema schema) throws SAXException { - Validator v = schema.newValidator(); - final SAXException[] error = {null}; - v.setErrorHandler(new ErrorHandler() { - public void warning(SAXParseException x) throws SAXException {} - public void error(SAXParseException x) throws SAXException { - // Just rethrowing it is bad because it will also print it to stderr. - error[0] = x; - } - public void fatalError(SAXParseException x) throws SAXException { - error[0] = x; - } - }); - try { - v.validate(new DOMSource(fixupNoNamespaceAttrs(data))); - } catch (IOException x) { - assert false : x; - } - if (error[0] != null) { - throw error[0]; - } - } - private static Element fixupNoNamespaceAttrs(Element root) { - // XXX #6529766: some versions of JAXP reject attributes set using setAttribute - // (rather than setAttributeNS) even though the schema calls for no-NS attrs! - // JDK 5 is fine; JDK 6 broken; JDK 6u2 supposedly will be fixed; current JDK 7 broken - Element copy = (Element) root.cloneNode(true); - NodeList nl = copy.getElementsByTagName("*"); - for (int i = 0; i < nl.getLength(); i++) { - Element e = (Element) nl.item(i); - Map replace = new HashMap(); - NamedNodeMap attrs = e.getAttributes(); - for (int j = 0; j < attrs.getLength(); j++) { - Attr attr = (Attr) attrs.item(j); - if (attr.getNamespaceURI() == null) { - replace.put(attr.getName(), attr.getValue()); - } - } - for (Map.Entry entry : replace.entrySet()) { - e.removeAttribute(entry.getKey()); - e.setAttributeNS(null, entry.getKey(), entry.getValue()); - } - } - return copy; - } private static String format(Element data) { LSSerializer ser = ((DOMImplementationLS) data.getOwnerDocument().getImplementation().getFeature("LS", "3.0")).createLSSerializer(); try { diff --git a/java.freeform/src/org/netbeans/modules/java/freeform/JavaProjectNature.java b/java.freeform/src/org/netbeans/modules/java/freeform/JavaProjectNature.java --- a/java.freeform/src/org/netbeans/modules/java/freeform/JavaProjectNature.java +++ b/java.freeform/src/org/netbeans/modules/java/freeform/JavaProjectNature.java @@ -43,9 +43,7 @@ import java.beans.PropertyChangeListener; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Set; import javax.swing.Icon; @@ -70,8 +68,6 @@ public static final String NS_JAVA_1 = "http://www.netbeans.org/ns/freeform-project-java/1"; // NOI18N public static final String NS_JAVA_2 = "http://www.netbeans.org/ns/freeform-project-java/2"; // NOI18N public static final String EL_JAVA = "java-data"; // NOI18N - private static final String SCHEMA_1 = "nbres:/org/netbeans/modules/java/freeform/resources/freeform-project-java.xsd"; // NOI18N - private static final String SCHEMA_2 = "nbres:/org/netbeans/modules/java/freeform/resources/freeform-project-java-2.xsd"; // NOI18N public static final String STYLE_PACKAGES = "packages"; // NOI18N @@ -79,10 +75,6 @@ public List getExtraTargets(Project project, AntProjectHelper projectHelper, PropertyEvaluator projectEvaluator, AuxiliaryConfiguration aux) { return new ArrayList(); - } - - public Set getSchemas() { - return new HashSet(Arrays.asList(SCHEMA_1, SCHEMA_2)); } public Set getSourceFolderViewStyles() { diff --git a/java.freeform/src/org/netbeans/modules/java/freeform/resources/layer.xml b/java.freeform/src/org/netbeans/modules/java/freeform/resources/layer.xml --- a/java.freeform/src/org/netbeans/modules/java/freeform/resources/layer.xml +++ b/java.freeform/src/org/netbeans/modules/java/freeform/resources/layer.xml @@ -77,5 +77,10 @@ - + + + + + + diff --git a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/ui/resources/layer.xml b/java.j2seproject/src/org/netbeans/modules/java/j2seproject/ui/resources/layer.xml --- a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/ui/resources/layer.xml +++ b/java.j2seproject/src/org/netbeans/modules/java/j2seproject/ui/resources/layer.xml @@ -144,4 +144,14 @@ + + + + + + + + + + diff --git a/openide.util/src/org/openide/xml/XMLUtil.java b/openide.util/src/org/openide/xml/XMLUtil.java --- a/openide.util/src/org/openide/xml/XMLUtil.java +++ b/openide.util/src/org/openide/xml/XMLUtil.java @@ -49,6 +49,7 @@ import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.FactoryConfigurationError; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.OutputKeys; @@ -59,6 +60,8 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.Validator; import org.openide.util.Lookup; import org.w3c.dom.CDATASection; import org.w3c.dom.DOMException; @@ -73,6 +76,7 @@ import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; /** @@ -455,7 +459,49 @@ collectCDATASections(children.item(i), cdataQNames); } } - + + /** + * Check whether a DOM tree is valid according to a schema. + * Example of usage: + *
+     * Element fragment = ...;
+     * SchemaFactory f = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+     * Schema s = f.newSchema(This.class.getResource("something.xsd"));
+     * try {
+     *     XMLUtil.validate(fragment, s);
+     *     // valid
+     * } catch (SAXException x) {
+     *     // invalid
+     * }
+     * 
+ * @param data a DOM tree + * @param schema a parsed schema + * @throws SAXException if validation failed + * @since XXX + */ + public static void validate(Element data, Schema schema) throws SAXException { + Validator v = schema.newValidator(); + final SAXException[] error = {null}; + v.setErrorHandler(new ErrorHandler() { + public void warning(SAXParseException x) throws SAXException {} + public void error(SAXParseException x) throws SAXException { + // Just rethrowing it is bad because it will also print it to stderr. + error[0] = x; + } + public void fatalError(SAXParseException x) throws SAXException { + error[0] = x; + } + }); + try { + v.validate(new DOMSource(data)); + } catch (IOException x) { + assert false : x; + } + if (error[0] != null) { + throw error[0]; + } + } + /** * Escape passed string as XML attibute value * (<, &, ' and " diff --git a/openide.util/test/unit/src/org/openide/xml/XMLUtilTest.java b/openide.util/test/unit/src/org/openide/xml/XMLUtilTest.java --- a/openide.util/test/unit/src/org/openide/xml/XMLUtilTest.java +++ b/openide.util/test/unit/src/org/openide/xml/XMLUtilTest.java @@ -47,7 +47,13 @@ import java.io.IOException; import java.io.StringReader; import java.lang.ref.WeakReference; +import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; import org.netbeans.junit.NbTestCase; import org.w3c.dom.CDATASection; import org.w3c.dom.Document; @@ -153,6 +159,37 @@ String data = ""; return new InputSource(new StringReader(data)); } + } + + public void testValidate() throws Exception { + Element r = XMLUtil.createDocument("root", "some://where", null, null).getDocumentElement(); + r.setAttribute("hello", "there"); + SchemaFactory f = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + String xsd = + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; + Schema s = f.newSchema(new StreamSource(new StringReader(xsd))); + XMLUtil.validate(r, s); + r.setAttribute("goodbye", "now"); + try { + XMLUtil.validate(r, s); + fail(); + } catch (SAXException x) {/*OK*/} + // Make sure Java #6529766 is fixed (no longer any need for fixupNoNamespaceAttrs): + String xml = ""; + r = XMLUtil.parse(new InputSource(new StringReader(xml)), false, true, null, null).getDocumentElement(); + r.setAttribute("hello", "there"); + XMLUtil.validate(r, s); + r.setAttribute("goodbye", "now"); + try { + XMLUtil.validate(r, s); + fail(); + } catch (SAXException x) {/*OK*/} } public void testToAttributeValue() throws IOException { diff --git a/project.ant/nbproject/project.xml b/project.ant/nbproject/project.xml --- a/project.ant/nbproject/project.xml +++ b/project.ant/nbproject/project.xml @@ -96,6 +96,15 @@ 1 + +
+ + org.netbeans.modules.xml.catalog + + + + 2 + 1.14 diff --git a/project.ant/src/org/netbeans/modules/project/ant/AntBasedProjectFactorySingleton.java b/project.ant/src/org/netbeans/modules/project/ant/AntBasedProjectFactorySingleton.java --- a/project.ant/src/org/netbeans/modules/project/ant/AntBasedProjectFactorySingleton.java +++ b/project.ant/src/org/netbeans/modules/project/ant/AntBasedProjectFactorySingleton.java @@ -163,18 +163,20 @@ return null; } Document projectXml; + Element projectEl; try { projectXml = XMLUtil.parse(new InputSource(projectDiskFile.toURI().toString()), false, true, Util.defaultErrorHandler(), null); + projectEl = projectXml.getDocumentElement(); + if (!"project".equals(projectEl.getLocalName()) || !PROJECT_NS.equals(projectEl.getNamespaceURI())) { // NOI18N + return null; + } + ProjectXMLCatalogReader.validate(projectEl); } catch (SAXException e) { IOException ioe = (IOException) new IOException(projectDiskFile + ": " + e.toString()).initCause(e); Exceptions.attachLocalizedMessage(ioe, NbBundle.getMessage(AntBasedProjectFactorySingleton.class, "AntBasedProjectFactorySingleton.parseError", projectDiskFile.getAbsolutePath(), e.getMessage())); throw ioe; - } - Element projectEl = projectXml.getDocumentElement(); - if (!"project".equals(projectEl.getLocalName()) || !PROJECT_NS.equals(projectEl.getNamespaceURI())) { // NOI18N - return null; } Element typeEl = Util.findElement(projectEl, "type", PROJECT_NS); // NOI18N if (typeEl == null) { diff --git a/project.ant/src/org/netbeans/modules/project/ant/Bundle.properties b/project.ant/src/org/netbeans/modules/project/ant/Bundle.properties --- a/project.ant/src/org/netbeans/modules/project/ant/Bundle.properties +++ b/project.ant/src/org/netbeans/modules/project/ant/Bundle.properties @@ -84,3 +84,7 @@ MSG_Invalid_Location=Valid location must be set MSG_Variable_Already_Exists=Variable with this name already exists MSG_Invalid_Name=Valid name must be set + +# ProjectXMLCatalogReader +LBL_project_xml_schemas=Project XML Schemas +HINT_project_xml_schemas=Permits validation of project.xml and private.xml files from the IDE. diff --git a/projectui/src/org/netbeans/modules/project/ui/ProjectXMLCatalogReader.java b/project.ant/src/org/netbeans/modules/project/ant/ProjectXMLCatalogReader.java rename from projectui/src/org/netbeans/modules/project/ui/ProjectXMLCatalogReader.java rename to project.ant/src/org/netbeans/modules/project/ant/ProjectXMLCatalogReader.java --- a/projectui/src/org/netbeans/modules/project/ui/ProjectXMLCatalogReader.java +++ b/project.ant/src/org/netbeans/modules/project/ant/ProjectXMLCatalogReader.java @@ -39,17 +39,31 @@ * made subject to such option by the copyright holder. */ -package org.netbeans.modules.project.ui; +package org.netbeans.modules.project.ant; import java.awt.Image; import java.beans.PropertyChangeListener; +import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; +import java.util.List; +import javax.xml.XMLConstants; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.SchemaFactory; import org.netbeans.modules.xml.catalog.spi.CatalogDescriptor; import org.netbeans.modules.xml.catalog.spi.CatalogListener; import org.netbeans.modules.xml.catalog.spi.CatalogReader; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileStateInvalidException; +import org.openide.filesystems.Repository; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; +import org.openide.util.NbCollections; import org.openide.util.Utilities; +import org.openide.xml.XMLUtil; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; /** * Supplies a catalog which lets users validate against project-related XML schemas. @@ -59,17 +73,24 @@ public class ProjectXMLCatalogReader implements CatalogReader, CatalogDescriptor { private static final String PREFIX = "http://www.netbeans.org/ns/"; // NOI18N - private static final String SUFFIX = ".xsd"; // NOI18N + private static final String EXTENSION = "xsd"; // NOI18N + private static final String CATALOG = "ProjectXMLCatalog"; // NOI18N /** Default constructor for use from layer. */ public ProjectXMLCatalogReader() {} public String resolveURI(String name) { if (name.startsWith(PREFIX)) { - return name + SUFFIX; - } else { - return null; + FileObject rsrc = Repository.getDefault().getDefaultFileSystem().findResource(CATALOG + "/" + name.substring(PREFIX.length()) + "." + EXTENSION); + if (rsrc != null) { + try { + return rsrc.getURL().toString(); + } catch (FileStateInvalidException x) { + Exceptions.printStackTrace(x); + } + } } + return null; } public String resolvePublic(String publicId) { @@ -105,5 +126,28 @@ public String getDisplayName() { return NbBundle.getMessage(ProjectXMLCatalogReader.class, "LBL_project_xml_schemas"); } + + /** + * Validate according to all *.xsd found in catalog. + * @param dom DOM fragment to validate + * @throws SAXException if schemas were malformed or the document was invalid + */ + public static void validate(Element dom) throws SAXException { + // XXX should cache schemas (but then needs to listen to changes in SFS in a tree) + List sources = new ArrayList(); + FileObject root = Repository.getDefault().getDefaultFileSystem().findResource(CATALOG); + if (root != null) { + for (FileObject f : NbCollections.iterable(root.getChildren(true))) { + if (f.isData() && f.hasExt(EXTENSION)) { + try { + sources.add(new StreamSource(f.getURL().toString())); + } catch (FileStateInvalidException x) { + Exceptions.printStackTrace(x); + } + } + } + } + XMLUtil.validate(dom, SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(sources.toArray(new Source[sources.size()]))); + } } diff --git a/projectui/src/org/netbeans/modules/project/ui/ProjectXMLCatalogReaderBeanInfo.java b/project.ant/src/org/netbeans/modules/project/ant/ProjectXMLCatalogReaderBeanInfo.java rename from projectui/src/org/netbeans/modules/project/ui/ProjectXMLCatalogReaderBeanInfo.java rename to project.ant/src/org/netbeans/modules/project/ant/ProjectXMLCatalogReaderBeanInfo.java --- a/projectui/src/org/netbeans/modules/project/ui/ProjectXMLCatalogReaderBeanInfo.java +++ b/project.ant/src/org/netbeans/modules/project/ant/ProjectXMLCatalogReaderBeanInfo.java @@ -39,7 +39,7 @@ * made subject to such option by the copyright holder. */ -package org.netbeans.modules.project.ui; +package org.netbeans.modules.project.ant; import java.beans.PropertyDescriptor; import java.beans.SimpleBeanInfo; diff --git a/project.ant/src/org/netbeans/modules/project/ant/resources/mf-layer.xml b/project.ant/src/org/netbeans/modules/project/ant/resources/mf-layer.xml --- a/project.ant/src/org/netbeans/modules/project/ant/resources/mf-layer.xml +++ b/project.ant/src/org/netbeans/modules/project/ant/resources/mf-layer.xml @@ -42,7 +42,6 @@ --> - @@ -51,5 +50,28 @@ - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/project.ant/src/org/netbeans/spi/project/support/ant/AntProjectHelper.java b/project.ant/src/org/netbeans/spi/project/support/ant/AntProjectHelper.java --- a/project.ant/src/org/netbeans/spi/project/support/ant/AntProjectHelper.java +++ b/project.ant/src/org/netbeans/spi/project/support/ant/AntProjectHelper.java @@ -61,6 +61,7 @@ import org.netbeans.modules.project.ant.FileChangeSupportEvent; import org.netbeans.modules.project.ant.FileChangeSupportListener; import org.netbeans.modules.project.ant.ProjectLibraryProvider; +import org.netbeans.modules.project.ant.ProjectXMLCatalogReader; import org.netbeans.modules.project.ant.UserQuestionHandler; import org.netbeans.modules.project.ant.Util; import org.netbeans.spi.project.AuxiliaryConfiguration; @@ -277,7 +278,9 @@ File f = FileUtil.toFile(xml); assert f != null; try { - return XMLUtil.parse(new InputSource(f.toURI().toString()), false, true, Util.defaultErrorHandler(), null); + Document doc = XMLUtil.parse(new InputSource(f.toURI().toString()), false, true, Util.defaultErrorHandler(), null); + ProjectXMLCatalogReader.validate(doc.getDocumentElement()); + return doc; } catch (IOException e) { if (!QUIETLY_SWALLOW_XML_LOAD_ERRORS) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); @@ -298,6 +301,11 @@ assert ProjectManager.mutex().isWriteAccess(); assert !writingXML; assert Thread.holdsLock(modifiedMetadataPaths); + try { + ProjectXMLCatalogReader.validate(doc.getDocumentElement()); + } catch (SAXException x) { + throw (IOException) new IOException(x.getMessage()).initCause(x); + } final FileLock[] _lock = new FileLock[1]; writingXML = true; try { @@ -778,6 +786,7 @@ return ; } path = PROJECT_XML_PATH; + // XXX should rather set a separate flag but keep the (valid) content loaded projectXml = null; } else if (f.equals(resolveFile(PRIVATE_XML_PATH))) { if (modifiedMetadataPaths.contains(PRIVATE_XML_PATH)) { diff --git a/project.ant/test/unit/src/org/netbeans/modules/project/ant/AntBasedProjectFactorySingletonTest.java b/project.ant/test/unit/src/org/netbeans/modules/project/ant/AntBasedProjectFactorySingletonTest.java --- a/project.ant/test/unit/src/org/netbeans/modules/project/ant/AntBasedProjectFactorySingletonTest.java +++ b/project.ant/test/unit/src/org/netbeans/modules/project/ant/AntBasedProjectFactorySingletonTest.java @@ -116,5 +116,9 @@ assertTrue(getAntBasedProjectTypeMethod.invoke(helper) == type2); } + + public void testDoNotLoadInvalidProject() throws Exception { + // XXX create malformed project.xml and check that loadProject dies + } } diff --git a/project.ant/test/unit/src/org/netbeans/spi/project/support/ant/AntProjectHelperTest.java b/project.ant/test/unit/src/org/netbeans/spi/project/support/ant/AntProjectHelperTest.java --- a/project.ant/test/unit/src/org/netbeans/spi/project/support/ant/AntProjectHelperTest.java +++ b/project.ant/test/unit/src/org/netbeans/spi/project/support/ant/AntProjectHelperTest.java @@ -190,6 +190,7 @@ assertNotNull("have ", data); Element details = Util.findElement(data, "details", "urn:test:shared"); assertNotNull("have
", details); + // XXX test well-formed but invalid content } /** @@ -535,6 +536,7 @@ // XXX try modifying both XML files, or different parts of the same, and saving in a batch // XXX try storing unmodified XML fragments and see what happens // XXX try storing a fresh Element not returned from getPrimaryConfigurationData + // XXX test storing invalid content } /** diff --git a/projectapi/manifest.mf b/projectapi/manifest.mf --- a/projectapi/manifest.mf +++ b/projectapi/manifest.mf @@ -3,4 +3,5 @@ OpenIDE-Module-Install: org/netbeans/modules/projectapi/Installer.class OpenIDE-Module-Specification-Version: 1.18 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/projectapi/Bundle.properties +OpenIDE-Module-Layer: org/netbeans/modules/projectapi/layer.xml diff --git a/projectapi/src/org/netbeans/modules/projectapi/layer.xml b/projectapi/src/org/netbeans/modules/projectapi/layer.xml new file mode 100644 --- /dev/null +++ b/projectapi/src/org/netbeans/modules/projectapi/layer.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/projectui/nbproject/project.xml b/projectui/nbproject/project.xml --- a/projectui/nbproject/project.xml +++ b/projectui/nbproject/project.xml @@ -115,15 +115,6 @@ 1 - - - - org.netbeans.modules.xml.catalog - - - - 2 - 1.8 diff --git a/projectui/src/org/netbeans/modules/project/ui/Bundle.properties b/projectui/src/org/netbeans/modules/project/ui/Bundle.properties --- a/projectui/src/org/netbeans/modules/project/ui/Bundle.properties +++ b/projectui/src/org/netbeans/modules/project/ui/Bundle.properties @@ -44,10 +44,6 @@ OpenIDE-Module-Long-Description=\ GUI infrastructure for working with projects in the IDE: the Projects and Files tabs, \ the project chooser dialog, the project-sensitive New File wizard, etc. - -# ProjectXMLCatalogReader -LBL_project_xml_schemas=Project XML Schemas -HINT_project_xml_schemas=Permits validation of project.xml and private.xml files from the IDE. #BrowseFolders BTN_BrowseFolders_Select_Option=&Select Folder diff --git a/projectui/src/org/netbeans/modules/project/ui/resources/layer.xml b/projectui/src/org/netbeans/modules/project/ui/resources/layer.xml --- a/projectui/src/org/netbeans/modules/project/ui/resources/layer.xml +++ b/projectui/src/org/netbeans/modules/project/ui/resources/layer.xml @@ -622,16 +622,6 @@ - - - - - - - - - - @@ -690,5 +680,9 @@ - + + + + + diff --git a/web.freeform/src/org/netbeans/modules/web/freeform/WebProjectNature.java b/web.freeform/src/org/netbeans/modules/web/freeform/WebProjectNature.java --- a/web.freeform/src/org/netbeans/modules/web/freeform/WebProjectNature.java +++ b/web.freeform/src/org/netbeans/modules/web/freeform/WebProjectNature.java @@ -43,7 +43,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Set; import org.netbeans.api.project.Project; @@ -63,10 +62,8 @@ public class WebProjectNature implements ProjectNature { public static final String NS_WEB_1 = "http://www.netbeans.org/ns/freeform-project-web/1"; // NOI18N - private static final String SCHEMA_1 = "nbres:/org/netbeans/modules/web/freeform/resources/freeform-project-web.xsd"; // NOI18N public static final String EL_WEB = "web-data"; public static final String NS_WEB_2 = "http://www.netbeans.org/ns/freeform-project-web/2"; // NOI18N - private static final String SCHEMA_2 = "nbres:/org/netbeans/modules/web/freeform/resources/freeform-project-web-2.xsd"; // NOI18N public WebProjectNature() {} @@ -80,10 +77,6 @@ return l; } - public Set getSchemas() { - return new HashSet(Arrays.asList(SCHEMA_1, SCHEMA_2)); - } - public Set getSourceFolderViewStyles() { return Collections.emptySet(); } diff --git a/web.freeform/src/org/netbeans/modules/web/freeform/resources/layer.xml b/web.freeform/src/org/netbeans/modules/web/freeform/resources/layer.xml --- a/web.freeform/src/org/netbeans/modules/web/freeform/resources/layer.xml +++ b/web.freeform/src/org/netbeans/modules/web/freeform/resources/layer.xml @@ -79,5 +79,10 @@ - + + + + + +