@@ -, +, @@ Register http://www.netbeans.org/ns/foo/1 as ProjectXMLCatalog/foo/1.xsd to be found. 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. Users can anyway use A-S-F9 to use the regular Validate action before saving. --- a/ant.freeform/nbproject/project.xml +++ a/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 --- a/ant.freeform/src/org/netbeans/modules/ant/freeform/FreeformProject.java +++ a/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() { --- a/ant.freeform/src/org/netbeans/modules/ant/freeform/ProjectXmlValidator.java +++ a/ant.freeform/src/org/netbeans/modules/ant/freeform/ProjectXmlValidator.java @@ -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) {} - - } - -} --- a/ant.freeform/src/org/netbeans/modules/ant/freeform/resources/layer.xml +++ a/ant.freeform/src/org/netbeans/modules/ant/freeform/resources/layer.xml @@ -59,5 +59,10 @@ - + + + + + + --- a/ant.freeform/src/org/netbeans/modules/ant/freeform/spi/ProjectNature.java +++ a/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. --- a/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 @@ -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 { --- a/java.freeform/src/org/netbeans/modules/java/freeform/JavaProjectNature.java +++ a/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() { --- a/java.freeform/src/org/netbeans/modules/java/freeform/resources/layer.xml +++ a/java.freeform/src/org/netbeans/modules/java/freeform/resources/layer.xml @@ -77,5 +77,10 @@ - + + + + + + --- a/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 @@ -144,4 +144,14 @@ + + + + + + + + + + --- a/openide.util/src/org/openide/xml/XMLUtil.java +++ a/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 " --- a/openide.util/test/unit/src/org/openide/xml/XMLUtilTest.java +++ a/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 { --- a/project.ant/nbproject/project.xml +++ a/project.ant/nbproject/project.xml @@ -96,6 +96,15 @@ 1 + +
+ + org.netbeans.modules.xml.catalog + + + + 2 + 1.14 --- a/project.ant/src/org/netbeans/modules/project/ant/AntBasedProjectFactorySingleton.java +++ a/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) { --- a/project.ant/src/org/netbeans/modules/project/ant/Bundle.properties +++ a/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. --- a/projectui/src/org/netbeans/modules/project/ui/ProjectXMLCatalogReader.java +++ a/projectui/src/org/netbeans/modules/project/ui/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()]))); + } } --- a/projectui/src/org/netbeans/modules/project/ui/ProjectXMLCatalogReaderBeanInfo.java +++ a/projectui/src/org/netbeans/modules/project/ui/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; --- a/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 @@ -42,7 +42,6 @@ --> - @@ -51,5 +50,28 @@ - + + + + + + + + + + + + + + + + + + + + + + + + --- a/project.ant/src/org/netbeans/spi/project/support/ant/AntProjectHelper.java +++ a/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)) { --- a/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 @@ -116,5 +116,9 @@ assertTrue(getAntBasedProjectTypeMethod.invoke(helper) == type2); } + + public void testDoNotLoadInvalidProject() throws Exception { + // XXX create malformed project.xml and check that loadProject dies + } } --- a/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 @@ -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 } /** --- a/projectapi/manifest.mf +++ a/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 --- a/projectapi/src/org/netbeans/modules/projectapi/layer.xml +++ a/projectapi/src/org/netbeans/modules/projectapi/layer.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + --- a/projectui/nbproject/project.xml +++ a/projectui/nbproject/project.xml @@ -115,15 +115,6 @@ 1 - - - - org.netbeans.modules.xml.catalog - - - - 2 - 1.8 --- a/projectui/src/org/netbeans/modules/project/ui/Bundle.properties +++ a/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 --- a/projectui/src/org/netbeans/modules/project/ui/resources/layer.xml +++ a/projectui/src/org/netbeans/modules/project/ui/resources/layer.xml @@ -622,16 +622,6 @@ - - - - - - - - - - @@ -690,5 +680,9 @@ - + + + + + --- a/web.freeform/src/org/netbeans/modules/web/freeform/WebProjectNature.java +++ a/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(); } --- a/web.freeform/src/org/netbeans/modules/web/freeform/resources/layer.xml +++ a/web.freeform/src/org/netbeans/modules/web/freeform/resources/layer.xml @@ -79,5 +79,10 @@ - + + + + + +