Spring Framework
  1. Spring Framework
  2. SPR-8368

Spring XSD validation fails in the presence of non-standard classloaders due to problems resolving "schema.handlers" etc.

    Details

    • Type: Bug Bug
    • Status: Open
    • Priority: Minor Minor
    • Resolution: Unresolved
    • Affects Version/s: 3.0.5
    • Fix Version/s: Waiting for Triage
    • Component/s: Core
    • Labels:
      None
    • Last commented by a User:
      true

      Description

      With the splitting of Spring Framework into multiple jar files in v3.0, there are now several files named "META-INF/spring.handlers", "spring.schemas" and "spring.tooling". This is not a problem when running in a normal servlet container, but poses problems when e.g. creating an executable JAR file from a webapp using an embedded web server such as Jetty, or running GWT in "Dev Mode", which uses a custom class loader.

      In the former scenario, a typical approach is to use a Maven assembly to extract all .class files from the project dependencies and merge them into one hierarchy, as a way of packaging all the dependencies and the webapp itself into one JAR file.

      However, in this case only one copy of "spring.handlers/schemas/tooling" can exist, and so any schemas that are used and /not/ in the one copy cannot be validated. This leads to exceptions such as this one:

      org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 65 in XML document from class path resource [spring/beans.xml] is invalid; nested exception is org.xml.sax.SAXParseException: cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element 'context:annotation-config'.
      at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:68)
      at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:85)
      at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:76)
      at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.importBeanDefinitionResource(DefaultBeanDefinitionDocumentReader.java:218)
      at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseDefaultElement(DefaultBeanDefinitionDocumentReader.java:147)

      Other people reporting similar problems can be found at Stack Overflow here and here.

      The workaround is to construct your own "custom" version of these three files, merging all the copies into one like so:

      //IOUtils and FileUtils come from Apache Commons IOfor(String s : new String[]

      {"spring.schemas", "spring.handlers", "spring.tooling"}

      ) {
      Enumeration<?> e = Test.class.getClassLoader().getResources("META-INF/"+s);
      StringBuilder out = new StringBuilder();while(e.hasMoreElements())

      { URL u = (URL) e.nextElement(); out.append(IOUtils.toString(u.openStream())).append("\n"); }

      File outf = new File(s);
      FileUtils.writeStringToFile(outf, out.toString(), "UTF-8");
      }

      However, the proper fix would be to use a different file-name for each instance of the schemas/handlers/tooling files. For example, inside "org.springframework.aop-3.0.5.RELEASE.jar/META-INF" you would find "spring-aop.schemas", "spring-aop.handlers" and "spring-aop.tooling".

      I'm afraid I'm not sufficiently up-to-speed with the Spring code-base to give you a patch to do this, however a brief investigation shows that "spring.handlers" and "spring.schemas" are specified in org.springframework.beans.factory.xml.PluggableSchemaResolver and DefaultNamespaceHandlerResolver, and that constructors exist for specifying different locations for these files. I hope you find this information useful.

      Best regards,

      • Ian

        Issue Links

          Activity

          Hide
          Costin Leau added a comment -

          There isn't an easy fix to this problem. By the looks of it, the issue lies with the way the GWT dev-mode works:
          http://groups.google.com/group/google-web-toolkit/browse_thread/thread/ac495ee6605d21b4?pli=1

          Basically it overrides findResources() delegating to the system classloader which obviously doesn't find the dev resources. This is a critical piece of functionality and to make it work we would have to rewrite it somehow to use only one resource (as oppose to multiple resources).
          Aggregating the XSD is not really a solution since it's easy to get into conflicts if there are different versions of the same jar or namespaces that declare the same element name.

          Show
          Costin Leau added a comment - There isn't an easy fix to this problem. By the looks of it, the issue lies with the way the GWT dev-mode works: http://groups.google.com/group/google-web-toolkit/browse_thread/thread/ac495ee6605d21b4?pli=1 Basically it overrides findResources() delegating to the system classloader which obviously doesn't find the dev resources. This is a critical piece of functionality and to make it work we would have to rewrite it somehow to use only one resource (as oppose to multiple resources). Aggregating the XSD is not really a solution since it's easy to get into conflicts if there are different versions of the same jar or namespaces that declare the same element name.
          Hide
          Ian Sollars added a comment -

          Hi Costin,

          I agree that aggregating the XSD isn't a proper solution, however, how about changing the file name of the "spring.*" files to indicate which JAR file they belong to, like I suggested in the second-to-last paragraph? e.g. spring-aop.handlers, etc.

          This wouldn't stop the problem of having different versions of the same jar, but would solve the problem of spring XML configuration files being unusable in certain environments.

          • I
          Show
          Ian Sollars added a comment - Hi Costin, I agree that aggregating the XSD isn't a proper solution, however, how about changing the file name of the "spring.*" files to indicate which JAR file they belong to, like I suggested in the second-to-last paragraph? e.g. spring-aop.handlers, etc. This wouldn't stop the problem of having different versions of the same jar, but would solve the problem of spring XML configuration files being unusable in certain environments. I
          Hide
          Costin Leau added a comment -

          That would cause several other problems such as:

          • breaks backwards compatibility - tools relying on spring.handlers to exist, would not find the file. We would still have to provide these as some sort of alias (either by including or literally copying the renamed files).
          • breaks the discovery process. The use of the same name is on purpose - ClassLoader.get/findResources() do not support pattern matching so one can't do a portable get("spring-*.handlers").

          I'd like to address your problem but I can't think of a way to do that w/o breaking existing functionality. To recap, multiple namespaces can exist (think of a library that provides a Spring namespace) and to find what's available one can only use get/findResources() - if that method is not implemented properly, multiple files are simply not supported. Which is not an option for us.
          Extending the dev classloader to properly implement get/findResources() seems like a much nicer (not to mention) easier solution.

          P.S. Seems that the broken classloader method has been implemented post GWT 1.7 - I wonder why. Have you consider raising this problem with the GWT team?

          Show
          Costin Leau added a comment - That would cause several other problems such as: breaks backwards compatibility - tools relying on spring.handlers to exist, would not find the file. We would still have to provide these as some sort of alias (either by including or literally copying the renamed files). breaks the discovery process. The use of the same name is on purpose - ClassLoader.get/findResources() do not support pattern matching so one can't do a portable get("spring-*.handlers"). I'd like to address your problem but I can't think of a way to do that w/o breaking existing functionality. To recap, multiple namespaces can exist (think of a library that provides a Spring namespace) and to find what's available one can only use get/findResources() - if that method is not implemented properly, multiple files are simply not supported. Which is not an option for us. Extending the dev classloader to properly implement get/findResources() seems like a much nicer (not to mention) easier solution. P.S. Seems that the broken classloader method has been implemented post GWT 1.7 - I wonder why. Have you consider raising this problem with the GWT team?
          Hide
          Ian Sollars added a comment -

          Hi Costin,

          Thanks for the short education You're right, it seems more of a GWT/classloader design problem than a Spring problem. I'll go take it up with them.

          • I
          Show
          Ian Sollars added a comment - Hi Costin, Thanks for the short education You're right, it seems more of a GWT/classloader design problem than a Spring problem. I'll go take it up with them. I
          Hide
          Michał Matłoka added a comment -

          I'm currently struggling with this problem. However I'm not using Gwt Dev mode nor embedded jetty but gwt-maven-plugin with standalone jetty.

          Show
          Michał Matłoka added a comment - I'm currently struggling with this problem. However I'm not using Gwt Dev mode nor embedded jetty but gwt-maven-plugin with standalone jetty.
          Hide
          Michał Matłoka added a comment -

          Sorry, It was my mistake. Everything works under gwt-maven-plugin.

          Show
          Michał Matłoka added a comment - Sorry, It was my mistake. Everything works under gwt-maven-plugin.

            People

            • Assignee:
              Costin Leau
              Reporter:
              Ian Sollars
              Last updater:
              Chris Beams
            • Votes:
              1 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

              • Created:
                Updated:
                Days since last comment:
                2 years, 31 weeks, 6 days ago