Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 331241

Summary: Enhancement Request: Case insensitive unmarshalling
Product: z_Archived Reporter: Blaise Doughan <blaise.doughan>
Component: EclipselinkAssignee: Nobody - feel free to take it <nobody>
Status: RESOLVED FIXED QA Contact:
Severity: enhancement    
Priority: P3 CC: marcel.valovy, martin.grebac, tomas.kraus
Version: unspecified   
Target Milestone: ---   
Hardware: PC   
OS: Windows XP   
Whiteboard:
Attachments:
Description Flags
Git patch
none
Git patch - new version
none
Git patch - v3
none
The turkey test
none
CaseInsensitiveUnmarshalling.patch none

Description Blaise Doughan CLA 2010-11-26 15:23:06 EST
It could be a useful option to provide case insensitive unmarshalling.  For example for the property firstName the following XML elements would be considered matches:

- <firstName>Jane</firstName>
- <firstname>Jane</firstname>
- <FIRSTNAME>Jane</FIRSTNAME>
- <FiRsTnAmE>Jane</FiRsTnAmE>

Source:
http://bdoughan.blogspot.com/2010/10/how-does-jaxb-compare-to-simple.html
Comment 1 Marcel Valovy CLA 2014-03-17 04:32:57 EDT
Starting work on it.
Comment 2 Marcel Valovy CLA 2014-03-25 07:54:26 EDT
Created attachment 241217 [details]
Git patch
Comment 3 Marcel Valovy CLA 2014-03-25 10:00:22 EDT
Comment on attachment 241217 [details]
Git patch

From 46674cfcd221ff8128ebd39ae1c1f51c6ccce878 Mon Sep 17 00:00:00 2001
From: Marcel Valovy <marcel.valovy@oracle.com>
Date: Mon, 24 Mar 2014 17:28:14 +0100
Subject: [PATCH] Bug 331241 - Enhancement Request: Case insensitive
 unmarshalling

Signed-off-by: Marcel Valovy <marcel.valovy@oracle.com>
---
 .../persistence/internal/helper/DatabaseField.java |  23 +++-
 .../persistence/internal/oxm/XPathFragment.java    |  24 +++--
 .../src/org/eclipse/persistence/oxm/XMLField.java  |  17 ++-
 moxy/eclipselink.moxy.test/antbuild.xml            |   1 +
 .../testing/jaxb/casesensitivity/customer.xml      |   1 +
 .../persistence/testing/jaxb/JAXBTestSuite5.java   |  37 +++++++
 .../testing/jaxb/casesensitivity/Customer.java     |  72 +++++++++++++
 .../JAXBCaseSensitivityUnmarshallingTestSuite.java | 119 +++++++++++++++++++++
 .../casesensitivity/camelCase/CustomerImpl.java    |  68 ++++++++++++
 .../casesensitivity/upperCase/CustomerImpl.java    |  71 ++++++++++++
 .../org/eclipse/persistence/jaxb/JAXBContext.java  |   7 +-
 .../eclipse/persistence/jaxb/JAXBUnmarshaller.java |   2 +-
 .../persistence/jaxb/UnmarshallerProperties.java   |  79 ++++++++++----
 .../persistence/jaxb/compiler/Generator.java       |  53 +++++++--
 .../jaxb/compiler/MappingsGenerator.java           |  71 +++++++++---
 15 files changed, 584 insertions(+), 61 deletions(-)
 create mode 100644 moxy/eclipselink.moxy.test/resource/org/eclipse/persistence/testing/jaxb/casesensitivity/customer.xml
 create mode 100644 moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/JAXBTestSuite5.java
 create mode 100644 moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/casesensitivity/Customer.java
 create mode 100644 moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/casesensitivity/JAXBCaseSensitivityUnmarshallingTestSuite.java
 create mode 100644 moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/casesensitivity/camelCase/CustomerImpl.java
 create mode 100644 moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/casesensitivity/upperCase/CustomerImpl.java

diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/DatabaseField.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/DatabaseField.java
index 3d283ba..4e3e2f1 100644
--- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/DatabaseField.java
+++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/DatabaseField.java
@@ -55,7 +55,8 @@ public class DatabaseField implements Cloneable, Serializable, CoreField  {
     protected boolean isCreatable;
     protected boolean isPrimaryKey;
     protected String columnDefinition;
-    
+    protected boolean unmarshallingCaseInsensitive;
+
     /** Column name of the field. */
     protected String name;
     
@@ -122,8 +123,13 @@ public class DatabaseField implements Cloneable, Serializable, CoreField  {
     public DatabaseField(String qualifiedName) {
         this(qualifiedName, null, null);
     }
-    
+
     public DatabaseField(String qualifiedName, String startDelimiter, String endDelimiter) {
+        this(qualifiedName, startDelimiter, endDelimiter, false);
+    }
+
+    public DatabaseField(String qualifiedName, String startDelimiter, String endDelimiter, boolean unmarshallingCaseInsensitive) {
+        this.unmarshallingCaseInsensitive = unmarshallingCaseInsensitive;
         this.index = -1;
         this.sqlType = NULL_SQL_TYPE;
         int index = qualifiedName.lastIndexOf('.');
@@ -143,10 +149,19 @@ public class DatabaseField implements Cloneable, Serializable, CoreField  {
     }
 
     public DatabaseField(String fieldName, DatabaseTable databaseTable) {
-        this(fieldName, databaseTable, null, null);
+        this(fieldName, databaseTable, null, null, false);
+    }
+
+    public DatabaseField(String fieldName, DatabaseTable databaseTable, boolean unmarshallingCaseInsensitive) {
+        this(fieldName, databaseTable, null, null, unmarshallingCaseInsensitive);
     }
     
     public DatabaseField(String fieldName, DatabaseTable databaseTable, String startDelimiter, String endDelimiter) {
+        this(fieldName, databaseTable, startDelimiter, endDelimiter, false);
+    }
+
+    public DatabaseField(String fieldName, DatabaseTable databaseTable, String startDelimiter, String endDelimiter, boolean unmarshallingCaseInsensitive) {
+        this.unmarshallingCaseInsensitive = unmarshallingCaseInsensitive;
         this.index = -1;
         this.sqlType = NULL_SQL_TYPE;
         setName(fieldName, startDelimiter, endDelimiter);
@@ -565,7 +580,7 @@ public class DatabaseField implements Cloneable, Serializable, CoreField  {
         this.nameForComparisons = null;
         this.qualifiedName = null;
     }
-    
+
     /**
      * Used for generating DDL. Set to true if the database column is 
      * nullable. 
diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/oxm/XPathFragment.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/oxm/XPathFragment.java
index f519c9a..7191e5f 100644
--- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/oxm/XPathFragment.java
+++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/oxm/XPathFragment.java
@@ -59,23 +59,33 @@ public class XPathFragment <
     protected boolean isSelfFragment = false;
     private QName leafElementType;
     private boolean generatedPrefix = false;   
-    private XPathPredicate predicate; 
+    private XPathPredicate predicate;
     
     private boolean namespaceAware;
     private char namespaceSeparator;
+    private boolean unmarshallingCaseInsensitive = false;
     
 
     public XPathFragment() {
         setNamespaceAware(true);     
         namespaceSeparator = Constants.COLON;    	
     }
-    
+
     public XPathFragment(String xpathString) {
-    	this(xpathString, Constants.COLON, true);
+        this(xpathString, false);
     }
-    
+
+    public XPathFragment(String xpathString, boolean unmarshallingCaseInsensitive) {
+    	this(xpathString, Constants.COLON, true, unmarshallingCaseInsensitive);
+    }
+
     public XPathFragment(String xpathString, char namespaceSeparator, boolean namespaceAware) {
-    	this.namespaceSeparator = namespaceSeparator;   
+        this(xpathString, namespaceSeparator, namespaceAware, false);
+    }
+
+    public XPathFragment(String xpathString, char namespaceSeparator, boolean namespaceAware, boolean unmarshallingCaseInsensitive) {
+    	this.namespaceSeparator = namespaceSeparator;
+        this.unmarshallingCaseInsensitive = unmarshallingCaseInsensitive;
     	setNamespaceAware(namespaceAware);     
     	setXPath(xpathString); 
     }
@@ -346,7 +356,7 @@ public class XPathFragment <
             if ((null == localName && null != xPathFragment.localName) || (null != localName && null == xPathFragment.localName)) {
                 return false;
             }
-            if (null != localName && !localName.equals(xPathFragment.localName)) {
+            if (null != localName && (xPathFragment.unmarshallingCaseInsensitive ? !localName.equalsIgnoreCase(xPathFragment.localName) : !localName.equals(xPathFragment.localName))) {
                 return false;
             }
             if (namespaceAware && xPathFragment.isNamespaceAware()) {
@@ -378,7 +388,7 @@ public class XPathFragment <
         if(null == localName) {
             return 1;
         } else {
-            return localName.hashCode();
+            return unmarshallingCaseInsensitive ? localName.toLowerCase().hashCode() : localName.hashCode();
         }
     }
 
diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/oxm/XMLField.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/oxm/XMLField.java
index f005d2b..c1919cc 100644
--- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/oxm/XMLField.java
+++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/oxm/XMLField.java
@@ -324,7 +324,18 @@ public class XMLField extends DatabaseField implements Field<XMLConversionManage
      * @param xPath The xPath statement for this field
      */
     public XMLField(String xPath) {
-        super(xPath, new DatabaseTable());
+        this(xPath, false);
+    }
+
+    /**
+     * Default constructor, create a new XMLField based on the specified xPath
+     * @param xPath The xPath statement for this field
+     */
+    public XMLField(String xPath, boolean unmarshallingCaseInsensitive) {
+        /* It would be better to set the unmarshallingCaseInsensitive as a field of this class.
+          * However because this.setName() method is called in DatabaseField constructor, that is
+          * before the full initialization of this instance, we must pass it as a constructor arg. */
+        super(xPath, new DatabaseTable(), unmarshallingCaseInsensitive);
         isTypedTextField = false;
     }
 
@@ -486,7 +497,7 @@ public class XMLField extends DatabaseField implements Field<XMLConversionManage
         if (hasPath(xPath)) {
             buildFragments(xPath);
         } else {
-            XPathFragment xPathFragment = new XPathFragment(xPath.intern());
+            XPathFragment xPathFragment = new XPathFragment(xPath.intern(), unmarshallingCaseInsensitive);
             xPathFragment.setXMLField(this);
             setXPathFragment(xPathFragment);
             setLastXPathFragment(xPathFragment);
@@ -536,7 +547,7 @@ public class XMLField extends DatabaseField implements Field<XMLConversionManage
                 } else {
                     next = next.intern();
                 }
-                nextXPathFragment = new XPathFragment(next);
+                nextXPathFragment = new XPathFragment(next, unmarshallingCaseInsensitive);
                 if (0 == i) {
                     setXPathFragment(nextXPathFragment);
                 } else {
diff --git a/moxy/eclipselink.moxy.test/antbuild.xml b/moxy/eclipselink.moxy.test/antbuild.xml
index 78b6aad..c814048 100644
--- a/moxy/eclipselink.moxy.test/antbuild.xml
+++ b/moxy/eclipselink.moxy.test/antbuild.xml
@@ -371,6 +371,7 @@
                         <include name="org/eclipse/persistence/testing/jaxb/JAXBTestSuite2.java"/>
                         <include name="org/eclipse/persistence/testing/jaxb/JAXBTestSuite3.java"/>
                         <include name="org/eclipse/persistence/testing/jaxb/JAXBTestSuite4.java"/>
+                        <include name="org/eclipse/persistence/testing/jaxb/JAXBTestSuite5.java"/>
                         <include name="org/eclipse/persistence/testing/jaxb/listofobjects/JAXBListOfObjectsSuite.java"/>
                         <include name="org/eclipse/persistence/testing/jaxb/annotations/AnnotationsTestSuite.java"/>
                         <include name="org/eclipse/persistence/testing/jaxb/externalizedmetadata/ExternalizedMetadataTestSuite.java"/>
diff --git a/moxy/eclipselink.moxy.test/resource/org/eclipse/persistence/testing/jaxb/casesensitivity/customer.xml b/moxy/eclipselink.moxy.test/resource/org/eclipse/persistence/testing/jaxb/casesensitivity/customer.xml
new file mode 100644
index 0000000..a4c5eeb
--- /dev/null
+++ b/moxy/eclipselink.moxy.test/resource/org/eclipse/persistence/testing/jaxb/casesensitivity/customer.xml
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><customerImpl age="24" id="1234007"><name>cafeBabe</name></customerImpl>
\ No newline at end of file
diff --git a/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/JAXBTestSuite5.java b/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/JAXBTestSuite5.java
new file mode 100644
index 0000000..5ed2a8c
--- /dev/null
+++ b/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/JAXBTestSuite5.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 1998, 2012 Oracle and/or its affiliates. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
+ * which accompanies this distribution.
+ * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ *     Oracle - initial API and implementation from Oracle TopLink
+ ******************************************************************************/
+package org.eclipse.persistence.testing.jaxb;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.eclipse.persistence.testing.jaxb.casesensitivity.JAXBCaseSensitivityUnmarshallingTestSuite;
+
+public class JAXBTestSuite5 extends TestCase {
+
+    public static Test suite() {
+        TestSuite suite = new TestSuite("JAXB20 Compiler Test Suite5");
+
+        suite.addTestSuite(JAXBCaseSensitivityUnmarshallingTestSuite.class);
+        return suite;
+
+    }
+
+    public static void main(String[] args) {
+        String[] arguments = {"-c", "org.eclipse.persistence.testing.jaxb.JAXBTestSuite5"};
+        // junit.swingui.TestRunner.main(arguments);
+        // System.setProperty("useLogging", "true");
+        junit.textui.TestRunner.main(arguments);
+    }
+
+}
\ No newline at end of file
diff --git a/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/casesensitivity/Customer.java b/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/casesensitivity/Customer.java
new file mode 100644
index 0000000..6c0a227
--- /dev/null
+++ b/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/casesensitivity/Customer.java
@@ -0,0 +1,72 @@
+package org.eclipse.persistence.testing.jaxb.casesensitivity;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * This class serves as a bridge between {@link org.eclipse.persistence.testing.jaxb.casesensitivity.camelCase.CustomerImpl}
+ * and {@link org.eclipse.persistence.testing.jaxb.casesensitivity.upperCase.CustomerImpl}.
+ * <p>Provides uniform bridged <i>equals()<i/> and <i>hashCode()<i/> methods.</p>
+ *
+ * @author Marcel Valovy - marcel.valovy@oracle.com
+ */
+@XmlTransient
+public abstract class Customer {
+
+    /**
+     * Getter for customer's id.
+     * A part of bridge allowing to compare camel case implementation and upper case implementation of Customer.
+     *
+     * @return id
+     */
+    public abstract int getIdBridge();
+
+    /**
+     * Getter for customer's age.
+     * A part of bridge allowing to compare camel case implementation and upper case implementation of Customer.
+     *
+     * @return id
+     */
+    public abstract int getAgeBridge();
+
+    /**
+     * Getter for customer's name.
+     * A part of bridge allowing to compare camel case implementation and upper case implementation of Customer.
+     *
+     * @return id
+     */
+    public abstract String getNameBridge();
+
+    /**
+     * Bridge for {@link org.eclipse.persistence.testing.jaxb.casesensitivity.camelCase.CustomerImpl#equals(Object)} and
+     * {@link org.eclipse.persistence.testing.jaxb.casesensitivity.upperCase.CustomerImpl#equals(Object)}
+     *
+     * @param o CustomerImpl object.
+     * @return true if the CustomerImpl classes' attributes match the same values.
+     */
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || this.getClass().getSuperclass() != o.getClass().getSuperclass()) return false;
+
+        Customer customer = (Customer) o;
+
+        if (getIdBridge() != customer.getIdBridge()) return false;
+        if (getAgeBridge() != customer.getAgeBridge()) return false;
+        if (getNameBridge() != null ? !getNameBridge().equals(customer.getNameBridge()) : customer.getNameBridge() != null)
+            return false;
+
+        return true;
+    }
+
+    /**
+     * Bridge for {@link org.eclipse.persistence.testing.jaxb.casesensitivity.camelCase.CustomerImpl#hashCode()} and
+     * {@link org.eclipse.persistence.testing.jaxb.casesensitivity.upperCase.CustomerImpl#hashCode()}
+     *
+     * @return hashCode
+     */
+    public int hashCode() {
+        int result = getNameBridge() != null ? getNameBridge().hashCode() : 0;
+        result = 31 * result + getAgeBridge();
+        result = 31 * result + getIdBridge();
+        return result;
+    }
+}
diff --git a/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/casesensitivity/JAXBCaseSensitivityUnmarshallingTestSuite.java b/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/casesensitivity/JAXBCaseSensitivityUnmarshallingTestSuite.java
new file mode 100644
index 0000000..e0254bf
--- /dev/null
+++ b/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/casesensitivity/JAXBCaseSensitivityUnmarshallingTestSuite.java
@@ -0,0 +1,119 @@
+package org.eclipse.persistence.testing.jaxb.casesensitivity;
+
+import junit.framework.TestCase;
+import org.eclipse.persistence.jaxb.JAXBContextFactory;
+import org.eclipse.persistence.jaxb.UnmarshallerProperties;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Tests the functionality correctness of the implementation of the Case-insensitive unmarshalling feature.
+ *
+ * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=331241">EclipseLink Forum, Bug 331241.</a>
+ * @author Marcel Valovy - marcel.valovy@oracle.com
+ */
+public class JAXBCaseSensitivityUnmarshallingTestSuite extends TestCase {
+
+    private static final File FILE = new File("org/eclipse/persistence/testing/jaxb/casesensitivity/customer.xml");
+    private static final Class[] CAMEL_CASE_CUSTOMER = new Class[]{org.eclipse.persistence.testing.jaxb.casesensitivity.camelCase.CustomerImpl.class};
+    private static final Class[] UPPER_CASE_CUSTOMER = new Class[]{org.eclipse.persistence.testing.jaxb.casesensitivity.upperCase.CustomerImpl.class};
+    private static final boolean DEBUG = false;
+
+    private org.eclipse.persistence.testing.jaxb.casesensitivity.camelCase.CustomerImpl baseCustomer;
+    private Unmarshaller unmarshallerCamelCaseInsensitive;
+    private Unmarshaller unmarshallerUpperCaseInsensitive;
+    private Unmarshaller unmarshallerCamelCaseSensitive;
+    private Unmarshaller unmarshallerUpperCaseSensitive;
+
+    @Test
+    public void testMain() throws Exception {
+        if (DEBUG) System.out.println("Case-insensitive unmarshalling test.");
+        assertTrue(unmarshalCamelCaseInsensitive().equals(baseCustomer));
+        assertTrue(unmarshalUpperCaseInsensitive().equals(baseCustomer));
+        if (DEBUG) System.out.println("Case-sensitive unmarshalling test.");
+        assertTrue(unmarshalCamelCaseSensitive().equals(baseCustomer));
+        assertFalse(unmarshalUpperCaseSensitive().equals(baseCustomer));
+    }
+
+    /* case insensitive part */
+    private org.eclipse.persistence.testing.jaxb.casesensitivity.camelCase.CustomerImpl unmarshalCamelCaseInsensitive() throws JAXBException {
+
+        org.eclipse.persistence.testing.jaxb.casesensitivity.camelCase.CustomerImpl camelCaseCustomer
+                = (org.eclipse.persistence.testing.jaxb.casesensitivity.camelCase.CustomerImpl) unmarshallerCamelCaseInsensitive.unmarshal(FILE);
+        if (DEBUG) System.out.println(camelCaseCustomer);
+
+        return camelCaseCustomer;
+    }
+
+    private org.eclipse.persistence.testing.jaxb.casesensitivity.upperCase.CustomerImpl unmarshalUpperCaseInsensitive() throws JAXBException {
+
+        org.eclipse.persistence.testing.jaxb.casesensitivity.upperCase.CustomerImpl upperCaseCustomer
+                = (org.eclipse.persistence.testing.jaxb.casesensitivity.upperCase.CustomerImpl) unmarshallerUpperCaseInsensitive.unmarshal(FILE);
+        if (DEBUG) System.out.println(upperCaseCustomer);
+
+        return upperCaseCustomer;
+    }
+
+    /* case sensitive part */
+    private org.eclipse.persistence.testing.jaxb.casesensitivity.camelCase.CustomerImpl unmarshalCamelCaseSensitive() throws JAXBException {
+
+        org.eclipse.persistence.testing.jaxb.casesensitivity.camelCase.CustomerImpl camelCaseCustomer
+                = (org.eclipse.persistence.testing.jaxb.casesensitivity.camelCase.CustomerImpl) unmarshallerCamelCaseSensitive.unmarshal(FILE);
+        if (DEBUG) System.out.println(camelCaseCustomer);
+
+        return camelCaseCustomer;
+    }
+
+    private org.eclipse.persistence.testing.jaxb.casesensitivity.upperCase.CustomerImpl unmarshalUpperCaseSensitive() throws JAXBException {
+
+        org.eclipse.persistence.testing.jaxb.casesensitivity.upperCase.CustomerImpl upperCaseCustomer
+                = (org.eclipse.persistence.testing.jaxb.casesensitivity.upperCase.CustomerImpl) unmarshallerUpperCaseSensitive.unmarshal(FILE);
+        if (DEBUG) System.out.println(upperCaseCustomer);
+
+        return upperCaseCustomer;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        baseCustomer = new org.eclipse.persistence.testing.jaxb.casesensitivity.camelCase.CustomerImpl();
+        baseCustomer.setId(1234007);
+        baseCustomer.setAge(24);
+        baseCustomer.setName("cafeBabe");
+
+        /* Create and assign case-insensitive unmarshallers */
+        Map<String, Boolean> properties = new HashMap<String, Boolean>();
+        properties.put(UnmarshallerProperties.UNMARSHALLING_CASE_INSENSITIVE, Boolean.TRUE);
+        JAXBContext ctxCamelCaseInsensitive = JAXBContextFactory.createContext(CAMEL_CASE_CUSTOMER, properties);
+        JAXBContext ctxUpperCaseInsensitive = JAXBContextFactory.createContext(UPPER_CASE_CUSTOMER, properties);
+
+        unmarshallerCamelCaseInsensitive = ctxCamelCaseInsensitive.createUnmarshaller();
+        unmarshallerUpperCaseInsensitive = ctxUpperCaseInsensitive.createUnmarshaller();
+
+        /* Create and assign case-sensitive unmarshallers */
+        properties.put(UnmarshallerProperties.UNMARSHALLING_CASE_INSENSITIVE, Boolean.FALSE);
+        JAXBContext ctxCamelCaseSensitive = JAXBContextFactory.createContext(CAMEL_CASE_CUSTOMER, properties);
+        JAXBContext ctxUpperCaseSensitive = JAXBContextFactory.createContext(UPPER_CASE_CUSTOMER, properties);
+
+        unmarshallerCamelCaseSensitive = ctxCamelCaseSensitive.createUnmarshaller();
+        unmarshallerUpperCaseSensitive = ctxUpperCaseSensitive.createUnmarshaller();
+
+        /* Marshal the baseCustomer into the customer.xml file. */
+        Marshaller marshallerInsensitive = ctxCamelCaseInsensitive.createMarshaller();
+        marshallerInsensitive.marshal(baseCustomer, FILE);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        baseCustomer = null;
+        unmarshallerCamelCaseInsensitive = unmarshallerUpperCaseInsensitive = unmarshallerCamelCaseSensitive = unmarshallerUpperCaseSensitive = null;
+    }
+}
diff --git a/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/casesensitivity/camelCase/CustomerImpl.java b/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/casesensitivity/camelCase/CustomerImpl.java
new file mode 100644
index 0000000..f2a8ca8
--- /dev/null
+++ b/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/casesensitivity/camelCase/CustomerImpl.java
@@ -0,0 +1,68 @@
+package org.eclipse.persistence.testing.jaxb.casesensitivity.camelCase;
+
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * @author Marcel Valovy - marcel.valovy@oracle.com
+ */
+@XmlRootElement
+public class CustomerImpl extends org.eclipse.persistence.testing.jaxb.casesensitivity.Customer {
+
+    private String name;
+    private int age;
+    private int id;
+
+    public String getName() {
+        return name;
+    }
+
+    @XmlElement
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getAge() {
+        return age;
+    }
+
+    @XmlAttribute
+    public void setAge(int age) {
+        this.age = age;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    @XmlAttribute
+    public void setId(int id) {
+        this.id = id;
+    }
+
+
+    @Override
+    public String toString() {
+        return "camelCase.Customer{" +
+                "name='" + name + '\'' +
+                ", age=" + age +
+                ", id=" + id +
+                '}';
+    }
+
+    @Override
+    public int getIdBridge() {
+        return id;
+    }
+
+    @Override
+    public int getAgeBridge() {
+        return age;
+    }
+
+    @Override
+    public String getNameBridge() {
+        return name;
+    }
+}
\ No newline at end of file
diff --git a/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/casesensitivity/upperCase/CustomerImpl.java b/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/casesensitivity/upperCase/CustomerImpl.java
new file mode 100644
index 0000000..93f76dd
--- /dev/null
+++ b/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/casesensitivity/upperCase/CustomerImpl.java
@@ -0,0 +1,71 @@
+package org.eclipse.persistence.testing.jaxb.casesensitivity.upperCase;
+
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * Implementation of Customer with Upper Case XML Elements and Attributes.
+ *
+ * @author Marcel Valovy - marcel.valovy@oracle.com
+ */
+@XmlRootElement
+public class CustomerImpl extends org.eclipse.persistence.testing.jaxb.casesensitivity.Customer {
+
+    private String NAME;
+    private int age;
+    private int ID;
+
+    public String getNAME() {
+        return NAME;
+    }
+
+    @XmlElement
+    public void setNAME(String NAME) {
+        this.NAME = NAME;
+    }
+
+    public int getAge() {
+        return age;
+    }
+
+    /* Attribute left Camel Case on purpose, to provide easier debugging. */
+    @XmlAttribute
+    public void setAge(int age) {
+        this.age = age;
+    }
+
+    public int getID() {
+        return ID;
+    }
+
+    @XmlAttribute
+    public void setID(int ID) {
+        this.ID = ID;
+    }
+
+
+    @Override
+    public String toString() {
+        return "upperCase.Customer{" +
+                "name='" + NAME + '\'' +
+                ", age=" + age +
+                ", id=" + ID +
+                '}';
+    }
+
+    @Override
+    public int getIdBridge() {
+        return ID;
+    }
+
+    @Override
+    public int getAgeBridge() {
+        return age;
+    }
+
+    @Override
+    public String getNameBridge() {
+        return NAME;
+    }
+}
\ No newline at end of file
diff --git a/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/JAXBContext.java b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/JAXBContext.java
index 6baa12e..6726668 100644
--- a/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/JAXBContext.java
+++ b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/JAXBContext.java
@@ -747,6 +747,7 @@ public class JAXBContext extends javax.xml.bind.JAXBContext {
 
         protected Map properties;
         protected ClassLoader classLoader;
+        protected boolean unmarshallingCaseInsensitive = false;
 
         /**
          * Create a new JAXBContextInput with the specified Map of properties and ClassLoader.
@@ -889,6 +890,7 @@ public class JAXBContext extends javax.xml.bind.JAXBContext {
             AnnotationHelper annotationHelper = null;
             boolean enableXmlAccessorFactory = false;
             if (properties != null) {
+                unmarshallingCaseInsensitive = Boolean.TRUE.equals(properties.get(UnmarshallerProperties.UNMARSHALLING_CASE_INSENSITIVE));
                 if ((defaultTargetNamespace = (String) properties.get(JAXBContextProperties.DEFAULT_TARGET_NAMESPACE)) == null) {
                     // try looking up the 'old' key
                     defaultTargetNamespace = (String) properties.get(JAXBContextFactory.DEFAULT_TARGET_NAMESPACE_KEY);
@@ -925,7 +927,7 @@ public class JAXBContext extends javax.xml.bind.JAXBContext {
             jModel.setHasXmlBindings(xmlBindings != null || !xmlBindings.isEmpty());
             JavaModelInputImpl inputImpl = new JavaModelInputImpl(classesToBeBound, jModel);
             try {
-                Generator generator = new Generator(inputImpl, xmlBindings, loader, defaultTargetNamespace, enableXmlAccessorFactory);
+                Generator generator = new Generator(inputImpl, xmlBindings, loader, defaultTargetNamespace, enableXmlAccessorFactory, unmarshallingCaseInsensitive);
                 return createContextState(generator, loader, classesToBeBound, properties);
             } catch (Exception ex) {
                 throw new javax.xml.bind.JAXBException(ex.getMessage(), ex);
@@ -1051,6 +1053,7 @@ public class JAXBContext extends javax.xml.bind.JAXBContext {
             AnnotationHelper annotationHelper = null;
             boolean enableXmlAccessorFactory = false;
             if (properties != null) {
+                unmarshallingCaseInsensitive = Boolean.TRUE.equals(properties.get(UnmarshallerProperties.UNMARSHALLING_CASE_INSENSITIVE));
                 if ((defaultTargetNamespace = (String) properties.get(JAXBContextProperties.DEFAULT_TARGET_NAMESPACE)) == null) {
                     // try looking up the 'old' key
                     defaultTargetNamespace = (String) properties.get(JAXBContextFactory.DEFAULT_TARGET_NAMESPACE_KEY);
@@ -1094,7 +1097,7 @@ public class JAXBContext extends javax.xml.bind.JAXBContext {
 
             JavaModelInputImpl inputImpl = new JavaModelInputImpl(typesToBeBound, jModel);
             try {
-                Generator generator = new Generator(inputImpl, typesToBeBound, inputImpl.getJavaClasses(), null, xmlBindings, classLoader, defaultTargetNamespace, enableXmlAccessorFactory);
+                Generator generator = new Generator(inputImpl, typesToBeBound, inputImpl.getJavaClasses(), null, xmlBindings, classLoader, defaultTargetNamespace, enableXmlAccessorFactory, unmarshallingCaseInsensitive);
                 JAXBContextState contextState = createContextState(generator, loader, typesToBeBound, properties);
                 return contextState;
             } catch (Exception ex) {
diff --git a/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/JAXBUnmarshaller.java b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/JAXBUnmarshaller.java
index 09705a2..0aabf66 100644
--- a/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/JAXBUnmarshaller.java
+++ b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/JAXBUnmarshaller.java
@@ -812,7 +812,7 @@ public class JAXBUnmarshaller implements Unmarshaller {
     }
 
     /**
-     * Get a property from the JAXBMarshaller. Attempting to get any unsupported
+     * Get a property from the JAXBUnmarshaller. Attempting to get any unsupported
      * property will result in a javax.xml.bind.PropertyException 
      * See <a href="#supportedProps">Supported Properties</a>.  
      * @see org.eclipse.persistence.jaxb.UnmarshallerProperties
diff --git a/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/UnmarshallerProperties.java b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/UnmarshallerProperties.java
index 833050d..428400e 100644
--- a/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/UnmarshallerProperties.java
+++ b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/UnmarshallerProperties.java
@@ -13,8 +13,8 @@
 package org.eclipse.persistence.jaxb;
 
 /**
- * These are properties that may be set on an instance of Unmarshaller.  Below 
- * is an example of using the property mechanism to enable MOXy's JSON binding 
+ * These are properties that may be set on an instance of Unmarshaller.  Below
+ * is an example of using the property mechanism to enable MOXy's JSON binding
  * for an instance of Unmarshaller.
  * <pre>
  * Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
@@ -24,7 +24,7 @@ package org.eclipse.persistence.jaxb;
 public class UnmarshallerProperties {
 
     /**
-     * The name of the property used to specify a custom IDResolver class, to 
+     * The name of the property used to specify a custom IDResolver class, to
      * allow customization of ID/IDREF processing.
      * @since 2.3.3
      * @see org.eclipse.persistence.jaxb.IDResolver
@@ -32,11 +32,11 @@ public class UnmarshallerProperties {
     public static final String ID_RESOLVER = "eclipselink.id-resolver";
 
     /**
-     * The name of the property used to specify a value that will be prepended 
-     * to all keys that are mapped to an XML attribute. By default there is no 
-     * attribute prefix.  There is no effect when media type is 
+     * The name of the property used to specify a value that will be prepended
+     * to all keys that are mapped to an XML attribute. By default there is no
+     * attribute prefix.  There is no effect when media type is
      * "application/xml".  When this property is specified at the
-     * <i>JAXBContext</i> level all instances of <i>Marshaller</i> and 
+     * <i>JAXBContext</i> level all instances of <i>Marshaller</i> and
      * <i>Unmarshaller</i> will default to this attribute prefix.
      * @since 2.4
      * @see org.eclipse.persistence.jaxb.JAXBContextProperties.JSON_ATTRIBUTE_PREFIX
@@ -48,7 +48,7 @@ public class UnmarshallerProperties {
      * The name of the property used to specify in the root node should be
      * included in the message (default is true). There is no effect when media
      * type is "application/xml".  When this property is specified at the
-     * <i>JAXBContext</i> level all instances of <i>Marshaller</i> and 
+     * <i>JAXBContext</i> level all instances of <i>Marshaller</i> and
      * <i>Unmarshaller</i> will default to this setting.
      * @since 2.4
      * @see org.eclipse.persistence.jaxb.JAXBContextProperties.JSON_INCLUDE_ROOT
@@ -68,9 +68,9 @@ public class UnmarshallerProperties {
     /**
      * The name of the property used to specify the character (default is '.')
      * that separates the prefix from the key name. It is only used if namespace
-     * qualification has been enabled be setting a namespace prefix mapper.  
-     * When this property is specified at the <i>JAXBContext</i> level all 
-     * instances of <i>Marshaller</i> and <i>Unmarshaller</i> will default to 
+     * qualification has been enabled be setting a namespace prefix mapper.
+     * When this property is specified at the <i>JAXBContext</i> level all
+     * instances of <i>Marshaller</i> and <i>Unmarshaller</i> will default to
      * this setting.
      * @since 2.4
      * @see org.eclipse.persistence.jaxb.JAXBContextProperties.NAMESPACE_SEPARATOR
@@ -81,8 +81,8 @@ public class UnmarshallerProperties {
     /**
      * The name of the property used to specify the key that will correspond to
      * the property mapped with <i>@XmlValue</i>.  This key will only be used if
-     * there are other mapped properties.  When this property is specified at 
-     * the <i>JAXBContext</i> level all instances of <i>Marshaller</i> and 
+     * there are other mapped properties.  When this property is specified at
+     * the <i>JAXBContext</i> level all instances of <i>Marshaller</i> and
      * <i>Unmarshaller</i> will default to this setting.
      * @since 2.4
      * @see org.eclipse.persistence.jaxb.JAXBContextPropertes.JSON_VALUE_WRAPPER
@@ -91,7 +91,7 @@ public class UnmarshallerProperties {
     public static final String JSON_VALUE_WRAPPER = JAXBContextProperties.JSON_VALUE_WRAPPER;
 
     /**
-     * The name of the property used to specify the type of binding to be 
+     * The name of the property used to specify the type of binding to be
      * performed.  When this property is specified at the <i>JAXBContext</i>
      * level all instances of <i>Marshaller</i> and <i>Unmarshaller</i> will
      * default to this media type. Supported values are:
@@ -109,36 +109,36 @@ public class UnmarshallerProperties {
     public static final String MEDIA_TYPE = JAXBContextProperties.MEDIA_TYPE;
 
     /**
-     * The name of the property used to specify if the media type should be  
+     * The name of the property used to specify if the media type should be
      * auto detected (default is false).  Only set to true when the media type
      * is unknown.  Otherwise set the MEDIA_TYPE property.   If the type can not
-     * be auto-detected an unmarshal with the MEDIA_TYPE value will be performed. 
+     * be auto-detected an unmarshal with the MEDIA_TYPE value will be performed.
      * @since 2.4
      * @see org.eclipse.persistence.jaxb.UnmarshallerProperties.MEDIA_TYPE
      * @see org.eclipse.persistence.oxm.MediaType
      */
     public static final String AUTO_DETECT_MEDIA_TYPE = "eclipselink.auto-detect-media-type";
 
-    
+
     public static final String OBJECT_GRAPH = "eclipselink.object-graph";
 
     /**
-     * The Constant JSON_WRAPPER_AS_ARRAY_NAME. If true the grouping 
-     * element will be used as the JSON key. There is no effect when media type 
+     * The Constant JSON_WRAPPER_AS_ARRAY_NAME. If true the grouping
+     * element will be used as the JSON key. There is no effect when media type
      * is "application/xml".  When this property is specified at the
-     * <i>JAXBContext</i> level all instances of <i>Marshaller</i> and 
+     * <i>JAXBContext</i> level all instances of <i>Marshaller</i> and
      * <i>Unmarshaller</i> will default to this.
-     * 
+     *
      * <p><b>Example</b></p>
      * <p>Given the following class:</p>
      * <pre>
      * &#64;XmlAccessorType(XmlAccessType.FIELD)
      * public class Customer {
-     * 
+     *
      *     &#64;XmlElementWrapper(name="phone-numbers")
      *     &#64;XmlElement(name="phone-number")
      *     private List<PhoneNumber> phoneNumbers;
-     * 
+     *
      * }
      * </pre>
      * <p>If the property is set to false (the default) the JSON output will be:</p>
@@ -169,4 +169,37 @@ public class UnmarshallerProperties {
      */
     public static final String JSON_WRAPPER_AS_ARRAY_NAME = JAXBContextProperties.JSON_WRAPPER_AS_ARRAY_NAME;
 
+    /**
+     * If set to <i>Boolean.TRUE</i>, {@link org.eclipse.persistence.jaxb.JAXBUnmarshaller} will match
+     * XML Elements and XML Attributes to Java fields case insensitively.
+     *
+     * <p><b>Example</b></p>
+     * <p>Given the following class:</p>
+     * <pre>
+     * &#64;XmlAccessorType(XmlAccessType.FIELD)
+     * public class Customer {
+     *
+     *     &#64;XmlElement
+     *     private String name;
+     *     &#64;XmlAttribute
+     *     private int id;
+     *
+     * }
+     * </pre>
+     * <p>If the property is set to true, the following XML object will match the class and will be unmarshaled.</p>
+     * <pre>
+     * &lt;customer iD="007"&gt;
+     *   &lt;nAMe&gt;cafeBabe&lt;/nAMe&gt;
+     * &lt;/customer&gt;
+     * </pre>
+     *
+     * <p><b>By default, case-insensitive unmarshalling is turned off.</b><p/>
+     *
+     * <p>The property must be passed to the {@link org.eclipse.persistence.jaxb.JAXBContextFactory}, when creating
+     * {@link org.eclipse.persistence.jaxb.JAXBContext}. It will affect only unmarshaller created from that context.</p>
+     *
+     * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=331241">EclipseLink Forum, Bug 331241.</a>
+     */
+    public static final String UNMARSHALLING_CASE_INSENSITIVE = "eclipselink.unmarshalling.case-insensitive";
+
 }
\ No newline at end of file
diff --git a/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/compiler/Generator.java b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/compiler/Generator.java
index 65f3963..493f7b1 100644
--- a/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/compiler/Generator.java
+++ b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/compiler/Generator.java
@@ -25,6 +25,7 @@ import javax.xml.bind.SchemaOutputResolver;
 import javax.xml.namespace.QName;
 
 import org.eclipse.persistence.core.sessions.CoreProject;
+import org.eclipse.persistence.internal.jaxb.JaxbClassLoader;
 import org.eclipse.persistence.internal.oxm.Constants;
 import org.eclipse.persistence.internal.oxm.mappings.Descriptor;
 import org.eclipse.persistence.internal.oxm.schema.SchemaModelProject;
@@ -33,6 +34,7 @@ import org.eclipse.persistence.jaxb.TypeMappingInfo;
 import org.eclipse.persistence.jaxb.javamodel.Helper;
 import org.eclipse.persistence.jaxb.javamodel.JavaClass;
 import org.eclipse.persistence.jaxb.javamodel.JavaModelInput;
+import org.eclipse.persistence.jaxb.javamodel.reflection.JavaModelInputImpl;
 import org.eclipse.persistence.jaxb.xmlmodel.XmlBindings;
 import org.eclipse.persistence.oxm.NamespaceResolver;
 import org.eclipse.persistence.oxm.XMLContext;
@@ -66,6 +68,7 @@ public class Generator {
     private MappingsGenerator mappingsGenerator;
     private Helper helper;
     private Map<Type, TypeMappingInfo> typeToTypeMappingInfo;
+    private boolean unmarshallingCaseInsensitive = false;
 
     /**
      * This is the preferred constructor.
@@ -94,6 +97,21 @@ public class Generator {
      * @param cLoader
      */
     public Generator(JavaModelInput jModelInput, Map<String, XmlBindings> xmlBindings, ClassLoader cLoader, String defaultTargetNamespace, boolean enableXmlAccessorFactory) {
+        this(jModelInput, xmlBindings, cLoader, defaultTargetNamespace, enableXmlAccessorFactory, false);
+    }
+
+    /**
+     * This constructor will process and apply the given XmlBindings as appropriate.  Classes
+     * declared in the bindings will be amalgamated with any classes in the JavaModelInput.
+     *
+     * If xmlBindings is null or empty, AnnotationsProcessor will be used to process
+     * annotations as per usual.
+     *
+     * @param jModelInput
+     * @param xmlBindings map of XmlBindings keyed on package name
+     * @param cLoader
+     */
+    public Generator(JavaModelInput jModelInput, Map<String, XmlBindings> xmlBindings, ClassLoader cLoader, String defaultTargetNamespace, boolean enableXmlAccessorFactory, boolean unmarshallingCaseInsensitive) {
         helper = new Helper(jModelInput.getJavaModel());
         annotationsProcessor = new AnnotationsProcessor(helper);
         annotationsProcessor.setXmlAccessorFactorySupport(enableXmlAccessorFactory);
@@ -105,6 +123,7 @@ public class Generator {
         } else {
             annotationsProcessor.processClassesAndProperties(jModelInput.getJavaClasses(), null);
         }
+        this.unmarshallingCaseInsensitive = unmarshallingCaseInsensitive;
     }
     
     /**
@@ -123,20 +142,37 @@ public class Generator {
         this.typeToTypeMappingInfo = typeToTypeMappingInfo;
         annotationsProcessor.processClassesAndProperties(javaClasses, typeMappingInfos);
     }
-    
+
+        /**
+         * This constructor will process and apply the given XmlBindings as appropriate.  Classes
+         * declared in the bindings will be amalgamated with any classes in the JavaModelInput.
+         *
+         * If xmlBindings is null or empty, AnnotationsProcessor will be used to process
+         * annotations as per usual.
+         *
+         * @param jModelInput
+         * @param javaClassToType
+         * @param xmlBindings map of XmlBindings keyed on package name
+         * @param cLoader
+         */
+    public Generator(JavaModelInput jModelInput, TypeMappingInfo[] typeMappingInfos, JavaClass[] javaClasses, Map<Type, TypeMappingInfo> typeToTypeMappingInfo, Map<String, XmlBindings> xmlBindings, ClassLoader cLoader, String defaultTargetNamespace, boolean enableXmlAccessorFactory) {
+        this(jModelInput, typeMappingInfos, javaClasses, typeToTypeMappingInfo, xmlBindings, cLoader, defaultTargetNamespace, enableXmlAccessorFactory, false);
+    }
+
     /**
      * This constructor will process and apply the given XmlBindings as appropriate.  Classes
      * declared in the bindings will be amalgamated with any classes in the JavaModelInput.
-     *  
-     * If xmlBindings is null or empty, AnnotationsProcessor will be used to process 
+     *
+     * If xmlBindings is null or empty, AnnotationsProcessor will be used to process
      * annotations as per usual.
-     *  
+     *
      * @param jModelInput
      * @param javaClassToType
      * @param xmlBindings map of XmlBindings keyed on package name
      * @param cLoader
-     */    
-    public Generator(JavaModelInput jModelInput, TypeMappingInfo[] typeMappingInfos, JavaClass[] javaClasses, Map<Type, TypeMappingInfo> typeToTypeMappingInfo, Map<String, XmlBindings> xmlBindings, ClassLoader cLoader, String defaultTargetNamespace, boolean enableXmlAccessorFactory) {
+     * @param unmarshallingCaseInsensitive enable case-insensitive unmarshalling
+     */
+    public Generator(JavaModelInput jModelInput, TypeMappingInfo[] typeMappingInfos, JavaClass[] javaClasses, Map<Type, TypeMappingInfo> typeToTypeMappingInfo, Map<String, XmlBindings> xmlBindings, ClassLoader cLoader, String defaultTargetNamespace, boolean enableXmlAccessorFactory, boolean unmarshallingCaseInsensitive) {
         helper = new Helper(jModelInput.getJavaModel());
         annotationsProcessor = new AnnotationsProcessor(helper);
         annotationsProcessor.setXmlAccessorFactorySupport(enableXmlAccessorFactory);
@@ -147,8 +183,9 @@ public class Generator {
         if (xmlBindings != null && xmlBindings.size() > 0) {
             new XMLProcessor(xmlBindings).processXML(annotationsProcessor, jModelInput, typeMappingInfos, javaClasses);
         } else {
-        	annotationsProcessor.processClassesAndProperties(javaClasses, typeMappingInfos);
+            annotationsProcessor.processClassesAndProperties(javaClasses, typeMappingInfos);
         }
+        this.unmarshallingCaseInsensitive = unmarshallingCaseInsensitive;
     }
 
     /**
@@ -185,7 +222,7 @@ public class Generator {
 
     public CoreProject generateProject() throws Exception {
         mappingsGenerator.getClassToGeneratedClasses().putAll(annotationsProcessor.getArrayClassesToGeneratedClasses());
-        CoreProject p = mappingsGenerator.generateProject(annotationsProcessor.getTypeInfoClasses(), annotationsProcessor.getTypeInfo(), annotationsProcessor.getUserDefinedSchemaTypes(), annotationsProcessor.getPackageToPackageInfoMappings(), annotationsProcessor.getGlobalElements(), annotationsProcessor.getLocalElements(), annotationsProcessor.getTypeMappingInfoToGeneratedClasses(), annotationsProcessor.getTypeMappingInfoToAdapterClasses(),annotationsProcessor.isDefaultNamespaceAllowed());
+        CoreProject p = mappingsGenerator.generateProject(annotationsProcessor.getTypeInfoClasses(), annotationsProcessor.getTypeInfo(), annotationsProcessor.getUserDefinedSchemaTypes(), annotationsProcessor.getPackageToPackageInfoMappings(), annotationsProcessor.getGlobalElements(), annotationsProcessor.getLocalElements(), annotationsProcessor.getTypeMappingInfoToGeneratedClasses(), annotationsProcessor.getTypeMappingInfoToAdapterClasses(),annotationsProcessor.isDefaultNamespaceAllowed(), unmarshallingCaseInsensitive);
         annotationsProcessor.getArrayClassesToGeneratedClasses().putAll(mappingsGenerator.getClassToGeneratedClasses());
         return p;
     }
diff --git a/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/compiler/MappingsGenerator.java b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/compiler/MappingsGenerator.java
index c128b13..fcadc21 100644
--- a/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/compiler/MappingsGenerator.java
+++ b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/compiler/MappingsGenerator.java
@@ -218,6 +218,10 @@ public class MappingsGenerator {
     }
 
     public CoreProject generateProject(ArrayList<JavaClass> typeInfoClasses, HashMap<String, TypeInfo> typeInfo, HashMap<String, QName> userDefinedSchemaTypes, HashMap<String, PackageInfo> packageToPackageInfoMappings, HashMap<QName, ElementDeclaration> globalElements, List<ElementDeclaration> localElements, Map<TypeMappingInfo, Class> typeMappingInfoToGeneratedClass, Map<TypeMappingInfo, Class> typeMappingInfoToAdapterClasses,  boolean isDefaultNamespaceAllowed) throws Exception {
+        return generateProject(typeInfoClasses, typeInfo, userDefinedSchemaTypes, packageToPackageInfoMappings, globalElements, localElements, typeMappingInfoToGeneratedClass, typeMappingInfoToAdapterClasses, isDefaultNamespaceAllowed, false);
+    }
+
+    public CoreProject generateProject(ArrayList<JavaClass> typeInfoClasses, HashMap<String, TypeInfo> typeInfo, HashMap<String, QName> userDefinedSchemaTypes, HashMap<String, PackageInfo> packageToPackageInfoMappings, HashMap<QName, ElementDeclaration> globalElements, List<ElementDeclaration> localElements, Map<TypeMappingInfo, Class> typeMappingInfoToGeneratedClass, Map<TypeMappingInfo, Class> typeMappingInfoToAdapterClasses,  boolean isDefaultNamespaceAllowed, boolean unmarshallingCaseInsensitive) throws Exception {
         this.typeInfo = typeInfo;
         this.userDefinedSchemaTypes = userDefinedSchemaTypes;
         this.packageToPackageInfoMappings = packageToPackageInfoMappings;
@@ -240,9 +244,9 @@ public class MappingsGenerator {
                 setupInheritance(next);
             }
         }
-        
+
         // Now create mappings
-        generateMappings();
+        generateMappings(unmarshallingCaseInsensitive);
         
         // Setup AttributeGroups
         for(JavaClass next : typeInfoClasses) {
@@ -642,6 +646,18 @@ public class MappingsGenerator {
      * @return newly created mapping
      */
     public Mapping generateMapping(Property property, Descriptor descriptor, JavaClass descriptorJavaClass, NamespaceInfo namespaceInfo) {
+        return generateMapping(property, descriptor, descriptorJavaClass, namespaceInfo, false);
+    }
+
+    /**
+     * Generate a mapping for a given Property.
+     *
+     * @param property
+     * @param descriptor
+     * @param namespaceInfo
+     * @return newly created mapping
+     */
+    public Mapping generateMapping(Property property, Descriptor descriptor, JavaClass descriptorJavaClass, NamespaceInfo namespaceInfo, boolean unmarshallingCaseInsensitive) {
         if (property.isSetXmlJavaTypeAdapter()) {
             // if we are dealing with a reference, generate mapping and return 
             if (property.isReference()) {
@@ -850,7 +866,7 @@ public class MappingsGenerator {
         if (property.isXmlLocation()) {
             return null;
         }
-        return generateDirectMapping(property, descriptor, namespaceInfo);
+        return generateDirectMapping(property, descriptor, namespaceInfo, unmarshallingCaseInsensitive);
     }
 
     private Mapping generateVariableXPathCollectionMapping(Property property, Descriptor descriptor, NamespaceInfo namespaceInfo, JavaClass actualType) {    	
@@ -1614,6 +1630,9 @@ public class MappingsGenerator {
     }
 
     public DirectMapping generateDirectMapping(Property property, Descriptor descriptor, NamespaceInfo namespaceInfo) {
+        return generateDirectMapping(property, descriptor, namespaceInfo, false);
+    }
+    public DirectMapping generateDirectMapping(Property property, Descriptor descriptor, NamespaceInfo namespaceInfo, boolean unmarshallingCaseInsensitive) {
     	DirectMapping mapping = new XMLDirectMapping();
         mapping.setNullValueMarshalled(true);
         
@@ -1622,9 +1641,9 @@ public class MappingsGenerator {
             mapping.setIsWriteOnly(true);
         }
         initializeXMLMapping((XMLMapping)mapping, property);
-   
+
         // if the XPath is set (via xml-path) use it; otherwise figure it out
-        Field xmlField = getXPathForField(property, namespaceInfo, true, false);
+        Field xmlField = getXPathForField(property, namespaceInfo, true, false, unmarshallingCaseInsensitive);
         mapping.setField(xmlField);
 
         if (property.getDefaultValue() != null) {
@@ -2416,6 +2435,10 @@ public class MappingsGenerator {
     }
 
     public void generateMappings() {
+        generateMappings(false);
+    }
+
+    public void generateMappings(boolean unmarshallingCaseInsensitive) {
         Iterator javaClasses = this.typeInfo.keySet().iterator();
         while (javaClasses.hasNext()) {
             String next = (String)javaClasses.next();
@@ -2428,7 +2451,7 @@ public class MappingsGenerator {
 
             Descriptor descriptor = info.getDescriptor();
             if (descriptor != null) {
-                generateMappings(info, descriptor, javaClass, namespaceInfo);
+                generateMappings(info, descriptor, javaClass, namespaceInfo, unmarshallingCaseInsensitive);
                 // set primary key fields (if necessary)
                 CoreMapping mapping;
                 // handle XmlID
@@ -2457,9 +2480,23 @@ public class MappingsGenerator {
      *
      * @param info
      * @param descriptor
+     * @param descriptorJavaClass
      * @param namespaceInfo
      */
     public void generateMappings(TypeInfo info, Descriptor descriptor, JavaClass descriptorJavaClass, NamespaceInfo namespaceInfo) {
+        generateMappings(info, descriptor, descriptorJavaClass, namespaceInfo, false);
+    }
+
+    /**
+     * Generate mappings for a given TypeInfo.
+     *
+     * @param info
+     * @param descriptor
+     * @param namespaceInfo
+     * @param descriptorJavaClass
+     * @param unmarshallingCaseInsensitive
+     */
+    public void generateMappings(TypeInfo info, Descriptor descriptor, JavaClass descriptorJavaClass, NamespaceInfo namespaceInfo, boolean unmarshallingCaseInsensitive) {
         if(info.isAnonymousComplexType()) {
             //may need to generate inherited mappings
             generateInheritedMappingsForAnonymousType(info, descriptor, descriptorJavaClass, namespaceInfo);
@@ -2468,7 +2505,7 @@ public class MappingsGenerator {
         for (int i = 0; i < propertiesInOrder.size(); i++) {
             Property next = propertiesInOrder.get(i);
             if (next != null && (!next.isTransient() || (next.isTransient() && next.isXmlLocation()))) {
-                Mapping mapping = generateMapping(next, descriptor, descriptorJavaClass, namespaceInfo);
+                Mapping mapping = generateMapping(next, descriptor, descriptorJavaClass, namespaceInfo, unmarshallingCaseInsensitive);
                 if (next.isVirtual()) {
                     VirtualAttributeAccessor accessor = new VirtualAttributeAccessor();
                     accessor.setAttributeName(mapping.getAttributeName());
@@ -2755,13 +2792,17 @@ public class MappingsGenerator {
         }
         return newXPath;
     }
-    
+
     public Field getXPathForField(Property property, NamespaceInfo namespaceInfo, boolean isTextMapping, boolean isAny) {
+        return getXPathForField(property, namespaceInfo, isTextMapping, isAny, false);
+    }
+
+    public Field getXPathForField(Property property, NamespaceInfo namespaceInfo, boolean isTextMapping, boolean isAny, boolean unmarshallingCaseInsensitive) {
         Field xmlField = null;
         String xPath = property.getXmlPath();
         if (null != xPath) {
             String newXPath = prefixCustomXPath(xPath, property, namespaceInfo);
-            xmlField = new XMLField(newXPath);
+            xmlField = new XMLField(newXPath, unmarshallingCaseInsensitive);
         } else {
             xPath = "";
             if (property.isSetXmlElementWrapper()) {
@@ -2784,7 +2825,7 @@ public class MappingsGenerator {
 
                 if (isAny || property.isMap()) {
                     xPath = xPath.substring(0, xPath.length() - 1);
-                    xmlField = new XMLField(xPath);
+                    xmlField = new XMLField(xPath, unmarshallingCaseInsensitive);
                     return xmlField;
                 }
 
@@ -2802,7 +2843,7 @@ public class MappingsGenerator {
                     	xPath += ATT + getQualifiedString(prefix, name.getLocalPart());
                     }
                 }
-                xmlField = new XMLField(xPath);
+                xmlField = new XMLField(xPath, unmarshallingCaseInsensitive);
             } else if (property.isXmlValue()) {                
             	if(isBinaryData(property.getActualType())){
             		xmlField = new XMLField(".");
@@ -2811,7 +2852,7 @@ public class MappingsGenerator {
             	}
             } else {
                 QName elementName = property.getSchemaName();
-                xmlField = getXPathForElement(xPath, elementName, namespaceInfo, isTextMapping);
+                xmlField = getXPathForElement(xPath, elementName, namespaceInfo, isTextMapping, unmarshallingCaseInsensitive);
             }
         }
 
@@ -2833,6 +2874,10 @@ public class MappingsGenerator {
     }
 
     public Field getXPathForElement(String path, QName elementName, NamespaceInfo namespaceInfo, boolean isText) {
+        return getXPathForElement(path, elementName, namespaceInfo, isText, false);
+    }
+
+    public Field getXPathForElement(String path, QName elementName, NamespaceInfo namespaceInfo, boolean isText, boolean unmarshallingCaseInsensitive) {
         String namespace = "";
         if (!elementName.getNamespaceURI().equals(Constants.EMPTY_STRING)) {
             namespace = elementName.getNamespaceURI();
@@ -2849,7 +2894,7 @@ public class MappingsGenerator {
                 path += TXT;
             }
         }
-        return new XMLField(path);
+        return new XMLField(path, unmarshallingCaseInsensitive);
     }
 
     public Property getXmlValueFieldForSimpleContent(ArrayList<Property> properties) {
-- 
1.8.3.2
Comment 4 Marcel Valovy CLA 2014-03-25 10:07:02 EDT
Comment on attachment 241217 [details]
Git patch

The file is obsolete, please, use the new version of this patch, called "Bug-331241-EnhReq-CIUnmarshalling_2.patch".
Comment 5 Marcel Valovy CLA 2014-03-25 10:08:28 EDT
Created attachment 241223 [details]
Git patch - new version

This version creates testing resource "customer.xml" in a more appropriate destination.
Comment 6 Marcel Valovy CLA 2014-03-26 11:03:56 EDT
Reposting the mail conversation so that we can continue discussing in here:

-------------------------------------------------------------------------

Ad 1. I agree with You that it doesn't look good. I view it as an unnecessary coupling in the code. I told Iaroslav Savytskyi about it and we share the opinion that it should be changed, but not to introduce such refactorings in this patch.

The reason itself for impacting DatabaseField (adding the field to it, instead to XMLField class) is that when we call XMLField (post)constructor, it calls the DatabaseField (post)constructor, and from there the method setName() is called on not yet fully-initialized instance of XMLField. The method requires the unmarshallingCaseInsensitive field to be already initialized, and that cannot be done any sooner than in the DatabaseField postconstructor.

Ad 2. I wanted the same, but I could not find a way to do it by only affecting the unmarshaller.

The problem is that the unmarshalled properties are mapped against a HashMap of XMLFields representing the mapped JAXB class. This HashMap gets created during the creation of the JAXBContext.
My solution is that when the unmarshallingCaseInsensitive property is present in JAXBContext, the results of equals() and hashCode() methods on XPathFragment get altered during the creation of this HashMap of properties, so that all the properties are behaving as lowercased when searched/put to the map. Then when unmarshalled properties (XPathFragments) are searched in the map, they use these altered methods aswell (but not yet, viz 3.) and they will find the case-insensitive matches.

3. In addition to that, I found out that my test case didn't fully cover the unmarshalling possibilities and that for case insensitive unmarshalling to fully work, I have to make an additional change to the XPathFragment class:

line 359 (equals()) - from:

if (null != localName && (xPathFragment.unmarshallingCaseInsensitive) ? (...)

to:

if (null != localName && ((unmarshallingCaseInsensitive || xPathFragment.unmarshallingCaseInsensitive) ? (...)

line 391 (hashCode()) - from:

return unmarshallingCaseInsensitive ? localName.toLowerCase().hashCode() : localName.hashCode();

to:

return localName.toLowerCase().hashCode();

Reason is that the XPathFragments created from Unmarshaller are not aware of the casesensitivity property anymore. This introduces overhead for the hashCode() method.

So to avoid this, I am wondering if there is a way to access properties on unmarshaller from the XPathFragment in MOXy?

It would indeed be nice to have the property set on unmarshaller and to access it from the XPathFragment. Then there would not be needed the changes I described - no overhead for hashCode().
-------------------------------------------------------------------------

On 25.3.2014 19:08, Blaise Doughan wrote:
 A couple items:

     If possible the change should be made without impacting DatabaseField, is there a reason why the necessary logic couldn't be put on XMLField?
     I envisioned this being an Unmarshaller property, as such I was surprised to see any changes in Generator and MappingsGenerator, why where these changes required?
Comment 7 Marcel Valovy CLA 2014-03-27 08:18:54 EDT
Comment on attachment 241223 [details]
Git patch - new version

Rewritten the code. This is obsolete.
Comment 8 Marcel Valovy CLA 2014-03-27 08:20:46 EDT
Created attachment 241322 [details]
Git patch - v3

Implemented as an Unmarshaller property.
Comment 9 Martin Grebac CLA 2014-03-28 09:19:11 EDT
Cau,
 overall approach looks good to me, just a little nit-picks, gotchas and general guidelines.

- keep in mind to update year (to 2014) in licence headers in any touched files; all your new files should contain licence header, too, with only 2014 in it
 
- it's better to avoid whitespace/formatting changes together with a fix - makes it harder for reviewers to focus on the real semantic changes, as well as makes backports more difficult; if required I recommend submitting whitespace changes in a separate commit/changeset

- UnmarshalRecordImpl:15 - explicit imports are better than wildcard imports, see
http://stackoverflow.com/questions/2067158/what-is-the-proper-style-for-listing-imports-in-java

- UnmarshalRecordImpl:1220, 1323, ...

    - it's not best practice to optimize prematurely, but this piece of code is called for every startElement (correct me if I'm wrong), so it strikes me whether TreeMap is not too slow compared to HashMap? Wouldn't some kind of case-insensitive HashMap or some other solution deliver better performance?

    - as for the comparator, using simple toLowerCase() fails 'The Turkey test' (see attached - maybe good to rework as a test, too); for proper implementation I believe we have to find a solution using equalsIgnoreCase() or any kind of its semantic alternative instead

    - instanceof may get tricky, too, because it is not the fastest operation to be called within every startelement; it's reasonably fast in simple cases, but in case an inheritence tree grows, suddenly some parts of your code may become slower and it may not be clear why - so solution without instanceof may be better
Comment 10 Martin Grebac CLA 2014-03-28 09:22:41 EDT
Created attachment 241371 [details]
The turkey test
Comment 11 Marcel Valovy CLA 2014-03-28 14:21:32 EDT
Hi Martin,

Thanks for the valuable review!

1) I agree on everything You said about formatting.

I used the imported formatting settings: "eclipselink.runtime/project-admin/EclipseLink-Eclipse-Format.xml", but the trouble still slipped through :(
I will revert the whitespaces. And thanks for the interesting article on Imports, I wasn't aware of that!

2) Ok, the compare method was in fact not good. Fixed version would look like this:
public int compare(XPathFragment o1, XPathFragment o2) {
    return o1.getLocalName().compareToIgnoreCase(o2.getLocalName());
}

3) I am curious about the overhead. I expect that it can be significant if the map grows large. I will write performance test cases and we could discuss this then, with some numbers in our hands. I propose to try:
 - TreeMap (as is now).
 - Custom HashMap.
 - Wrapper object around XPFragment + standard HashMap.
 - Iterate through the HashMap, store the original localNames in some auxiliary data structure and change the localNames on the map to lowerCase. Then replace the lowerCase names with the original ones.

 + open to more suggestions.

4) It is a good point that the inheritance tree here is large. The instanceof would check against: TreeMap, AbstractMap, NavigableMap, SortedMap, Map.
On the other hand, the check is only performed if Case Insensitivity is turned on And only the first time it will check against all 5 Maps, the second time it will go true on the first comparison and skip the - I hope, I might search through the JDK source codes and find the source code for instanceof.

Compared to its close alternatives, instanceof is the best choice in terms of performance:
http://stackoverflow.com/questions/496928/what-is-the-difference-between-instanceof-and-class-isassignablefrom

Nevertheless, we could replace the instanceof check by an instance field for storing the information whether the map has already been transformed.

- Best Regards,
Marcel
Comment 12 Marcel Valovy CLA 2014-04-03 09:08:58 EDT
Created attachment 241550 [details]
CaseInsensitiveUnmarshalling.patch
Comment 13 Martin Grebac CLA 2014-04-04 04:19:10 EDT
Looks good to me.
Comment 15 Eclipse Webmaster CLA 2022-06-09 10:17:05 EDT
The Eclipselink project has moved to Github: https://github.com/eclipse-ee4j/eclipselink
Comment 16 Eclipse Webmaster CLA 2022-06-09 10:30:10 EDT
The Eclipselink project has moved to Github: https://github.com/eclipse-ee4j/eclipselink