Le XML Schema définit est assez simple:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="Salaries" type="SalariesType">
    <xs:unique name="Salary-Ctrl">
      <xs:selector xpath="Salary" />
      <xs:field xpath="@institutionIDRef" />
      <xs:field xpath="Code" />
    </xs:unique>
  </xs:element>
  <xs:complexType name="SalariesType">
    <xs:sequence>
      <xs:element name="Salary" type="SalaryType" maxOccurs="unbounded" />
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="SalaryType">
    <xs:sequence>
      <xs:element name="ValidAsOf" type="xs:date" minOccurs="0" />
      <xs:element name="Code" type="xs:string" minOccurs="0" />
      <xs:element name="AnnualBasis" type="xs:string" />
    </xs:sequence>
    <xs:attribute name="institutionIDRef" type="xs:string" use="required" />
  </xs:complexType>
</xs:schema>

Et voici une structure XML qui correspond bien au schéma ci-dessus:

<Salaries>
  <Salary institutionIDRef="someID">
    <AnnualBasis>someContent</AnnualBasis>
    <Code>01</Code>
    <Code>02</Code>
  </Salary>
</Salaries>

Ce qu'il faut noter, c'est que le tag <Salary> peut également contenir zéro ou plusieurs tags <Code> avec du texte. Toutefois, celui-ci doit être différent, tel que spécifié par la contrainte Salary-Ctrl. Le fichier XML suivant n'est donc pas valide:

<Salaries>
  <Salary institutionIDRef="someID">
    <AnnualBasis>someContent</AnnualBasis>
    <Code>01</Code>
    <Code>01</Code>
  </Salary>
</Salaries>

Par contre, le contenu ci-dessous est parfaitement correct:

<Salaries>
  <Salary institutionIDRef="someID">
    <AnnualBasis>someContent</AnnualBasis>
  </Salary>
</Salaries>

Finalement, voici le morceau de code que j'utilise afin de valider les structures XML:

String xsd = "...";
String xml = "...";
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = factory.newSchema(new SAXSource(new InputSource(new ByteArrayInputStream(xsd.getBytes()))));
Validator validator = schema.newValidator();
 
validator.validate(new SAXSource(new InputSource(new ByteArrayInputStream(xml.getBytes()))));
System.out.println("Validated !");

A la base, notre application tournait sur un glassfish v2 (Java EE 5) avec le JDK 1.6 et tout se passait sans problème. Suite à la sortie de Java EE 7, nous avons décidé de passer sur glassfish v3 (Java EE 6), en restant toujours avec le JDK 1.6. Et c'est là que le problème est apparu dans le code ci-dessus pour le dernier exemple (sans le tag Code):

Exception in thread "main" org.xml.sax.SAXParseException: Not enough values specified for <unique> identity constraint specified for element "Salaries".
    at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:195)
    at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:131)
    at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:384)
    at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:318)
    at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator$XSIErrorReporter.reportError(XMLSchemaValidator.java:423)
    at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.reportSchemaError(XMLSchemaValidator.java:3188)
    at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator$ValueStoreBase.endValueScope(XMLSchemaValidator.java:3463)
    at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.endValueScopeFor(XMLSchemaValidator.java:1479)
    at com.sun.org.apache.xerces.internal.impl.xs.identity.Selector$Matcher.endElement(Selector.java:235)
    at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleEndElement(XMLSchemaValidator.java:2169)
    at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.endElement(XMLSchemaValidator.java:824)
    at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorHandlerImpl.endElement(ValidatorHandlerImpl.java:565)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(AbstractSAXParser.java:601)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEndElement(XMLDocumentFragmentScannerImpl.java:1782)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2939)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:647)
    at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:140)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:511)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:808)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:737)
    at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:119)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1205)
    at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:522)
    at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorHandlerImpl.validate(ValidatorHandlerImpl.java:700)
    at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorImpl.validate(ValidatorImpl.java:97)
    at javax.xml.validation.Validator.validate(Validator.java:127)
    at ch.Test.main(Test.java:53)

Evidemment, notre code n'ayant pas changé, cette régression est surprenant. J'ai donc lancé exactement le même code avec le JDK 1.7 et tout (re)fonctionne correctement. J'ai ouvert un thread sur StackOverflow à ce propos et visiblement il s'agit effectivement d'un bug dans le JDK 1.6.

Pourquoi est-ce que cela marchait auparavant ? Je soupçonne 2 choses: soit la librairie xerces a changé entre glassfish v2 et v3 (mais pourquoi est-ce que ça fonctionne en JDK 1.7 alors que glassfish n'est pas utilisé ?), soit que cela provienne du fait que le glassfish v2 était lancé dans une JVM avec l'option -source 1.5 (mais pourquoi est-ce que ça fonctionnait puisque le JDK était le même ?). Je n'ai pas eu l'occasion d'approfondir ces dernières questions...