You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by sa...@apache.org on 2017/06/23 14:14:38 UTC

[1/6] lucene-solr:branch_6x: SOLR-10503, SOLR-10502: Deprecate CurrencyField in favor of new CurrencyFieldType, which works with point fields and provides control over dynamic fields used for the raw amount and currency code sub-fields.

Repository: lucene-solr
Updated Branches:
  refs/heads/branch_6x 2ad8a6afd -> 5232b2ce5
  refs/heads/master af1ee47f2 -> a6f4f2dd9


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/example/example-DIH/solr/mail/conf/managed-schema
----------------------------------------------------------------------
diff --git a/solr/example/example-DIH/solr/mail/conf/managed-schema b/solr/example/example-DIH/solr/mail/conf/managed-schema
index 7b058eb..e027717 100644
--- a/solr/example/example-DIH/solr/mail/conf/managed-schema
+++ b/solr/example/example-DIH/solr/mail/conf/managed-schema
@@ -138,8 +138,10 @@
    <dynamicField name="*_i"  type="int"    indexed="true"  stored="true"/>
    <dynamicField name="*_is" type="int"    indexed="true"  stored="true"  multiValued="true"/>
    <dynamicField name="*_s"  type="string"  indexed="true"  stored="true" />
+   <dynamicField name="*_s_ns"  type="string"  indexed="true"  stored="false" />
    <dynamicField name="*_ss" type="string"  indexed="true"  stored="true" multiValued="true"/>
    <dynamicField name="*_l"  type="long"   indexed="true"  stored="true"/>
+   <dynamicField name="*_l_ns"  type="long"   indexed="true"  stored="false"/>
    <dynamicField name="*_ls" type="long"   indexed="true"  stored="true"  multiValued="true"/>
    <dynamicField name="*_t"  type="text_general"    indexed="true"  stored="true"/>
    <dynamicField name="*_txt" type="text_general"   indexed="true"  stored="true" multiValued="true"/>
@@ -623,17 +625,22 @@
 
    <!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
         Parameters:
-          defaultCurrency: Specifies the default currency if none specified. Defaults to "USD"
-          precisionStep:   Specifies the precisionStep for the TrieLong field used for the amount
-          providerClass:   Lets you plug in other exchange provider backend:
-                           solr.FileExchangeRateProvider is the default and takes one parameter:
-                             currencyConfig: name of an xml file holding exchange rates
-                           solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
-                             ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
-                             refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
+          amountLongSuffix: Required. Refers to a dynamic field for the raw amount sub-field. 
+                              The dynamic field must have a field type that extends LongValueFieldType.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          codeStrSuffix:    Required. Refers to a dynamic field for the currency code sub-field.
+                              The dynamic field must have a field type that extends StrField.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          defaultCurrency:  Specifies the default currency if none specified. Defaults to "USD"
+          providerClass:    Lets you plug in other exchange provider backend:
+                            solr.FileExchangeRateProvider is the default and takes one parameter:
+                              currencyConfig: name of an xml file holding exchange rates
+                            solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
+                              ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
+                              refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
    -->
-    <fieldType name="currency" class="solr.CurrencyField" precisionStep="8" defaultCurrency="USD" currencyConfig="currency.xml" />
-             
+    <fieldType name="currency" class="solr.CurrencyFieldType" amountLongSuffix="_l_ns" codeStrSuffix="_s_ns"
+               defaultCurrency="USD" currencyConfig="currency.xml" />
 
 
    <!-- some examples for different languages (generally ordered by ISO code) -->

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/example/example-DIH/solr/solr/conf/currency.xml
----------------------------------------------------------------------
diff --git a/solr/example/example-DIH/solr/solr/conf/currency.xml b/solr/example/example-DIH/solr/solr/conf/currency.xml
index 3a9c58a..532221a 100644
--- a/solr/example/example-DIH/solr/solr/conf/currency.xml
+++ b/solr/example/example-DIH/solr/solr/conf/currency.xml
@@ -16,7 +16,7 @@
  limitations under the License.
 -->
 
-<!-- Example exchange rates file for CurrencyField type named "currency" in example schema -->
+<!-- Example exchange rates file for CurrencyFieldType named "currency" in example schema -->
 
 <currencyConfig version="1.0">
   <rates>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/example/example-DIH/solr/solr/conf/managed-schema
----------------------------------------------------------------------
diff --git a/solr/example/example-DIH/solr/solr/conf/managed-schema b/solr/example/example-DIH/solr/solr/conf/managed-schema
index 679f567..338aa35 100644
--- a/solr/example/example-DIH/solr/solr/conf/managed-schema
+++ b/solr/example/example-DIH/solr/solr/conf/managed-schema
@@ -202,8 +202,10 @@
    <dynamicField name="*_i"  type="int"    indexed="true"  stored="true"/>
    <dynamicField name="*_is" type="int"    indexed="true"  stored="true"  multiValued="true"/>
    <dynamicField name="*_s"  type="string"  indexed="true"  stored="true" />
+   <dynamicField name="*_s_ns"  type="string"  indexed="true"  stored="false" />
    <dynamicField name="*_ss" type="string"  indexed="true"  stored="true" multiValued="true"/>
    <dynamicField name="*_l"  type="long"   indexed="true"  stored="true"/>
+   <dynamicField name="*_l_ns"  type="long"   indexed="true"  stored="false"/>
    <dynamicField name="*_ls" type="long"   indexed="true"  stored="true"  multiValued="true"/>
    <dynamicField name="*_t"  type="text_general"    indexed="true"  stored="true"/>
    <dynamicField name="*_txt" type="text_general"   indexed="true"  stored="true" multiValued="true"/>
@@ -703,17 +705,22 @@
 
    <!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
         Parameters:
-          defaultCurrency: Specifies the default currency if none specified. Defaults to "USD"
-          precisionStep:   Specifies the precisionStep for the TrieLong field used for the amount
-          providerClass:   Lets you plug in other exchange provider backend:
-                           solr.FileExchangeRateProvider is the default and takes one parameter:
-                             currencyConfig: name of an xml file holding exchange rates
-                           solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
-                             ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
-                             refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
+          amountLongSuffix: Required. Refers to a dynamic field for the raw amount sub-field. 
+                              The dynamic field must have a field type that extends LongValueFieldType.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          codeStrSuffix:    Required. Refers to a dynamic field for the currency code sub-field.
+                              The dynamic field must have a field type that extends StrField.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          defaultCurrency:  Specifies the default currency if none specified. Defaults to "USD"
+          providerClass:    Lets you plug in other exchange provider backend:
+                            solr.FileExchangeRateProvider is the default and takes one parameter:
+                              currencyConfig: name of an xml file holding exchange rates
+                            solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
+                              ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
+                              refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
    -->
-    <fieldType name="currency" class="solr.CurrencyField" precisionStep="8" defaultCurrency="USD" currencyConfig="currency.xml" />
-             
+    <fieldType name="currency" class="solr.CurrencyFieldType" amountLongSuffix="_l_ns" codeStrSuffix="_s_ns"
+               defaultCurrency="USD" currencyConfig="currency.xml" />
 
 
    <!-- some examples for different languages (generally ordered by ISO code) -->

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/example/files/conf/managed-schema
----------------------------------------------------------------------
diff --git a/solr/example/files/conf/managed-schema b/solr/example/files/conf/managed-schema
index bf154f4..fe91a5d 100644
--- a/solr/example/files/conf/managed-schema
+++ b/solr/example/files/conf/managed-schema
@@ -13,7 +13,7 @@
   <fieldType name="binary" class="solr.BinaryField"/>
   <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true"/>
   <fieldType name="booleans" class="solr.BoolField" sortMissingLast="true" multiValued="true"/>
-  <fieldType name="currency" class="solr.CurrencyField" currencyConfig="currency.xml" defaultCurrency="USD" precisionStep="8"/>
+  <fieldType name="currency" class="solr.CurrencyFieldType" amountLongSuffix="_l_ns" codeStrSuffix="_s_ns" defaultCurrency="USD" currencyConfig="currency.xml" />
   <fieldType name="date" class="solr.TrieDateField" positionIncrementGap="0" precisionStep="0"/>
   <fieldType name="dates" class="solr.TrieDateField" positionIncrementGap="0" multiValued="true" precisionStep="0"/>
   <fieldType name="descendent_path" class="solr.TextField">
@@ -504,6 +504,8 @@
   <dynamicField name="*_tdts" type="tdates" indexed="true" stored="true"/>
   <dynamicField name="attr_*" type="text_general" multiValued="true" indexed="true" stored="true"/>
   <dynamicField name="*_pdts" type="pdates"  indexed="true"  stored="true"/>
+  <dynamicField name="*_l_ns" type="long" indexed="true" stored="false"/>
+  <dynamicField name="*_s_ns" type="string" indexed="true" stored="false"/>
   <dynamicField name="*_txt" type="text_general" indexed="true" stored="true"/>
   <dynamicField name="*_dts" type="date" multiValued="true" indexed="true" stored="true"/>
   <dynamicField name="*_tis" type="tints" indexed="true" stored="true"/>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/server/solr/configsets/basic_configs/conf/managed-schema
----------------------------------------------------------------------
diff --git a/solr/server/solr/configsets/basic_configs/conf/managed-schema b/solr/server/solr/configsets/basic_configs/conf/managed-schema
index 599b0cc..c2db621 100644
--- a/solr/server/solr/configsets/basic_configs/conf/managed-schema
+++ b/solr/server/solr/configsets/basic_configs/conf/managed-schema
@@ -136,8 +136,10 @@
     <dynamicField name="*_i"  type="int"    indexed="true"  stored="true"/>
     <dynamicField name="*_is" type="ints"    indexed="true"  stored="true"/>
     <dynamicField name="*_s"  type="string"  indexed="true"  stored="true" />
+    <dynamicField name="*_s_ns"  type="string"  indexed="true"  stored="false" />
     <dynamicField name="*_ss" type="strings"  indexed="true"  stored="true"/>
     <dynamicField name="*_l"  type="long"   indexed="true"  stored="true"/>
+    <dynamicField name="*_l_ns"  type="long"   indexed="true"  stored="false"/>
     <dynamicField name="*_ls" type="longs"   indexed="true"  stored="true"/>
     <dynamicField name="*_t"   type="text_general" indexed="true" stored="true"/>
     <dynamicField name="*_txt" type="text_general" indexed="true" stored="true"/>
@@ -601,19 +603,24 @@
     <fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
                geo="true" distErrPct="0.025" maxDistErr="0.001" distanceUnits="kilometers" />
 
-    <!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
+   <!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
         Parameters:
-          defaultCurrency: Specifies the default currency if none specified. Defaults to "USD"
-          precisionStep:   Specifies the precisionStep for the TrieLong field used for the amount
-          providerClass:   Lets you plug in other exchange provider backend:
-                           solr.FileExchangeRateProvider is the default and takes one parameter:
-                             currencyConfig: name of an xml file holding exchange rates
-                           solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
-                             ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
-                             refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
-    -->
-    <fieldType name="currency" class="solr.CurrencyField" precisionStep="8" defaultCurrency="USD" currencyConfig="currency.xml" />
-             
+          amountLongSuffix: Required. Refers to a dynamic field for the raw amount sub-field. 
+                              The dynamic field must have a field type that extends LongValueFieldType.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          codeStrSuffix:    Required. Refers to a dynamic field for the currency code sub-field.
+                              The dynamic field must have a field type that extends StrField.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          defaultCurrency:  Specifies the default currency if none specified. Defaults to "USD"
+          providerClass:    Lets you plug in other exchange provider backend:
+                            solr.FileExchangeRateProvider is the default and takes one parameter:
+                              currencyConfig: name of an xml file holding exchange rates
+                            solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
+                              ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
+                              refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
+   -->
+    <fieldType name="currency" class="solr.CurrencyFieldType" amountLongSuffix="_l_ns" codeStrSuffix="_s_ns"
+               defaultCurrency="USD" currencyConfig="currency.xml" />
 
 
     <!-- some examples for different languages (generally ordered by ISO code) -->

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/server/solr/configsets/data_driven_schema_configs/conf/currency.xml
----------------------------------------------------------------------
diff --git a/solr/server/solr/configsets/data_driven_schema_configs/conf/currency.xml b/solr/server/solr/configsets/data_driven_schema_configs/conf/currency.xml
index 3a9c58a..532221a 100644
--- a/solr/server/solr/configsets/data_driven_schema_configs/conf/currency.xml
+++ b/solr/server/solr/configsets/data_driven_schema_configs/conf/currency.xml
@@ -16,7 +16,7 @@
  limitations under the License.
 -->
 
-<!-- Example exchange rates file for CurrencyField type named "currency" in example schema -->
+<!-- Example exchange rates file for CurrencyFieldType named "currency" in example schema -->
 
 <currencyConfig version="1.0">
   <rates>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/server/solr/configsets/data_driven_schema_configs/conf/managed-schema
----------------------------------------------------------------------
diff --git a/solr/server/solr/configsets/data_driven_schema_configs/conf/managed-schema b/solr/server/solr/configsets/data_driven_schema_configs/conf/managed-schema
index 80a58fb..5604a93 100644
--- a/solr/server/solr/configsets/data_driven_schema_configs/conf/managed-schema
+++ b/solr/server/solr/configsets/data_driven_schema_configs/conf/managed-schema
@@ -136,8 +136,10 @@
     <dynamicField name="*_i"  type="int"    indexed="true"  stored="true"/>
     <dynamicField name="*_is" type="ints"    indexed="true"  stored="true"/>
     <dynamicField name="*_s"  type="string"  indexed="true"  stored="true" />
+    <dynamicField name="*_s_ns"  type="string"  indexed="true"  stored="false" />
     <dynamicField name="*_ss" type="strings"  indexed="true"  stored="true"/>
     <dynamicField name="*_l"  type="long"   indexed="true"  stored="true"/>
+    <dynamicField name="*_l_ns"  type="long"   indexed="true"  stored="false"/>
     <dynamicField name="*_ls" type="longs"   indexed="true"  stored="true"/>
     <dynamicField name="*_t"   type="text_general" indexed="true" stored="true"/>
     <dynamicField name="*_txt" type="text_general" indexed="true" stored="true"/>
@@ -625,19 +627,24 @@
       </analyzer>
     </fieldType>
 
-    <!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
+   <!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
         Parameters:
-          defaultCurrency: Specifies the default currency if none specified. Defaults to "USD"
-          precisionStep:   Specifies the precisionStep for the TrieLong field used for the amount
-          providerClass:   Lets you plug in other exchange provider backend:
-                           solr.FileExchangeRateProvider is the default and takes one parameter:
-                             currencyConfig: name of an xml file holding exchange rates
-                           solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
-                             ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
-                             refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
-    -->
-    <fieldType name="currency" class="solr.CurrencyField" precisionStep="8" defaultCurrency="USD" currencyConfig="currency.xml" />
-             
+          amountLongSuffix: Required. Refers to a dynamic field for the raw amount sub-field. 
+                              The dynamic field must have a field type that extends LongValueFieldType.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          codeStrSuffix:    Required. Refers to a dynamic field for the currency code sub-field.
+                              The dynamic field must have a field type that extends StrField.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          defaultCurrency:  Specifies the default currency if none specified. Defaults to "USD"
+          providerClass:    Lets you plug in other exchange provider backend:
+                            solr.FileExchangeRateProvider is the default and takes one parameter:
+                              currencyConfig: name of an xml file holding exchange rates
+                            solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
+                              ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
+                              refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
+   -->
+    <fieldType name="currency" class="solr.CurrencyFieldType" amountLongSuffix="_l_ns" codeStrSuffix="_s_ns"
+               defaultCurrency="USD" currencyConfig="currency.xml" />
 
 
     <!-- some examples for different languages (generally ordered by ISO code) -->

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/server/solr/configsets/sample_techproducts_configs/conf/managed-schema
----------------------------------------------------------------------
diff --git a/solr/server/solr/configsets/sample_techproducts_configs/conf/managed-schema b/solr/server/solr/configsets/sample_techproducts_configs/conf/managed-schema
index 1c4f1fe..cd33c4f 100644
--- a/solr/server/solr/configsets/sample_techproducts_configs/conf/managed-schema
+++ b/solr/server/solr/configsets/sample_techproducts_configs/conf/managed-schema
@@ -199,8 +199,10 @@
    <dynamicField name="*_i"  type="int"    indexed="true"  stored="true"/>
    <dynamicField name="*_is" type="int"    indexed="true"  stored="true"  multiValued="true"/>
    <dynamicField name="*_s"  type="string"  indexed="true"  stored="true" />
+   <dynamicField name="*_s_ns"  type="string"  indexed="true"  stored="false" />
    <dynamicField name="*_ss" type="string"  indexed="true"  stored="true" multiValued="true"/>
    <dynamicField name="*_l"  type="long"   indexed="true"  stored="true"/>
+   <dynamicField name="*_l_ns"  type="long"   indexed="true"  stored="false"/>
    <dynamicField name="*_ls" type="long"   indexed="true"  stored="true"  multiValued="true"/>
    <dynamicField name="*_t"  type="text_general"    indexed="true"  stored="true"/>
    <dynamicField name="*_txt" type="text_general"   indexed="true"  stored="true" multiValued="true"/>
@@ -760,17 +762,22 @@
 
    <!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
         Parameters:
-          defaultCurrency: Specifies the default currency if none specified. Defaults to "USD"
-          precisionStep:   Specifies the precisionStep for the TrieLong field used for the amount
-          providerClass:   Lets you plug in other exchange provider backend:
-                           solr.FileExchangeRateProvider is the default and takes one parameter:
-                             currencyConfig: name of an xml file holding exchange rates
-                           solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
-                             ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
-                             refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
+          amountLongSuffix: Required. Refers to a dynamic field for the raw amount sub-field. 
+                              The dynamic field must have a field type that extends LongValueFieldType.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          codeStrSuffix:    Required. Refers to a dynamic field for the currency code sub-field.
+                              The dynamic field must have a field type that extends StrField.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          defaultCurrency:  Specifies the default currency if none specified. Defaults to "USD"
+          providerClass:    Lets you plug in other exchange provider backend:
+                            solr.FileExchangeRateProvider is the default and takes one parameter:
+                              currencyConfig: name of an xml file holding exchange rates
+                            solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
+                              ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
+                              refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
    -->
-    <fieldType name="currency" class="solr.CurrencyField" precisionStep="8" defaultCurrency="USD" currencyConfig="currency.xml" />
-             
+    <fieldType name="currency" class="solr.CurrencyFieldType" amountLongSuffix="_l_ns" codeStrSuffix="_s_ns"
+               defaultCurrency="USD" currencyConfig="currency.xml" />
 
 
    <!-- some examples for different languages (generally ordered by ISO code) -->

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/solr-ref-guide/src/field-types-included-with-solr.adoc
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/field-types-included-with-solr.adoc b/solr/solr-ref-guide/src/field-types-included-with-solr.adoc
index 88af33b..5c82970 100644
--- a/solr/solr-ref-guide/src/field-types-included-with-solr.adoc
+++ b/solr/solr-ref-guide/src/field-types-included-with-solr.adoc
@@ -28,7 +28,8 @@ The following table lists the field types that are available in Solr. The `org.a
 |BinaryField |Binary data.
 |BoolField |Contains either true or false. Values of "1", "t", or "T" in the first character are interpreted as true. Any other values in the first character are interpreted as false.
 |CollationField |Supports Unicode collation for sorting and range queries. ICUCollationField is a better choice if you can use ICU4J. See the section <<language-analysis.adoc#LanguageAnalysis-UnicodeCollation,Unicode Collation>>.
-|CurrencyField |Supports currencies and exchange rates. See the section <<working-with-currencies-and-exchange-rates.adoc#working-with-currencies-and-exchange-rates,Working with Currencies and Exchange Rates>>.
+|CurrencyField |Deprecated in favor of CurrencyFieldType.
+|CurrencyFieldType |Supports currencies and exchange rates. See the section <<working-with-currencies-and-exchange-rates.adoc#working-with-currencies-and-exchange-rates,Working with Currencies and Exchange Rates>>.
 |DateRangeField |Supports indexing date ranges, to include point in time date instances as well (single-millisecond durations). See the section <<working-with-dates.adoc#working-with-dates,Working with Dates>> for more detail on using this field type. Consider using this field type even if it's just for date instances, particularly when the queries typically fall on UTC year/month/day/hour, etc., boundaries.
 |ExternalFileField |Pulls values from a file on disk. See the section <<working-with-external-files-and-processes.adoc#working-with-external-files-and-processes,Working with External Files and Processes>>.
 |EnumField |Allows defining an enumerated set of values which may not be easily sorted by either alphabetic or numeric order (such as a list of severities, for example). This field type takes a configuration file, which lists the proper order of the field values. See the section <<working-with-enum-fields.adoc#working-with-enum-fields,Working with Enum Fields>> for more information.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/solr-ref-guide/src/updating-parts-of-documents.adoc
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/updating-parts-of-documents.adoc b/solr/solr-ref-guide/src/updating-parts-of-documents.adoc
index ecd9b4c..75e6d1f 100644
--- a/solr/solr-ref-guide/src/updating-parts-of-documents.adoc
+++ b/solr/solr-ref-guide/src/updating-parts-of-documents.adoc
@@ -74,7 +74,7 @@ The core functionality of atomically updating a document requires that all field
 
 If `<copyField/>` destinations are configured as stored, then Solr will attempt to index both the current value of the field as well as an additional copy from any source fields. If such fields contain some information that comes from the indexing program and some information that comes from copyField, then the information which originally came from the indexing program will be lost when an atomic update is made.
 
-There are other kinds of derived fields that must also be set so they aren't stored. Some spatial field types use derived fields. Examples of this are solr.BBoxField and solr.LatLonType. These types create additional fields which are normally specified by a dynamic field definition. That dynamic field definition must be not stored, or indexing will fail.
+There are other kinds of derived fields that must also be set so they aren't stored. Some spatial field types use derived fields. Examples of this are solr.BBoxField and solr.LatLonType. CurrencyFieldType also uses derived fields.  These types create additional fields which are normally specified by a dynamic field definition. That dynamic field definition must be not stored, or indexing will fail.
 
 [[UpdatingPartsofDocuments-Example]]
 === Example

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/solr-ref-guide/src/working-with-currencies-and-exchange-rates.adoc
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/working-with-currencies-and-exchange-rates.adoc b/solr/solr-ref-guide/src/working-with-currencies-and-exchange-rates.adoc
index c651263..5ed4a56 100644
--- a/solr/solr-ref-guide/src/working-with-currencies-and-exchange-rates.adoc
+++ b/solr/solr-ref-guide/src/working-with-currencies-and-exchange-rates.adoc
@@ -30,11 +30,18 @@ The `currency` FieldType provides support for monetary values to Solr/Lucene wit
 [[WorkingwithCurrenciesandExchangeRates-ConfiguringCurrencies]]
 == Configuring Currencies
 
-The `currency` field type is defined in `schema.xml`. This is the default configuration of this type:
+.CurrencyField has been Deprecated
+[WARNING]
+====
+CurrencyField has been deprecated in favor of CurrencyFieldType; all configuration examples below use CurrencyFieldType.
+====
+
+The `currency` field type is defined in `schema.xml`. This is the default configuration of this type.
 
 [source,xml]
 ----
-<fieldType name="currency" class="solr.CurrencyField" precisionStep="8" 
+<fieldType name="currency" class="solr.CurrencyFieldType" 
+           amountLongSuffix="_l_ns" codeStrSuffix="_s_ns"
            defaultCurrency="USD" currencyConfig="currency.xml" />
 ----
 
@@ -53,6 +60,26 @@ At indexing time, money fields can be indexed in a native currency. For example,
 
 During query processing, range and point queries are both supported.
 
+[[WorkingwithCurrenciesandExchangeRates-Sub-fieldSuffixes]]
+=== Sub-field Suffixes
+
+You must specify parameters `amountLongSuffix` and `codeStrSuffix`, corresponding to dynamic fields to be used for the raw amount and the currency dynamic sub-fields, e.g.: 
+
+[source,xml]
+----
+<fieldType name="currency" class="solr.CurrencyFieldType"
+           amountLongSuffix="_l_ns" codeStrSuffix="_s_ns"
+           defaultCurrency="USD" currencyConfig="currency.xml" />
+----
+
+In the above example, the raw amount field will use the `"*_l_ns"` dynamic field, which must exist in the schema and use a long field type, i.e. one that extends `LongValueFieldType`.  The currency code field will use the `"*_s_ns"` dynamic field, which must exist in the schema and use a string field type, i.e. one that is or extends `StrField`.
+
+.Atomic Updates won't work if dynamic sub-fields are stored
+[NOTE]
+====
+As noted on <<updating-parts-of-documents.adoc#UpdatingPartsofDocuments-FieldStorage,Updating Parts of Documents>>, stored dynamic sub-fields will cause indexing to fail when you use Atomic Updates. To avoid this problem, specify `stored="false"` on those dynamic fields.
+====
+
 [[WorkingwithCurrenciesandExchangeRates-ExchangeRates]]
 == Exchange Rates
 
@@ -95,7 +122,8 @@ In this case, you need to specify the `providerClass` in the definitions for the
 
 [source,xml]
 ----
-<fieldType name="currency" class="solr.CurrencyField" precisionStep="8" 
+<fieldType name="currency" class="solr.CurrencyFieldType" 
+           amountLongSuffix="_l_ns" codeStrSuffix="_s_ns"
            providerClass="solr.OpenExchangeRatesOrgProvider"
            refreshInterval="60" 
            ratesFileLocation="http://www.openexchangerates.org/api/latest.json?app_id=yourPersonalAppIdKey"/>


[6/6] lucene-solr:master: SOLR-10503, SOLR-10502: Deprecate CurrencyField in favor of new CurrencyFieldType, which works with point fields and provides control over dynamic fields used for the raw amount and currency code sub-fields.

Posted by sa...@apache.org.
SOLR-10503,SOLR-10502: Deprecate CurrencyField in favor of new CurrencyFieldType, which works with point fields and provides control over dynamic fields used for the raw amount and currency code sub-fields.


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/a6f4f2dd
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/a6f4f2dd
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/a6f4f2dd

Branch: refs/heads/master
Commit: a6f4f2dd9ce33a0aa3597e73a5ced0b96302c847
Parents: af1ee47
Author: Steve Rowe <sa...@apache.org>
Authored: Thu Jun 22 21:22:42 2017 -0400
Committer: Steve Rowe <sa...@apache.org>
Committed: Fri Jun 23 10:14:28 2017 -0400

----------------------------------------------------------------------
 solr/CHANGES.txt                                |   6 +
 .../org/apache/solr/schema/CurrencyField.java   | 999 +------------------
 .../apache/solr/schema/CurrencyFieldType.java   | 829 +++++++++++++++
 .../solr/schema/FileExchangeRateProvider.java   | 252 +++++
 .../schema/OpenExchangeRatesOrgProvider.java    |   2 +-
 .../apache/solr/search/ValueSourceParser.java   |   8 +-
 .../bad-schema-currency-ft-amount-suffix.xml    |  34 +
 .../conf/bad-schema-currency-ft-code-suffix.xml |  33 +
 .../conf/bad-schema-currency-ft-oer-norates.xml |   2 +-
 ...ma-currencyfieldtype-bogus-amount-suffix.xml |  34 +
 ...hema-currencyfieldtype-bogus-code-suffix.xml |  35 +
 ...ma-currencyfieldtype-dynamic-multivalued.xml |  36 +
 ...a-currencyfieldtype-ft-bogus-code-in-xml.xml |  41 +
 ...-currencyfieldtype-ft-bogus-default-code.xml |  41 +
 ...-schema-currencyfieldtype-ft-multivalued.xml |  36 +
 ...-schema-currencyfieldtype-ft-oer-norates.xml |  40 +
 ...-currencyfieldtype-missing-amount-suffix.xml |  34 +
 ...ma-currencyfieldtype-missing-code-suffix.xml |  35 +
 ...bad-schema-currencyfieldtype-multivalued.xml |  36 +
 ...schema-currencyfieldtype-wrong-amount-ft.xml |  36 +
 ...d-schema-currencyfieldtype-wrong-code-ft.xml |  35 +
 .../test-files/solr/collection1/conf/schema.xml |  16 +
 .../solr/schema/AbstractCurrencyFieldTest.java  | 426 --------
 .../apache/solr/schema/BadIndexSchemaTest.java  |  41 +-
 .../schema/CurrencyFieldOpenExchangeTest.java   |  27 -
 .../solr/schema/CurrencyFieldTypeTest.java      | 494 +++++++++
 .../solr/schema/CurrencyFieldXmlFileTest.java   |  42 -
 .../OpenExchangeRatesOrgProviderTest.java       |   2 +-
 .../example-DIH/solr/db/conf/managed-schema     |  27 +-
 .../example-DIH/solr/mail/conf/managed-schema   |  27 +-
 .../example-DIH/solr/solr/conf/currency.xml     |   2 +-
 .../example-DIH/solr/solr/conf/managed-schema   |  27 +-
 solr/example/files/conf/managed-schema          |   4 +-
 .../basic_configs/conf/managed-schema           |  31 +-
 .../conf/currency.xml                           |   2 +-
 .../conf/managed-schema                         |  31 +-
 .../conf/managed-schema                         |  27 +-
 .../src/field-types-included-with-solr.adoc     |   3 +-
 .../src/updating-parts-of-documents.adoc        |   2 +-
 ...king-with-currencies-and-exchange-rates.adoc |  34 +-
 40 files changed, 2331 insertions(+), 1538 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 6477ada..85535c3 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -343,6 +343,8 @@ Upgrade Notes
 
 * SOLR-10379: ManagedSynonymFilterFactory has been deprecated in favor of ManagedSynonymGraphFilterFactory.
 
+* SOLR-10503: CurrencyField has been deprecated in favor of new CurrencyFieldType.
+
 New Features
 ----------------------
 
@@ -490,6 +492,10 @@ Other Changes
   rendered visibly in the PDF.  Also add .adoc file checks to the top-level validate target, including
   for the invisible substitutions PDF problem.  (Steve Rowe)
 
+* SOLR-10503,SOLR-10502: Deprecate CurrencyField in favor of new CurrencyFieldType, which works
+  with point fields and provides control over dynamic fields used for the raw amount and currency
+  code sub-fields.  (hossman, Steve Rowe)
+
 ==================  6.6.1 ==================
 
 Bug Fixes

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/java/org/apache/solr/schema/CurrencyField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/CurrencyField.java b/solr/core/src/java/org/apache/solr/schema/CurrencyField.java
index 286d2c1..e2676fe 100644
--- a/solr/core/src/java/org/apache/solr/schema/CurrencyField.java
+++ b/solr/core/src/java/org/apache/solr/schema/CurrencyField.java
@@ -14,189 +14,74 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.solr.schema;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.invoke.MethodHandles;
+
 import java.util.ArrayList;
-import java.util.Currency;
+import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
-
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathExpressionException;
-import javax.xml.xpath.XPathFactory;
 
-import org.apache.lucene.analysis.util.ResourceLoader;
 import org.apache.lucene.analysis.util.ResourceLoaderAware;
-import org.apache.lucene.document.StoredField;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.IndexableField;
-import org.apache.lucene.queries.function.FunctionValues;
-import org.apache.lucene.queries.function.ValueSource;
-import org.apache.lucene.search.BooleanClause.Occur;
-import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.FieldValueQuery;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.SortField;
-import org.apache.solr.uninverting.UninvertingReader.Type;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
-import org.apache.solr.response.TextResponseWriter;
-import org.apache.solr.search.Filter;
-import org.apache.solr.search.QParser;
-import org.apache.solr.search.QueryWrapperFilter;
-import org.apache.solr.search.SolrConstantScoreQuery;
-import org.apache.solr.search.function.ValueSourceRangeFilter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.w3c.dom.Document;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.SAXException;
+
 
 /**
  * Field type for support of monetary values.
  * <p>
  * See <a href="http://wiki.apache.org/solr/CurrencyField">http://wiki.apache.org/solr/CurrencyField</a>
+ * @deprecated Use {@link CurrencyFieldType}
  */
-public class CurrencyField extends FieldType implements SchemaAware, ResourceLoaderAware {
-  protected static final String PARAM_DEFAULT_CURRENCY      = "defaultCurrency";
-  protected static final String PARAM_RATE_PROVIDER_CLASS   = "providerClass";
-  protected static final Object PARAM_PRECISION_STEP        = "precisionStep";
-  protected static final String DEFAULT_RATE_PROVIDER_CLASS = "solr.FileExchangeRateProvider";
-  protected static final String DEFAULT_DEFAULT_CURRENCY    = "USD";
-  protected static final String DEFAULT_PRECISION_STEP      = "0";
-  protected static final String FIELD_SUFFIX_AMOUNT_RAW     = "_amount_raw";
-  protected static final String FIELD_SUFFIX_CURRENCY       = "_currency";
-
-  private IndexSchema schema;
-  protected FieldType fieldTypeCurrency;
-  protected FieldType fieldTypeAmountRaw;
-  private String exchangeRateProviderClass;
-  private String defaultCurrency;
-  private ExchangeRateProvider provider;
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  /**
-   * A wrapper arround <code>Currency.getInstance</code> that returns null
-   * instead of throwing <code>IllegalArgumentException</code>
-   * if the specified Currency does not exist in this JVM.
-   *
-   * @see Currency#getInstance(String)
-   */
-  public static Currency getCurrency(final String code) {
-    try {
-      return Currency.getInstance(code);
-    } catch (IllegalArgumentException e) {
-      /* :NOOP: */
-    }
-    return null;
-  }
+@Deprecated
+public class CurrencyField extends CurrencyFieldType implements SchemaAware, ResourceLoaderAware {
+  protected static final String FIELD_SUFFIX_AMOUNT_RAW = "_amount_raw";
+  protected static final String FIELD_SUFFIX_CURRENCY = "_currency";
+  protected static final String FIELD_TYPE_AMOUNT_RAW = "amount_raw_type_long";
+  protected static final String FIELD_TYPE_CURRENCY = "currency_type_string";
+  protected static final String PARAM_PRECISION_STEP = "precisionStep";
+  protected static final String DEFAULT_PRECISION_STEP = "0";
 
   @Override
   protected void init(IndexSchema schema, Map<String, String> args) {
-    super.init(schema, args);
-    if (this.isMultiValued()) { 
-      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, 
-                              "CurrencyField types can not be multiValued: " + 
-                              this.typeName);
-    }
-    this.schema = schema;
-    this.exchangeRateProviderClass = args.get(PARAM_RATE_PROVIDER_CLASS);
-    this.defaultCurrency = args.get(PARAM_DEFAULT_CURRENCY);
-
-    if (this.defaultCurrency == null) {
-      this.defaultCurrency = DEFAULT_DEFAULT_CURRENCY;
-    }
     
-    if (this.exchangeRateProviderClass == null) {
-      this.exchangeRateProviderClass = DEFAULT_RATE_PROVIDER_CLASS;
+    // Fail if amountLongSuffix or codeStrSuffix are specified
+    List<String> unknownParams = new ArrayList<>();
+    fieldSuffixAmountRaw = args.get(PARAM_FIELD_SUFFIX_AMOUNT_RAW);
+    if (fieldSuffixAmountRaw != null) {
+      unknownParams.add(PARAM_FIELD_SUFFIX_AMOUNT_RAW); 
     }
-
-    if (null == getCurrency(this.defaultCurrency)) {
-      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Default currency code is not supported by this JVM: " + this.defaultCurrency);
+    fieldSuffixCurrency = args.get(PARAM_FIELD_SUFFIX_CURRENCY);
+    if (fieldSuffixCurrency != null) {
+      unknownParams.add(PARAM_FIELD_SUFFIX_CURRENCY);
     }
-
+    if ( ! unknownParams.isEmpty()) {
+      throw new SolrException(ErrorCode.SERVER_ERROR, "Unknown parameter(s): " + unknownParams);
+    }
+    
     String precisionStepString = args.get(PARAM_PRECISION_STEP);
     if (precisionStepString == null) {
       precisionStepString = DEFAULT_PRECISION_STEP;
+    } else {
+      args.remove(PARAM_PRECISION_STEP);
     }
 
     // Initialize field type for amount
     fieldTypeAmountRaw = new TrieLongField();
-    fieldTypeAmountRaw.setTypeName("amount_raw_type_tlong");
+    fieldTypeAmountRaw.setTypeName(FIELD_TYPE_AMOUNT_RAW);
     Map<String,String> map = new HashMap<>(1);
     map.put("precisionStep", precisionStepString);
     fieldTypeAmountRaw.init(schema, map);
-    
+    fieldSuffixAmountRaw = FIELD_SUFFIX_AMOUNT_RAW;
+
     // Initialize field type for currency string
     fieldTypeCurrency = new StrField();
-    fieldTypeCurrency.setTypeName("currency_type_string");
-    fieldTypeCurrency.init(schema, new HashMap<String,String>());
-    
-    args.remove(PARAM_RATE_PROVIDER_CLASS);
-    args.remove(PARAM_DEFAULT_CURRENCY);
-    args.remove(PARAM_PRECISION_STEP);
-
-    try {
-      Class<? extends ExchangeRateProvider> c = schema.getResourceLoader().findClass(exchangeRateProviderClass, ExchangeRateProvider.class);
-      provider = c.newInstance();
-      provider.init(args);
-    } catch (Exception e) {
-      throw new SolrException(ErrorCode.BAD_REQUEST, "Error instantiating exchange rate provider "+exchangeRateProviderClass+": " + e.getMessage(), e);
-    }
-  }
-
-  @Override
-  public boolean isPolyField() {
-    return true;
-  }
-
-  @Override
-  public void checkSchemaField(final SchemaField field) throws SolrException {
-    super.checkSchemaField(field);
-    if (field.multiValued()) {
-      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, 
-                              "CurrencyFields can not be multiValued: " + 
-                              field.getName());
-    }
-  }
-
-  @Override
-  public List<IndexableField> createFields(SchemaField field, Object externalVal) {
-    CurrencyValue value = CurrencyValue.parse(externalVal.toString(), defaultCurrency);
-
-    List<IndexableField> f = new ArrayList<>();
-    SchemaField amountField = getAmountField(field);
-    f.add(amountField.createField(String.valueOf(value.getAmount())));
-    SchemaField currencyField = getCurrencyField(field);
-    f.add(currencyField.createField(value.getCurrencyCode()));
-
-    if (field.stored()) {
-      String storedValue = externalVal.toString().trim();
-      if (storedValue.indexOf(",") < 0) {
-        storedValue += "," + defaultCurrency;
-      }
-      f.add(createField(field.getName(), storedValue, StoredField.TYPE));
-    }
-
-    return f;
-  }
-
-  private SchemaField getAmountField(SchemaField field) {
-    return schema.getField(field.getName() + POLY_FIELD_SEPARATOR + FIELD_SUFFIX_AMOUNT_RAW);
-  }
+    fieldTypeCurrency.setTypeName(FIELD_TYPE_CURRENCY);
+    fieldTypeCurrency.init(schema, Collections.emptyMap());
+    fieldSuffixCurrency = FIELD_SUFFIX_CURRENCY;
 
-  private SchemaField getCurrencyField(SchemaField field) {
-    return schema.getField(field.getName() + POLY_FIELD_SEPARATOR + FIELD_SUFFIX_CURRENCY);
+    super.init(schema, args); // Must be called last so that field types are not doubly created
   }
 
   private void createDynamicCurrencyField(String suffix, FieldType type) {
@@ -212,825 +97,15 @@ public class CurrencyField extends FieldType implements SchemaAware, ResourceLoa
 
   /**
    * When index schema is informed, add dynamic fields "*____currency" and "*____amount_raw". 
-   * 
+   *
    * {@inheritDoc}
-   * 
+   *
    * @param schema {@inheritDoc}
    */
   @Override
   public void inform(IndexSchema schema) {
-    this.schema = schema;
     createDynamicCurrencyField(FIELD_SUFFIX_CURRENCY,   fieldTypeCurrency);
     createDynamicCurrencyField(FIELD_SUFFIX_AMOUNT_RAW, fieldTypeAmountRaw);
-  }
-
-  /**
-   * Load the currency config when resource loader initialized.
-   *
-   * @param resourceLoader The resource loader.
-   */
-  @Override
-  public void inform(ResourceLoader resourceLoader) {
-    provider.inform(resourceLoader);
-    boolean reloaded = provider.reload();
-    if(!reloaded) {
-      log.warn("Failed reloading currencies");
-    }
-  }
-
-  @Override
-  public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) {
-    CurrencyValue value = CurrencyValue.parse(externalVal, defaultCurrency);
-    CurrencyValue valueDefault;
-    valueDefault = value.convertTo(provider, defaultCurrency);
-
-    return getRangeQuery(parser, field, valueDefault, valueDefault, true, true);
-  }
-
-  /**
-   * <p>
-   * Returns a ValueSource over this field in which the numeric value for 
-   * each document represents the indexed value as converted to the default 
-   * currency for the field, normalized to its most granular form based 
-   * on the default fractional digits.
-   * </p>
-   * <p>
-   * For example: If the default Currency specified for a field is 
-   * <code>USD</code>, then the values returned by this value source would 
-   * represent the equivilent number of "cents" (ie: value in dollars * 100) 
-   * after converting each document's native currency to USD -- because the 
-   * default fractional digits for <code>USD</code> is "<code>2</code>".  
-   * So for a document whose indexed value was currently equivilent to 
-   * "<code>5.43,USD</code>" using the the exchange provider for this field, 
-   * this ValueSource would return a value of "<code>543</code>"
-   * </p>
-   *
-   * @see #PARAM_DEFAULT_CURRENCY
-   * @see #DEFAULT_DEFAULT_CURRENCY
-   * @see Currency#getDefaultFractionDigits
-   * @see #getConvertedValueSource
-   */
-  public RawCurrencyValueSource getValueSource(SchemaField field, 
-                                               QParser parser) {
-    getAmountField(field).checkFieldCacheSource();
-    getCurrencyField(field).checkFieldCacheSource();
-    return new RawCurrencyValueSource(field, defaultCurrency, parser);
-  }
-
-  /**
-   * <p>
-   * Returns a ValueSource over this field in which the numeric value for 
-   * each document represents the value from the underlying 
-   * <code>RawCurrencyValueSource</code> as converted to the specified target 
-   * Currency.
-   * </p>
-   * <p>
-   * For example: If the <code>targetCurrencyCode</code> param is set to
-   * <code>USD</code>, then the values returned by this value source would 
-   * represent the equivilent number of dollars after converting each 
-   * document's raw value to <code>USD</code>.  So for a document whose 
-   * indexed value was currently equivilent to "<code>5.43,USD</code>" 
-   * using the the exchange provider for this field, this ValueSource would 
-   * return a value of "<code>5.43</code>"
-   * </p>
-   *
-   * @param targetCurrencyCode The target currency for the resulting value source, if null the defaultCurrency for this field type will be used
-   * @param source the raw ValueSource to wrap
-   * @see #PARAM_DEFAULT_CURRENCY
-   * @see #DEFAULT_DEFAULT_CURRENCY
-   * @see #getValueSource
-   */
-  public ValueSource getConvertedValueSource(String targetCurrencyCode, 
-                                             RawCurrencyValueSource source) {
-    if (null == targetCurrencyCode) { 
-      targetCurrencyCode = defaultCurrency; 
-    }
-    return new ConvertedCurrencyValueSource(targetCurrencyCode, 
-                                            source);
-  }
-
-  @Override
-  public Query getRangeQuery(QParser parser, SchemaField field, String part1, String part2, final boolean minInclusive, final boolean maxInclusive) {
-      final CurrencyValue p1 = CurrencyValue.parse(part1, defaultCurrency);
-      final CurrencyValue p2 = CurrencyValue.parse(part2, defaultCurrency);
-
-      if (p1 != null && p2 != null && !p1.getCurrencyCode().equals(p2.getCurrencyCode())) {
-          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
-                  "Cannot parse range query " + part1 + " to " + part2 +
-                          ": range queries only supported when upper and lower bound have same currency.");
-      }
-
-      return getRangeQuery(parser, field, p1, p2, minInclusive, maxInclusive);
-  }
-
-  public Query getRangeQuery(QParser parser, SchemaField field, final CurrencyValue p1, final CurrencyValue p2, final boolean minInclusive, final boolean maxInclusive) {
-    String currencyCode = (p1 != null) ? p1.getCurrencyCode() :
-                          (p2 != null) ? p2.getCurrencyCode() : defaultCurrency;
-
-    // ValueSourceRangeFilter doesn't check exists(), so we have to
-    final Filter docsWithValues = new QueryWrapperFilter(new FieldValueQuery(getAmountField(field).getName()));
-    final Filter vsRangeFilter = new ValueSourceRangeFilter
-      (new RawCurrencyValueSource(field, currencyCode, parser),
-       p1 == null ? null : p1.getAmount() + "", 
-       p2 == null ? null : p2.getAmount() + "",
-       minInclusive, maxInclusive);
-    final BooleanQuery.Builder docsInRange = new BooleanQuery.Builder();
-    docsInRange.add(docsWithValues, Occur.FILTER);
-    docsInRange.add(vsRangeFilter, Occur.FILTER);
-
-    return new SolrConstantScoreQuery(new QueryWrapperFilter(docsInRange.build()));
-  }
-
-  @Override
-  public SortField getSortField(SchemaField field, boolean reverse) {
-    // Convert all values to default currency for sorting.
-    return (new RawCurrencyValueSource(field, defaultCurrency, null)).getSortField(reverse);
-  }
-  
-  @Override
-  public Type getUninversionType(SchemaField sf) {
-    return null;
-  }
-
-  @Override
-  public void write(TextResponseWriter writer, String name, IndexableField field) throws IOException {
-    writer.writeStr(name, field.stringValue(), true);
-  }
-
-  public ExchangeRateProvider getProvider() {
-    return provider;
-  }
-
-  /**
-   * <p>
-   * A value source whose values represent the "normal" values
-   * in the specified target currency.
-   * </p>
-   * @see RawCurrencyValueSource
-   */
-  class ConvertedCurrencyValueSource extends ValueSource {
-    private final Currency targetCurrency;
-    private final RawCurrencyValueSource source;
-    private final double rate;
-    public ConvertedCurrencyValueSource(String targetCurrencyCode, 
-                                        RawCurrencyValueSource source) {
-      this.source = source;
-      this.targetCurrency = getCurrency(targetCurrencyCode);
-      if (null == targetCurrency) {
-        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Currency code not supported by this JVM: " + targetCurrencyCode);
-      }
-      // the target digits & currency of our source, 
-      // become the source digits & currency of ourselves
-      this.rate = provider.getExchangeRate
-        (source.getTargetCurrency().getCurrencyCode(), 
-         targetCurrency.getCurrencyCode());
-    }
-
-    @Override
-    public FunctionValues getValues(Map context, LeafReaderContext reader) 
-      throws IOException {
-      final FunctionValues amounts = source.getValues(context, reader);
-      // the target digits & currency of our source, 
-      // become the source digits & currency of ourselves
-      final String sourceCurrencyCode = source.getTargetCurrency().getCurrencyCode();
-      final int sourceFractionDigits = source.getTargetCurrency().getDefaultFractionDigits();
-      final double divisor = Math.pow(10D, targetCurrency.getDefaultFractionDigits());
-      return new FunctionValues() {
-        @Override
-        public boolean exists(int doc) throws IOException {
-          return amounts.exists(doc);
-        }
-        @Override
-        public long longVal(int doc) throws IOException {
-          return (long) doubleVal(doc);
-        }
-        @Override
-        public int intVal(int doc) throws IOException {
-          return (int) doubleVal(doc);
-        }
-
-        @Override
-        public double doubleVal(int doc) throws IOException {
-          return CurrencyValue.convertAmount(rate, sourceCurrencyCode, amounts.longVal(doc), targetCurrency.getCurrencyCode()) / divisor;
-        }
-
-        @Override
-        public float floatVal(int doc) throws IOException {
-          return CurrencyValue.convertAmount(rate, sourceCurrencyCode, amounts.longVal(doc), targetCurrency.getCurrencyCode()) / ((float)divisor);
-        }
-
-        @Override
-        public String strVal(int doc) throws IOException {
-          return Double.toString(doubleVal(doc));
-        }
-
-        @Override
-        public String toString(int doc) throws IOException {
-          return name() + '(' + strVal(doc) + ')';
-        }
-      };
-    }
-    public String name() {
-      return "currency";
-    }
-
-    @Override
-    public String description() {
-      return name() + "(" + source.getField().getName() + "," + targetCurrency.getCurrencyCode()+")";
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) return true;
-      if (o == null || getClass() != o.getClass()) return false;
-
-      ConvertedCurrencyValueSource that = (ConvertedCurrencyValueSource) o;
-
-      return !(source != null ? !source.equals(that.source) : that.source != null) &&
-        (rate == that.rate) && 
-        !(targetCurrency != null ? !targetCurrency.equals(that.targetCurrency) : that.targetCurrency != null);
-
-    }
-
-    @Override
-    public int hashCode() {
-      int result = targetCurrency != null ? targetCurrency.hashCode() : 0;
-      result = 31 * result + (source != null ? source.hashCode() : 0);
-      result = 31 * (int) Double.doubleToLongBits(rate);
-      return result;
-    }
-  }
-
-  /**
-   * <p>
-   * A value source whose values represent the "raw" (ie: normalized using 
-   * the number of default fractional digits) values in the specified 
-   * target currency).
-   * </p>
-   * <p>
-   * For example: if the specified target currency is "<code>USD</code>" 
-   * then the numeric values are the number of pennies in the value 
-   * (ie: <code>$n * 100</code>) since the number of defalt fractional 
-   * digits for <code>USD</code> is "<code>2</code>")
-   * </p>
-   * @see ConvertedCurrencyValueSource
-   */
-  class RawCurrencyValueSource extends ValueSource {
-    private static final long serialVersionUID = 1L;
-    private final Currency targetCurrency;
-    private ValueSource currencyValues;
-    private ValueSource amountValues;
-    private final SchemaField sf;
-
-    public RawCurrencyValueSource(SchemaField sfield, String targetCurrencyCode, QParser parser) {
-      this.sf = sfield;
-      this.targetCurrency = getCurrency(targetCurrencyCode);
-      if (null == targetCurrency) {
-        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Currency code not supported by this JVM: " + targetCurrencyCode);
-      }
-
-      SchemaField amountField = getAmountField(sf);
-      SchemaField currencyField = getCurrencyField(sf);
-
-      currencyValues = currencyField.getType().getValueSource(currencyField, parser);
-      amountValues = amountField.getType().getValueSource(amountField, parser);
-    }
-    
-    public SchemaField getField() { return sf; }
-    public Currency getTargetCurrency() { return targetCurrency; }
-
-    @Override
-    public FunctionValues getValues(Map context, LeafReaderContext reader) throws IOException {
-      final FunctionValues amounts = amountValues.getValues(context, reader);
-      final FunctionValues currencies = currencyValues.getValues(context, reader);
-
-      return new FunctionValues() {
-        private static final int MAX_CURRENCIES_TO_CACHE = 256;
-        private final int[] fractionDigitCache = new int[MAX_CURRENCIES_TO_CACHE];
-        private final String[] currencyOrdToCurrencyCache = new String[MAX_CURRENCIES_TO_CACHE];
-        private final double[] exchangeRateCache = new double[MAX_CURRENCIES_TO_CACHE];
-        private int targetFractionDigits = -1;
-        private int targetCurrencyOrd = -1;
-        private boolean initializedCache;
-
-        private String getDocCurrencyCode(int doc, int currencyOrd) throws IOException {
-          if (currencyOrd < MAX_CURRENCIES_TO_CACHE) {
-            String currency = currencyOrdToCurrencyCache[currencyOrd];
-
-            if (currency == null) {
-              currencyOrdToCurrencyCache[currencyOrd] = currency = currencies.strVal(doc);
-            }
-            
-            if (currency == null) {
-              currency = defaultCurrency;
-            }
-
-            if (targetCurrencyOrd == -1 && 
-                currency.equals(targetCurrency.getCurrencyCode() )) {
-              targetCurrencyOrd = currencyOrd;
-            }
-
-            return currency;
-          } else {
-            return currencies.strVal(doc);
-          }
-        }
-        /** throws a (Server Error) SolrException if the code is not valid */
-        private Currency getDocCurrency(int doc, int currencyOrd) throws IOException {
-          String code = getDocCurrencyCode(doc, currencyOrd);
-          Currency c = getCurrency(code);
-          if (null == c) {
-            throw new SolrException
-              (SolrException.ErrorCode.SERVER_ERROR, 
-               "Currency code of document is not supported by this JVM: "+code);
-          }
-          return c;
-        }
-
-        @Override
-        public boolean exists(int doc) throws IOException {
-          return amounts.exists(doc);
-        }
-        
-        @Override
-        public long longVal(int doc) throws IOException {
-          long amount = amounts.longVal(doc);
-          // bail fast using whatever amounts defaults to if no value
-          // (if we don't do this early, currencyOrd may be < 0, 
-          // causing index bounds exception
-          if ( ! exists(doc) ) {
-            return amount;
-          }
-
-          if (!initializedCache) {
-            for (int i = 0; i < fractionDigitCache.length; i++) {
-              fractionDigitCache[i] = -1;
-            }
-
-            initializedCache = true;
-          }
-
-          int currencyOrd = currencies.ordVal(doc);
-
-          if (currencyOrd == targetCurrencyOrd) {
-            return amount;
-          }
-
-          double exchangeRate;
-          int sourceFractionDigits;
-
-          if (targetFractionDigits == -1) {
-            targetFractionDigits = targetCurrency.getDefaultFractionDigits();
-          }
-
-          if (currencyOrd < MAX_CURRENCIES_TO_CACHE) {
-            exchangeRate = exchangeRateCache[currencyOrd];
-
-            if (exchangeRate <= 0.0) {
-              String sourceCurrencyCode = getDocCurrencyCode(doc, currencyOrd);
-              exchangeRate = exchangeRateCache[currencyOrd] = provider.getExchangeRate(sourceCurrencyCode, targetCurrency.getCurrencyCode());
-            }
-
-            sourceFractionDigits = fractionDigitCache[currencyOrd];
-
-            if (sourceFractionDigits == -1) {
-              sourceFractionDigits = fractionDigitCache[currencyOrd] = getDocCurrency(doc, currencyOrd).getDefaultFractionDigits();
-            }
-          } else {
-            Currency source = getDocCurrency(doc, currencyOrd);
-            exchangeRate = provider.getExchangeRate(source.getCurrencyCode(), targetCurrency.getCurrencyCode());
-            sourceFractionDigits = source.getDefaultFractionDigits();
-          }
-
-          return CurrencyValue.convertAmount(exchangeRate, sourceFractionDigits, amount, targetFractionDigits);
-        }
-
-        @Override
-        public int intVal(int doc) throws IOException {
-          return (int) longVal(doc);
-        }
-
-        @Override
-        public double doubleVal(int doc) throws IOException {
-          return (double) longVal(doc);
-        }
-
-        @Override
-        public float floatVal(int doc) throws IOException {
-          return (float) longVal(doc);
-        }
-
-        @Override
-        public String strVal(int doc) throws IOException {
-          return Long.toString(longVal(doc));
-        }
-
-        @Override
-        public String toString(int doc) throws IOException {
-          return name() + '(' + amounts.toString(doc) + ',' + currencies.toString(doc) + ')';
-        }
-      };
-    }
-
-    public String name() {
-      return "rawcurrency";
-    }
-
-    @Override
-    public String description() {
-      return name() + "(" + sf.getName() + 
-        ",target="+targetCurrency.getCurrencyCode()+")";
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) return true;
-      if (o == null || getClass() != o.getClass()) return false;
-
-      RawCurrencyValueSource that = (RawCurrencyValueSource) o;
-
-      return !(amountValues != null ? !amountValues.equals(that.amountValues) : that.amountValues != null) &&
-              !(currencyValues != null ? !currencyValues.equals(that.currencyValues) : that.currencyValues != null) &&
-              !(targetCurrency != null ? !targetCurrency.equals(that.targetCurrency) : that.targetCurrency != null);
-
-    }
-
-    @Override
-    public int hashCode() {
-      int result = targetCurrency != null ? targetCurrency.hashCode() : 0;
-      result = 31 * result + (currencyValues != null ? currencyValues.hashCode() : 0);
-      result = 31 * result + (amountValues != null ? amountValues.hashCode() : 0);
-      return result;
-    }
-  }
-}
-
-/**
- * Configuration for currency. Provides currency exchange rates.
- */
-class FileExchangeRateProvider implements ExchangeRateProvider {
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-  protected static final String PARAM_CURRENCY_CONFIG       = "currencyConfig";
-
-  // Exchange rate map, maps Currency Code -> Currency Code -> Rate
-  private Map<String, Map<String, Double>> rates = new HashMap<>();
-
-  private String currencyConfigFile;
-  private ResourceLoader loader;
-
-  /**
-   * Returns the currently known exchange rate between two currencies. If a direct rate has been loaded,
-   * it is used. Otherwise, if a rate is known to convert the target currency to the source, the inverse
-   * exchange rate is computed.
-   *
-   * @param sourceCurrencyCode The source currency being converted from.
-   * @param targetCurrencyCode The target currency being converted to.
-   * @return The exchange rate.
-   * @throws SolrException if the requested currency pair cannot be found
-   */
-  @Override
-  public double getExchangeRate(String sourceCurrencyCode, String targetCurrencyCode) {
-    if (sourceCurrencyCode == null || targetCurrencyCode == null) {
-      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Cannot get exchange rate; currency was null.");
-    }
-    
-    if (sourceCurrencyCode.equals(targetCurrencyCode)) {
-      return 1.0;
-    }
-
-    Double directRate = lookupRate(sourceCurrencyCode, targetCurrencyCode);
-
-    if (directRate != null) {
-      return directRate;
-    }
-
-    Double symmetricRate = lookupRate(targetCurrencyCode, sourceCurrencyCode);
-
-    if (symmetricRate != null) {
-      return 1.0 / symmetricRate;
-    }
-
-    throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No available conversion rate between " + sourceCurrencyCode + " to " + targetCurrencyCode);
-  }
-
-  /**
-   * Looks up the current known rate, if any, between the source and target currencies.
-   *
-   * @param sourceCurrencyCode The source currency being converted from.
-   * @param targetCurrencyCode The target currency being converted to.
-   * @return The exchange rate, or null if no rate has been registered.
-   */
-  private Double lookupRate(String sourceCurrencyCode, String targetCurrencyCode) {
-    Map<String, Double> rhs = rates.get(sourceCurrencyCode);
-
-    if (rhs != null) {
-      return rhs.get(targetCurrencyCode);
-    }
-
-    return null;
-  }
-
-  /**
-   * Registers the specified exchange rate.
-   *
-   * @param ratesMap           The map to add rate to
-   * @param sourceCurrencyCode The source currency.
-   * @param targetCurrencyCode The target currency.
-   * @param rate               The known exchange rate.
-   */
-  private void addRate(Map<String, Map<String, Double>> ratesMap, String sourceCurrencyCode, String targetCurrencyCode, double rate) {
-    Map<String, Double> rhs = ratesMap.get(sourceCurrencyCode);
-
-    if (rhs == null) {
-      rhs = new HashMap<>();
-      ratesMap.put(sourceCurrencyCode, rhs);
-    }
-
-    rhs.put(targetCurrencyCode, rate);
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-
-    FileExchangeRateProvider that = (FileExchangeRateProvider) o;
-
-    return !(rates != null ? !rates.equals(that.rates) : that.rates != null);
-  }
-
-  @Override
-  public int hashCode() {
-    return rates != null ? rates.hashCode() : 0;
-  }
-
-  @Override
-  public String toString() {
-    return "["+this.getClass().getName()+" : " + rates.size() + " rates.]";
-  }
-
-  @Override
-  public Set<String> listAvailableCurrencies() {
-    Set<String> currencies = new HashSet<>();
-    for(String from : rates.keySet()) {
-      currencies.add(from);
-      for(String to : rates.get(from).keySet()) {
-        currencies.add(to);
-      }
-    }
-    return currencies;
-  }
-
-  @Override
-  public boolean reload() throws SolrException {
-    InputStream is = null;
-    Map<String, Map<String, Double>> tmpRates = new HashMap<>();
-    try {
-      log.debug("Reloading exchange rates from file "+this.currencyConfigFile);
-
-      is = loader.openResource(currencyConfigFile);
-      javax.xml.parsers.DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
-      try {
-        dbf.setXIncludeAware(true);
-        dbf.setNamespaceAware(true);
-      } catch (UnsupportedOperationException e) {
-        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "XML parser doesn't support XInclude option", e);
-      }
-      
-      try {
-        Document doc = dbf.newDocumentBuilder().parse(is);
-        XPathFactory xpathFactory = XPathFactory.newInstance();
-        XPath xpath = xpathFactory.newXPath();
-        
-        // Parse exchange rates.
-        NodeList nodes = (NodeList) xpath.evaluate("/currencyConfig/rates/rate", doc, XPathConstants.NODESET);
-        
-        for (int i = 0; i < nodes.getLength(); i++) {
-          Node rateNode = nodes.item(i);
-          NamedNodeMap attributes = rateNode.getAttributes();
-          Node from = attributes.getNamedItem("from");
-          Node to = attributes.getNamedItem("to");
-          Node rate = attributes.getNamedItem("rate");
-          
-          if (from == null || to == null || rate == null) {
-            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Exchange rate missing attributes (required: from, to, rate) " + rateNode);
-          }
-          
-          String fromCurrency = from.getNodeValue();
-          String toCurrency = to.getNodeValue();
-          Double exchangeRate;
-          
-          if (null == CurrencyField.getCurrency(fromCurrency)) {
-            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Specified 'from' currency not supported in this JVM: " + fromCurrency);
-          }
-          if (null == CurrencyField.getCurrency(toCurrency)) {
-            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Specified 'to' currency not supported in this JVM: " + toCurrency);
-          }
-          
-          try {
-            exchangeRate = Double.parseDouble(rate.getNodeValue());
-          } catch (NumberFormatException e) {
-            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Could not parse exchange rate: " + rateNode, e);
-          }
-          
-          addRate(tmpRates, fromCurrency, toCurrency, exchangeRate);
-        }
-      } catch (SAXException | XPathExpressionException | ParserConfigurationException | IOException e) {
-        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error parsing currency config.", e);
-      }
-    } catch (IOException e) {
-      throw new SolrException(ErrorCode.SERVER_ERROR, "Error while opening Currency configuration file "+currencyConfigFile, e);
-    } finally {
-      try {
-        if (is != null) {
-          is.close();
-        }
-      } catch (IOException e) {
-        e.printStackTrace();
-      }
-    }
-    // Atomically swap in the new rates map, if it loaded successfully
-    this.rates = tmpRates;
-    return true;
-  }
-
-  @Override
-  public void init(Map<String,String> params) throws SolrException {
-    this.currencyConfigFile = params.get(PARAM_CURRENCY_CONFIG);
-    if(currencyConfigFile == null) {
-      throw new SolrException(ErrorCode.NOT_FOUND, "Missing required configuration "+PARAM_CURRENCY_CONFIG);
-    }
-    
-    // Removing config params custom to us
-    params.remove(PARAM_CURRENCY_CONFIG);
-  }
-
-  @Override
-  public void inform(ResourceLoader loader) throws SolrException {
-    if(loader == null) {
-      throw new SolrException(ErrorCode.SERVER_ERROR, "Needs ResourceLoader in order to load config file");
-    }
-    this.loader = loader;
-    reload();
-  }
-}
-
-/**
- * Represents a Currency field value, which includes a long amount and ISO currency code.
- */
-class CurrencyValue {
-  private long amount;
-  private String currencyCode;
-
-  /**
-   * Constructs a new currency value.
-   *
-   * @param amount       The amount.
-   * @param currencyCode The currency code.
-   */
-  public CurrencyValue(long amount, String currencyCode) {
-    this.amount = amount;
-    this.currencyCode = currencyCode;
-  }
-
-  /**
-   * Constructs a new currency value by parsing the specific input.
-   * <p/>
-   * Currency values are expected to be in the format &lt;amount&gt;,&lt;currency code&gt;,
-   * for example, "500,USD" would represent 5 U.S. Dollars.
-   * <p/>
-   * If no currency code is specified, the default is assumed.
-   *
-   * @param externalVal The value to parse.
-   * @param defaultCurrency The default currency.
-   * @return The parsed CurrencyValue.
-   */
-  public static CurrencyValue parse(String externalVal, String defaultCurrency) {
-    if (externalVal == null) {
-      return null;
-    }
-    String amount = externalVal;
-    String code = defaultCurrency;
-
-    if (externalVal.contains(",")) {
-      String[] amountAndCode = externalVal.split(",");
-      amount = amountAndCode[0];
-      code = amountAndCode[1];
-    }
-
-    if (amount.equals("*")) {
-      return null;
-    }
-    
-    Currency currency = CurrencyField.getCurrency(code);
-
-    if (currency == null) {
-      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Currency code not supported by this JVM: " + code);
-    }
-
-    try {
-      double value = Double.parseDouble(amount);
-      long currencyValue = Math.round(value * Math.pow(10.0, currency.getDefaultFractionDigits()));
-
-      return new CurrencyValue(currencyValue, code);
-    } catch (NumberFormatException e) {
-      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
-    }
-  }
-
-  /**
-   * The amount of the CurrencyValue.
-   *
-   * @return The amount.
-   */
-  public long getAmount() {
-    return amount;
-  }
-
-  /**
-   * The ISO currency code of the CurrencyValue.
-   *
-   * @return The currency code.
-   */
-  public String getCurrencyCode() {
-    return currencyCode;
-  }
-
-  /**
-   * Performs a currency conversion & unit conversion.
-   *
-   * @param exchangeRates      Exchange rates to apply.
-   * @param sourceCurrencyCode The source currency code.
-   * @param sourceAmount       The source amount.
-   * @param targetCurrencyCode The target currency code.
-   * @return The converted indexable units after the exchange rate and currency fraction digits are applied.
-   */
-  public static long convertAmount(ExchangeRateProvider exchangeRates, String sourceCurrencyCode, long sourceAmount, String targetCurrencyCode) {
-    double exchangeRate = exchangeRates.getExchangeRate(sourceCurrencyCode, targetCurrencyCode);
-    return convertAmount(exchangeRate, sourceCurrencyCode, sourceAmount, targetCurrencyCode);
-  }
-
-  /**
-   * Performs a currency conversion & unit conversion.
-   *
-   * @param exchangeRate         Exchange rate to apply.
-   * @param sourceFractionDigits The fraction digits of the source.
-   * @param sourceAmount         The source amount.
-   * @param targetFractionDigits The fraction digits of the target.
-   * @return The converted indexable units after the exchange rate and currency fraction digits are applied.
-   */
-  public static long convertAmount(final double exchangeRate, final int sourceFractionDigits, final long sourceAmount, final int targetFractionDigits) {
-    int digitDelta = targetFractionDigits - sourceFractionDigits;
-    double value = ((double) sourceAmount * exchangeRate);
-
-    if (digitDelta != 0) {
-      if (digitDelta < 0) {
-        for (int i = 0; i < -digitDelta; i++) {
-          value *= 0.1;
-        }
-      } else {
-        for (int i = 0; i < digitDelta; i++) {
-          value *= 10.0;
-        }
-      }
-    }
-
-    return (long) value;
-  }
-
-  /**
-   * Performs a currency conversion & unit conversion.
-   *
-   * @param exchangeRate       Exchange rate to apply.
-   * @param sourceCurrencyCode The source currency code.
-   * @param sourceAmount       The source amount.
-   * @param targetCurrencyCode The target currency code.
-   * @return The converted indexable units after the exchange rate and currency fraction digits are applied.
-   */
-  public static long convertAmount(double exchangeRate, String sourceCurrencyCode, long sourceAmount, String targetCurrencyCode) {
-    if (targetCurrencyCode.equals(sourceCurrencyCode)) {
-      return sourceAmount;
-    }
-
-    int sourceFractionDigits = Currency.getInstance(sourceCurrencyCode).getDefaultFractionDigits();
-    Currency targetCurrency = Currency.getInstance(targetCurrencyCode);
-    int targetFractionDigits = targetCurrency.getDefaultFractionDigits();
-    return convertAmount(exchangeRate, sourceFractionDigits, sourceAmount, targetFractionDigits);
-  }
-
-  /**
-   * Returns a new CurrencyValue that is the conversion of this CurrencyValue to the specified currency.
-   *
-   * @param exchangeRates      The exchange rate provider.
-   * @param targetCurrencyCode The target currency code to convert this CurrencyValue to.
-   * @return The converted CurrencyValue.
-   */
-  public CurrencyValue convertTo(ExchangeRateProvider exchangeRates, String targetCurrencyCode) {
-    return new CurrencyValue(convertAmount(exchangeRates, this.getCurrencyCode(), this.getAmount(), targetCurrencyCode), targetCurrencyCode);
-  }
-
-  @Override
-  public String toString() {
-    return String.valueOf(amount) + "," + currencyCode;
+    super.inform(schema);
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/java/org/apache/solr/schema/CurrencyFieldType.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/CurrencyFieldType.java b/solr/core/src/java/org/apache/solr/schema/CurrencyFieldType.java
new file mode 100644
index 0000000..0ace37e
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/schema/CurrencyFieldType.java
@@ -0,0 +1,829 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.schema;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Currency;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lucene.analysis.util.ResourceLoader;
+import org.apache.lucene.analysis.util.ResourceLoaderAware;
+import org.apache.lucene.document.StoredField;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.queries.function.FunctionValues;
+import org.apache.lucene.queries.function.ValueSource;
+import org.apache.lucene.search.BooleanClause.Occur;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.FieldValueQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.SortField;
+import org.apache.solr.uninverting.UninvertingReader.Type;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.response.TextResponseWriter;
+import org.apache.solr.search.Filter;
+import org.apache.solr.search.QParser;
+import org.apache.solr.search.QueryWrapperFilter;
+import org.apache.solr.search.SolrConstantScoreQuery;
+import org.apache.solr.search.function.ValueSourceRangeFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Field type for support of monetary values.
+ * <p>
+ * See <a href="http://wiki.apache.org/solr/CurrencyField">http://wiki.apache.org/solr/CurrencyField</a>
+ */
+public class CurrencyFieldType extends FieldType implements SchemaAware, ResourceLoaderAware {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+  
+  protected static final String PARAM_DEFAULT_CURRENCY = "defaultCurrency";
+  protected static final String DEFAULT_DEFAULT_CURRENCY = "USD";
+  protected static final String PARAM_RATE_PROVIDER_CLASS = "providerClass";
+  protected static final String DEFAULT_RATE_PROVIDER_CLASS = "solr.FileExchangeRateProvider";
+  protected static final String PARAM_FIELD_SUFFIX_AMOUNT_RAW = "amountLongSuffix";
+  protected static final String PARAM_FIELD_SUFFIX_CURRENCY = "codeStrSuffix";
+
+  protected IndexSchema schema;
+  protected FieldType fieldTypeCurrency;
+  protected FieldType fieldTypeAmountRaw;
+  protected String fieldSuffixAmountRaw;
+  protected String fieldSuffixCurrency;
+  
+  private String exchangeRateProviderClass;
+  private String defaultCurrency;
+  private ExchangeRateProvider provider;
+
+  /**
+   * A wrapper around <code>Currency.getInstance</code> that returns null
+   * instead of throwing <code>IllegalArgumentException</code>
+   * if the specified Currency does not exist in this JVM.
+   *
+   * @see Currency#getInstance(String)
+   */
+  public static Currency getCurrency(final String code) {
+    try {
+      return Currency.getInstance(code);
+    } catch (IllegalArgumentException e) {
+      /* :NOOP: */
+    }
+    return null;
+  }
+
+  @Override
+  protected void init(IndexSchema schema, Map<String, String> args) {
+    super.init(schema, args);
+    if (this.isMultiValued()) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
+          getClass().getSimpleName() + " types can not be multiValued: " + this.typeName);
+    }
+    this.schema = schema;
+
+    this.defaultCurrency = args.get(PARAM_DEFAULT_CURRENCY);
+    if (this.defaultCurrency == null) {
+      this.defaultCurrency = DEFAULT_DEFAULT_CURRENCY;
+    } else {
+      args.remove(PARAM_DEFAULT_CURRENCY);
+    }
+    if (null == getCurrency(this.defaultCurrency)) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+          "Default currency code is not supported by this JVM: " + this.defaultCurrency);
+    }
+
+    this.exchangeRateProviderClass = args.get(PARAM_RATE_PROVIDER_CLASS);
+    if (this.exchangeRateProviderClass == null) {
+      this.exchangeRateProviderClass = DEFAULT_RATE_PROVIDER_CLASS;
+    } else {
+      args.remove(PARAM_RATE_PROVIDER_CLASS);
+    }
+    try {
+      Class<? extends ExchangeRateProvider> c
+          = schema.getResourceLoader().findClass(exchangeRateProviderClass, ExchangeRateProvider.class);
+      provider = c.newInstance();
+      provider.init(args);
+    } catch (Exception e) {
+      throw new SolrException(ErrorCode.SERVER_ERROR,
+          "Error instantiating exchange rate provider " + exchangeRateProviderClass + ": " + e.getMessage(), e);
+    }
+
+    if (fieldTypeAmountRaw == null) {      // Don't initialize if subclass already has done so
+      fieldSuffixAmountRaw = args.get(PARAM_FIELD_SUFFIX_AMOUNT_RAW);
+      if (fieldSuffixAmountRaw == null) {
+        throw new SolrException(ErrorCode.SERVER_ERROR, "Missing required param " + PARAM_FIELD_SUFFIX_AMOUNT_RAW);
+      } else {
+        args.remove(PARAM_FIELD_SUFFIX_AMOUNT_RAW);
+      }
+    }
+    
+    if (fieldTypeCurrency == null) {       // Don't initialize if subclass already has done so
+      fieldSuffixCurrency = args.get(PARAM_FIELD_SUFFIX_CURRENCY);
+      if (fieldSuffixCurrency == null) {
+        throw new SolrException(ErrorCode.SERVER_ERROR, "Missing required param " + PARAM_FIELD_SUFFIX_CURRENCY);
+      } else {
+        args.remove(PARAM_FIELD_SUFFIX_CURRENCY);
+      }
+    }
+  }
+
+  @Override
+  public boolean isPolyField() {
+    return true;
+  }
+
+  @Override
+  public void checkSchemaField(final SchemaField field) throws SolrException {
+    super.checkSchemaField(field);
+    if (field.multiValued()) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
+          getClass().getSimpleName() + " fields can not be multiValued: " + field.getName());
+    }
+  }
+
+  @Override
+  public List<IndexableField> createFields(SchemaField field, Object externalVal) {
+    CurrencyValue value = CurrencyValue.parse(externalVal.toString(), defaultCurrency);
+
+    List<IndexableField> f = new ArrayList<>();
+    SchemaField amountField = getAmountField(field);
+    f.add(amountField.createField(String.valueOf(value.getAmount())));
+    SchemaField currencyField = getCurrencyField(field);
+    f.add(currencyField.createField(value.getCurrencyCode()));
+
+    if (field.stored()) {
+      String storedValue = externalVal.toString().trim();
+      if (storedValue.indexOf(",") < 0) {
+        storedValue += "," + defaultCurrency;
+      }
+      f.add(createField(field.getName(), storedValue, StoredField.TYPE));
+    }
+
+    return f;
+  }
+  
+  private SchemaField getAmountField(SchemaField field) {
+    return schema.getField(field.getName() + POLY_FIELD_SEPARATOR + fieldSuffixAmountRaw);
+  }
+
+  private SchemaField getCurrencyField(SchemaField field) {
+    return schema.getField(field.getName() + POLY_FIELD_SEPARATOR + fieldSuffixCurrency);
+  }
+
+  /**
+   * When index schema is informed, get field types for the configured dynamic sub-fields 
+   *
+   * {@inheritDoc}
+   *
+   * @param schema {@inheritDoc}
+   */
+  @Override
+  public void inform(IndexSchema schema) {
+    this.schema = schema;
+    if (null == fieldTypeAmountRaw) {
+      assert null != fieldSuffixAmountRaw : "How did we get here?";
+      SchemaField field = schema.getFieldOrNull(POLY_FIELD_SEPARATOR + fieldSuffixAmountRaw);
+      if (field == null) {
+        throw new SolrException(ErrorCode.SERVER_ERROR, "Field type \"" + this.getTypeName()
+            + "\": Undefined dynamic field for " + PARAM_FIELD_SUFFIX_AMOUNT_RAW + "=\"" + fieldSuffixAmountRaw + "\"");
+      }
+      fieldTypeAmountRaw = field.getType();
+      if (!(fieldTypeAmountRaw instanceof LongValueFieldType)) {
+        throw new SolrException(ErrorCode.SERVER_ERROR, "Field type \"" + this.getTypeName()
+            + "\": Dynamic field for " + PARAM_FIELD_SUFFIX_AMOUNT_RAW + "=\"" + fieldSuffixAmountRaw
+            + "\" must have type class extending LongValueFieldType");
+      }
+    }
+    if (null == fieldTypeCurrency) {
+      assert null != fieldSuffixCurrency : "How did we get here?";
+      SchemaField field = schema.getFieldOrNull(POLY_FIELD_SEPARATOR + fieldSuffixCurrency);
+      if (field == null) {
+        throw new SolrException(ErrorCode.SERVER_ERROR, "Field type \"" + this.getTypeName()
+            + "\": Undefined dynamic field for " + PARAM_FIELD_SUFFIX_CURRENCY + "=\"" + fieldSuffixCurrency + "\"");
+      }
+      fieldTypeCurrency = field.getType();
+      if (!(fieldTypeCurrency instanceof StrField)) {
+        throw new SolrException(ErrorCode.SERVER_ERROR, "Field type \"" + this.getTypeName()
+            + "\": Dynamic field for " + PARAM_FIELD_SUFFIX_CURRENCY + "=\"" + fieldSuffixCurrency
+            + "\" must have type class of (or extending) StrField");
+      }
+    }
+  }
+
+  /**
+   * Load the currency config when resource loader initialized.
+   *
+   * @param resourceLoader The resource loader.
+   */
+  @Override
+  public void inform(ResourceLoader resourceLoader) {
+    provider.inform(resourceLoader);
+    boolean reloaded = provider.reload();
+    if(!reloaded) {
+      log.warn("Failed reloading currencies");
+    }
+  }
+
+  @Override
+  public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) {
+    CurrencyValue value = CurrencyValue.parse(externalVal, defaultCurrency);
+    CurrencyValue valueDefault;
+    valueDefault = value.convertTo(provider, defaultCurrency);
+
+    return getRangeQuery(parser, field, valueDefault, valueDefault, true, true);
+  }
+
+  /**
+   * <p>
+   * Returns a ValueSource over this field in which the numeric value for 
+   * each document represents the indexed value as converted to the default 
+   * currency for the field, normalized to its most granular form based 
+   * on the default fractional digits.
+   * </p>
+   * <p>
+   * For example: If the default Currency specified for a field is 
+   * <code>USD</code>, then the values returned by this value source would 
+   * represent the equivilent number of "cents" (ie: value in dollars * 100) 
+   * after converting each document's native currency to USD -- because the 
+   * default fractional digits for <code>USD</code> is "<code>2</code>".  
+   * So for a document whose indexed value was currently equivilent to 
+   * "<code>5.43,USD</code>" using the the exchange provider for this field, 
+   * this ValueSource would return a value of "<code>543</code>"
+   * </p>
+   *
+   * @see #PARAM_DEFAULT_CURRENCY
+   * @see #DEFAULT_DEFAULT_CURRENCY
+   * @see Currency#getDefaultFractionDigits
+   * @see #getConvertedValueSource
+   */
+  public RawCurrencyValueSource getValueSource(SchemaField field,
+                                               QParser parser) {
+    getAmountField(field).checkFieldCacheSource();
+    getCurrencyField(field).checkFieldCacheSource();
+    return new RawCurrencyValueSource(field, defaultCurrency, parser);
+  }
+
+  /**
+   * <p>
+   * Returns a ValueSource over this field in which the numeric value for 
+   * each document represents the value from the underlying 
+   * <code>RawCurrencyValueSource</code> as converted to the specified target 
+   * Currency.
+   * </p>
+   * <p>
+   * For example: If the <code>targetCurrencyCode</code> param is set to
+   * <code>USD</code>, then the values returned by this value source would 
+   * represent the equivilent number of dollars after converting each 
+   * document's raw value to <code>USD</code>.  So for a document whose 
+   * indexed value was currently equivilent to "<code>5.43,USD</code>" 
+   * using the the exchange provider for this field, this ValueSource would 
+   * return a value of "<code>5.43</code>"
+   * </p>
+   *
+   * @param targetCurrencyCode The target currency for the resulting value source, if null the defaultCurrency for this field type will be used
+   * @param source the raw ValueSource to wrap
+   * @see #PARAM_DEFAULT_CURRENCY
+   * @see #DEFAULT_DEFAULT_CURRENCY
+   * @see #getValueSource
+   */
+  public ValueSource getConvertedValueSource(String targetCurrencyCode,
+                                             RawCurrencyValueSource source) {
+    if (null == targetCurrencyCode) {
+      targetCurrencyCode = defaultCurrency;
+    }
+    return new ConvertedCurrencyValueSource(targetCurrencyCode,
+        source);
+  }
+
+  @Override
+  public Query getRangeQuery(QParser parser, SchemaField field, String part1, String part2, final boolean minInclusive, final boolean maxInclusive) {
+    final CurrencyValue p1 = CurrencyValue.parse(part1, defaultCurrency);
+    final CurrencyValue p2 = CurrencyValue.parse(part2, defaultCurrency);
+
+    if (p1 != null && p2 != null && !p1.getCurrencyCode().equals(p2.getCurrencyCode())) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+          "Cannot parse range query " + part1 + " to " + part2 +
+              ": range queries only supported when upper and lower bound have same currency.");
+    }
+
+    return getRangeQuery(parser, field, p1, p2, minInclusive, maxInclusive);
+  }
+
+  public Query getRangeQuery(QParser parser, SchemaField field, final CurrencyValue p1, final CurrencyValue p2, final boolean minInclusive, final boolean maxInclusive) {
+    String currencyCode = (p1 != null) ? p1.getCurrencyCode() :
+        (p2 != null) ? p2.getCurrencyCode() : defaultCurrency;
+
+    // ValueSourceRangeFilter doesn't check exists(), so we have to
+    final Filter docsWithValues = new QueryWrapperFilter(new FieldValueQuery(getAmountField(field).getName()));
+    final Filter vsRangeFilter = new ValueSourceRangeFilter
+        (new RawCurrencyValueSource(field, currencyCode, parser),
+            p1 == null ? null : p1.getAmount() + "",
+            p2 == null ? null : p2.getAmount() + "",
+            minInclusive, maxInclusive);
+    final BooleanQuery.Builder docsInRange = new BooleanQuery.Builder();
+    docsInRange.add(docsWithValues, Occur.FILTER);
+    docsInRange.add(vsRangeFilter, Occur.FILTER);
+
+    return new SolrConstantScoreQuery(new QueryWrapperFilter(docsInRange.build()));
+  }
+
+  @Override
+  public SortField getSortField(SchemaField field, boolean reverse) {
+    // Convert all values to default currency for sorting.
+    return (new RawCurrencyValueSource(field, defaultCurrency, null)).getSortField(reverse);
+  }
+
+  @Override
+  public Type getUninversionType(SchemaField sf) {
+    return null;
+  }
+
+  @Override
+  public void write(TextResponseWriter writer, String name, IndexableField field) throws IOException {
+    writer.writeStr(name, field.stringValue(), true);
+  }
+
+  public ExchangeRateProvider getProvider() {
+    return provider;
+  }
+
+  /**
+   * <p>
+   * A value source whose values represent the "normal" values
+   * in the specified target currency.
+   * </p>
+   * @see RawCurrencyValueSource
+   */
+  class ConvertedCurrencyValueSource extends ValueSource {
+    private final Currency targetCurrency;
+    private final RawCurrencyValueSource source;
+    private final double rate;
+    public ConvertedCurrencyValueSource(String targetCurrencyCode,
+                                        RawCurrencyValueSource source) {
+      this.source = source;
+      this.targetCurrency = getCurrency(targetCurrencyCode);
+      if (null == targetCurrency) {
+        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Currency code not supported by this JVM: " + targetCurrencyCode);
+      }
+      // the target digits & currency of our source, 
+      // become the source digits & currency of ourselves
+      this.rate = provider.getExchangeRate
+          (source.getTargetCurrency().getCurrencyCode(),
+              targetCurrency.getCurrencyCode());
+    }
+
+    @Override
+    public FunctionValues getValues(Map context, LeafReaderContext reader)
+        throws IOException {
+      final FunctionValues amounts = source.getValues(context, reader);
+      // the target digits & currency of our source, 
+      // become the source digits & currency of ourselves
+      final String sourceCurrencyCode = source.getTargetCurrency().getCurrencyCode();
+      final double divisor = Math.pow(10D, targetCurrency.getDefaultFractionDigits());
+      return new FunctionValues() {
+        @Override
+        public boolean exists(int doc) throws IOException {
+          return amounts.exists(doc);
+        }
+        @Override
+        public long longVal(int doc) throws IOException {
+          return (long) doubleVal(doc);
+        }
+        @Override
+        public int intVal(int doc) throws IOException {
+          return (int) doubleVal(doc);
+        }
+
+        @Override
+        public double doubleVal(int doc) throws IOException {
+          return CurrencyValue.convertAmount(rate, sourceCurrencyCode, amounts.longVal(doc), targetCurrency.getCurrencyCode()) / divisor;
+        }
+
+        @Override
+        public float floatVal(int doc) throws IOException {
+          return CurrencyValue.convertAmount(rate, sourceCurrencyCode, amounts.longVal(doc), targetCurrency.getCurrencyCode()) / ((float)divisor);
+        }
+
+        @Override
+        public String strVal(int doc) throws IOException {
+          return Double.toString(doubleVal(doc));
+        }
+
+        @Override
+        public String toString(int doc) throws IOException {
+          return name() + '(' + strVal(doc) + ')';
+        }
+      };
+    }
+    public String name() {
+      return "currency";
+    }
+
+    @Override
+    public String description() {
+      return name() + "(" + source.getField().getName() + "," + targetCurrency.getCurrencyCode()+")";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+
+      ConvertedCurrencyValueSource that = (ConvertedCurrencyValueSource) o;
+
+      return !(source != null ? !source.equals(that.source) : that.source != null) &&
+          (rate == that.rate) &&
+          !(targetCurrency != null ? !targetCurrency.equals(that.targetCurrency) : that.targetCurrency != null);
+
+    }
+
+    @Override
+    public int hashCode() {
+      int result = targetCurrency != null ? targetCurrency.hashCode() : 0;
+      result = 31 * result + (source != null ? source.hashCode() : 0);
+      result = 31 * (int) Double.doubleToLongBits(rate);
+      return result;
+    }
+  }
+
+  /**
+   * <p>
+   * A value source whose values represent the "raw" (ie: normalized using 
+   * the number of default fractional digits) values in the specified 
+   * target currency).
+   * </p>
+   * <p>
+   * For example: if the specified target currency is "<code>USD</code>" 
+   * then the numeric values are the number of pennies in the value 
+   * (ie: <code>$n * 100</code>) since the number of default fractional 
+   * digits for <code>USD</code> is "<code>2</code>")
+   * </p>
+   * @see ConvertedCurrencyValueSource
+   */
+  class RawCurrencyValueSource extends ValueSource {
+    private static final long serialVersionUID = 1L;
+    private final Currency targetCurrency;
+    private ValueSource currencyValues;
+    private ValueSource amountValues;
+    private final SchemaField sf;
+
+    public RawCurrencyValueSource(SchemaField sfield, String targetCurrencyCode, QParser parser) {
+      this.sf = sfield;
+      this.targetCurrency = getCurrency(targetCurrencyCode);
+      if (null == targetCurrency) {
+        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Currency code not supported by this JVM: " + targetCurrencyCode);
+      }
+
+      SchemaField amountField = getAmountField(sf);
+      SchemaField currencyField = getCurrencyField(sf);
+
+      currencyValues = currencyField.getType().getValueSource(currencyField, parser);
+      amountValues = amountField.getType().getValueSource(amountField, parser);
+    }
+
+    public SchemaField getField() { return sf; }
+    public Currency getTargetCurrency() { return targetCurrency; }
+
+    @Override
+    public FunctionValues getValues(Map context, LeafReaderContext reader) throws IOException {
+      final FunctionValues amounts = amountValues.getValues(context, reader);
+      final FunctionValues currencies = currencyValues.getValues(context, reader);
+
+      return new FunctionValues() {
+        private static final int MAX_CURRENCIES_TO_CACHE = 256;
+        private final int[] fractionDigitCache = new int[MAX_CURRENCIES_TO_CACHE];
+        private final String[] currencyOrdToCurrencyCache = new String[MAX_CURRENCIES_TO_CACHE];
+        private final double[] exchangeRateCache = new double[MAX_CURRENCIES_TO_CACHE];
+        private int targetFractionDigits = -1;
+        private int targetCurrencyOrd = -1;
+        private boolean initializedCache;
+
+        private String getDocCurrencyCode(int doc, int currencyOrd) throws IOException {
+          if (currencyOrd < MAX_CURRENCIES_TO_CACHE) {
+            String currency = currencyOrdToCurrencyCache[currencyOrd];
+
+            if (currency == null) {
+              currencyOrdToCurrencyCache[currencyOrd] = currency = currencies.strVal(doc);
+            }
+
+            if (currency == null) {
+              currency = defaultCurrency;
+            }
+
+            if (targetCurrencyOrd == -1 &&
+                currency.equals(targetCurrency.getCurrencyCode() )) {
+              targetCurrencyOrd = currencyOrd;
+            }
+
+            return currency;
+          } else {
+            return currencies.strVal(doc);
+          }
+        }
+        /** throws a (Server Error) SolrException if the code is not valid */
+        private Currency getDocCurrency(int doc, int currencyOrd) throws IOException {
+          String code = getDocCurrencyCode(doc, currencyOrd);
+          Currency c = getCurrency(code);
+          if (null == c) {
+            throw new SolrException
+                (SolrException.ErrorCode.SERVER_ERROR,
+                    "Currency code of document is not supported by this JVM: "+code);
+          }
+          return c;
+        }
+
+        @Override
+        public boolean exists(int doc) throws IOException {
+          return amounts.exists(doc);
+        }
+
+        @Override
+        public long longVal(int doc) throws IOException {
+          long amount = amounts.longVal(doc);
+          // bail fast using whatever amounts defaults to if no value
+          // (if we don't do this early, currencyOrd may be < 0, 
+          // causing index bounds exception
+          if ( ! exists(doc) ) {
+            return amount;
+          }
+
+          if (!initializedCache) {
+            for (int i = 0; i < fractionDigitCache.length; i++) {
+              fractionDigitCache[i] = -1;
+            }
+
+            initializedCache = true;
+          }
+
+          int currencyOrd = currencies.ordVal(doc);
+
+          if (currencyOrd == targetCurrencyOrd) {
+            return amount;
+          }
+
+          double exchangeRate;
+          int sourceFractionDigits;
+
+          if (targetFractionDigits == -1) {
+            targetFractionDigits = targetCurrency.getDefaultFractionDigits();
+          }
+
+          if (currencyOrd < MAX_CURRENCIES_TO_CACHE) {
+            exchangeRate = exchangeRateCache[currencyOrd];
+
+            if (exchangeRate <= 0.0) {
+              String sourceCurrencyCode = getDocCurrencyCode(doc, currencyOrd);
+              exchangeRate = exchangeRateCache[currencyOrd] = provider.getExchangeRate(sourceCurrencyCode, targetCurrency.getCurrencyCode());
+            }
+
+            sourceFractionDigits = fractionDigitCache[currencyOrd];
+
+            if (sourceFractionDigits == -1) {
+              sourceFractionDigits = fractionDigitCache[currencyOrd] = getDocCurrency(doc, currencyOrd).getDefaultFractionDigits();
+            }
+          } else {
+            Currency source = getDocCurrency(doc, currencyOrd);
+            exchangeRate = provider.getExchangeRate(source.getCurrencyCode(), targetCurrency.getCurrencyCode());
+            sourceFractionDigits = source.getDefaultFractionDigits();
+          }
+
+          return CurrencyValue.convertAmount(exchangeRate, sourceFractionDigits, amount, targetFractionDigits);
+        }
+
+        @Override
+        public int intVal(int doc) throws IOException {
+          return (int) longVal(doc);
+        }
+
+        @Override
+        public double doubleVal(int doc) throws IOException {
+          return (double) longVal(doc);
+        }
+
+        @Override
+        public float floatVal(int doc) throws IOException {
+          return (float) longVal(doc);
+        }
+
+        @Override
+        public String strVal(int doc) throws IOException {
+          return Long.toString(longVal(doc));
+        }
+
+        @Override
+        public String toString(int doc) throws IOException {
+          return name() + '(' + amounts.toString(doc) + ',' + currencies.toString(doc) + ')';
+        }
+      };
+    }
+
+    public String name() {
+      return "rawcurrency";
+    }
+
+    @Override
+    public String description() {
+      return name() + "(" + sf.getName() +
+          ",target="+targetCurrency.getCurrencyCode()+")";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+
+      RawCurrencyValueSource that = (RawCurrencyValueSource) o;
+
+      return !(amountValues != null ? !amountValues.equals(that.amountValues) : that.amountValues != null) &&
+          !(currencyValues != null ? !currencyValues.equals(that.currencyValues) : that.currencyValues != null) &&
+          !(targetCurrency != null ? !targetCurrency.equals(that.targetCurrency) : that.targetCurrency != null);
+
+    }
+
+    @Override
+    public int hashCode() {
+      int result = targetCurrency != null ? targetCurrency.hashCode() : 0;
+      result = 31 * result + (currencyValues != null ? currencyValues.hashCode() : 0);
+      result = 31 * result + (amountValues != null ? amountValues.hashCode() : 0);
+      return result;
+    }
+  }
+
+  /**
+   * Represents a Currency field value, which includes a long amount and ISO currency code.
+   */
+  static class CurrencyValue {
+    private long amount;
+    private String currencyCode;
+  
+    /**
+     * Constructs a new currency value.
+     *
+     * @param amount       The amount.
+     * @param currencyCode The currency code.
+     */
+    public CurrencyValue(long amount, String currencyCode) {
+      this.amount = amount;
+      this.currencyCode = currencyCode;
+    }
+  
+    /**
+     * Constructs a new currency value by parsing the specific input.
+     * <p/>
+     * Currency values are expected to be in the format &lt;amount&gt;,&lt;currency code&gt;,
+     * for example, "500,USD" would represent 5 U.S. Dollars.
+     * <p/>
+     * If no currency code is specified, the default is assumed.
+     *
+     * @param externalVal The value to parse.
+     * @param defaultCurrency The default currency.
+     * @return The parsed CurrencyValue.
+     */
+    public static CurrencyValue parse(String externalVal, String defaultCurrency) {
+      if (externalVal == null) {
+        return null;
+      }
+      String amount = externalVal;
+      String code = defaultCurrency;
+  
+      if (externalVal.contains(",")) {
+        String[] amountAndCode = externalVal.split(",");
+        amount = amountAndCode[0];
+        code = amountAndCode[1];
+      }
+  
+      if (amount.equals("*")) {
+        return null;
+      }
+      
+      Currency currency = getCurrency(code);
+  
+      if (currency == null) {
+        throw new SolrException(ErrorCode.BAD_REQUEST, "Currency code not supported by this JVM: " + code);
+      }
+  
+      try {
+        double value = Double.parseDouble(amount);
+        long currencyValue = Math.round(value * Math.pow(10.0, currency.getDefaultFractionDigits()));
+  
+        return new CurrencyValue(currencyValue, code);
+      } catch (NumberFormatException e) {
+        throw new SolrException(ErrorCode.BAD_REQUEST, e);
+      }
+    }
+  
+    /**
+     * The amount of the CurrencyValue.
+     *
+     * @return The amount.
+     */
+    public long getAmount() {
+      return amount;
+    }
+  
+    /**
+     * The ISO currency code of the CurrencyValue.
+     *
+     * @return The currency code.
+     */
+    public String getCurrencyCode() {
+      return currencyCode;
+    }
+  
+    /**
+     * Performs a currency conversion & unit conversion.
+     *
+     * @param exchangeRates      Exchange rates to apply.
+     * @param sourceCurrencyCode The source currency code.
+     * @param sourceAmount       The source amount.
+     * @param targetCurrencyCode The target currency code.
+     * @return The converted indexable units after the exchange rate and currency fraction digits are applied.
+     */
+    public static long convertAmount(ExchangeRateProvider exchangeRates, String sourceCurrencyCode, long sourceAmount, String targetCurrencyCode) {
+      double exchangeRate = exchangeRates.getExchangeRate(sourceCurrencyCode, targetCurrencyCode);
+      return convertAmount(exchangeRate, sourceCurrencyCode, sourceAmount, targetCurrencyCode);
+    }
+  
+    /**
+     * Performs a currency conversion & unit conversion.
+     *
+     * @param exchangeRate         Exchange rate to apply.
+     * @param sourceFractionDigits The fraction digits of the source.
+     * @param sourceAmount         The source amount.
+     * @param targetFractionDigits The fraction digits of the target.
+     * @return The converted indexable units after the exchange rate and currency fraction digits are applied.
+     */
+    public static long convertAmount(final double exchangeRate, final int sourceFractionDigits, final long sourceAmount, final int targetFractionDigits) {
+      int digitDelta = targetFractionDigits - sourceFractionDigits;
+      double value = ((double) sourceAmount * exchangeRate);
+  
+      if (digitDelta != 0) {
+        if (digitDelta < 0) {
+          for (int i = 0; i < -digitDelta; i++) {
+            value *= 0.1;
+          }
+        } else {
+          for (int i = 0; i < digitDelta; i++) {
+            value *= 10.0;
+          }
+        }
+      }
+  
+      return (long) value;
+    }
+  
+    /**
+     * Performs a currency conversion & unit conversion.
+     *
+     * @param exchangeRate       Exchange rate to apply.
+     * @param sourceCurrencyCode The source currency code.
+     * @param sourceAmount       The source amount.
+     * @param targetCurrencyCode The target currency code.
+     * @return The converted indexable units after the exchange rate and currency fraction digits are applied.
+     */
+    public static long convertAmount(double exchangeRate, String sourceCurrencyCode, long sourceAmount, String targetCurrencyCode) {
+      if (targetCurrencyCode.equals(sourceCurrencyCode)) {
+        return sourceAmount;
+      }
+  
+      int sourceFractionDigits = Currency.getInstance(sourceCurrencyCode).getDefaultFractionDigits();
+      Currency targetCurrency = Currency.getInstance(targetCurrencyCode);
+      int targetFractionDigits = targetCurrency.getDefaultFractionDigits();
+      return convertAmount(exchangeRate, sourceFractionDigits, sourceAmount, targetFractionDigits);
+    }
+  
+    /**
+     * Returns a new CurrencyValue that is the conversion of this CurrencyValue to the specified currency.
+     *
+     * @param exchangeRates      The exchange rate provider.
+     * @param targetCurrencyCode The target currency code to convert this CurrencyValue to.
+     * @return The converted CurrencyValue.
+     */
+    public CurrencyValue convertTo(ExchangeRateProvider exchangeRates, String targetCurrencyCode) {
+      return new CurrencyValue(convertAmount(exchangeRates, this.getCurrencyCode(), this.getAmount(), targetCurrencyCode), targetCurrencyCode);
+    }
+  
+    @Override
+    public String toString() {
+      return String.valueOf(amount) + "," + currencyCode;
+    }
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/java/org/apache/solr/schema/FileExchangeRateProvider.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/FileExchangeRateProvider.java b/solr/core/src/java/org/apache/solr/schema/FileExchangeRateProvider.java
new file mode 100644
index 0000000..0148617
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/schema/FileExchangeRateProvider.java
@@ -0,0 +1,252 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.schema;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.invoke.MethodHandles;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.lucene.analysis.util.ResourceLoader;
+import org.apache.solr.common.SolrException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ * Configuration for currency. Provides currency exchange rates.
+ */
+class FileExchangeRateProvider implements ExchangeRateProvider {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+  protected static final String PARAM_CURRENCY_CONFIG       = "currencyConfig";
+
+  // Exchange rate map, maps Currency Code -> Currency Code -> Rate
+  private Map<String, Map<String, Double>> rates = new HashMap<>();
+
+  private String currencyConfigFile;
+  private ResourceLoader loader;
+
+  /**
+   * Returns the currently known exchange rate between two currencies. If a direct rate has been loaded,
+   * it is used. Otherwise, if a rate is known to convert the target currency to the source, the inverse
+   * exchange rate is computed.
+   *
+   * @param sourceCurrencyCode The source currency being converted from.
+   * @param targetCurrencyCode The target currency being converted to.
+   * @return The exchange rate.
+   * @throws SolrException if the requested currency pair cannot be found
+   */
+  @Override
+  public double getExchangeRate(String sourceCurrencyCode, String targetCurrencyCode) {
+    if (sourceCurrencyCode == null || targetCurrencyCode == null) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Cannot get exchange rate; currency was null.");
+    }
+    
+    if (sourceCurrencyCode.equals(targetCurrencyCode)) {
+      return 1.0;
+    }
+
+    Double directRate = lookupRate(sourceCurrencyCode, targetCurrencyCode);
+
+    if (directRate != null) {
+      return directRate;
+    }
+
+    Double symmetricRate = lookupRate(targetCurrencyCode, sourceCurrencyCode);
+
+    if (symmetricRate != null) {
+      return 1.0 / symmetricRate;
+    }
+
+    throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No available conversion rate between " + sourceCurrencyCode + " to " + targetCurrencyCode);
+  }
+
+  /**
+   * Looks up the current known rate, if any, between the source and target currencies.
+   *
+   * @param sourceCurrencyCode The source currency being converted from.
+   * @param targetCurrencyCode The target currency being converted to.
+   * @return The exchange rate, or null if no rate has been registered.
+   */
+  private Double lookupRate(String sourceCurrencyCode, String targetCurrencyCode) {
+    Map<String, Double> rhs = rates.get(sourceCurrencyCode);
+
+    if (rhs != null) {
+      return rhs.get(targetCurrencyCode);
+    }
+
+    return null;
+  }
+
+  /**
+   * Registers the specified exchange rate.
+   *
+   * @param ratesMap           The map to add rate to
+   * @param sourceCurrencyCode The source currency.
+   * @param targetCurrencyCode The target currency.
+   * @param rate               The known exchange rate.
+   */
+  private void addRate(Map<String, Map<String, Double>> ratesMap, String sourceCurrencyCode, String targetCurrencyCode, double rate) {
+    Map<String, Double> rhs = ratesMap.get(sourceCurrencyCode);
+
+    if (rhs == null) {
+      rhs = new HashMap<>();
+      ratesMap.put(sourceCurrencyCode, rhs);
+    }
+
+    rhs.put(targetCurrencyCode, rate);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    FileExchangeRateProvider that = (FileExchangeRateProvider) o;
+
+    return !(rates != null ? !rates.equals(that.rates) : that.rates != null);
+  }
+
+  @Override
+  public int hashCode() {
+    return rates != null ? rates.hashCode() : 0;
+  }
+
+  @Override
+  public String toString() {
+    return "["+this.getClass().getName()+" : " + rates.size() + " rates.]";
+  }
+
+  @Override
+  public Set<String> listAvailableCurrencies() {
+    Set<String> currencies = new HashSet<>();
+    for(String from : rates.keySet()) {
+      currencies.add(from);
+      for(String to : rates.get(from).keySet()) {
+        currencies.add(to);
+      }
+    }
+    return currencies;
+  }
+
+  @Override
+  public boolean reload() throws SolrException {
+    InputStream is = null;
+    Map<String, Map<String, Double>> tmpRates = new HashMap<>();
+    try {
+      log.debug("Reloading exchange rates from file "+this.currencyConfigFile);
+
+      is = loader.openResource(currencyConfigFile);
+      javax.xml.parsers.DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+      try {
+        dbf.setXIncludeAware(true);
+        dbf.setNamespaceAware(true);
+      } catch (UnsupportedOperationException e) {
+        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "XML parser doesn't support XInclude option", e);
+      }
+      
+      try {
+        Document doc = dbf.newDocumentBuilder().parse(is);
+        XPathFactory xpathFactory = XPathFactory.newInstance();
+        XPath xpath = xpathFactory.newXPath();
+        
+        // Parse exchange rates.
+        NodeList nodes = (NodeList) xpath.evaluate("/currencyConfig/rates/rate", doc, XPathConstants.NODESET);
+        
+        for (int i = 0; i < nodes.getLength(); i++) {
+          Node rateNode = nodes.item(i);
+          NamedNodeMap attributes = rateNode.getAttributes();
+          Node from = attributes.getNamedItem("from");
+          Node to = attributes.getNamedItem("to");
+          Node rate = attributes.getNamedItem("rate");
+          
+          if (from == null || to == null || rate == null) {
+            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Exchange rate missing attributes (required: from, to, rate) " + rateNode);
+          }
+          
+          String fromCurrency = from.getNodeValue();
+          String toCurrency = to.getNodeValue();
+          Double exchangeRate;
+          
+          if (null == CurrencyFieldType.getCurrency(fromCurrency)) {
+            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Specified 'from' currency not supported in this JVM: " + fromCurrency);
+          }
+          if (null == CurrencyFieldType.getCurrency(toCurrency)) {
+            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Specified 'to' currency not supported in this JVM: " + toCurrency);
+          }
+          
+          try {
+            exchangeRate = Double.parseDouble(rate.getNodeValue());
+          } catch (NumberFormatException e) {
+            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Could not parse exchange rate: " + rateNode, e);
+          }
+          
+          addRate(tmpRates, fromCurrency, toCurrency, exchangeRate);
+        }
+      } catch (SAXException | XPathExpressionException | ParserConfigurationException | IOException e) {
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error parsing currency config.", e);
+      }
+    } catch (IOException e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error while opening Currency configuration file "+currencyConfigFile, e);
+    } finally {
+      try {
+        if (is != null) {
+          is.close();
+        }
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+    }
+    // Atomically swap in the new rates map, if it loaded successfully
+    this.rates = tmpRates;
+    return true;
+  }
+
+  @Override
+  public void init(Map<String,String> params) throws SolrException {
+    this.currencyConfigFile = params.get(PARAM_CURRENCY_CONFIG);
+    if(currencyConfigFile == null) {
+      throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "Missing required configuration "+PARAM_CURRENCY_CONFIG);
+    }
+    
+    // Removing config params custom to us
+    params.remove(PARAM_CURRENCY_CONFIG);
+  }
+
+  @Override
+  public void inform(ResourceLoader loader) throws SolrException {
+    if(loader == null) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Needs ResourceLoader in order to load config file");
+    }
+    this.loader = loader;
+    reload();
+  }
+}


[5/6] lucene-solr:master: SOLR-10503, SOLR-10502: Deprecate CurrencyField in favor of new CurrencyFieldType, which works with point fields and provides control over dynamic fields used for the raw amount and currency code sub-fields.

Posted by sa...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/java/org/apache/solr/schema/OpenExchangeRatesOrgProvider.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/OpenExchangeRatesOrgProvider.java b/solr/core/src/java/org/apache/solr/schema/OpenExchangeRatesOrgProvider.java
index 2d16108..2b6cbf6 100644
--- a/solr/core/src/java/org/apache/solr/schema/OpenExchangeRatesOrgProvider.java
+++ b/solr/core/src/java/org/apache/solr/schema/OpenExchangeRatesOrgProvider.java
@@ -35,7 +35,7 @@ import org.slf4j.LoggerFactory;
 
 /**
  * <p>
- * Exchange Rates Provider for {@link CurrencyField} capable of fetching &amp; 
+ * Exchange Rates Provider for {@link CurrencyField} and {@link CurrencyFieldType} capable of fetching &amp; 
  * parsing the freely available exchange rates from openexchangerates.org
  * </p>
  * <p>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java b/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java
index b599272..7d6d162 100644
--- a/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java
+++ b/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java
@@ -47,7 +47,7 @@ import org.apache.lucene.util.BytesRefBuilder;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.request.SolrRequestInfo;
-import org.apache.solr.schema.CurrencyField;
+import org.apache.solr.schema.CurrencyFieldType;
 import org.apache.solr.schema.FieldType;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.schema.StrField;
@@ -444,11 +444,11 @@ public abstract class ValueSourceParser implements NamedListInitializedPlugin {
 
         String fieldName = fp.parseArg();
         SchemaField f = fp.getReq().getSchema().getField(fieldName);
-        if (! (f.getType() instanceof CurrencyField)) {
+        if (! (f.getType() instanceof CurrencyFieldType)) {
           throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
-                                  "Currency function input must be the name of a CurrencyField: " + fieldName);
+                                  "Currency function input must be the name of a CurrencyFieldType: " + fieldName);
         }
-        CurrencyField ft = (CurrencyField) f.getType();
+        CurrencyFieldType ft = (CurrencyFieldType) f.getType();
         String code = fp.hasMoreArguments() ? fp.parseArg() : null;
         return ft.getConvertedValueSource(code, ft.getValueSource(f, fp));
       }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-amount-suffix.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-amount-suffix.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-amount-suffix.xml
new file mode 100644
index 0000000..bafdb37
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-amount-suffix.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currency-ft-amount-suffix" version="1.4">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" multiValued="false"/>
+  
+  <!-- BEGIN BAD STUFF: amountLongSuffix not allowed -->
+  <fieldType name="currency" class="solr.CurrencyField" amountLongSuffix="_l" multiValued="false"/>
+  <!-- END BAD STUFF -->
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  <field name="money" type="currency" indexed="true" stored="true"/>
+  
+  <dyanmicField name="*_l" type="plong" multiValued="false"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-code-suffix.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-code-suffix.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-code-suffix.xml
new file mode 100644
index 0000000..06973f4
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-code-suffix.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currency-ft-code-suffix" version="1.4">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  
+  <!-- BEGIN BAD STUFF: codeStrSuffix not allowed -->
+  <fieldType name="currency" class="solr.CurrencyField" codeStrSuffix="_s" multiValued="false"/>
+  <!-- END BAD STUFF -->
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  <field name="money" type="currency" indexed="true" stored="true"/>
+  
+  <dyanmicField name="*_s" type="string" multiValued="false"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-oer-norates.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-oer-norates.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-oer-norates.xml
index 539f503..a1d664c 100644
--- a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-oer-norates.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-oer-norates.xml
@@ -18,7 +18,7 @@
 
 <schema name="bad-schema-currency-ft-oer-norates" version="1.4">
   <fieldType name="string" class="solr.StrField" multiValued="true"/>
-  <!-- BEGIN BAD STUFF: multiValued -->
+  <!-- BEGIN BAD STUFF: no rates-->
   <fieldType name="currency"
              class="solr.CurrencyField"
              providerClass="solr.OpenExchangeRatesOrgProvider"

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-bogus-amount-suffix.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-bogus-amount-suffix.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-bogus-amount-suffix.xml
new file mode 100644
index 0000000..37b6d20
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-bogus-amount-suffix.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-bogus-amount-suffix" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+
+  <!-- BEGIN BAD STUFF: bogus amount field suffix -->
+  <fieldType name="currency" class="solr.CurrencyFieldType" currencyConfig="currency.xml" multiValued="false"
+             amountLongSuffix="_NOT_THERE" codeStrSuffix="_s"/>
+  <!-- END BAD STUFF -->
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  
+  <dynamicField name="*_s" type="string" multiValued="false"/>
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+  <dynamicField name="*_c" type="currency" indexed="true" stored="true"/>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-bogus-code-suffix.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-bogus-code-suffix.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-bogus-code-suffix.xml
new file mode 100644
index 0000000..7172455
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-bogus-code-suffix.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-bogus-code-suffix" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+
+  <!-- BEGIN BAD STUFF: bogus code field suffix -->
+  <fieldType name="currency" class="solr.CurrencyFieldType" currencyConfig="currency.xml" multiValued="false"
+             amountLongSuffix="_l" codeStrSuffix="_NOT_THERE"/>
+  <!-- END BAD STUFF -->
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  
+  <dynamicField name="*_c" type="currency" indexed="true" stored="true"/>
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-dynamic-multivalued.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-dynamic-multivalued.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-dynamic-multivalued.xml
new file mode 100644
index 0000000..2fba82a
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-dynamic-multivalued.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-dynamic-multivalued" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+  <fieldType name="currency" class="solr.CurrencyFieldType" currencyConfig="currency.xml" multiValued="false"
+             amountLongSuffix="_l" codeStrSuffix="_s"/>
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  
+  <!-- BEGIN BAD STUFF: multiValued="true" -->
+  <dynamicField name="*_c" type="currency" indexed="true" stored="true" multiValued="true"/>
+  <!-- END BAD STUFF -->
+
+  <dynamicField name="*_s" type="string" multiValued="false"/>
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-bogus-code-in-xml.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-bogus-code-in-xml.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-bogus-code-in-xml.xml
new file mode 100644
index 0000000..3b5c695
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-bogus-code-in-xml.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-ft-bogus-code-in-xml" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+  
+  <!-- BEGIN BAD STUFF: bad-currency.xml has bogus code-->
+  <fieldType name="currency"
+             class="solr.CurrencyFieldType"
+             defaultCurrency="USD"
+             currencyConfig="bad-currency.xml"
+             multiValued="false"
+             amountLongSuffix="_l" 
+             codeStrSuffix="_s"/>
+  <!-- END BAD STUFF -->
+  
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  <field name="money" type="currency" indexed="true" stored="true"/>
+
+  <dynamicField name="*_s" type="string" multiValued="false"/>
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-bogus-default-code.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-bogus-default-code.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-bogus-default-code.xml
new file mode 100644
index 0000000..2748372
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-bogus-default-code.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-ft-bogus-default-code" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+
+  <!-- BEGIN BAD STUFF: defaultCurrency -->
+  <fieldType name="currency"
+             class="solr.CurrencyFieldType"
+             defaultCurrency="HOSS"
+             currencyConfig="currency.xml"
+             multiValued="false"
+             amountLongSuffix="_l" 
+             codeStrSuffix="_s"/>
+  <!-- END BAD STUFF -->
+  
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  <field name="money" type="currency" indexed="true" stored="true"/>
+
+  <dynamicField name="*_s" type="string" multiValued="false"/>
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-multivalued.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-multivalued.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-multivalued.xml
new file mode 100644
index 0000000..6afcea4
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-multivalued.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-ft-multivalued" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+  
+  <!-- BEGIN BAD STUFF: multiValued -->
+  <fieldType name="currency" class="solr.CurrencyFieldType" currencyConfig="currency.xml" multiValued="true"
+             amountLongSuffix="_l" codeStrSuffix="_s"/>
+  <!-- END BAD STUFF -->
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  <field name="money" type="currency" indexed="true" stored="true"/>
+
+  <dynamicField name="*_s" type="string" multiValued="false"/>
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-oer-norates.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-oer-norates.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-oer-norates.xml
new file mode 100644
index 0000000..8d8533d
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-oer-norates.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-ft-oer-norates" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+
+  <!-- BEGIN BAD STUFF: no rates -->
+  <fieldType name="currency"
+             class="solr.CurrencyFieldType"
+             providerClass="solr.OpenExchangeRatesOrgProvider"
+             multiValued="false"
+             amountLongSuffix="_l" 
+             codeStrSuffix="_s"/>
+  <!-- END BAD STUFF -->
+  
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  <field name="money" type="currency" indexed="true" stored="true"/>
+
+  <dynamicField name="*_s" type="string" multiValued="false"/>
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-missing-amount-suffix.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-missing-amount-suffix.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-missing-amount-suffix.xml
new file mode 100644
index 0000000..77a531d
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-missing-amount-suffix.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-missing-amount-suffix" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+
+  <!-- BEGIN BAD STUFF: missing amountLongSuffix -->
+  <fieldType name="currency" class="solr.CurrencyFieldType" currencyConfig="currency.xml" multiValued="false"
+             codeStrSuffix="_s"/>
+  <!-- END BAD STUFF -->
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  
+  <dynamicField name="*_s" type="string" multiValued="false"/>
+  <dynamicField name="*_c" type="currency" indexed="true" stored="true"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-missing-code-suffix.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-missing-code-suffix.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-missing-code-suffix.xml
new file mode 100644
index 0000000..89c7080
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-missing-code-suffix.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-missing-code-suffix" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+
+  <!-- BEGIN BAD STUFF: missing codeStrSuffix -->
+  <fieldType name="currency" class="solr.CurrencyFieldType" currencyConfig="currency.xml" multiValued="false"
+             amountLongSuffix="_l"/>
+  <!-- END BAD STUFF -->
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+  <dynamicField name="*_c" type="currency" indexed="true" stored="true"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-multivalued.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-multivalued.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-multivalued.xml
new file mode 100644
index 0000000..9e95458
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-multivalued.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-multivalued" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="currency" class="solr.CurrencyFieldType" currencyConfig="currency.xml"
+             amountLongSuffix="_l" codeStrSuffix="_s"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  
+  <!-- BEGIN BAD STUFF: multiValued="true" -->
+  <field name="money" type="currency" indexed="true" stored="true" multiValued="true"/>
+  <!-- END BAD STUFF -->
+
+  <dynamicField name="*_s" type="string" multiValued="false"/>
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-wrong-amount-ft.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-wrong-amount-ft.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-wrong-amount-ft.xml
new file mode 100644
index 0000000..5188190
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-wrong-amount-ft.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-wrong-amount-ft" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+
+  <!-- BEGIN BAD STUFF: code dynamic field type is wrong -->
+  <fieldType name="currency" class="solr.CurrencyFieldType" currencyConfig="currency.xml" multiValued="false"
+             amountLongSuffix="_s" codeStrSuffix="_s"/>
+  <!-- END BAD STUFF -->
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  
+  <dynamicField name="*_s" type="string" multiValued="false"/>
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+  <dynamicField name="*_c" type="currency" indexed="true" stored="true"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-wrong-code-ft.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-wrong-code-ft.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-wrong-code-ft.xml
new file mode 100644
index 0000000..c3eadc1
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-wrong-code-ft.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-wrong-code-ft" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+
+  <!-- BEGIN BAD STUFF: code dynamic field type is wrong -->
+  <fieldType name="currency" class="solr.CurrencyFieldType" currencyConfig="currency.xml" multiValued="false"
+             amountLongSuffix="_l" codeStrSuffix="_l"/>
+  <!-- END BAD STUFF -->
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+  <dynamicField name="*_c" type="currency" indexed="true" stored="true"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test-files/solr/collection1/conf/schema.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/schema.xml b/solr/core/src/test-files/solr/collection1/conf/schema.xml
index 23c1045..4aaef48 100644
--- a/solr/core/src/test-files/solr/collection1/conf/schema.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema.xml
@@ -451,6 +451,17 @@
              multiValued="false"
              providerClass="solr.OpenExchangeRatesOrgProvider"
              ratesFileLocation="open-exchange-rates.json"/>
+  <fieldType name="currency_CFT" class="solr.CurrencyFieldType" amountLongSuffix="_l1_ns" codeStrSuffix="_s1"
+             currencyConfig="currency.xml" multiValued="false"/>
+  <fieldType name="mock_currency_CFT" class="solr.CurrencyFieldType" amountLongSuffix="_l1_ns" codeStrSuffix="_s1"
+             providerClass="solr.MockExchangeRateProvider" foo="bar" multiValued="false"/>
+  <fieldType name="oer_currency_CFT"
+             class="solr.CurrencyFieldType"
+             amountLongSuffix="_l1_ns"
+             codeStrSuffix="_s1_ns"
+             multiValued="false"
+             providerClass="solr.OpenExchangeRatesOrgProvider"
+             ratesFileLocation="open-exchange-rates.json"/>
 
   <!-- omitPositions example -->
   <fieldType name="nopositions" class="solr.TextField" omitPositions="true">
@@ -539,6 +550,9 @@
   <field name="amount" type="currency" indexed="true" stored="true" multiValued="false"/>
   <field name="mock_amount" type="mock_currency" indexed="true" stored="true"/>
   <field name="oer_amount" type="oer_currency" indexed="true" stored="true"/>
+  <field name="amount_CFT" type="currency_CFT" indexed="true" stored="true" multiValued="false"/>
+  <field name="mock_amount_CFT" type="mock_currency_CFT" indexed="true" stored="true"/>
+  <field name="oer_amount_CFT" type="oer_currency_CFT" indexed="true" stored="true"/>
 
   <!-- test different combinations of indexed and stored -->
   <field name="bind" type="boolean" indexed="true" stored="false"/>
@@ -652,8 +666,10 @@
 
   <dynamicField name="*_s" type="string" indexed="true" stored="true"/>
   <dynamicField name="*_s1" type="string" indexed="true" stored="true" multiValued="false"/>
+  <dynamicField name="*_s1_ns" type="string" indexed="true" stored="false" multiValued="false"/>
   <dynamicField name="*_l" type="long" indexed="true" stored="true"/>
   <dynamicField name="*_l1" type="long" indexed="true" stored="true" multiValued="false"/>
+  <dynamicField name="*_l1_ns" type="long" indexed="true" stored="false" multiValued="false"/>
   <dynamicField name="*_t" type="text" indexed="true" stored="true"/>
   <dynamicField name="*_b" type="boolean" indexed="true" stored="true"/>
   <dynamicField name="*_b1" type="boolean" indexed="true" stored="true" multiValued="false"/>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test/org/apache/solr/schema/AbstractCurrencyFieldTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/schema/AbstractCurrencyFieldTest.java b/solr/core/src/test/org/apache/solr/schema/AbstractCurrencyFieldTest.java
deleted file mode 100644
index be719db..0000000
--- a/solr/core/src/test/org/apache/solr/schema/AbstractCurrencyFieldTest.java
+++ /dev/null
@@ -1,426 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.schema;
-import java.util.Currency;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-
-import org.apache.lucene.index.IndexableField;
-import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.core.SolrCore;
-import org.apache.solr.util.RTimer;
-import org.junit.Assume;
-import org.junit.BeforeClass;
-import org.junit.Ignore;
-import org.junit.Test;
-
-/**
- * Tests currency field type.
- * @see #field
- */
-@Ignore("Abstract base class with test methods")
-public abstract class AbstractCurrencyFieldTest extends SolrTestCaseJ4 {
-
-  /**
-   * "Assumes" that the specified list of currency codes are
-   * supported in this JVM
-   */
-  public static void assumeCurrencySupport(String... codes) {
-    try {
-      // each JDK might have a diff list of supported currencies,
-      // these are the ones needed for this test to work.
-      for (String code : codes) {
-        Currency obj = Currency.getInstance(code);
-        assertNotNull(code, obj);
-      }
-    } catch (IllegalArgumentException e) {
-      Assume.assumeNoException(e);
-    }
-
-  }
-  @BeforeClass
-  public static void beforeClass() throws Exception {
-    assumeCurrencySupport("USD", "EUR", "MXN", "GBP", "JPY", "NOK");
-    initCore("solrconfig.xml", "schema.xml");
-  }
-
-  /** The field name to use in all tests */
-  public abstract String field();
-
-  @Test
-  public void testCurrencySchema() throws Exception {
-    IndexSchema schema = h.getCore().getLatestSchema();
-
-    SchemaField amount = schema.getField(field());
-    assertNotNull(amount);
-    assertTrue(amount.isPolyField());
-
-    SchemaField[] dynFields = schema.getDynamicFieldPrototypes();
-    boolean seenCurrency = false;
-    boolean seenAmount = false;
-
-    for (SchemaField dynField : dynFields) {
-      if (dynField.getName().equals("*" + FieldType.POLY_FIELD_SEPARATOR + CurrencyField.FIELD_SUFFIX_CURRENCY)) {
-        seenCurrency = true;
-      }
-
-      if (dynField.getName().equals("*" + FieldType.POLY_FIELD_SEPARATOR + CurrencyField.FIELD_SUFFIX_AMOUNT_RAW)) {
-        seenAmount = true;
-      }
-    }
-
-    assertTrue("Didn't find the expected currency code dynamic field", seenCurrency);
-    assertTrue("Didn't find the expected value dynamic field", seenAmount);
-  }
-
-  @Test
-  public void testCurrencyFieldType() throws Exception {
-    SolrCore core = h.getCore();
-    IndexSchema schema = core.getLatestSchema();
-    SchemaField amount = schema.getField(field());
-    assertNotNull(amount);
-    assertTrue(field() + " is not a poly field", amount.isPolyField());
-    FieldType tmp = amount.getType();
-    assertTrue(tmp instanceof CurrencyField);
-    String currencyValue = "1.50,EUR";
-    List<IndexableField> fields = amount.createFields(currencyValue);
-    assertEquals(fields.size(), 3);
-
-    // First field is currency code, second is value, third is stored.
-    for (int i = 0; i < 3; i++) {
-      boolean hasValue = fields.get(i).readerValue() != null
-              || fields.get(i).numericValue() != null
-              || fields.get(i).stringValue() != null;
-      assertTrue("Doesn't have a value: " + fields.get(i), hasValue);
-    }
-
-    assertEquals(schema.getFieldTypeByName("string").toExternal(fields.get(2)), "1.50,EUR");
-    
-    // A few tests on the provider directly
-    ExchangeRateProvider p = ((CurrencyField) tmp).getProvider();
-    Set<String> availableCurrencies = p.listAvailableCurrencies();
-    assertEquals(5, availableCurrencies.size());
-    assert(p.reload() == true);
-    assert(p.getExchangeRate("USD", "EUR") == 2.5);
-  }
-
-  @Test
-  public void testMockExchangeRateProvider() throws Exception {
-    SolrCore core = h.getCore();
-    IndexSchema schema = core.getLatestSchema();
-    SchemaField amount = schema.getField("mock_amount");
-
-    // A few tests on the provider directly
-    ExchangeRateProvider p = ((CurrencyField)amount.getType()).getProvider();
-    Set<String> availableCurrencies = p.listAvailableCurrencies();
-    assert(availableCurrencies.size() == 3);
-    assert(p.reload() == true);
-    assert(p.getExchangeRate("USD", "EUR") == 0.8);
-  }
-
-  @Test
-  public void testCurrencyRangeSearch() throws Exception {
-    clearIndex();
-    final int emptyDocs = atLeast(50); // times 2
-    final int negDocs = atLeast(5);
-    
-    assertU(adoc("id", "0", field(), "0,USD")); // 0
-    // lots of docs w/o values
-    for (int i = 100; i <= 100 + emptyDocs; i++) {
-      assertU(adoc("id", "" + i));
-    }
-    // docs with values in ranges we'll query
-    for (int i = 1; i <= 10; i++) {
-      assertU(adoc("id", "" + i, field(), i + ",USD"));
-    }
-    // more docs w/o values
-    for (int i = 500; i <= 500 + emptyDocs; i++) {
-      assertU(adoc("id", "" + i));
-    }
-    // some negative values
-    for (int i = -100; i > -100 - negDocs; i--) {
-      assertU(adoc("id", "" + i, field(), i + ",USD"));
-    }
-    assertU(adoc("id", "40", field(), "0,USD")); // 0
-
-    assertU(commit());
-
-    assertQ(req("fl", "*,score", "q",
-            field()+":[2.00,USD TO 5.00,USD]"),
-            "//*[@numFound='4']");
-
-    assertQ(req("fl", "*,score", "q",
-            field()+":[0.50,USD TO 1.00,USD]"),
-            "//*[@numFound='1']");
-
-    assertQ(req("fl", "*,score", "q",
-            field()+":[24.00,USD TO 25.00,USD]"),
-            "//*[@numFound='0']");
-
-    // "GBP" currency code is 1/2 of a USD dollar, for testing.
-    assertQ(req("fl", "*,score", "q",
-            field()+":[0.50,GBP TO 1.00,GBP]"),
-            "//*[@numFound='2']");
-
-    // "EUR" currency code is 2.5X of a USD dollar, for testing.
-    assertQ(req("fl", "*,score", "q",
-            field()+":[24.00,EUR TO 25.00,EUR]"),
-            "//*[@numFound='1']");
-
-    // Slight asymmetric rate should work.
-    assertQ(req("fl", "*,score", "q",
-            field()+":[24.99,EUR TO 25.01,EUR]"),
-            "//*[@numFound='1']");
-    
-    // Open ended ranges without currency
-    assertQ(req("fl", "*,score", "q",
-            field()+":[* TO *]"),
-            "//*[@numFound='" + (2 + 10 + negDocs) + "']");
-    
-    // Open ended ranges with currency
-    assertQ(req("fl", "*,score", "q",
-            field()+":[*,EUR TO *,EUR]"),
-            "//*[@numFound='" + (2 + 10 + negDocs) + "']");
-
-    // Open ended start range without currency
-    assertQ(req("fl", "*,score", "q",
-            field()+":[* TO 5,USD]"),
-            "//*[@numFound='" + (2 + 5 + negDocs) + "']");
-
-    // Open ended start range with currency (currency for the * won't matter)
-    assertQ(req("fl", "*,score", "q",
-            field()+":[*,USD TO 5,USD]"),
-            "//*[@numFound='" + (2 + 5 + negDocs) + "']");
-
-    // Open ended end range
-    assertQ(req("fl", "*,score", "q",
-            field()+":[3 TO *]"),
-            "//*[@numFound='8']");
-  }
-
-  @Test
-  public void testBogusCurrency() throws Exception {
-    ignoreException("HOSS");
-
-    // bogus currency
-    assertQEx("Expected exception for invalid currency",
-              req("fl", "*,score", "q",
-                  field()+":[3,HOSS TO *]"),
-              400);
-  }
-
-  @Test
-  public void testCurrencyPointQuery() throws Exception {
-    clearIndex();
-    assertU(adoc("id", "" + 1, field(), "10.00,USD"));
-    assertU(adoc("id", "" + 2, field(), "15.00,MXN"));
-    assertU(commit());
-    assertQ(req("fl", "*,score", "q", field()+":10.00,USD"), "//str[@name='id']='1'");
-    assertQ(req("fl", "*,score", "q", field()+":9.99,USD"), "//*[@numFound='0']");
-    assertQ(req("fl", "*,score", "q", field()+":10.01,USD"), "//*[@numFound='0']");
-    assertQ(req("fl", "*,score", "q", field()+":15.00,MXN"), "//str[@name='id']='2'");
-    assertQ(req("fl", "*,score", "q", field()+":7.50,USD"), "//str[@name='id']='2'");
-    assertQ(req("fl", "*,score", "q", field()+":7.49,USD"), "//*[@numFound='0']");
-    assertQ(req("fl", "*,score", "q", field()+":7.51,USD"), "//*[@numFound='0']");
-  }
-
-  @Ignore
-  public void testPerformance() throws Exception {
-    clearIndex();
-
-    Random r = random();
-    int initDocs = 200000;
-
-    for (int i = 1; i <= initDocs; i++) {
-      assertU(adoc("id", "" + i, field(), (r.nextInt(10) + 1.00) + ",USD"));
-      if (i % 1000 == 0)
-        System.out.println(i);
-    }
-
-    assertU(commit());
-    for (int i = 0; i < 1000; i++) {
-      double lower = r.nextInt(10) + 1.00;
-      assertQ(req("fl", "*,score", "q", field()+":[" +  lower + ",USD TO " + (lower + 10.00) + ",USD]"), "//*");
-      assertQ(req("fl", "*,score", "q", field()+":[" +  lower + ",EUR TO " + (lower + 10.00) + ",EUR]"), "//*");
-    }
-
-    for (int j = 0; j < 3; j++) {
-      final RTimer timer = new RTimer();
-      for (int i = 0; i < 1000; i++) {
-        double lower = r.nextInt(10) + 1.00;
-        assertQ(req("fl", "*,score", "q", field()+":[" +  lower + ",USD TO " + (lower + (9.99 - (j * 0.01))) + ",USD]"), "//*");
-      }
-
-      System.out.println(timer.getTime());
-    }
-
-    System.out.println("---");
-
-    for (int j = 0; j < 3; j++) {
-      final RTimer timer = new RTimer();
-      for (int i = 0; i < 1000; i++) {
-        double lower = r.nextInt(10) + 1.00;
-        assertQ(req("fl", "*,score", "q", field()+":[" +  lower + ",EUR TO " + (lower + (9.99 - (j * 0.01))) + ",EUR]"), "//*");
-      }
-
-      System.out.println(timer.getTime());
-    }
-  }
-
-  @Test
-  public void testCurrencySort() throws Exception {
-    clearIndex();
-
-    assertU(adoc("id", "" + 1, field(), "10.00,USD"));
-    assertU(adoc("id", "" + 2, field(), "15.00,EUR"));
-    assertU(adoc("id", "" + 3, field(), "7.00,EUR"));
-    assertU(adoc("id", "" + 4, field(), "6.00,GBP"));
-    assertU(adoc("id", "" + 5, field(), "2.00,GBP"));
-    assertU(commit());
-
-    assertQ(req("fl", "*,score", "q", "*:*", "sort", field()+" desc", "limit", "1"), "//str[@name='id']='4'");
-    assertQ(req("fl", "*,score", "q", "*:*", "sort", field()+" asc", "limit", "1"), "//str[@name='id']='3'");
-  }
-
-  public void testFunctionUsage() throws Exception {
-    clearIndex();
-    for (int i = 1; i <= 8; i++) {
-      // "GBP" currency code is 1/2 of a USD dollar, for testing.
-      assertU(adoc("id", "" + i, field(), (((float)i)/2) + ",GBP"));
-    }
-    for (int i = 9; i <= 11; i++) {
-      assertU(adoc("id", "" + i, field(), i + ",USD"));
-    }
-
-    assertU(commit());
-
-    // direct value source usage, gets "raw" form od default currency
-    // default==USD, so raw==penies
-    assertQ(req("fl", "id,func:field($f)",
-                "f", field(),
-                "q", "id:5"),
-            "//*[@numFound='1']",
-            "//doc/float[@name='func' and .=500]");
-    assertQ(req("fl", "id,func:field($f)",
-                "f", field(),
-                "q", "id:10"),
-            "//*[@numFound='1']",
-            "//doc/float[@name='func' and .=1000]");
-    assertQ(req("fl", "id,score,"+field(), 
-                "q", "{!frange u=500}"+field())
-            ,"//*[@numFound='5']"
-            ,"//str[@name='id']='1'"
-            ,"//str[@name='id']='2'"
-            ,"//str[@name='id']='3'"
-            ,"//str[@name='id']='4'"
-            ,"//str[@name='id']='5'"
-            );
-    assertQ(req("fl", "id,score,"+field(), 
-                "q", "{!frange l=500 u=1000}"+field())
-            ,"//*[@numFound='6']"
-            ,"//str[@name='id']='5'"
-            ,"//str[@name='id']='6'"
-            ,"//str[@name='id']='7'"
-            ,"//str[@name='id']='8'"
-            ,"//str[@name='id']='9'"
-            ,"//str[@name='id']='10'"
-            );
-
-    // use the currency function to convert to default (USD)
-    assertQ(req("fl", "id,func:currency($f)",
-                "f", field(),
-                "q", "id:10"),
-            "//*[@numFound='1']",
-            "//doc/float[@name='func' and .=10]");
-    assertQ(req("fl", "id,func:currency($f)",
-                "f", field(),
-                "q", "id:5"),
-            "//*[@numFound='1']",
-            "//doc/float[@name='func' and .=5]");
-    assertQ(req("fl", "id,score"+field(), 
-                "f", field(),
-                "q", "{!frange u=5}currency($f)")
-            ,"//*[@numFound='5']"
-            ,"//str[@name='id']='1'"
-            ,"//str[@name='id']='2'"
-            ,"//str[@name='id']='3'"
-            ,"//str[@name='id']='4'"
-            ,"//str[@name='id']='5'"
-            );
-    assertQ(req("fl", "id,score"+field(), 
-                "f", field(),
-                "q", "{!frange l=5 u=10}currency($f)")
-            ,"//*[@numFound='6']"
-            ,"//str[@name='id']='5'"
-            ,"//str[@name='id']='6'"
-            ,"//str[@name='id']='7'"
-            ,"//str[@name='id']='8'"
-            ,"//str[@name='id']='9'"
-            ,"//str[@name='id']='10'"
-            );
-    
-    // use the currency function to convert to MXN
-    assertQ(req("fl", "id,func:currency($f,MXN)",
-                "f", field(),
-                "q", "id:5"),
-            "//*[@numFound='1']",
-            "//doc/float[@name='func' and .=10]");
-    assertQ(req("fl", "id,func:currency($f,MXN)",
-                "f", field(),
-                "q", "id:10"),
-            "//*[@numFound='1']",
-            "//doc/float[@name='func' and .=20]");
-    assertQ(req("fl", "*,score,"+field(), 
-                "f", field(),
-                "q", "{!frange u=10}currency($f,MXN)")
-            ,"//*[@numFound='5']"
-            ,"//str[@name='id']='1'"
-            ,"//str[@name='id']='2'"
-            ,"//str[@name='id']='3'"
-            ,"//str[@name='id']='4'"
-            ,"//str[@name='id']='5'"
-            );
-    assertQ(req("fl", "*,score,"+field(), 
-                "f", field(),
-                "q", "{!frange l=10 u=20}currency($f,MXN)")
-            ,"//*[@numFound='6']"
-            ,"//str[@name='id']='5'"
-            ,"//str[@name='id']='6'"
-            ,"//str[@name='id']='7'"
-            ,"//str[@name='id']='8'"
-            ,"//str[@name='id']='9'"
-            ,"//str[@name='id']='10'"
-            );
-
-  }
-
-  @Test
-  public void testMockFieldType() throws Exception {
-    clearIndex();
-
-    assertU(adoc("id", "1", "mock_amount", "1.00,USD"));
-    assertU(adoc("id", "2", "mock_amount", "1.00,EUR"));
-    assertU(adoc("id", "3", "mock_amount", "1.00,NOK"));
-    assertU(commit());
-
-    assertQ(req("fl", "*,score", "q", "mock_amount:5.0,NOK"),   "//*[@numFound='1']", "//str[@name='id']='1'");
-    assertQ(req("fl", "*,score", "q", "mock_amount:1.2,USD"), "//*[@numFound='1']",   "//str[@name='id']='2'");
-    assertQ(req("fl", "*,score", "q", "mock_amount:0.2,USD"), "//*[@numFound='1']",   "//str[@name='id']='3'");
-    assertQ(req("fl", "*,score", "q", "mock_amount:99,USD"),  "//*[@numFound='0']");
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test/org/apache/solr/schema/BadIndexSchemaTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/schema/BadIndexSchemaTest.java b/solr/core/src/test/org/apache/solr/schema/BadIndexSchemaTest.java
index b7d00a9..b9dc1aa 100644
--- a/solr/core/src/test/org/apache/solr/schema/BadIndexSchemaTest.java
+++ b/solr/core/src/test/org/apache/solr/schema/BadIndexSchemaTest.java
@@ -68,14 +68,22 @@ public class BadIndexSchemaTest extends AbstractBadConfigTestBase {
     doTest("bad-schema-currency-ft-multivalued.xml", 
            "types can not be multiValued: currency");
     doTest("bad-schema-currency-multivalued.xml", 
-           "Fields can not be multiValued: money");
+           "fields can not be multiValued: money");
     doTest("bad-schema-currency-dynamic-multivalued.xml", 
-           "Fields can not be multiValued: *_c");
+           "fields can not be multiValued: *_c");
+    doTest("bad-schema-currencyfieldtype-ft-multivalued.xml",
+        "types can not be multiValued: currency");
+    doTest("bad-schema-currencyfieldtype-multivalued.xml",
+        "fields can not be multiValued: money");
+    doTest("bad-schema-currencyfieldtype-dynamic-multivalued.xml",
+        "fields can not be multiValued: *_c");
   }
 
   public void testCurrencyOERNoRates() throws Exception {
     doTest("bad-schema-currency-ft-oer-norates.xml", 
            "ratesFileLocation");
+    doTest("bad-schema-currencyfieldtype-ft-oer-norates.xml",
+        "ratesFileLocation");
   }
 
   public void testCurrencyBogusCode() throws Exception {
@@ -83,6 +91,35 @@ public class BadIndexSchemaTest extends AbstractBadConfigTestBase {
            "HOSS");
     doTest("bad-schema-currency-ft-bogus-code-in-xml.xml", 
            "HOSS");
+    doTest("bad-schema-currencyfieldtype-ft-bogus-default-code.xml",
+        "HOSS");
+    doTest("bad-schema-currencyfieldtype-ft-bogus-code-in-xml.xml",
+        "HOSS");
+  }
+  
+  public void testCurrencyDisallowedSuffixParams() throws Exception {
+    doTest("bad-schema-currency-ft-code-suffix.xml", 
+        "Unknown parameter(s)");
+    doTest("bad-schema-currency-ft-amount-suffix.xml",
+        "Unknown parameter(s)");
+  }
+  
+  public void testCurrencyBogusSuffixes() throws Exception {
+    doTest("bad-schema-currencyfieldtype-bogus-code-suffix.xml",
+           "Undefined dynamic field for codeStrSuffix");
+    doTest("bad-schema-currencyfieldtype-bogus-amount-suffix.xml",
+           "Undefined dynamic field for amountLongSuffix");
+    doTest("bad-schema-currencyfieldtype-wrong-code-ft.xml",
+           "Dynamic field for codeStrSuffix=\"_l\" must have type class of (or extending) StrField");
+    doTest("bad-schema-currencyfieldtype-wrong-amount-ft.xml",
+           "Dynamic field for amountLongSuffix=\"_s\" must have type class extending LongValueFieldType");
+  } 
+  
+  public void testCurrencyMissingSuffixes() throws Exception {
+    doTest("bad-schema-currencyfieldtype-missing-code-suffix.xml",
+        "Missing required param codeStrSuffix");
+    doTest("bad-schema-currencyfieldtype-missing-amount-suffix.xml",
+        "Missing required param amountLongSuffix");
   }
 
   public void testPerFieldtypeSimButNoSchemaSimFactory() throws Exception {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test/org/apache/solr/schema/CurrencyFieldOpenExchangeTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/schema/CurrencyFieldOpenExchangeTest.java b/solr/core/src/test/org/apache/solr/schema/CurrencyFieldOpenExchangeTest.java
deleted file mode 100644
index fed51eb..0000000
--- a/solr/core/src/test/org/apache/solr/schema/CurrencyFieldOpenExchangeTest.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.schema;
-
-/**
- * Tests currency field type using OpenExchangeRatesOrgProvider.
- */
-public class CurrencyFieldOpenExchangeTest extends AbstractCurrencyFieldTest {
-
-  public String field() {
-    return "oer_amount";
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test/org/apache/solr/schema/CurrencyFieldTypeTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/schema/CurrencyFieldTypeTest.java b/solr/core/src/test/org/apache/solr/schema/CurrencyFieldTypeTest.java
new file mode 100644
index 0000000..c2f8f2d
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/schema/CurrencyFieldTypeTest.java
@@ -0,0 +1,494 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.schema;
+
+import java.util.Arrays;
+import java.util.Currency;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
+import org.apache.lucene.index.IndexableField;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.util.RTimer;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/** Tests CurrencyField and CurrencyFieldType. */
+public class CurrencyFieldTypeTest extends SolrTestCaseJ4 {
+  private final String fieldName;
+  private final Class<? extends ExchangeRateProvider> expectedProviderClass;
+  
+  public CurrencyFieldTypeTest(String fieldName, Class<? extends ExchangeRateProvider> expectedProviderClass) {
+    this.fieldName = fieldName;
+    this.expectedProviderClass = expectedProviderClass;
+  }
+
+  @ParametersFactory
+  public static Iterable<Object[]> parameters() {
+    return Arrays.asList(new Object[][] {
+        {"amount", FileExchangeRateProvider.class},    // CurrencyField
+        {"mock_amount", MockExchangeRateProvider.class},                 // CurrencyField
+        {"oer_amount", OpenExchangeRatesOrgProvider.class},              // CurrencyField
+        {"amount_CFT", FileExchangeRateProvider.class},  // CurrencyFieldType
+        {"mock_amount_CFT", MockExchangeRateProvider.class},               // CurrencyFieldType
+        {"oer_amount_CFT", OpenExchangeRatesOrgProvider.class}             // CurrencyFieldType
+      });
+  }
+  
+  /**
+   * "Assumes" that the specified list of currency codes are
+   * supported in this JVM
+   */
+  public static void assumeCurrencySupport(String... codes) {
+    try {
+      // each JDK might have a diff list of supported currencies,
+      // these are the ones needed for this test to work.
+      for (String code : codes) {
+        Currency obj = Currency.getInstance(code);
+        assertNotNull(code, obj);
+      }
+    } catch (IllegalArgumentException e) {
+      Assume.assumeNoException(e);
+    }
+
+  }
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    assumeCurrencySupport("USD", "EUR", "MXN", "GBP", "JPY", "NOK");
+    initCore("solrconfig.xml", "schema.xml");
+  }
+
+  @Test
+  public void testCurrencySchema() throws Exception {
+    IndexSchema schema = h.getCore().getLatestSchema();
+
+    SchemaField amount = schema.getField(fieldName);
+    assertNotNull(amount);
+    assertTrue(amount.isPolyField());
+
+    CurrencyFieldType type = (CurrencyFieldType)amount.getType();
+    String currencyDynamicField
+        = "*" + (type instanceof CurrencyField ? FieldType.POLY_FIELD_SEPARATOR : "") + type.fieldSuffixCurrency;
+    String amountDynamicField 
+        = "*" + (type instanceof CurrencyField ? FieldType.POLY_FIELD_SEPARATOR : "") + type.fieldSuffixAmountRaw;
+
+    SchemaField[] dynFields = schema.getDynamicFieldPrototypes();
+    boolean seenCurrency = false;
+    boolean seenAmount = false;
+
+    for (SchemaField dynField : dynFields) {
+      if (dynField.getName().equals(amountDynamicField)) {
+        seenAmount = true;
+      }
+
+      if (dynField.getName().equals(currencyDynamicField)) {
+        seenCurrency = true;
+      }
+    }
+
+    assertTrue("Didn't find the expected currency code dynamic field " + currencyDynamicField, seenCurrency);
+    assertTrue("Didn't find the expected value dynamic field " + amountDynamicField, seenAmount);
+  }
+
+  @Test
+  public void testCurrencyFieldType() throws Exception {
+    assumeTrue("This test is only applicable to the XML file based exchange rate provider",
+        expectedProviderClass.equals(FileExchangeRateProvider.class));
+
+    SolrCore core = h.getCore();
+    IndexSchema schema = core.getLatestSchema();
+    SchemaField amount = schema.getField(fieldName);
+    assertNotNull(amount);
+    assertTrue(fieldName + " is not a poly field", amount.isPolyField());
+    FieldType tmp = amount.getType();
+    assertTrue(fieldName + " is not an instance of CurrencyFieldType", tmp instanceof CurrencyFieldType);
+    String currencyValue = "1.50,EUR";
+    List<IndexableField> fields = amount.createFields(currencyValue);
+    assertEquals(fields.size(), 3);
+
+    // First field is currency code, second is value, third is stored.
+    for (int i = 0; i < 3; i++) {
+      boolean hasValue = fields.get(i).readerValue() != null
+              || fields.get(i).numericValue() != null
+              || fields.get(i).stringValue() != null;
+      assertTrue("Doesn't have a value: " + fields.get(i), hasValue);
+    }
+
+    assertEquals(schema.getFieldTypeByName("string").toExternal(fields.get(2)), "1.50,EUR");
+    
+    // A few tests on the provider directly
+    ExchangeRateProvider p = ((CurrencyFieldType)tmp).getProvider();
+    Set<String> availableCurrencies = p.listAvailableCurrencies();
+    assertEquals(5, availableCurrencies.size());
+    assertTrue(p.reload());
+    assertEquals(2.5, p.getExchangeRate("USD", "EUR"), 0.00000000001);
+  }
+
+  @Test
+  public void testMockExchangeRateProvider() throws Exception {
+    assumeTrue("This test is only applicable to the mock exchange rate provider",
+        expectedProviderClass.equals(MockExchangeRateProvider.class));
+    
+    SolrCore core = h.getCore();
+    IndexSchema schema = core.getLatestSchema();
+    SchemaField field = schema.getField(fieldName);
+    FieldType fieldType = field.getType();
+    ExchangeRateProvider provider = ((CurrencyFieldType)fieldType).getProvider();
+
+    // A few tests on the provider directly
+    assertEquals(3, provider.listAvailableCurrencies().size());
+    assertTrue(provider.reload());
+    assertEquals(0.8, provider.getExchangeRate("USD", "EUR"), 0.00000000001);
+  }
+
+  @Test
+  public void testCurrencyRangeSearch() throws Exception {
+    assumeTrue("This test is only applicable to the XML file based exchange rate provider",
+        expectedProviderClass.equals(FileExchangeRateProvider.class));
+    
+    clearIndex();
+    final int emptyDocs = atLeast(50); // times 2
+    final int negDocs = atLeast(5);
+    
+    assertU(adoc("id", "0", fieldName, "0,USD")); // 0
+    // lots of docs w/o values
+    for (int i = 100; i <= 100 + emptyDocs; i++) {
+      assertU(adoc("id", "" + i));
+    }
+    // docs with values in ranges we'll query
+    for (int i = 1; i <= 10; i++) {
+      assertU(adoc("id", "" + i, fieldName, i + ",USD"));
+    }
+    // more docs w/o values
+    for (int i = 500; i <= 500 + emptyDocs; i++) {
+      assertU(adoc("id", "" + i));
+    }
+    // some negative values
+    for (int i = -100; i > -100 - negDocs; i--) {
+      assertU(adoc("id", "" + i, fieldName, i + ",USD"));
+    }
+    assertU(adoc("id", "40", fieldName, "0,USD")); // 0
+
+    assertU(commit());
+
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[2.00,USD TO 5.00,USD]"),
+            "//*[@numFound='4']");
+
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[0.50,USD TO 1.00,USD]"),
+            "//*[@numFound='1']");
+
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[24.00,USD TO 25.00,USD]"),
+            "//*[@numFound='0']");
+
+    // "GBP" currency code is 1/2 of a USD dollar, for testing.
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[0.50,GBP TO 1.00,GBP]"),
+            "//*[@numFound='2']");
+
+    // "EUR" currency code is 2.5X of a USD dollar, for testing.
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[24.00,EUR TO 25.00,EUR]"),
+            "//*[@numFound='1']");
+
+    // Slight asymmetric rate should work.
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[24.99,EUR TO 25.01,EUR]"),
+            "//*[@numFound='1']");
+    
+    // Open ended ranges without currency
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[* TO *]"),
+            "//*[@numFound='" + (2 + 10 + negDocs) + "']");
+    
+    // Open ended ranges with currency
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[*,EUR TO *,EUR]"),
+            "//*[@numFound='" + (2 + 10 + negDocs) + "']");
+
+    // Open ended start range without currency
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[* TO 5,USD]"),
+            "//*[@numFound='" + (2 + 5 + negDocs) + "']");
+
+    // Open ended start range with currency (currency for the * won't matter)
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[*,USD TO 5,USD]"),
+            "//*[@numFound='" + (2 + 5 + negDocs) + "']");
+
+    // Open ended end range
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[3 TO *]"),
+            "//*[@numFound='8']");
+  }
+
+  @Test
+  public void testBogusCurrency() throws Exception {
+    ignoreException("HOSS");
+
+    // bogus currency
+    assertQEx("Expected exception for invalid currency",
+              req("fl", "*,score", "q",
+                  fieldName+":[3,HOSS TO *]"),
+              400);
+  }
+
+  @Test
+  public void testCurrencyPointQuery() throws Exception {
+    assumeTrue("This test is only applicable to the XML file based exchange rate provider",
+        expectedProviderClass.equals(FileExchangeRateProvider.class));
+
+    clearIndex();
+    assertU(adoc("id", "" + 1, fieldName, "10.00,USD"));
+    assertU(adoc("id", "" + 2, fieldName, "15.00,MXN"));
+    assertU(commit());
+    assertQ(req("fl", "*,score", "q", fieldName+":10.00,USD"), "//str[@name='id']='1'");
+    assertQ(req("fl", "*,score", "q", fieldName+":9.99,USD"), "//*[@numFound='0']");
+    assertQ(req("fl", "*,score", "q", fieldName+":10.01,USD"), "//*[@numFound='0']");
+    assertQ(req("fl", "*,score", "q", fieldName+":15.00,MXN"), "//str[@name='id']='2'");
+    assertQ(req("fl", "*,score", "q", fieldName+":7.50,USD"), "//str[@name='id']='2'");
+    assertQ(req("fl", "*,score", "q", fieldName+":7.49,USD"), "//*[@numFound='0']");
+    assertQ(req("fl", "*,score", "q", fieldName+":7.51,USD"), "//*[@numFound='0']");
+  }
+
+  @Ignore
+  public void testPerformance() throws Exception {
+    clearIndex();
+
+    Random r = random();
+    int initDocs = 200000;
+
+    for (int i = 1; i <= initDocs; i++) {
+      assertU(adoc("id", "" + i, fieldName, (r.nextInt(10) + 1.00) + ",USD"));
+      if (i % 1000 == 0)
+        System.out.println(i);
+    }
+
+    assertU(commit());
+    for (int i = 0; i < 1000; i++) {
+      double lower = r.nextInt(10) + 1.00;
+      assertQ(req("fl", "*,score", "q", fieldName+":[" +  lower + ",USD TO " + (lower + 10.00) + ",USD]"), "//*");
+      assertQ(req("fl", "*,score", "q", fieldName+":[" +  lower + ",EUR TO " + (lower + 10.00) + ",EUR]"), "//*");
+    }
+
+    for (int j = 0; j < 3; j++) {
+      final RTimer timer = new RTimer();
+      for (int i = 0; i < 1000; i++) {
+        double lower = r.nextInt(10) + 1.00;
+        assertQ(req("fl", "*,score", "q", fieldName+":[" +  lower + ",USD TO " + (lower + (9.99 - (j * 0.01))) + ",USD]"), "//*");
+      }
+
+      System.out.println(timer.getTime());
+    }
+
+    System.out.println("---");
+
+    for (int j = 0; j < 3; j++) {
+      final RTimer timer = new RTimer();
+      for (int i = 0; i < 1000; i++) {
+        double lower = r.nextInt(10) + 1.00;
+        assertQ(req("fl", "*,score", "q", fieldName+":[" +  lower + ",EUR TO " + (lower + (9.99 - (j * 0.01))) + ",EUR]"), "//*");
+      }
+
+      System.out.println(timer.getTime());
+    }
+  }
+
+  @Test
+  public void testCurrencySort() throws Exception {
+    assumeTrue("This test is only applicable to the XML file based exchange rate provider",
+        expectedProviderClass.equals(FileExchangeRateProvider.class));
+
+    clearIndex();
+
+    assertU(adoc("id", "" + 1, fieldName, "10.00,USD"));
+    assertU(adoc("id", "" + 2, fieldName, "15.00,EUR"));
+    assertU(adoc("id", "" + 3, fieldName, "7.00,EUR"));
+    assertU(adoc("id", "" + 4, fieldName, "6.00,GBP"));
+    assertU(adoc("id", "" + 5, fieldName, "2.00,GBP"));
+    assertU(commit());
+
+    assertQ(req("fl", "*,score", "q", "*:*", "sort", fieldName+" desc", "limit", "1"), "//str[@name='id']='4'");
+    assertQ(req("fl", "*,score", "q", "*:*", "sort", fieldName+" asc", "limit", "1"), "//str[@name='id']='3'");
+  }
+
+  public void testExpectedProvider() {
+      SolrCore core = h.getCore();
+      IndexSchema schema = core.getLatestSchema();
+      SchemaField field = schema.getField(fieldName);
+      FieldType fieldType = field.getType();
+      ExchangeRateProvider provider = ((CurrencyFieldType)fieldType).getProvider();
+      assertEquals(expectedProviderClass, provider.getClass());
+    }
+  
+  public void testFunctionUsage() throws Exception {
+    assumeTrue("This test is only applicable to the XML file based exchange rate provider",
+        expectedProviderClass.equals(FileExchangeRateProvider.class));
+
+    clearIndex();
+    for (int i = 1; i <= 8; i++) {
+      // "GBP" currency code is 1/2 of a USD dollar, for testing.
+      assertU(adoc("id", "" + i, fieldName, (((float)i)/2) + ",GBP"));
+    }
+    for (int i = 9; i <= 11; i++) {
+      assertU(adoc("id", "" + i, fieldName, i + ",USD"));
+    }
+
+    assertU(commit());
+
+    // direct value source usage, gets "raw" form od default currency
+    // default==USD, so raw==penies
+    assertQ(req("fl", "id,func:field($f)",
+                "f", fieldName,
+                "q", "id:5"),
+            "//*[@numFound='1']",
+            "//doc/float[@name='func' and .=500]");
+    assertQ(req("fl", "id,func:field($f)",
+                "f", fieldName,
+                "q", "id:10"),
+            "//*[@numFound='1']",
+            "//doc/float[@name='func' and .=1000]");
+    assertQ(req("fl", "id,score,"+fieldName, 
+                "q", "{!frange u=500}"+fieldName)
+            ,"//*[@numFound='5']"
+            ,"//str[@name='id']='1'"
+            ,"//str[@name='id']='2'"
+            ,"//str[@name='id']='3'"
+            ,"//str[@name='id']='4'"
+            ,"//str[@name='id']='5'"
+            );
+    assertQ(req("fl", "id,score,"+fieldName, 
+                "q", "{!frange l=500 u=1000}"+fieldName)
+            ,"//*[@numFound='6']"
+            ,"//str[@name='id']='5'"
+            ,"//str[@name='id']='6'"
+            ,"//str[@name='id']='7'"
+            ,"//str[@name='id']='8'"
+            ,"//str[@name='id']='9'"
+            ,"//str[@name='id']='10'"
+            );
+
+    // use the currency function to convert to default (USD)
+    assertQ(req("fl", "id,func:currency($f)",
+                "f", fieldName,
+                "q", "id:10"),
+            "//*[@numFound='1']",
+            "//doc/float[@name='func' and .=10]");
+    assertQ(req("fl", "id,func:currency($f)",
+                "f", fieldName,
+                "q", "id:5"),
+            "//*[@numFound='1']",
+            "//doc/float[@name='func' and .=5]");
+    assertQ(req("fl", "id,score"+fieldName, 
+                "f", fieldName,
+                "q", "{!frange u=5}currency($f)")
+            ,"//*[@numFound='5']"
+            ,"//str[@name='id']='1'"
+            ,"//str[@name='id']='2'"
+            ,"//str[@name='id']='3'"
+            ,"//str[@name='id']='4'"
+            ,"//str[@name='id']='5'"
+            );
+    assertQ(req("fl", "id,score"+fieldName, 
+                "f", fieldName,
+                "q", "{!frange l=5 u=10}currency($f)")
+            ,"//*[@numFound='6']"
+            ,"//str[@name='id']='5'"
+            ,"//str[@name='id']='6'"
+            ,"//str[@name='id']='7'"
+            ,"//str[@name='id']='8'"
+            ,"//str[@name='id']='9'"
+            ,"//str[@name='id']='10'"
+            );
+    
+    // use the currency function to convert to MXN
+    assertQ(req("fl", "id,func:currency($f,MXN)",
+                "f", fieldName,
+                "q", "id:5"),
+            "//*[@numFound='1']",
+            "//doc/float[@name='func' and .=10]");
+    assertQ(req("fl", "id,func:currency($f,MXN)",
+                "f", fieldName,
+                "q", "id:10"),
+            "//*[@numFound='1']",
+            "//doc/float[@name='func' and .=20]");
+    assertQ(req("fl", "*,score,"+fieldName, 
+                "f", fieldName,
+                "q", "{!frange u=10}currency($f,MXN)")
+            ,"//*[@numFound='5']"
+            ,"//str[@name='id']='1'"
+            ,"//str[@name='id']='2'"
+            ,"//str[@name='id']='3'"
+            ,"//str[@name='id']='4'"
+            ,"//str[@name='id']='5'"
+            );
+    assertQ(req("fl", "*,score,"+fieldName, 
+                "f", fieldName,
+                "q", "{!frange l=10 u=20}currency($f,MXN)")
+            ,"//*[@numFound='6']"
+            ,"//str[@name='id']='5'"
+            ,"//str[@name='id']='6'"
+            ,"//str[@name='id']='7'"
+            ,"//str[@name='id']='8'"
+            ,"//str[@name='id']='9'"
+            ,"//str[@name='id']='10'"
+            );
+
+  }
+
+  @Test
+  public void testMockFieldType() throws Exception {
+    assumeTrue("This test is only applicable to the mock exchange rate provider",
+        expectedProviderClass.equals(MockExchangeRateProvider.class));
+
+    clearIndex();
+
+    assertU(adoc("id", "1", fieldName, "1.00,USD"));
+    assertU(adoc("id", "2", fieldName, "1.00,EUR"));
+    assertU(adoc("id", "3", fieldName, "1.00,NOK"));
+    assertU(commit());
+
+    assertQ(req("fl", "*,score", "q", fieldName+":5.0,NOK"),   "//*[@numFound='1']", "//str[@name='id']='1'");
+    assertQ(req("fl", "*,score", "q", fieldName+":1.2,USD"), "//*[@numFound='1']",   "//str[@name='id']='2'");
+    assertQ(req("fl", "*,score", "q", fieldName+":0.2,USD"), "//*[@numFound='1']",   "//str[@name='id']='3'");
+    assertQ(req("fl", "*,score", "q", fieldName+":99,USD"),  "//*[@numFound='0']");
+  }
+
+  @Test
+  public void testAsymmetricPointQuery() throws Exception {
+    assumeTrue("This test is only applicable to the XML file based exchange rate provider",
+        expectedProviderClass.equals(FileExchangeRateProvider.class));
+
+    clearIndex();
+    assertU(adoc("id", "" + 1, fieldName, "10.00,USD"));
+    assertU(adoc("id", "" + 2, fieldName, "15.00,EUR"));
+    assertU(commit());
+
+    assertQ(req("fl", "*,score", "q", fieldName+":15.00,EUR"), "//str[@name='id']='2'");
+    assertQ(req("fl", "*,score", "q", fieldName+":7.50,USD"), "//str[@name='id']='2'");
+    assertQ(req("fl", "*,score", "q", fieldName+":7.49,USD"), "//*[@numFound='0']");
+    assertQ(req("fl", "*,score", "q", fieldName+":7.51,USD"), "//*[@numFound='0']");
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test/org/apache/solr/schema/CurrencyFieldXmlFileTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/schema/CurrencyFieldXmlFileTest.java b/solr/core/src/test/org/apache/solr/schema/CurrencyFieldXmlFileTest.java
deleted file mode 100644
index e8bffe7..0000000
--- a/solr/core/src/test/org/apache/solr/schema/CurrencyFieldXmlFileTest.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.schema;
-import org.junit.Test;
-
-/**
- * Tests currency field type using FileExchangeRateProvider
- */
-public class CurrencyFieldXmlFileTest extends AbstractCurrencyFieldTest {
-
-  public String field() {
-    return "amount";
-  }
-
-  @Test
-  public void testAsymetricPointQuery() throws Exception {
-    clearIndex();
-    assertU(adoc("id", "" + 1, field(), "10.00,USD"));
-    assertU(adoc("id", "" + 2, field(), "15.00,EUR"));
-    assertU(commit());
-
-    assertQ(req("fl", "*,score", "q", field()+":15.00,EUR"), "//str[@name='id']='2'");
-    assertQ(req("fl", "*,score", "q", field()+":7.50,USD"), "//str[@name='id']='2'");
-    assertQ(req("fl", "*,score", "q", field()+":7.49,USD"), "//*[@numFound='0']");
-    assertQ(req("fl", "*,score", "q", field()+":7.51,USD"), "//*[@numFound='0']");
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/core/src/test/org/apache/solr/schema/OpenExchangeRatesOrgProviderTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/schema/OpenExchangeRatesOrgProviderTest.java b/solr/core/src/test/org/apache/solr/schema/OpenExchangeRatesOrgProviderTest.java
index ce44c38..972ebe6 100644
--- a/solr/core/src/test/org/apache/solr/schema/OpenExchangeRatesOrgProviderTest.java
+++ b/solr/core/src/test/org/apache/solr/schema/OpenExchangeRatesOrgProviderTest.java
@@ -40,7 +40,7 @@ public class OpenExchangeRatesOrgProviderTest extends SolrTestCaseJ4 {
   @Override
   @Before
   public void setUp() throws Exception {
-    AbstractCurrencyFieldTest.assumeCurrencySupport
+    CurrencyFieldTypeTest.assumeCurrencySupport
       ("USD", "EUR", "MXN", "GBP", "JPY");
 
     super.setUp();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/example/example-DIH/solr/db/conf/managed-schema
----------------------------------------------------------------------
diff --git a/solr/example/example-DIH/solr/db/conf/managed-schema b/solr/example/example-DIH/solr/db/conf/managed-schema
index de138ba..b96ff4c 100644
--- a/solr/example/example-DIH/solr/db/conf/managed-schema
+++ b/solr/example/example-DIH/solr/db/conf/managed-schema
@@ -202,8 +202,10 @@
    <dynamicField name="*_i"  type="int"    indexed="true"  stored="true"/>
    <dynamicField name="*_is" type="int"    indexed="true"  stored="true"  multiValued="true"/>
    <dynamicField name="*_s"  type="string"  indexed="true"  stored="true" />
+   <dynamicField name="*_s_ns"  type="string"  indexed="true"  stored="false" />
    <dynamicField name="*_ss" type="string"  indexed="true"  stored="true" multiValued="true"/>
    <dynamicField name="*_l"  type="long"   indexed="true"  stored="true"/>
+   <dynamicField name="*_l_ns"  type="long"   indexed="true"  stored="false"/>
    <dynamicField name="*_ls" type="long"   indexed="true"  stored="true"  multiValued="true"/>
    <dynamicField name="*_t"  type="text_general"    indexed="true"  stored="true"/>
    <dynamicField name="*_txt" type="text_general"   indexed="true"  stored="true" multiValued="true"/>
@@ -703,17 +705,22 @@
 
    <!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
         Parameters:
-          defaultCurrency: Specifies the default currency if none specified. Defaults to "USD"
-          precisionStep:   Specifies the precisionStep for the TrieLong field used for the amount
-          providerClass:   Lets you plug in other exchange provider backend:
-                           solr.FileExchangeRateProvider is the default and takes one parameter:
-                             currencyConfig: name of an xml file holding exchange rates
-                           solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
-                             ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
-                             refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
+          amountLongSuffix: Required. Refers to a dynamic field for the raw amount sub-field. 
+                              The dynamic field must have a field type that extends LongValueFieldType.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          codeStrSuffix:    Required. Refers to a dynamic field for the currency code sub-field.
+                              The dynamic field must have a field type that extends StrField.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          defaultCurrency:  Specifies the default currency if none specified. Defaults to "USD"
+          providerClass:    Lets you plug in other exchange provider backend:
+                            solr.FileExchangeRateProvider is the default and takes one parameter:
+                              currencyConfig: name of an xml file holding exchange rates
+                            solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
+                              ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
+                              refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
    -->
-    <fieldType name="currency" class="solr.CurrencyField" precisionStep="8" defaultCurrency="USD" currencyConfig="currency.xml" />
-             
+    <fieldType name="currency" class="solr.CurrencyFieldType" amountLongSuffix="_l_ns" codeStrSuffix="_s_ns"
+               defaultCurrency="USD" currencyConfig="currency.xml" />
 
 
    <!-- some examples for different languages (generally ordered by ISO code) -->

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/example/example-DIH/solr/mail/conf/managed-schema
----------------------------------------------------------------------
diff --git a/solr/example/example-DIH/solr/mail/conf/managed-schema b/solr/example/example-DIH/solr/mail/conf/managed-schema
index 7b058eb..e027717 100644
--- a/solr/example/example-DIH/solr/mail/conf/managed-schema
+++ b/solr/example/example-DIH/solr/mail/conf/managed-schema
@@ -138,8 +138,10 @@
    <dynamicField name="*_i"  type="int"    indexed="true"  stored="true"/>
    <dynamicField name="*_is" type="int"    indexed="true"  stored="true"  multiValued="true"/>
    <dynamicField name="*_s"  type="string"  indexed="true"  stored="true" />
+   <dynamicField name="*_s_ns"  type="string"  indexed="true"  stored="false" />
    <dynamicField name="*_ss" type="string"  indexed="true"  stored="true" multiValued="true"/>
    <dynamicField name="*_l"  type="long"   indexed="true"  stored="true"/>
+   <dynamicField name="*_l_ns"  type="long"   indexed="true"  stored="false"/>
    <dynamicField name="*_ls" type="long"   indexed="true"  stored="true"  multiValued="true"/>
    <dynamicField name="*_t"  type="text_general"    indexed="true"  stored="true"/>
    <dynamicField name="*_txt" type="text_general"   indexed="true"  stored="true" multiValued="true"/>
@@ -623,17 +625,22 @@
 
    <!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
         Parameters:
-          defaultCurrency: Specifies the default currency if none specified. Defaults to "USD"
-          precisionStep:   Specifies the precisionStep for the TrieLong field used for the amount
-          providerClass:   Lets you plug in other exchange provider backend:
-                           solr.FileExchangeRateProvider is the default and takes one parameter:
-                             currencyConfig: name of an xml file holding exchange rates
-                           solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
-                             ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
-                             refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
+          amountLongSuffix: Required. Refers to a dynamic field for the raw amount sub-field. 
+                              The dynamic field must have a field type that extends LongValueFieldType.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          codeStrSuffix:    Required. Refers to a dynamic field for the currency code sub-field.
+                              The dynamic field must have a field type that extends StrField.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          defaultCurrency:  Specifies the default currency if none specified. Defaults to "USD"
+          providerClass:    Lets you plug in other exchange provider backend:
+                            solr.FileExchangeRateProvider is the default and takes one parameter:
+                              currencyConfig: name of an xml file holding exchange rates
+                            solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
+                              ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
+                              refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
    -->
-    <fieldType name="currency" class="solr.CurrencyField" precisionStep="8" defaultCurrency="USD" currencyConfig="currency.xml" />
-             
+    <fieldType name="currency" class="solr.CurrencyFieldType" amountLongSuffix="_l_ns" codeStrSuffix="_s_ns"
+               defaultCurrency="USD" currencyConfig="currency.xml" />
 
 
    <!-- some examples for different languages (generally ordered by ISO code) -->


[4/6] lucene-solr:master: SOLR-10503, SOLR-10502: Deprecate CurrencyField in favor of new CurrencyFieldType, which works with point fields and provides control over dynamic fields used for the raw amount and currency code sub-fields.

Posted by sa...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/example/example-DIH/solr/solr/conf/currency.xml
----------------------------------------------------------------------
diff --git a/solr/example/example-DIH/solr/solr/conf/currency.xml b/solr/example/example-DIH/solr/solr/conf/currency.xml
index 3a9c58a..532221a 100644
--- a/solr/example/example-DIH/solr/solr/conf/currency.xml
+++ b/solr/example/example-DIH/solr/solr/conf/currency.xml
@@ -16,7 +16,7 @@
  limitations under the License.
 -->
 
-<!-- Example exchange rates file for CurrencyField type named "currency" in example schema -->
+<!-- Example exchange rates file for CurrencyFieldType named "currency" in example schema -->
 
 <currencyConfig version="1.0">
   <rates>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/example/example-DIH/solr/solr/conf/managed-schema
----------------------------------------------------------------------
diff --git a/solr/example/example-DIH/solr/solr/conf/managed-schema b/solr/example/example-DIH/solr/solr/conf/managed-schema
index 679f567..338aa35 100644
--- a/solr/example/example-DIH/solr/solr/conf/managed-schema
+++ b/solr/example/example-DIH/solr/solr/conf/managed-schema
@@ -202,8 +202,10 @@
    <dynamicField name="*_i"  type="int"    indexed="true"  stored="true"/>
    <dynamicField name="*_is" type="int"    indexed="true"  stored="true"  multiValued="true"/>
    <dynamicField name="*_s"  type="string"  indexed="true"  stored="true" />
+   <dynamicField name="*_s_ns"  type="string"  indexed="true"  stored="false" />
    <dynamicField name="*_ss" type="string"  indexed="true"  stored="true" multiValued="true"/>
    <dynamicField name="*_l"  type="long"   indexed="true"  stored="true"/>
+   <dynamicField name="*_l_ns"  type="long"   indexed="true"  stored="false"/>
    <dynamicField name="*_ls" type="long"   indexed="true"  stored="true"  multiValued="true"/>
    <dynamicField name="*_t"  type="text_general"    indexed="true"  stored="true"/>
    <dynamicField name="*_txt" type="text_general"   indexed="true"  stored="true" multiValued="true"/>
@@ -703,17 +705,22 @@
 
    <!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
         Parameters:
-          defaultCurrency: Specifies the default currency if none specified. Defaults to "USD"
-          precisionStep:   Specifies the precisionStep for the TrieLong field used for the amount
-          providerClass:   Lets you plug in other exchange provider backend:
-                           solr.FileExchangeRateProvider is the default and takes one parameter:
-                             currencyConfig: name of an xml file holding exchange rates
-                           solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
-                             ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
-                             refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
+          amountLongSuffix: Required. Refers to a dynamic field for the raw amount sub-field. 
+                              The dynamic field must have a field type that extends LongValueFieldType.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          codeStrSuffix:    Required. Refers to a dynamic field for the currency code sub-field.
+                              The dynamic field must have a field type that extends StrField.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          defaultCurrency:  Specifies the default currency if none specified. Defaults to "USD"
+          providerClass:    Lets you plug in other exchange provider backend:
+                            solr.FileExchangeRateProvider is the default and takes one parameter:
+                              currencyConfig: name of an xml file holding exchange rates
+                            solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
+                              ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
+                              refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
    -->
-    <fieldType name="currency" class="solr.CurrencyField" precisionStep="8" defaultCurrency="USD" currencyConfig="currency.xml" />
-             
+    <fieldType name="currency" class="solr.CurrencyFieldType" amountLongSuffix="_l_ns" codeStrSuffix="_s_ns"
+               defaultCurrency="USD" currencyConfig="currency.xml" />
 
 
    <!-- some examples for different languages (generally ordered by ISO code) -->

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/example/files/conf/managed-schema
----------------------------------------------------------------------
diff --git a/solr/example/files/conf/managed-schema b/solr/example/files/conf/managed-schema
index bf154f4..fe91a5d 100644
--- a/solr/example/files/conf/managed-schema
+++ b/solr/example/files/conf/managed-schema
@@ -13,7 +13,7 @@
   <fieldType name="binary" class="solr.BinaryField"/>
   <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true"/>
   <fieldType name="booleans" class="solr.BoolField" sortMissingLast="true" multiValued="true"/>
-  <fieldType name="currency" class="solr.CurrencyField" currencyConfig="currency.xml" defaultCurrency="USD" precisionStep="8"/>
+  <fieldType name="currency" class="solr.CurrencyFieldType" amountLongSuffix="_l_ns" codeStrSuffix="_s_ns" defaultCurrency="USD" currencyConfig="currency.xml" />
   <fieldType name="date" class="solr.TrieDateField" positionIncrementGap="0" precisionStep="0"/>
   <fieldType name="dates" class="solr.TrieDateField" positionIncrementGap="0" multiValued="true" precisionStep="0"/>
   <fieldType name="descendent_path" class="solr.TextField">
@@ -504,6 +504,8 @@
   <dynamicField name="*_tdts" type="tdates" indexed="true" stored="true"/>
   <dynamicField name="attr_*" type="text_general" multiValued="true" indexed="true" stored="true"/>
   <dynamicField name="*_pdts" type="pdates"  indexed="true"  stored="true"/>
+  <dynamicField name="*_l_ns" type="long" indexed="true" stored="false"/>
+  <dynamicField name="*_s_ns" type="string" indexed="true" stored="false"/>
   <dynamicField name="*_txt" type="text_general" indexed="true" stored="true"/>
   <dynamicField name="*_dts" type="date" multiValued="true" indexed="true" stored="true"/>
   <dynamicField name="*_tis" type="tints" indexed="true" stored="true"/>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/server/solr/configsets/basic_configs/conf/managed-schema
----------------------------------------------------------------------
diff --git a/solr/server/solr/configsets/basic_configs/conf/managed-schema b/solr/server/solr/configsets/basic_configs/conf/managed-schema
index 599b0cc..c2db621 100644
--- a/solr/server/solr/configsets/basic_configs/conf/managed-schema
+++ b/solr/server/solr/configsets/basic_configs/conf/managed-schema
@@ -136,8 +136,10 @@
     <dynamicField name="*_i"  type="int"    indexed="true"  stored="true"/>
     <dynamicField name="*_is" type="ints"    indexed="true"  stored="true"/>
     <dynamicField name="*_s"  type="string"  indexed="true"  stored="true" />
+    <dynamicField name="*_s_ns"  type="string"  indexed="true"  stored="false" />
     <dynamicField name="*_ss" type="strings"  indexed="true"  stored="true"/>
     <dynamicField name="*_l"  type="long"   indexed="true"  stored="true"/>
+    <dynamicField name="*_l_ns"  type="long"   indexed="true"  stored="false"/>
     <dynamicField name="*_ls" type="longs"   indexed="true"  stored="true"/>
     <dynamicField name="*_t"   type="text_general" indexed="true" stored="true"/>
     <dynamicField name="*_txt" type="text_general" indexed="true" stored="true"/>
@@ -601,19 +603,24 @@
     <fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
                geo="true" distErrPct="0.025" maxDistErr="0.001" distanceUnits="kilometers" />
 
-    <!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
+   <!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
         Parameters:
-          defaultCurrency: Specifies the default currency if none specified. Defaults to "USD"
-          precisionStep:   Specifies the precisionStep for the TrieLong field used for the amount
-          providerClass:   Lets you plug in other exchange provider backend:
-                           solr.FileExchangeRateProvider is the default and takes one parameter:
-                             currencyConfig: name of an xml file holding exchange rates
-                           solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
-                             ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
-                             refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
-    -->
-    <fieldType name="currency" class="solr.CurrencyField" precisionStep="8" defaultCurrency="USD" currencyConfig="currency.xml" />
-             
+          amountLongSuffix: Required. Refers to a dynamic field for the raw amount sub-field. 
+                              The dynamic field must have a field type that extends LongValueFieldType.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          codeStrSuffix:    Required. Refers to a dynamic field for the currency code sub-field.
+                              The dynamic field must have a field type that extends StrField.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          defaultCurrency:  Specifies the default currency if none specified. Defaults to "USD"
+          providerClass:    Lets you plug in other exchange provider backend:
+                            solr.FileExchangeRateProvider is the default and takes one parameter:
+                              currencyConfig: name of an xml file holding exchange rates
+                            solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
+                              ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
+                              refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
+   -->
+    <fieldType name="currency" class="solr.CurrencyFieldType" amountLongSuffix="_l_ns" codeStrSuffix="_s_ns"
+               defaultCurrency="USD" currencyConfig="currency.xml" />
 
 
     <!-- some examples for different languages (generally ordered by ISO code) -->

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/server/solr/configsets/data_driven_schema_configs/conf/currency.xml
----------------------------------------------------------------------
diff --git a/solr/server/solr/configsets/data_driven_schema_configs/conf/currency.xml b/solr/server/solr/configsets/data_driven_schema_configs/conf/currency.xml
index 3a9c58a..532221a 100644
--- a/solr/server/solr/configsets/data_driven_schema_configs/conf/currency.xml
+++ b/solr/server/solr/configsets/data_driven_schema_configs/conf/currency.xml
@@ -16,7 +16,7 @@
  limitations under the License.
 -->
 
-<!-- Example exchange rates file for CurrencyField type named "currency" in example schema -->
+<!-- Example exchange rates file for CurrencyFieldType named "currency" in example schema -->
 
 <currencyConfig version="1.0">
   <rates>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/server/solr/configsets/data_driven_schema_configs/conf/managed-schema
----------------------------------------------------------------------
diff --git a/solr/server/solr/configsets/data_driven_schema_configs/conf/managed-schema b/solr/server/solr/configsets/data_driven_schema_configs/conf/managed-schema
index 80a58fb..5604a93 100644
--- a/solr/server/solr/configsets/data_driven_schema_configs/conf/managed-schema
+++ b/solr/server/solr/configsets/data_driven_schema_configs/conf/managed-schema
@@ -136,8 +136,10 @@
     <dynamicField name="*_i"  type="int"    indexed="true"  stored="true"/>
     <dynamicField name="*_is" type="ints"    indexed="true"  stored="true"/>
     <dynamicField name="*_s"  type="string"  indexed="true"  stored="true" />
+    <dynamicField name="*_s_ns"  type="string"  indexed="true"  stored="false" />
     <dynamicField name="*_ss" type="strings"  indexed="true"  stored="true"/>
     <dynamicField name="*_l"  type="long"   indexed="true"  stored="true"/>
+    <dynamicField name="*_l_ns"  type="long"   indexed="true"  stored="false"/>
     <dynamicField name="*_ls" type="longs"   indexed="true"  stored="true"/>
     <dynamicField name="*_t"   type="text_general" indexed="true" stored="true"/>
     <dynamicField name="*_txt" type="text_general" indexed="true" stored="true"/>
@@ -625,19 +627,24 @@
       </analyzer>
     </fieldType>
 
-    <!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
+   <!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
         Parameters:
-          defaultCurrency: Specifies the default currency if none specified. Defaults to "USD"
-          precisionStep:   Specifies the precisionStep for the TrieLong field used for the amount
-          providerClass:   Lets you plug in other exchange provider backend:
-                           solr.FileExchangeRateProvider is the default and takes one parameter:
-                             currencyConfig: name of an xml file holding exchange rates
-                           solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
-                             ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
-                             refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
-    -->
-    <fieldType name="currency" class="solr.CurrencyField" precisionStep="8" defaultCurrency="USD" currencyConfig="currency.xml" />
-             
+          amountLongSuffix: Required. Refers to a dynamic field for the raw amount sub-field. 
+                              The dynamic field must have a field type that extends LongValueFieldType.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          codeStrSuffix:    Required. Refers to a dynamic field for the currency code sub-field.
+                              The dynamic field must have a field type that extends StrField.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          defaultCurrency:  Specifies the default currency if none specified. Defaults to "USD"
+          providerClass:    Lets you plug in other exchange provider backend:
+                            solr.FileExchangeRateProvider is the default and takes one parameter:
+                              currencyConfig: name of an xml file holding exchange rates
+                            solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
+                              ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
+                              refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
+   -->
+    <fieldType name="currency" class="solr.CurrencyFieldType" amountLongSuffix="_l_ns" codeStrSuffix="_s_ns"
+               defaultCurrency="USD" currencyConfig="currency.xml" />
 
 
     <!-- some examples for different languages (generally ordered by ISO code) -->

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/server/solr/configsets/sample_techproducts_configs/conf/managed-schema
----------------------------------------------------------------------
diff --git a/solr/server/solr/configsets/sample_techproducts_configs/conf/managed-schema b/solr/server/solr/configsets/sample_techproducts_configs/conf/managed-schema
index 1c4f1fe..cd33c4f 100644
--- a/solr/server/solr/configsets/sample_techproducts_configs/conf/managed-schema
+++ b/solr/server/solr/configsets/sample_techproducts_configs/conf/managed-schema
@@ -199,8 +199,10 @@
    <dynamicField name="*_i"  type="int"    indexed="true"  stored="true"/>
    <dynamicField name="*_is" type="int"    indexed="true"  stored="true"  multiValued="true"/>
    <dynamicField name="*_s"  type="string"  indexed="true"  stored="true" />
+   <dynamicField name="*_s_ns"  type="string"  indexed="true"  stored="false" />
    <dynamicField name="*_ss" type="string"  indexed="true"  stored="true" multiValued="true"/>
    <dynamicField name="*_l"  type="long"   indexed="true"  stored="true"/>
+   <dynamicField name="*_l_ns"  type="long"   indexed="true"  stored="false"/>
    <dynamicField name="*_ls" type="long"   indexed="true"  stored="true"  multiValued="true"/>
    <dynamicField name="*_t"  type="text_general"    indexed="true"  stored="true"/>
    <dynamicField name="*_txt" type="text_general"   indexed="true"  stored="true" multiValued="true"/>
@@ -760,17 +762,22 @@
 
    <!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
         Parameters:
-          defaultCurrency: Specifies the default currency if none specified. Defaults to "USD"
-          precisionStep:   Specifies the precisionStep for the TrieLong field used for the amount
-          providerClass:   Lets you plug in other exchange provider backend:
-                           solr.FileExchangeRateProvider is the default and takes one parameter:
-                             currencyConfig: name of an xml file holding exchange rates
-                           solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
-                             ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
-                             refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
+          amountLongSuffix: Required. Refers to a dynamic field for the raw amount sub-field. 
+                              The dynamic field must have a field type that extends LongValueFieldType.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          codeStrSuffix:    Required. Refers to a dynamic field for the currency code sub-field.
+                              The dynamic field must have a field type that extends StrField.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          defaultCurrency:  Specifies the default currency if none specified. Defaults to "USD"
+          providerClass:    Lets you plug in other exchange provider backend:
+                            solr.FileExchangeRateProvider is the default and takes one parameter:
+                              currencyConfig: name of an xml file holding exchange rates
+                            solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
+                              ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
+                              refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
    -->
-    <fieldType name="currency" class="solr.CurrencyField" precisionStep="8" defaultCurrency="USD" currencyConfig="currency.xml" />
-             
+    <fieldType name="currency" class="solr.CurrencyFieldType" amountLongSuffix="_l_ns" codeStrSuffix="_s_ns"
+               defaultCurrency="USD" currencyConfig="currency.xml" />
 
 
    <!-- some examples for different languages (generally ordered by ISO code) -->

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/solr-ref-guide/src/field-types-included-with-solr.adoc
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/field-types-included-with-solr.adoc b/solr/solr-ref-guide/src/field-types-included-with-solr.adoc
index 88af33b..5c82970 100644
--- a/solr/solr-ref-guide/src/field-types-included-with-solr.adoc
+++ b/solr/solr-ref-guide/src/field-types-included-with-solr.adoc
@@ -28,7 +28,8 @@ The following table lists the field types that are available in Solr. The `org.a
 |BinaryField |Binary data.
 |BoolField |Contains either true or false. Values of "1", "t", or "T" in the first character are interpreted as true. Any other values in the first character are interpreted as false.
 |CollationField |Supports Unicode collation for sorting and range queries. ICUCollationField is a better choice if you can use ICU4J. See the section <<language-analysis.adoc#LanguageAnalysis-UnicodeCollation,Unicode Collation>>.
-|CurrencyField |Supports currencies and exchange rates. See the section <<working-with-currencies-and-exchange-rates.adoc#working-with-currencies-and-exchange-rates,Working with Currencies and Exchange Rates>>.
+|CurrencyField |Deprecated in favor of CurrencyFieldType.
+|CurrencyFieldType |Supports currencies and exchange rates. See the section <<working-with-currencies-and-exchange-rates.adoc#working-with-currencies-and-exchange-rates,Working with Currencies and Exchange Rates>>.
 |DateRangeField |Supports indexing date ranges, to include point in time date instances as well (single-millisecond durations). See the section <<working-with-dates.adoc#working-with-dates,Working with Dates>> for more detail on using this field type. Consider using this field type even if it's just for date instances, particularly when the queries typically fall on UTC year/month/day/hour, etc., boundaries.
 |ExternalFileField |Pulls values from a file on disk. See the section <<working-with-external-files-and-processes.adoc#working-with-external-files-and-processes,Working with External Files and Processes>>.
 |EnumField |Allows defining an enumerated set of values which may not be easily sorted by either alphabetic or numeric order (such as a list of severities, for example). This field type takes a configuration file, which lists the proper order of the field values. See the section <<working-with-enum-fields.adoc#working-with-enum-fields,Working with Enum Fields>> for more information.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/solr-ref-guide/src/updating-parts-of-documents.adoc
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/updating-parts-of-documents.adoc b/solr/solr-ref-guide/src/updating-parts-of-documents.adoc
index fac3cac..5ff8a28 100644
--- a/solr/solr-ref-guide/src/updating-parts-of-documents.adoc
+++ b/solr/solr-ref-guide/src/updating-parts-of-documents.adoc
@@ -59,7 +59,7 @@ The core functionality of atomically updating a document requires that all field
 
 If `<copyField/>` destinations are configured as stored, then Solr will attempt to index both the current value of the field as well as an additional copy from any source fields. If such fields contain some information that comes from the indexing program and some information that comes from copyField, then the information which originally came from the indexing program will be lost when an atomic update is made.
 
-There are other kinds of derived fields that must also be set so they aren't stored. Some spatial field types use derived fields. Examples of this are solr.BBoxField and solr.LatLonType. These types create additional fields which are normally specified by a dynamic field definition. That dynamic field definition must be not stored, or indexing will fail.
+There are other kinds of derived fields that must also be set so they aren't stored. Some spatial field types use derived fields. Examples of this are solr.BBoxField and solr.LatLonType. CurrencyFieldType also uses derived fields.  These types create additional fields which are normally specified by a dynamic field definition. That dynamic field definition must be not stored, or indexing will fail.
 
 [[UpdatingPartsofDocuments-Example]]
 === Example

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a6f4f2dd/solr/solr-ref-guide/src/working-with-currencies-and-exchange-rates.adoc
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/working-with-currencies-and-exchange-rates.adoc b/solr/solr-ref-guide/src/working-with-currencies-and-exchange-rates.adoc
index c651263..5ed4a56 100644
--- a/solr/solr-ref-guide/src/working-with-currencies-and-exchange-rates.adoc
+++ b/solr/solr-ref-guide/src/working-with-currencies-and-exchange-rates.adoc
@@ -30,11 +30,18 @@ The `currency` FieldType provides support for monetary values to Solr/Lucene wit
 [[WorkingwithCurrenciesandExchangeRates-ConfiguringCurrencies]]
 == Configuring Currencies
 
-The `currency` field type is defined in `schema.xml`. This is the default configuration of this type:
+.CurrencyField has been Deprecated
+[WARNING]
+====
+CurrencyField has been deprecated in favor of CurrencyFieldType; all configuration examples below use CurrencyFieldType.
+====
+
+The `currency` field type is defined in `schema.xml`. This is the default configuration of this type.
 
 [source,xml]
 ----
-<fieldType name="currency" class="solr.CurrencyField" precisionStep="8" 
+<fieldType name="currency" class="solr.CurrencyFieldType" 
+           amountLongSuffix="_l_ns" codeStrSuffix="_s_ns"
            defaultCurrency="USD" currencyConfig="currency.xml" />
 ----
 
@@ -53,6 +60,26 @@ At indexing time, money fields can be indexed in a native currency. For example,
 
 During query processing, range and point queries are both supported.
 
+[[WorkingwithCurrenciesandExchangeRates-Sub-fieldSuffixes]]
+=== Sub-field Suffixes
+
+You must specify parameters `amountLongSuffix` and `codeStrSuffix`, corresponding to dynamic fields to be used for the raw amount and the currency dynamic sub-fields, e.g.: 
+
+[source,xml]
+----
+<fieldType name="currency" class="solr.CurrencyFieldType"
+           amountLongSuffix="_l_ns" codeStrSuffix="_s_ns"
+           defaultCurrency="USD" currencyConfig="currency.xml" />
+----
+
+In the above example, the raw amount field will use the `"*_l_ns"` dynamic field, which must exist in the schema and use a long field type, i.e. one that extends `LongValueFieldType`.  The currency code field will use the `"*_s_ns"` dynamic field, which must exist in the schema and use a string field type, i.e. one that is or extends `StrField`.
+
+.Atomic Updates won't work if dynamic sub-fields are stored
+[NOTE]
+====
+As noted on <<updating-parts-of-documents.adoc#UpdatingPartsofDocuments-FieldStorage,Updating Parts of Documents>>, stored dynamic sub-fields will cause indexing to fail when you use Atomic Updates. To avoid this problem, specify `stored="false"` on those dynamic fields.
+====
+
 [[WorkingwithCurrenciesandExchangeRates-ExchangeRates]]
 == Exchange Rates
 
@@ -95,7 +122,8 @@ In this case, you need to specify the `providerClass` in the definitions for the
 
 [source,xml]
 ----
-<fieldType name="currency" class="solr.CurrencyField" precisionStep="8" 
+<fieldType name="currency" class="solr.CurrencyFieldType" 
+           amountLongSuffix="_l_ns" codeStrSuffix="_s_ns"
            providerClass="solr.OpenExchangeRatesOrgProvider"
            refreshInterval="60" 
            ratesFileLocation="http://www.openexchangerates.org/api/latest.json?app_id=yourPersonalAppIdKey"/>


[2/6] lucene-solr:branch_6x: SOLR-10503, SOLR-10502: Deprecate CurrencyField in favor of new CurrencyFieldType, which works with point fields and provides control over dynamic fields used for the raw amount and currency code sub-fields.

Posted by sa...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/java/org/apache/solr/schema/OpenExchangeRatesOrgProvider.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/OpenExchangeRatesOrgProvider.java b/solr/core/src/java/org/apache/solr/schema/OpenExchangeRatesOrgProvider.java
index 2d16108..2b6cbf6 100644
--- a/solr/core/src/java/org/apache/solr/schema/OpenExchangeRatesOrgProvider.java
+++ b/solr/core/src/java/org/apache/solr/schema/OpenExchangeRatesOrgProvider.java
@@ -35,7 +35,7 @@ import org.slf4j.LoggerFactory;
 
 /**
  * <p>
- * Exchange Rates Provider for {@link CurrencyField} capable of fetching &amp; 
+ * Exchange Rates Provider for {@link CurrencyField} and {@link CurrencyFieldType} capable of fetching &amp; 
  * parsing the freely available exchange rates from openexchangerates.org
  * </p>
  * <p>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java b/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java
index 1ef6d7f..c457557 100644
--- a/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java
+++ b/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java
@@ -47,7 +47,7 @@ import org.apache.lucene.util.BytesRefBuilder;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.request.SolrRequestInfo;
-import org.apache.solr.schema.CurrencyField;
+import org.apache.solr.schema.CurrencyFieldType;
 import org.apache.solr.schema.FieldType;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.schema.StrField;
@@ -449,11 +449,11 @@ public abstract class ValueSourceParser implements NamedListInitializedPlugin {
 
         String fieldName = fp.parseArg();
         SchemaField f = fp.getReq().getSchema().getField(fieldName);
-        if (! (f.getType() instanceof CurrencyField)) {
+        if (! (f.getType() instanceof CurrencyFieldType)) {
           throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
-                                  "Currency function input must be the name of a CurrencyField: " + fieldName);
+                                  "Currency function input must be the name of a CurrencyFieldType: " + fieldName);
         }
-        CurrencyField ft = (CurrencyField) f.getType();
+        CurrencyFieldType ft = (CurrencyFieldType) f.getType();
         String code = fp.hasMoreArguments() ? fp.parseArg() : null;
         return ft.getConvertedValueSource(code, ft.getValueSource(f, fp));
       }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-amount-suffix.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-amount-suffix.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-amount-suffix.xml
new file mode 100644
index 0000000..bafdb37
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-amount-suffix.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currency-ft-amount-suffix" version="1.4">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" multiValued="false"/>
+  
+  <!-- BEGIN BAD STUFF: amountLongSuffix not allowed -->
+  <fieldType name="currency" class="solr.CurrencyField" amountLongSuffix="_l" multiValued="false"/>
+  <!-- END BAD STUFF -->
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  <field name="money" type="currency" indexed="true" stored="true"/>
+  
+  <dyanmicField name="*_l" type="plong" multiValued="false"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-code-suffix.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-code-suffix.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-code-suffix.xml
new file mode 100644
index 0000000..06973f4
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-code-suffix.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currency-ft-code-suffix" version="1.4">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  
+  <!-- BEGIN BAD STUFF: codeStrSuffix not allowed -->
+  <fieldType name="currency" class="solr.CurrencyField" codeStrSuffix="_s" multiValued="false"/>
+  <!-- END BAD STUFF -->
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  <field name="money" type="currency" indexed="true" stored="true"/>
+  
+  <dyanmicField name="*_s" type="string" multiValued="false"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-oer-norates.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-oer-norates.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-oer-norates.xml
index 539f503..a1d664c 100644
--- a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-oer-norates.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currency-ft-oer-norates.xml
@@ -18,7 +18,7 @@
 
 <schema name="bad-schema-currency-ft-oer-norates" version="1.4">
   <fieldType name="string" class="solr.StrField" multiValued="true"/>
-  <!-- BEGIN BAD STUFF: multiValued -->
+  <!-- BEGIN BAD STUFF: no rates-->
   <fieldType name="currency"
              class="solr.CurrencyField"
              providerClass="solr.OpenExchangeRatesOrgProvider"

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-bogus-amount-suffix.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-bogus-amount-suffix.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-bogus-amount-suffix.xml
new file mode 100644
index 0000000..37b6d20
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-bogus-amount-suffix.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-bogus-amount-suffix" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+
+  <!-- BEGIN BAD STUFF: bogus amount field suffix -->
+  <fieldType name="currency" class="solr.CurrencyFieldType" currencyConfig="currency.xml" multiValued="false"
+             amountLongSuffix="_NOT_THERE" codeStrSuffix="_s"/>
+  <!-- END BAD STUFF -->
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  
+  <dynamicField name="*_s" type="string" multiValued="false"/>
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+  <dynamicField name="*_c" type="currency" indexed="true" stored="true"/>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-bogus-code-suffix.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-bogus-code-suffix.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-bogus-code-suffix.xml
new file mode 100644
index 0000000..7172455
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-bogus-code-suffix.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-bogus-code-suffix" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+
+  <!-- BEGIN BAD STUFF: bogus code field suffix -->
+  <fieldType name="currency" class="solr.CurrencyFieldType" currencyConfig="currency.xml" multiValued="false"
+             amountLongSuffix="_l" codeStrSuffix="_NOT_THERE"/>
+  <!-- END BAD STUFF -->
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  
+  <dynamicField name="*_c" type="currency" indexed="true" stored="true"/>
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-dynamic-multivalued.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-dynamic-multivalued.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-dynamic-multivalued.xml
new file mode 100644
index 0000000..2fba82a
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-dynamic-multivalued.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-dynamic-multivalued" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+  <fieldType name="currency" class="solr.CurrencyFieldType" currencyConfig="currency.xml" multiValued="false"
+             amountLongSuffix="_l" codeStrSuffix="_s"/>
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  
+  <!-- BEGIN BAD STUFF: multiValued="true" -->
+  <dynamicField name="*_c" type="currency" indexed="true" stored="true" multiValued="true"/>
+  <!-- END BAD STUFF -->
+
+  <dynamicField name="*_s" type="string" multiValued="false"/>
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-bogus-code-in-xml.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-bogus-code-in-xml.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-bogus-code-in-xml.xml
new file mode 100644
index 0000000..3b5c695
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-bogus-code-in-xml.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-ft-bogus-code-in-xml" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+  
+  <!-- BEGIN BAD STUFF: bad-currency.xml has bogus code-->
+  <fieldType name="currency"
+             class="solr.CurrencyFieldType"
+             defaultCurrency="USD"
+             currencyConfig="bad-currency.xml"
+             multiValued="false"
+             amountLongSuffix="_l" 
+             codeStrSuffix="_s"/>
+  <!-- END BAD STUFF -->
+  
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  <field name="money" type="currency" indexed="true" stored="true"/>
+
+  <dynamicField name="*_s" type="string" multiValued="false"/>
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-bogus-default-code.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-bogus-default-code.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-bogus-default-code.xml
new file mode 100644
index 0000000..2748372
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-bogus-default-code.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-ft-bogus-default-code" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+
+  <!-- BEGIN BAD STUFF: defaultCurrency -->
+  <fieldType name="currency"
+             class="solr.CurrencyFieldType"
+             defaultCurrency="HOSS"
+             currencyConfig="currency.xml"
+             multiValued="false"
+             amountLongSuffix="_l" 
+             codeStrSuffix="_s"/>
+  <!-- END BAD STUFF -->
+  
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  <field name="money" type="currency" indexed="true" stored="true"/>
+
+  <dynamicField name="*_s" type="string" multiValued="false"/>
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-multivalued.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-multivalued.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-multivalued.xml
new file mode 100644
index 0000000..6afcea4
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-multivalued.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-ft-multivalued" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+  
+  <!-- BEGIN BAD STUFF: multiValued -->
+  <fieldType name="currency" class="solr.CurrencyFieldType" currencyConfig="currency.xml" multiValued="true"
+             amountLongSuffix="_l" codeStrSuffix="_s"/>
+  <!-- END BAD STUFF -->
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  <field name="money" type="currency" indexed="true" stored="true"/>
+
+  <dynamicField name="*_s" type="string" multiValued="false"/>
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-oer-norates.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-oer-norates.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-oer-norates.xml
new file mode 100644
index 0000000..8d8533d
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-ft-oer-norates.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-ft-oer-norates" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+
+  <!-- BEGIN BAD STUFF: no rates -->
+  <fieldType name="currency"
+             class="solr.CurrencyFieldType"
+             providerClass="solr.OpenExchangeRatesOrgProvider"
+             multiValued="false"
+             amountLongSuffix="_l" 
+             codeStrSuffix="_s"/>
+  <!-- END BAD STUFF -->
+  
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  <field name="money" type="currency" indexed="true" stored="true"/>
+
+  <dynamicField name="*_s" type="string" multiValued="false"/>
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-missing-amount-suffix.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-missing-amount-suffix.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-missing-amount-suffix.xml
new file mode 100644
index 0000000..77a531d
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-missing-amount-suffix.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-missing-amount-suffix" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+
+  <!-- BEGIN BAD STUFF: missing amountLongSuffix -->
+  <fieldType name="currency" class="solr.CurrencyFieldType" currencyConfig="currency.xml" multiValued="false"
+             codeStrSuffix="_s"/>
+  <!-- END BAD STUFF -->
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  
+  <dynamicField name="*_s" type="string" multiValued="false"/>
+  <dynamicField name="*_c" type="currency" indexed="true" stored="true"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-missing-code-suffix.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-missing-code-suffix.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-missing-code-suffix.xml
new file mode 100644
index 0000000..89c7080
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-missing-code-suffix.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-missing-code-suffix" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+
+  <!-- BEGIN BAD STUFF: missing codeStrSuffix -->
+  <fieldType name="currency" class="solr.CurrencyFieldType" currencyConfig="currency.xml" multiValued="false"
+             amountLongSuffix="_l"/>
+  <!-- END BAD STUFF -->
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+  <dynamicField name="*_c" type="currency" indexed="true" stored="true"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-multivalued.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-multivalued.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-multivalued.xml
new file mode 100644
index 0000000..9e95458
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-multivalued.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-multivalued" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="currency" class="solr.CurrencyFieldType" currencyConfig="currency.xml"
+             amountLongSuffix="_l" codeStrSuffix="_s"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  
+  <!-- BEGIN BAD STUFF: multiValued="true" -->
+  <field name="money" type="currency" indexed="true" stored="true" multiValued="true"/>
+  <!-- END BAD STUFF -->
+
+  <dynamicField name="*_s" type="string" multiValued="false"/>
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-wrong-amount-ft.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-wrong-amount-ft.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-wrong-amount-ft.xml
new file mode 100644
index 0000000..5188190
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-wrong-amount-ft.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-wrong-amount-ft" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+
+  <!-- BEGIN BAD STUFF: code dynamic field type is wrong -->
+  <fieldType name="currency" class="solr.CurrencyFieldType" currencyConfig="currency.xml" multiValued="false"
+             amountLongSuffix="_s" codeStrSuffix="_s"/>
+  <!-- END BAD STUFF -->
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  
+  <dynamicField name="*_s" type="string" multiValued="false"/>
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+  <dynamicField name="*_c" type="currency" indexed="true" stored="true"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-wrong-code-ft.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-wrong-code-ft.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-wrong-code-ft.xml
new file mode 100644
index 0000000..c3eadc1
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-currencyfieldtype-wrong-code-ft.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="bad-schema-currencyfieldtype-wrong-code-ft" version="1.6">
+  <fieldType name="string" class="solr.StrField" multiValued="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+
+  <!-- BEGIN BAD STUFF: code dynamic field type is wrong -->
+  <fieldType name="currency" class="solr.CurrencyFieldType" currencyConfig="currency.xml" multiValued="false"
+             amountLongSuffix="_l" codeStrSuffix="_l"/>
+  <!-- END BAD STUFF -->
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false"/>
+  
+  <dynamicField name="*_l" type="plong" multiValued="false"/>
+  <dynamicField name="*_c" type="currency" indexed="true" stored="true"/>
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test-files/solr/collection1/conf/schema.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/schema.xml b/solr/core/src/test-files/solr/collection1/conf/schema.xml
index 611a3d9..ba01c9c 100644
--- a/solr/core/src/test-files/solr/collection1/conf/schema.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema.xml
@@ -451,6 +451,17 @@
              multiValued="false"
              providerClass="solr.OpenExchangeRatesOrgProvider"
              ratesFileLocation="open-exchange-rates.json"/>
+  <fieldType name="currency_CFT" class="solr.CurrencyFieldType" amountLongSuffix="_l1_ns" codeStrSuffix="_s1"
+             currencyConfig="currency.xml" multiValued="false"/>
+  <fieldType name="mock_currency_CFT" class="solr.CurrencyFieldType" amountLongSuffix="_l1_ns" codeStrSuffix="_s1"
+             providerClass="solr.MockExchangeRateProvider" foo="bar" multiValued="false"/>
+  <fieldType name="oer_currency_CFT"
+             class="solr.CurrencyFieldType"
+             amountLongSuffix="_l1_ns"
+             codeStrSuffix="_s1_ns"
+             multiValued="false"
+             providerClass="solr.OpenExchangeRatesOrgProvider"
+             ratesFileLocation="open-exchange-rates.json"/>
 
   <!-- omitPositions example -->
   <fieldType name="nopositions" class="solr.TextField" omitPositions="true">
@@ -539,6 +550,9 @@
   <field name="amount" type="currency" indexed="true" stored="true" multiValued="false"/>
   <field name="mock_amount" type="mock_currency" indexed="true" stored="true"/>
   <field name="oer_amount" type="oer_currency" indexed="true" stored="true"/>
+  <field name="amount_CFT" type="currency_CFT" indexed="true" stored="true" multiValued="false"/>
+  <field name="mock_amount_CFT" type="mock_currency_CFT" indexed="true" stored="true"/>
+  <field name="oer_amount_CFT" type="oer_currency_CFT" indexed="true" stored="true"/>
 
   <!-- test different combinations of indexed and stored -->
   <field name="bind" type="boolean" indexed="true" stored="false"/>
@@ -653,8 +667,10 @@
 
   <dynamicField name="*_s" type="string" indexed="true" stored="true"/>
   <dynamicField name="*_s1" type="string" indexed="true" stored="true" multiValued="false"/>
+  <dynamicField name="*_s1_ns" type="string" indexed="true" stored="false" multiValued="false"/>
   <dynamicField name="*_l" type="${solr.tests.longClass:plong}" indexed="true" stored="true"/>
   <dynamicField name="*_l1" type="${solr.tests.longClass:plong}" indexed="true" stored="true" multiValued="false"/>
+  <dynamicField name="*_l1_ns" type="${solr.tests.longClass:plong}" indexed="true" stored="false" multiValued="false"/>
   <dynamicField name="*_t" type="text" indexed="true" stored="true"/>
   <dynamicField name="*_b" type="boolean" indexed="true" stored="true"/>
   <dynamicField name="*_b1" type="boolean" indexed="true" stored="true" multiValued="false"/>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test/org/apache/solr/schema/AbstractCurrencyFieldTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/schema/AbstractCurrencyFieldTest.java b/solr/core/src/test/org/apache/solr/schema/AbstractCurrencyFieldTest.java
deleted file mode 100644
index 6577eaa..0000000
--- a/solr/core/src/test/org/apache/solr/schema/AbstractCurrencyFieldTest.java
+++ /dev/null
@@ -1,426 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.schema;
-import java.util.Currency;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-
-import org.apache.lucene.index.IndexableField;
-import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.core.SolrCore;
-import org.apache.solr.util.RTimer;
-import org.junit.Assume;
-import org.junit.BeforeClass;
-import org.junit.Ignore;
-import org.junit.Test;
-
-/**
- * Tests currency field type.
- * @see #field
- */
-@Ignore("Abstract base class with test methods")
-public abstract class AbstractCurrencyFieldTest extends SolrTestCaseJ4 {
-
-  /**
-   * "Assumes" that the specified list of currency codes are
-   * supported in this JVM
-   */
-  public static void assumeCurrencySupport(String... codes) {
-    try {
-      // each JDK might have a diff list of supported currencies,
-      // these are the ones needed for this test to work.
-      for (String code : codes) {
-        Currency obj = Currency.getInstance(code);
-        assertNotNull(code, obj);
-      }
-    } catch (IllegalArgumentException e) {
-      Assume.assumeNoException(e);
-    }
-
-  }
-  @BeforeClass
-  public static void beforeClass() throws Exception {
-    assumeCurrencySupport("USD", "EUR", "MXN", "GBP", "JPY", "NOK");
-    initCore("solrconfig.xml", "schema.xml");
-  }
-
-  /** The field name to use in all tests */
-  public abstract String field();
-
-  @Test
-  public void testCurrencySchema() throws Exception {
-    IndexSchema schema = h.getCore().getLatestSchema();
-
-    SchemaField amount = schema.getField(field());
-    assertNotNull(amount);
-    assertTrue(amount.isPolyField());
-
-    SchemaField[] dynFields = schema.getDynamicFieldPrototypes();
-    boolean seenCurrency = false;
-    boolean seenAmount = false;
-
-    for (SchemaField dynField : dynFields) {
-      if (dynField.getName().equals("*" + FieldType.POLY_FIELD_SEPARATOR + CurrencyField.FIELD_SUFFIX_CURRENCY)) {
-        seenCurrency = true;
-      }
-
-      if (dynField.getName().equals("*" + FieldType.POLY_FIELD_SEPARATOR + CurrencyField.FIELD_SUFFIX_AMOUNT_RAW)) {
-        seenAmount = true;
-      }
-    }
-
-    assertTrue("Didn't find the expected currency code dynamic field", seenCurrency);
-    assertTrue("Didn't find the expected value dynamic field", seenAmount);
-  }
-
-  @Test
-  public void testCurrencyFieldType() throws Exception {
-    SolrCore core = h.getCore();
-    IndexSchema schema = core.getLatestSchema();
-    SchemaField amount = schema.getField(field());
-    assertNotNull(amount);
-    assertTrue(field() + " is not a poly field", amount.isPolyField());
-    FieldType tmp = amount.getType();
-    assertTrue(tmp instanceof CurrencyField);
-    String currencyValue = "1.50,EUR";
-    List<IndexableField> fields = amount.createFields(currencyValue, 2);
-    assertEquals(fields.size(), 3);
-
-    // First field is currency code, second is value, third is stored.
-    for (int i = 0; i < 3; i++) {
-      boolean hasValue = fields.get(i).readerValue() != null
-              || fields.get(i).numericValue() != null
-              || fields.get(i).stringValue() != null;
-      assertTrue("Doesn't have a value: " + fields.get(i), hasValue);
-    }
-
-    assertEquals(schema.getFieldTypeByName("string").toExternal(fields.get(2)), "1.50,EUR");
-    
-    // A few tests on the provider directly
-    ExchangeRateProvider p = ((CurrencyField) tmp).getProvider();
-    Set<String> availableCurrencies = p.listAvailableCurrencies();
-    assertEquals(5, availableCurrencies.size());
-    assert(p.reload() == true);
-    assert(p.getExchangeRate("USD", "EUR") == 2.5);
-  }
-
-  @Test
-  public void testMockExchangeRateProvider() throws Exception {
-    SolrCore core = h.getCore();
-    IndexSchema schema = core.getLatestSchema();
-    SchemaField amount = schema.getField("mock_amount");
-
-    // A few tests on the provider directly
-    ExchangeRateProvider p = ((CurrencyField)amount.getType()).getProvider();
-    Set<String> availableCurrencies = p.listAvailableCurrencies();
-    assert(availableCurrencies.size() == 3);
-    assert(p.reload() == true);
-    assert(p.getExchangeRate("USD", "EUR") == 0.8);
-  }
-
-  @Test
-  public void testCurrencyRangeSearch() throws Exception {
-    clearIndex();
-    final int emptyDocs = atLeast(50); // times 2
-    final int negDocs = atLeast(5);
-    
-    assertU(adoc("id", "0", field(), "0,USD")); // 0
-    // lots of docs w/o values
-    for (int i = 100; i <= 100 + emptyDocs; i++) {
-      assertU(adoc("id", "" + i));
-    }
-    // docs with values in ranges we'll query
-    for (int i = 1; i <= 10; i++) {
-      assertU(adoc("id", "" + i, field(), i + ",USD"));
-    }
-    // more docs w/o values
-    for (int i = 500; i <= 500 + emptyDocs; i++) {
-      assertU(adoc("id", "" + i));
-    }
-    // some negative values
-    for (int i = -100; i > -100 - negDocs; i--) {
-      assertU(adoc("id", "" + i, field(), i + ",USD"));
-    }
-    assertU(adoc("id", "40", field(), "0,USD")); // 0
-
-    assertU(commit());
-
-    assertQ(req("fl", "*,score", "q",
-            field()+":[2.00,USD TO 5.00,USD]"),
-            "//*[@numFound='4']");
-
-    assertQ(req("fl", "*,score", "q",
-            field()+":[0.50,USD TO 1.00,USD]"),
-            "//*[@numFound='1']");
-
-    assertQ(req("fl", "*,score", "q",
-            field()+":[24.00,USD TO 25.00,USD]"),
-            "//*[@numFound='0']");
-
-    // "GBP" currency code is 1/2 of a USD dollar, for testing.
-    assertQ(req("fl", "*,score", "q",
-            field()+":[0.50,GBP TO 1.00,GBP]"),
-            "//*[@numFound='2']");
-
-    // "EUR" currency code is 2.5X of a USD dollar, for testing.
-    assertQ(req("fl", "*,score", "q",
-            field()+":[24.00,EUR TO 25.00,EUR]"),
-            "//*[@numFound='1']");
-
-    // Slight asymmetric rate should work.
-    assertQ(req("fl", "*,score", "q",
-            field()+":[24.99,EUR TO 25.01,EUR]"),
-            "//*[@numFound='1']");
-    
-    // Open ended ranges without currency
-    assertQ(req("fl", "*,score", "q",
-            field()+":[* TO *]"),
-            "//*[@numFound='" + (2 + 10 + negDocs) + "']");
-    
-    // Open ended ranges with currency
-    assertQ(req("fl", "*,score", "q",
-            field()+":[*,EUR TO *,EUR]"),
-            "//*[@numFound='" + (2 + 10 + negDocs) + "']");
-
-    // Open ended start range without currency
-    assertQ(req("fl", "*,score", "q",
-            field()+":[* TO 5,USD]"),
-            "//*[@numFound='" + (2 + 5 + negDocs) + "']");
-
-    // Open ended start range with currency (currency for the * won't matter)
-    assertQ(req("fl", "*,score", "q",
-            field()+":[*,USD TO 5,USD]"),
-            "//*[@numFound='" + (2 + 5 + negDocs) + "']");
-
-    // Open ended end range
-    assertQ(req("fl", "*,score", "q",
-            field()+":[3 TO *]"),
-            "//*[@numFound='8']");
-  }
-
-  @Test
-  public void testBogusCurrency() throws Exception {
-    ignoreException("HOSS");
-
-    // bogus currency
-    assertQEx("Expected exception for invalid currency",
-              req("fl", "*,score", "q",
-                  field()+":[3,HOSS TO *]"),
-              400);
-  }
-
-  @Test
-  public void testCurrencyPointQuery() throws Exception {
-    clearIndex();
-    assertU(adoc("id", "" + 1, field(), "10.00,USD"));
-    assertU(adoc("id", "" + 2, field(), "15.00,MXN"));
-    assertU(commit());
-    assertQ(req("fl", "*,score", "q", field()+":10.00,USD"), "//str[@name='id']='1'");
-    assertQ(req("fl", "*,score", "q", field()+":9.99,USD"), "//*[@numFound='0']");
-    assertQ(req("fl", "*,score", "q", field()+":10.01,USD"), "//*[@numFound='0']");
-    assertQ(req("fl", "*,score", "q", field()+":15.00,MXN"), "//str[@name='id']='2'");
-    assertQ(req("fl", "*,score", "q", field()+":7.50,USD"), "//str[@name='id']='2'");
-    assertQ(req("fl", "*,score", "q", field()+":7.49,USD"), "//*[@numFound='0']");
-    assertQ(req("fl", "*,score", "q", field()+":7.51,USD"), "//*[@numFound='0']");
-  }
-
-  @Ignore
-  public void testPerformance() throws Exception {
-    clearIndex();
-
-    Random r = random();
-    int initDocs = 200000;
-
-    for (int i = 1; i <= initDocs; i++) {
-      assertU(adoc("id", "" + i, field(), (r.nextInt(10) + 1.00) + ",USD"));
-      if (i % 1000 == 0)
-        System.out.println(i);
-    }
-
-    assertU(commit());
-    for (int i = 0; i < 1000; i++) {
-      double lower = r.nextInt(10) + 1.00;
-      assertQ(req("fl", "*,score", "q", field()+":[" +  lower + ",USD TO " + (lower + 10.00) + ",USD]"), "//*");
-      assertQ(req("fl", "*,score", "q", field()+":[" +  lower + ",EUR TO " + (lower + 10.00) + ",EUR]"), "//*");
-    }
-
-    for (int j = 0; j < 3; j++) {
-      final RTimer timer = new RTimer();
-      for (int i = 0; i < 1000; i++) {
-        double lower = r.nextInt(10) + 1.00;
-        assertQ(req("fl", "*,score", "q", field()+":[" +  lower + ",USD TO " + (lower + (9.99 - (j * 0.01))) + ",USD]"), "//*");
-      }
-
-      System.out.println(timer.getTime());
-    }
-
-    System.out.println("---");
-
-    for (int j = 0; j < 3; j++) {
-      final RTimer timer = new RTimer();
-      for (int i = 0; i < 1000; i++) {
-        double lower = r.nextInt(10) + 1.00;
-        assertQ(req("fl", "*,score", "q", field()+":[" +  lower + ",EUR TO " + (lower + (9.99 - (j * 0.01))) + ",EUR]"), "//*");
-      }
-
-      System.out.println(timer.getTime());
-    }
-  }
-
-  @Test
-  public void testCurrencySort() throws Exception {
-    clearIndex();
-
-    assertU(adoc("id", "" + 1, field(), "10.00,USD"));
-    assertU(adoc("id", "" + 2, field(), "15.00,EUR"));
-    assertU(adoc("id", "" + 3, field(), "7.00,EUR"));
-    assertU(adoc("id", "" + 4, field(), "6.00,GBP"));
-    assertU(adoc("id", "" + 5, field(), "2.00,GBP"));
-    assertU(commit());
-
-    assertQ(req("fl", "*,score", "q", "*:*", "sort", field()+" desc", "limit", "1"), "//str[@name='id']='4'");
-    assertQ(req("fl", "*,score", "q", "*:*", "sort", field()+" asc", "limit", "1"), "//str[@name='id']='3'");
-  }
-
-  public void testFunctionUsage() throws Exception {
-    clearIndex();
-    for (int i = 1; i <= 8; i++) {
-      // "GBP" currency code is 1/2 of a USD dollar, for testing.
-      assertU(adoc("id", "" + i, field(), (((float)i)/2) + ",GBP"));
-    }
-    for (int i = 9; i <= 11; i++) {
-      assertU(adoc("id", "" + i, field(), i + ",USD"));
-    }
-
-    assertU(commit());
-
-    // direct value source usage, gets "raw" form od default currency
-    // default==USD, so raw==penies
-    assertQ(req("fl", "id,func:field($f)",
-                "f", field(),
-                "q", "id:5"),
-            "//*[@numFound='1']",
-            "//doc/float[@name='func' and .=500]");
-    assertQ(req("fl", "id,func:field($f)",
-                "f", field(),
-                "q", "id:10"),
-            "//*[@numFound='1']",
-            "//doc/float[@name='func' and .=1000]");
-    assertQ(req("fl", "id,score,"+field(), 
-                "q", "{!frange u=500}"+field())
-            ,"//*[@numFound='5']"
-            ,"//str[@name='id']='1'"
-            ,"//str[@name='id']='2'"
-            ,"//str[@name='id']='3'"
-            ,"//str[@name='id']='4'"
-            ,"//str[@name='id']='5'"
-            );
-    assertQ(req("fl", "id,score,"+field(), 
-                "q", "{!frange l=500 u=1000}"+field())
-            ,"//*[@numFound='6']"
-            ,"//str[@name='id']='5'"
-            ,"//str[@name='id']='6'"
-            ,"//str[@name='id']='7'"
-            ,"//str[@name='id']='8'"
-            ,"//str[@name='id']='9'"
-            ,"//str[@name='id']='10'"
-            );
-
-    // use the currency function to convert to default (USD)
-    assertQ(req("fl", "id,func:currency($f)",
-                "f", field(),
-                "q", "id:10"),
-            "//*[@numFound='1']",
-            "//doc/float[@name='func' and .=10]");
-    assertQ(req("fl", "id,func:currency($f)",
-                "f", field(),
-                "q", "id:5"),
-            "//*[@numFound='1']",
-            "//doc/float[@name='func' and .=5]");
-    assertQ(req("fl", "id,score"+field(), 
-                "f", field(),
-                "q", "{!frange u=5}currency($f)")
-            ,"//*[@numFound='5']"
-            ,"//str[@name='id']='1'"
-            ,"//str[@name='id']='2'"
-            ,"//str[@name='id']='3'"
-            ,"//str[@name='id']='4'"
-            ,"//str[@name='id']='5'"
-            );
-    assertQ(req("fl", "id,score"+field(), 
-                "f", field(),
-                "q", "{!frange l=5 u=10}currency($f)")
-            ,"//*[@numFound='6']"
-            ,"//str[@name='id']='5'"
-            ,"//str[@name='id']='6'"
-            ,"//str[@name='id']='7'"
-            ,"//str[@name='id']='8'"
-            ,"//str[@name='id']='9'"
-            ,"//str[@name='id']='10'"
-            );
-    
-    // use the currency function to convert to MXN
-    assertQ(req("fl", "id,func:currency($f,MXN)",
-                "f", field(),
-                "q", "id:5"),
-            "//*[@numFound='1']",
-            "//doc/float[@name='func' and .=10]");
-    assertQ(req("fl", "id,func:currency($f,MXN)",
-                "f", field(),
-                "q", "id:10"),
-            "//*[@numFound='1']",
-            "//doc/float[@name='func' and .=20]");
-    assertQ(req("fl", "*,score,"+field(), 
-                "f", field(),
-                "q", "{!frange u=10}currency($f,MXN)")
-            ,"//*[@numFound='5']"
-            ,"//str[@name='id']='1'"
-            ,"//str[@name='id']='2'"
-            ,"//str[@name='id']='3'"
-            ,"//str[@name='id']='4'"
-            ,"//str[@name='id']='5'"
-            );
-    assertQ(req("fl", "*,score,"+field(), 
-                "f", field(),
-                "q", "{!frange l=10 u=20}currency($f,MXN)")
-            ,"//*[@numFound='6']"
-            ,"//str[@name='id']='5'"
-            ,"//str[@name='id']='6'"
-            ,"//str[@name='id']='7'"
-            ,"//str[@name='id']='8'"
-            ,"//str[@name='id']='9'"
-            ,"//str[@name='id']='10'"
-            );
-
-  }
-
-  @Test
-  public void testMockFieldType() throws Exception {
-    clearIndex();
-
-    assertU(adoc("id", "1", "mock_amount", "1.00,USD"));
-    assertU(adoc("id", "2", "mock_amount", "1.00,EUR"));
-    assertU(adoc("id", "3", "mock_amount", "1.00,NOK"));
-    assertU(commit());
-
-    assertQ(req("fl", "*,score", "q", "mock_amount:5.0,NOK"),   "//*[@numFound='1']", "//str[@name='id']='1'");
-    assertQ(req("fl", "*,score", "q", "mock_amount:1.2,USD"), "//*[@numFound='1']",   "//str[@name='id']='2'");
-    assertQ(req("fl", "*,score", "q", "mock_amount:0.2,USD"), "//*[@numFound='1']",   "//str[@name='id']='3'");
-    assertQ(req("fl", "*,score", "q", "mock_amount:99,USD"),  "//*[@numFound='0']");
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test/org/apache/solr/schema/BadIndexSchemaTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/schema/BadIndexSchemaTest.java b/solr/core/src/test/org/apache/solr/schema/BadIndexSchemaTest.java
index 81f8a42..7c8eb3a 100644
--- a/solr/core/src/test/org/apache/solr/schema/BadIndexSchemaTest.java
+++ b/solr/core/src/test/org/apache/solr/schema/BadIndexSchemaTest.java
@@ -73,14 +73,22 @@ public class BadIndexSchemaTest extends AbstractBadConfigTestBase {
     doTest("bad-schema-currency-ft-multivalued.xml", 
            "types can not be multiValued: currency");
     doTest("bad-schema-currency-multivalued.xml", 
-           "Fields can not be multiValued: money");
+           "fields can not be multiValued: money");
     doTest("bad-schema-currency-dynamic-multivalued.xml", 
-           "Fields can not be multiValued: *_c");
+           "fields can not be multiValued: *_c");
+    doTest("bad-schema-currencyfieldtype-ft-multivalued.xml",
+        "types can not be multiValued: currency");
+    doTest("bad-schema-currencyfieldtype-multivalued.xml",
+        "fields can not be multiValued: money");
+    doTest("bad-schema-currencyfieldtype-dynamic-multivalued.xml",
+        "fields can not be multiValued: *_c");
   }
 
   public void testCurrencyOERNoRates() throws Exception {
     doTest("bad-schema-currency-ft-oer-norates.xml", 
            "ratesFileLocation");
+    doTest("bad-schema-currencyfieldtype-ft-oer-norates.xml",
+        "ratesFileLocation");
   }
 
   public void testCurrencyBogusCode() throws Exception {
@@ -88,6 +96,35 @@ public class BadIndexSchemaTest extends AbstractBadConfigTestBase {
            "HOSS");
     doTest("bad-schema-currency-ft-bogus-code-in-xml.xml", 
            "HOSS");
+    doTest("bad-schema-currencyfieldtype-ft-bogus-default-code.xml",
+        "HOSS");
+    doTest("bad-schema-currencyfieldtype-ft-bogus-code-in-xml.xml",
+        "HOSS");
+  }
+  
+  public void testCurrencyDisallowedSuffixParams() throws Exception {
+    doTest("bad-schema-currency-ft-code-suffix.xml", 
+        "Unknown parameter(s)");
+    doTest("bad-schema-currency-ft-amount-suffix.xml",
+        "Unknown parameter(s)");
+  }
+  
+  public void testCurrencyBogusSuffixes() throws Exception {
+    doTest("bad-schema-currencyfieldtype-bogus-code-suffix.xml",
+           "Undefined dynamic field for codeStrSuffix");
+    doTest("bad-schema-currencyfieldtype-bogus-amount-suffix.xml",
+           "Undefined dynamic field for amountLongSuffix");
+    doTest("bad-schema-currencyfieldtype-wrong-code-ft.xml",
+           "Dynamic field for codeStrSuffix=\"_l\" must have type class of (or extending) StrField");
+    doTest("bad-schema-currencyfieldtype-wrong-amount-ft.xml",
+           "Dynamic field for amountLongSuffix=\"_s\" must have type class extending LongValueFieldType");
+  } 
+  
+  public void testCurrencyMissingSuffixes() throws Exception {
+    doTest("bad-schema-currencyfieldtype-missing-code-suffix.xml",
+        "Missing required param codeStrSuffix");
+    doTest("bad-schema-currencyfieldtype-missing-amount-suffix.xml",
+        "Missing required param amountLongSuffix");
   }
 
   public void testPerFieldtypeSimButNoSchemaSimFactory() throws Exception {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test/org/apache/solr/schema/CurrencyFieldOpenExchangeTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/schema/CurrencyFieldOpenExchangeTest.java b/solr/core/src/test/org/apache/solr/schema/CurrencyFieldOpenExchangeTest.java
deleted file mode 100644
index fed51eb..0000000
--- a/solr/core/src/test/org/apache/solr/schema/CurrencyFieldOpenExchangeTest.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.schema;
-
-/**
- * Tests currency field type using OpenExchangeRatesOrgProvider.
- */
-public class CurrencyFieldOpenExchangeTest extends AbstractCurrencyFieldTest {
-
-  public String field() {
-    return "oer_amount";
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test/org/apache/solr/schema/CurrencyFieldTypeTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/schema/CurrencyFieldTypeTest.java b/solr/core/src/test/org/apache/solr/schema/CurrencyFieldTypeTest.java
new file mode 100644
index 0000000..d8499b7
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/schema/CurrencyFieldTypeTest.java
@@ -0,0 +1,494 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.schema;
+
+import java.util.Arrays;
+import java.util.Currency;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
+import org.apache.lucene.index.IndexableField;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.util.RTimer;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/** Tests CurrencyField and CurrencyFieldType. */
+public class CurrencyFieldTypeTest extends SolrTestCaseJ4 {
+  private final String fieldName;
+  private final Class<? extends ExchangeRateProvider> expectedProviderClass;
+  
+  public CurrencyFieldTypeTest(String fieldName, Class<? extends ExchangeRateProvider> expectedProviderClass) {
+    this.fieldName = fieldName;
+    this.expectedProviderClass = expectedProviderClass;
+  }
+
+  @ParametersFactory
+  public static Iterable<Object[]> parameters() {
+    return Arrays.asList(new Object[][] {
+        {"amount", FileExchangeRateProvider.class},    // CurrencyField
+        {"mock_amount", MockExchangeRateProvider.class},                 // CurrencyField
+        {"oer_amount", OpenExchangeRatesOrgProvider.class},              // CurrencyField
+        {"amount_CFT", FileExchangeRateProvider.class},  // CurrencyFieldType
+        {"mock_amount_CFT", MockExchangeRateProvider.class},               // CurrencyFieldType
+        {"oer_amount_CFT", OpenExchangeRatesOrgProvider.class}             // CurrencyFieldType
+      });
+  }
+  
+  /**
+   * "Assumes" that the specified list of currency codes are
+   * supported in this JVM
+   */
+  public static void assumeCurrencySupport(String... codes) {
+    try {
+      // each JDK might have a diff list of supported currencies,
+      // these are the ones needed for this test to work.
+      for (String code : codes) {
+        Currency obj = Currency.getInstance(code);
+        assertNotNull(code, obj);
+      }
+    } catch (IllegalArgumentException e) {
+      Assume.assumeNoException(e);
+    }
+
+  }
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    assumeCurrencySupport("USD", "EUR", "MXN", "GBP", "JPY", "NOK");
+    initCore("solrconfig.xml", "schema.xml");
+  }
+
+  @Test
+  public void testCurrencySchema() throws Exception {
+    IndexSchema schema = h.getCore().getLatestSchema();
+
+    SchemaField amount = schema.getField(fieldName);
+    assertNotNull(amount);
+    assertTrue(amount.isPolyField());
+
+    CurrencyFieldType type = (CurrencyFieldType)amount.getType();
+    String currencyDynamicField
+        = "*" + (type instanceof CurrencyField ? FieldType.POLY_FIELD_SEPARATOR : "") + type.fieldSuffixCurrency;
+    String amountDynamicField 
+        = "*" + (type instanceof CurrencyField ? FieldType.POLY_FIELD_SEPARATOR : "") + type.fieldSuffixAmountRaw;
+
+    SchemaField[] dynFields = schema.getDynamicFieldPrototypes();
+    boolean seenCurrency = false;
+    boolean seenAmount = false;
+
+    for (SchemaField dynField : dynFields) {
+      if (dynField.getName().equals(amountDynamicField)) {
+        seenAmount = true;
+      }
+
+      if (dynField.getName().equals(currencyDynamicField)) {
+        seenCurrency = true;
+      }
+    }
+
+    assertTrue("Didn't find the expected currency code dynamic field " + currencyDynamicField, seenCurrency);
+    assertTrue("Didn't find the expected value dynamic field " + amountDynamicField, seenAmount);
+  }
+
+  @Test
+  public void testCurrencyFieldType() throws Exception {
+    assumeTrue("This test is only applicable to the XML file based exchange rate provider",
+        expectedProviderClass.equals(FileExchangeRateProvider.class));
+
+    SolrCore core = h.getCore();
+    IndexSchema schema = core.getLatestSchema();
+    SchemaField amount = schema.getField(fieldName);
+    assertNotNull(amount);
+    assertTrue(fieldName + " is not a poly field", amount.isPolyField());
+    FieldType tmp = amount.getType();
+    assertTrue(fieldName + " is not an instance of CurrencyFieldType", tmp instanceof CurrencyFieldType);
+    String currencyValue = "1.50,EUR";
+    List<IndexableField> fields = amount.createFields(currencyValue, 2);
+    assertEquals(fields.size(), 3);
+
+    // First field is currency code, second is value, third is stored.
+    for (int i = 0; i < 3; i++) {
+      boolean hasValue = fields.get(i).readerValue() != null
+              || fields.get(i).numericValue() != null
+              || fields.get(i).stringValue() != null;
+      assertTrue("Doesn't have a value: " + fields.get(i), hasValue);
+    }
+
+    assertEquals(schema.getFieldTypeByName("string").toExternal(fields.get(2)), "1.50,EUR");
+    
+    // A few tests on the provider directly
+    ExchangeRateProvider p = ((CurrencyFieldType)tmp).getProvider();
+    Set<String> availableCurrencies = p.listAvailableCurrencies();
+    assertEquals(5, availableCurrencies.size());
+    assertTrue(p.reload());
+    assertEquals(2.5, p.getExchangeRate("USD", "EUR"), 0.00000000001);
+  }
+
+  @Test
+  public void testMockExchangeRateProvider() throws Exception {
+    assumeTrue("This test is only applicable to the mock exchange rate provider",
+        expectedProviderClass.equals(MockExchangeRateProvider.class));
+    
+    SolrCore core = h.getCore();
+    IndexSchema schema = core.getLatestSchema();
+    SchemaField field = schema.getField(fieldName);
+    FieldType fieldType = field.getType();
+    ExchangeRateProvider provider = ((CurrencyFieldType)fieldType).getProvider();
+
+    // A few tests on the provider directly
+    assertEquals(3, provider.listAvailableCurrencies().size());
+    assertTrue(provider.reload());
+    assertEquals(0.8, provider.getExchangeRate("USD", "EUR"), 0.00000000001);
+  }
+
+  @Test
+  public void testCurrencyRangeSearch() throws Exception {
+    assumeTrue("This test is only applicable to the XML file based exchange rate provider",
+        expectedProviderClass.equals(FileExchangeRateProvider.class));
+    
+    clearIndex();
+    final int emptyDocs = atLeast(50); // times 2
+    final int negDocs = atLeast(5);
+    
+    assertU(adoc("id", "0", fieldName, "0,USD")); // 0
+    // lots of docs w/o values
+    for (int i = 100; i <= 100 + emptyDocs; i++) {
+      assertU(adoc("id", "" + i));
+    }
+    // docs with values in ranges we'll query
+    for (int i = 1; i <= 10; i++) {
+      assertU(adoc("id", "" + i, fieldName, i + ",USD"));
+    }
+    // more docs w/o values
+    for (int i = 500; i <= 500 + emptyDocs; i++) {
+      assertU(adoc("id", "" + i));
+    }
+    // some negative values
+    for (int i = -100; i > -100 - negDocs; i--) {
+      assertU(adoc("id", "" + i, fieldName, i + ",USD"));
+    }
+    assertU(adoc("id", "40", fieldName, "0,USD")); // 0
+
+    assertU(commit());
+
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[2.00,USD TO 5.00,USD]"),
+            "//*[@numFound='4']");
+
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[0.50,USD TO 1.00,USD]"),
+            "//*[@numFound='1']");
+
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[24.00,USD TO 25.00,USD]"),
+            "//*[@numFound='0']");
+
+    // "GBP" currency code is 1/2 of a USD dollar, for testing.
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[0.50,GBP TO 1.00,GBP]"),
+            "//*[@numFound='2']");
+
+    // "EUR" currency code is 2.5X of a USD dollar, for testing.
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[24.00,EUR TO 25.00,EUR]"),
+            "//*[@numFound='1']");
+
+    // Slight asymmetric rate should work.
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[24.99,EUR TO 25.01,EUR]"),
+            "//*[@numFound='1']");
+    
+    // Open ended ranges without currency
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[* TO *]"),
+            "//*[@numFound='" + (2 + 10 + negDocs) + "']");
+    
+    // Open ended ranges with currency
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[*,EUR TO *,EUR]"),
+            "//*[@numFound='" + (2 + 10 + negDocs) + "']");
+
+    // Open ended start range without currency
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[* TO 5,USD]"),
+            "//*[@numFound='" + (2 + 5 + negDocs) + "']");
+
+    // Open ended start range with currency (currency for the * won't matter)
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[*,USD TO 5,USD]"),
+            "//*[@numFound='" + (2 + 5 + negDocs) + "']");
+
+    // Open ended end range
+    assertQ(req("fl", "*,score", "q",
+            fieldName+":[3 TO *]"),
+            "//*[@numFound='8']");
+  }
+
+  @Test
+  public void testBogusCurrency() throws Exception {
+    ignoreException("HOSS");
+
+    // bogus currency
+    assertQEx("Expected exception for invalid currency",
+              req("fl", "*,score", "q",
+                  fieldName+":[3,HOSS TO *]"),
+              400);
+  }
+
+  @Test
+  public void testCurrencyPointQuery() throws Exception {
+    assumeTrue("This test is only applicable to the XML file based exchange rate provider",
+        expectedProviderClass.equals(FileExchangeRateProvider.class));
+
+    clearIndex();
+    assertU(adoc("id", "" + 1, fieldName, "10.00,USD"));
+    assertU(adoc("id", "" + 2, fieldName, "15.00,MXN"));
+    assertU(commit());
+    assertQ(req("fl", "*,score", "q", fieldName+":10.00,USD"), "//str[@name='id']='1'");
+    assertQ(req("fl", "*,score", "q", fieldName+":9.99,USD"), "//*[@numFound='0']");
+    assertQ(req("fl", "*,score", "q", fieldName+":10.01,USD"), "//*[@numFound='0']");
+    assertQ(req("fl", "*,score", "q", fieldName+":15.00,MXN"), "//str[@name='id']='2'");
+    assertQ(req("fl", "*,score", "q", fieldName+":7.50,USD"), "//str[@name='id']='2'");
+    assertQ(req("fl", "*,score", "q", fieldName+":7.49,USD"), "//*[@numFound='0']");
+    assertQ(req("fl", "*,score", "q", fieldName+":7.51,USD"), "//*[@numFound='0']");
+  }
+
+  @Ignore
+  public void testPerformance() throws Exception {
+    clearIndex();
+
+    Random r = random();
+    int initDocs = 200000;
+
+    for (int i = 1; i <= initDocs; i++) {
+      assertU(adoc("id", "" + i, fieldName, (r.nextInt(10) + 1.00) + ",USD"));
+      if (i % 1000 == 0)
+        System.out.println(i);
+    }
+
+    assertU(commit());
+    for (int i = 0; i < 1000; i++) {
+      double lower = r.nextInt(10) + 1.00;
+      assertQ(req("fl", "*,score", "q", fieldName+":[" +  lower + ",USD TO " + (lower + 10.00) + ",USD]"), "//*");
+      assertQ(req("fl", "*,score", "q", fieldName+":[" +  lower + ",EUR TO " + (lower + 10.00) + ",EUR]"), "//*");
+    }
+
+    for (int j = 0; j < 3; j++) {
+      final RTimer timer = new RTimer();
+      for (int i = 0; i < 1000; i++) {
+        double lower = r.nextInt(10) + 1.00;
+        assertQ(req("fl", "*,score", "q", fieldName+":[" +  lower + ",USD TO " + (lower + (9.99 - (j * 0.01))) + ",USD]"), "//*");
+      }
+
+      System.out.println(timer.getTime());
+    }
+
+    System.out.println("---");
+
+    for (int j = 0; j < 3; j++) {
+      final RTimer timer = new RTimer();
+      for (int i = 0; i < 1000; i++) {
+        double lower = r.nextInt(10) + 1.00;
+        assertQ(req("fl", "*,score", "q", fieldName+":[" +  lower + ",EUR TO " + (lower + (9.99 - (j * 0.01))) + ",EUR]"), "//*");
+      }
+
+      System.out.println(timer.getTime());
+    }
+  }
+
+  @Test
+  public void testCurrencySort() throws Exception {
+    assumeTrue("This test is only applicable to the XML file based exchange rate provider",
+        expectedProviderClass.equals(FileExchangeRateProvider.class));
+
+    clearIndex();
+
+    assertU(adoc("id", "" + 1, fieldName, "10.00,USD"));
+    assertU(adoc("id", "" + 2, fieldName, "15.00,EUR"));
+    assertU(adoc("id", "" + 3, fieldName, "7.00,EUR"));
+    assertU(adoc("id", "" + 4, fieldName, "6.00,GBP"));
+    assertU(adoc("id", "" + 5, fieldName, "2.00,GBP"));
+    assertU(commit());
+
+    assertQ(req("fl", "*,score", "q", "*:*", "sort", fieldName+" desc", "limit", "1"), "//str[@name='id']='4'");
+    assertQ(req("fl", "*,score", "q", "*:*", "sort", fieldName+" asc", "limit", "1"), "//str[@name='id']='3'");
+  }
+
+  public void testExpectedProvider() {
+      SolrCore core = h.getCore();
+      IndexSchema schema = core.getLatestSchema();
+      SchemaField field = schema.getField(fieldName);
+      FieldType fieldType = field.getType();
+      ExchangeRateProvider provider = ((CurrencyFieldType)fieldType).getProvider();
+      assertEquals(expectedProviderClass, provider.getClass());
+    }
+  
+  public void testFunctionUsage() throws Exception {
+    assumeTrue("This test is only applicable to the XML file based exchange rate provider",
+        expectedProviderClass.equals(FileExchangeRateProvider.class));
+
+    clearIndex();
+    for (int i = 1; i <= 8; i++) {
+      // "GBP" currency code is 1/2 of a USD dollar, for testing.
+      assertU(adoc("id", "" + i, fieldName, (((float)i)/2) + ",GBP"));
+    }
+    for (int i = 9; i <= 11; i++) {
+      assertU(adoc("id", "" + i, fieldName, i + ",USD"));
+    }
+
+    assertU(commit());
+
+    // direct value source usage, gets "raw" form od default currency
+    // default==USD, so raw==penies
+    assertQ(req("fl", "id,func:field($f)",
+                "f", fieldName,
+                "q", "id:5"),
+            "//*[@numFound='1']",
+            "//doc/float[@name='func' and .=500]");
+    assertQ(req("fl", "id,func:field($f)",
+                "f", fieldName,
+                "q", "id:10"),
+            "//*[@numFound='1']",
+            "//doc/float[@name='func' and .=1000]");
+    assertQ(req("fl", "id,score,"+fieldName, 
+                "q", "{!frange u=500}"+fieldName)
+            ,"//*[@numFound='5']"
+            ,"//str[@name='id']='1'"
+            ,"//str[@name='id']='2'"
+            ,"//str[@name='id']='3'"
+            ,"//str[@name='id']='4'"
+            ,"//str[@name='id']='5'"
+            );
+    assertQ(req("fl", "id,score,"+fieldName, 
+                "q", "{!frange l=500 u=1000}"+fieldName)
+            ,"//*[@numFound='6']"
+            ,"//str[@name='id']='5'"
+            ,"//str[@name='id']='6'"
+            ,"//str[@name='id']='7'"
+            ,"//str[@name='id']='8'"
+            ,"//str[@name='id']='9'"
+            ,"//str[@name='id']='10'"
+            );
+
+    // use the currency function to convert to default (USD)
+    assertQ(req("fl", "id,func:currency($f)",
+                "f", fieldName,
+                "q", "id:10"),
+            "//*[@numFound='1']",
+            "//doc/float[@name='func' and .=10]");
+    assertQ(req("fl", "id,func:currency($f)",
+                "f", fieldName,
+                "q", "id:5"),
+            "//*[@numFound='1']",
+            "//doc/float[@name='func' and .=5]");
+    assertQ(req("fl", "id,score"+fieldName, 
+                "f", fieldName,
+                "q", "{!frange u=5}currency($f)")
+            ,"//*[@numFound='5']"
+            ,"//str[@name='id']='1'"
+            ,"//str[@name='id']='2'"
+            ,"//str[@name='id']='3'"
+            ,"//str[@name='id']='4'"
+            ,"//str[@name='id']='5'"
+            );
+    assertQ(req("fl", "id,score"+fieldName, 
+                "f", fieldName,
+                "q", "{!frange l=5 u=10}currency($f)")
+            ,"//*[@numFound='6']"
+            ,"//str[@name='id']='5'"
+            ,"//str[@name='id']='6'"
+            ,"//str[@name='id']='7'"
+            ,"//str[@name='id']='8'"
+            ,"//str[@name='id']='9'"
+            ,"//str[@name='id']='10'"
+            );
+    
+    // use the currency function to convert to MXN
+    assertQ(req("fl", "id,func:currency($f,MXN)",
+                "f", fieldName,
+                "q", "id:5"),
+            "//*[@numFound='1']",
+            "//doc/float[@name='func' and .=10]");
+    assertQ(req("fl", "id,func:currency($f,MXN)",
+                "f", fieldName,
+                "q", "id:10"),
+            "//*[@numFound='1']",
+            "//doc/float[@name='func' and .=20]");
+    assertQ(req("fl", "*,score,"+fieldName, 
+                "f", fieldName,
+                "q", "{!frange u=10}currency($f,MXN)")
+            ,"//*[@numFound='5']"
+            ,"//str[@name='id']='1'"
+            ,"//str[@name='id']='2'"
+            ,"//str[@name='id']='3'"
+            ,"//str[@name='id']='4'"
+            ,"//str[@name='id']='5'"
+            );
+    assertQ(req("fl", "*,score,"+fieldName, 
+                "f", fieldName,
+                "q", "{!frange l=10 u=20}currency($f,MXN)")
+            ,"//*[@numFound='6']"
+            ,"//str[@name='id']='5'"
+            ,"//str[@name='id']='6'"
+            ,"//str[@name='id']='7'"
+            ,"//str[@name='id']='8'"
+            ,"//str[@name='id']='9'"
+            ,"//str[@name='id']='10'"
+            );
+
+  }
+
+  @Test
+  public void testMockFieldType() throws Exception {
+    assumeTrue("This test is only applicable to the mock exchange rate provider",
+        expectedProviderClass.equals(MockExchangeRateProvider.class));
+
+    clearIndex();
+
+    assertU(adoc("id", "1", fieldName, "1.00,USD"));
+    assertU(adoc("id", "2", fieldName, "1.00,EUR"));
+    assertU(adoc("id", "3", fieldName, "1.00,NOK"));
+    assertU(commit());
+
+    assertQ(req("fl", "*,score", "q", fieldName+":5.0,NOK"),   "//*[@numFound='1']", "//str[@name='id']='1'");
+    assertQ(req("fl", "*,score", "q", fieldName+":1.2,USD"), "//*[@numFound='1']",   "//str[@name='id']='2'");
+    assertQ(req("fl", "*,score", "q", fieldName+":0.2,USD"), "//*[@numFound='1']",   "//str[@name='id']='3'");
+    assertQ(req("fl", "*,score", "q", fieldName+":99,USD"),  "//*[@numFound='0']");
+  }
+
+  @Test
+  public void testAsymmetricPointQuery() throws Exception {
+    assumeTrue("This test is only applicable to the XML file based exchange rate provider",
+        expectedProviderClass.equals(FileExchangeRateProvider.class));
+
+    clearIndex();
+    assertU(adoc("id", "" + 1, fieldName, "10.00,USD"));
+    assertU(adoc("id", "" + 2, fieldName, "15.00,EUR"));
+    assertU(commit());
+
+    assertQ(req("fl", "*,score", "q", fieldName+":15.00,EUR"), "//str[@name='id']='2'");
+    assertQ(req("fl", "*,score", "q", fieldName+":7.50,USD"), "//str[@name='id']='2'");
+    assertQ(req("fl", "*,score", "q", fieldName+":7.49,USD"), "//*[@numFound='0']");
+    assertQ(req("fl", "*,score", "q", fieldName+":7.51,USD"), "//*[@numFound='0']");
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test/org/apache/solr/schema/CurrencyFieldXmlFileTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/schema/CurrencyFieldXmlFileTest.java b/solr/core/src/test/org/apache/solr/schema/CurrencyFieldXmlFileTest.java
deleted file mode 100644
index e8bffe7..0000000
--- a/solr/core/src/test/org/apache/solr/schema/CurrencyFieldXmlFileTest.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.schema;
-import org.junit.Test;
-
-/**
- * Tests currency field type using FileExchangeRateProvider
- */
-public class CurrencyFieldXmlFileTest extends AbstractCurrencyFieldTest {
-
-  public String field() {
-    return "amount";
-  }
-
-  @Test
-  public void testAsymetricPointQuery() throws Exception {
-    clearIndex();
-    assertU(adoc("id", "" + 1, field(), "10.00,USD"));
-    assertU(adoc("id", "" + 2, field(), "15.00,EUR"));
-    assertU(commit());
-
-    assertQ(req("fl", "*,score", "q", field()+":15.00,EUR"), "//str[@name='id']='2'");
-    assertQ(req("fl", "*,score", "q", field()+":7.50,USD"), "//str[@name='id']='2'");
-    assertQ(req("fl", "*,score", "q", field()+":7.49,USD"), "//*[@numFound='0']");
-    assertQ(req("fl", "*,score", "q", field()+":7.51,USD"), "//*[@numFound='0']");
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/test/org/apache/solr/schema/OpenExchangeRatesOrgProviderTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/schema/OpenExchangeRatesOrgProviderTest.java b/solr/core/src/test/org/apache/solr/schema/OpenExchangeRatesOrgProviderTest.java
index ce44c38..972ebe6 100644
--- a/solr/core/src/test/org/apache/solr/schema/OpenExchangeRatesOrgProviderTest.java
+++ b/solr/core/src/test/org/apache/solr/schema/OpenExchangeRatesOrgProviderTest.java
@@ -40,7 +40,7 @@ public class OpenExchangeRatesOrgProviderTest extends SolrTestCaseJ4 {
   @Override
   @Before
   public void setUp() throws Exception {
-    AbstractCurrencyFieldTest.assumeCurrencySupport
+    CurrencyFieldTypeTest.assumeCurrencySupport
       ("USD", "EUR", "MXN", "GBP", "JPY");
 
     super.setUp();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/example/example-DIH/solr/db/conf/managed-schema
----------------------------------------------------------------------
diff --git a/solr/example/example-DIH/solr/db/conf/managed-schema b/solr/example/example-DIH/solr/db/conf/managed-schema
index de138ba..b96ff4c 100644
--- a/solr/example/example-DIH/solr/db/conf/managed-schema
+++ b/solr/example/example-DIH/solr/db/conf/managed-schema
@@ -202,8 +202,10 @@
    <dynamicField name="*_i"  type="int"    indexed="true"  stored="true"/>
    <dynamicField name="*_is" type="int"    indexed="true"  stored="true"  multiValued="true"/>
    <dynamicField name="*_s"  type="string"  indexed="true"  stored="true" />
+   <dynamicField name="*_s_ns"  type="string"  indexed="true"  stored="false" />
    <dynamicField name="*_ss" type="string"  indexed="true"  stored="true" multiValued="true"/>
    <dynamicField name="*_l"  type="long"   indexed="true"  stored="true"/>
+   <dynamicField name="*_l_ns"  type="long"   indexed="true"  stored="false"/>
    <dynamicField name="*_ls" type="long"   indexed="true"  stored="true"  multiValued="true"/>
    <dynamicField name="*_t"  type="text_general"    indexed="true"  stored="true"/>
    <dynamicField name="*_txt" type="text_general"   indexed="true"  stored="true" multiValued="true"/>
@@ -703,17 +705,22 @@
 
    <!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
         Parameters:
-          defaultCurrency: Specifies the default currency if none specified. Defaults to "USD"
-          precisionStep:   Specifies the precisionStep for the TrieLong field used for the amount
-          providerClass:   Lets you plug in other exchange provider backend:
-                           solr.FileExchangeRateProvider is the default and takes one parameter:
-                             currencyConfig: name of an xml file holding exchange rates
-                           solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
-                             ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
-                             refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
+          amountLongSuffix: Required. Refers to a dynamic field for the raw amount sub-field. 
+                              The dynamic field must have a field type that extends LongValueFieldType.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          codeStrSuffix:    Required. Refers to a dynamic field for the currency code sub-field.
+                              The dynamic field must have a field type that extends StrField.
+                              Note: If you expect to use Atomic Updates, this dynamic field may not be stored.
+          defaultCurrency:  Specifies the default currency if none specified. Defaults to "USD"
+          providerClass:    Lets you plug in other exchange provider backend:
+                            solr.FileExchangeRateProvider is the default and takes one parameter:
+                              currencyConfig: name of an xml file holding exchange rates
+                            solr.OpenExchangeRatesOrgProvider uses rates from openexchangerates.org:
+                              ratesFileLocation: URL or path to rates JSON file (default latest.json on the web)
+                              refreshInterval: Number of minutes between each rates fetch (default: 1440, min: 60)
    -->
-    <fieldType name="currency" class="solr.CurrencyField" precisionStep="8" defaultCurrency="USD" currencyConfig="currency.xml" />
-             
+    <fieldType name="currency" class="solr.CurrencyFieldType" amountLongSuffix="_l_ns" codeStrSuffix="_s_ns"
+               defaultCurrency="USD" currencyConfig="currency.xml" />
 
 
    <!-- some examples for different languages (generally ordered by ISO code) -->


[3/6] lucene-solr:branch_6x: SOLR-10503, SOLR-10502: Deprecate CurrencyField in favor of new CurrencyFieldType, which works with point fields and provides control over dynamic fields used for the raw amount and currency code sub-fields.

Posted by sa...@apache.org.
SOLR-10503,SOLR-10502: Deprecate CurrencyField in favor of new CurrencyFieldType, which works with point fields and provides control over dynamic fields used for the raw amount and currency code sub-fields.

 Conflicts:
	solr/core/src/java/org/apache/solr/schema/CurrencyField.java
	solr/core/src/test-files/solr/collection1/conf/schema.xml


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/5232b2ce
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/5232b2ce
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/5232b2ce

Branch: refs/heads/branch_6x
Commit: 5232b2ce5a6c92f57a75031ffb273c799ab7f5c7
Parents: 2ad8a6a
Author: Steve Rowe <sa...@apache.org>
Authored: Thu Jun 22 21:22:42 2017 -0400
Committer: Steve Rowe <sa...@apache.org>
Committed: Fri Jun 23 10:14:11 2017 -0400

----------------------------------------------------------------------
 solr/CHANGES.txt                                |    6 +
 .../org/apache/solr/schema/CurrencyField.java   | 1001 +-----------------
 .../apache/solr/schema/CurrencyFieldType.java   |  831 +++++++++++++++
 .../solr/schema/FileExchangeRateProvider.java   |  252 +++++
 .../schema/OpenExchangeRatesOrgProvider.java    |    2 +-
 .../apache/solr/search/ValueSourceParser.java   |    8 +-
 .../bad-schema-currency-ft-amount-suffix.xml    |   34 +
 .../conf/bad-schema-currency-ft-code-suffix.xml |   33 +
 .../conf/bad-schema-currency-ft-oer-norates.xml |    2 +-
 ...ma-currencyfieldtype-bogus-amount-suffix.xml |   34 +
 ...hema-currencyfieldtype-bogus-code-suffix.xml |   35 +
 ...ma-currencyfieldtype-dynamic-multivalued.xml |   36 +
 ...a-currencyfieldtype-ft-bogus-code-in-xml.xml |   41 +
 ...-currencyfieldtype-ft-bogus-default-code.xml |   41 +
 ...-schema-currencyfieldtype-ft-multivalued.xml |   36 +
 ...-schema-currencyfieldtype-ft-oer-norates.xml |   40 +
 ...-currencyfieldtype-missing-amount-suffix.xml |   34 +
 ...ma-currencyfieldtype-missing-code-suffix.xml |   35 +
 ...bad-schema-currencyfieldtype-multivalued.xml |   36 +
 ...schema-currencyfieldtype-wrong-amount-ft.xml |   36 +
 ...d-schema-currencyfieldtype-wrong-code-ft.xml |   35 +
 .../test-files/solr/collection1/conf/schema.xml |   16 +
 .../solr/schema/AbstractCurrencyFieldTest.java  |  426 --------
 .../apache/solr/schema/BadIndexSchemaTest.java  |   41 +-
 .../schema/CurrencyFieldOpenExchangeTest.java   |   27 -
 .../solr/schema/CurrencyFieldTypeTest.java      |  494 +++++++++
 .../solr/schema/CurrencyFieldXmlFileTest.java   |   42 -
 .../OpenExchangeRatesOrgProviderTest.java       |    2 +-
 .../example-DIH/solr/db/conf/managed-schema     |   27 +-
 .../example-DIH/solr/mail/conf/managed-schema   |   27 +-
 .../example-DIH/solr/solr/conf/currency.xml     |    2 +-
 .../example-DIH/solr/solr/conf/managed-schema   |   27 +-
 solr/example/files/conf/managed-schema          |    4 +-
 .../basic_configs/conf/managed-schema           |   31 +-
 .../conf/currency.xml                           |    2 +-
 .../conf/managed-schema                         |   31 +-
 .../conf/managed-schema                         |   27 +-
 .../src/field-types-included-with-solr.adoc     |    3 +-
 .../src/updating-parts-of-documents.adoc        |    2 +-
 ...king-with-currencies-and-exchange-rates.adoc |   34 +-
 40 files changed, 2333 insertions(+), 1540 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 2403042..094f680 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -41,6 +41,8 @@ Upgrade Notes
 
 * SOLR-10379: ManagedSynonymFilterFactory has been deprecated in favor of ManagedSynonymGraphFilterFactory.
 
+* SOLR-10503: CurrencyField has been deprecated in favor of new CurrencyFieldType.
+
 New Features
 ----------------------
 
@@ -188,6 +190,10 @@ Other Changes
   rendered visibly in the PDF.  Also add .adoc file checks to the top-level validate target, including
   for the invisible substitutions PDF problem.  (Steve Rowe)
 
+* SOLR-10503,SOLR-10502: Deprecate CurrencyField in favor of new CurrencyFieldType, which works
+  with point fields and provides control over dynamic fields used for the raw amount and currency
+  code sub-fields.  (hossman, Steve Rowe)
+
 ==================  6.6.1 ==================
 
 Bug Fixes

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/java/org/apache/solr/schema/CurrencyField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/CurrencyField.java b/solr/core/src/java/org/apache/solr/schema/CurrencyField.java
index 0b31123..e2676fe 100644
--- a/solr/core/src/java/org/apache/solr/schema/CurrencyField.java
+++ b/solr/core/src/java/org/apache/solr/schema/CurrencyField.java
@@ -14,191 +14,74 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.solr.schema;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.invoke.MethodHandles;
+
 import java.util.ArrayList;
-import java.util.Currency;
+import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
-
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathExpressionException;
-import javax.xml.xpath.XPathFactory;
 
-import org.apache.lucene.analysis.util.ResourceLoader;
 import org.apache.lucene.analysis.util.ResourceLoaderAware;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.IndexableField;
-import org.apache.lucene.queries.function.FunctionValues;
-import org.apache.lucene.queries.function.ValueSource;
-import org.apache.lucene.search.BooleanClause.Occur;
-import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.FieldValueQuery;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.SortField;
-import org.apache.solr.uninverting.UninvertingReader.Type;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
-import org.apache.solr.response.TextResponseWriter;
-import org.apache.solr.search.Filter;
-import org.apache.solr.search.QParser;
-import org.apache.solr.search.QueryWrapperFilter;
-import org.apache.solr.search.SolrConstantScoreQuery;
-import org.apache.solr.search.function.ValueSourceRangeFilter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.w3c.dom.Document;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.SAXException;
+
 
 /**
  * Field type for support of monetary values.
  * <p>
  * See <a href="http://wiki.apache.org/solr/CurrencyField">http://wiki.apache.org/solr/CurrencyField</a>
+ * @deprecated Use {@link CurrencyFieldType}
  */
-public class CurrencyField extends FieldType implements SchemaAware, ResourceLoaderAware {
-  protected static final String PARAM_DEFAULT_CURRENCY      = "defaultCurrency";
-  protected static final String PARAM_RATE_PROVIDER_CLASS   = "providerClass";
-  protected static final Object PARAM_PRECISION_STEP        = "precisionStep";
-  protected static final String DEFAULT_RATE_PROVIDER_CLASS = "solr.FileExchangeRateProvider";
-  protected static final String DEFAULT_DEFAULT_CURRENCY    = "USD";
-  protected static final String DEFAULT_PRECISION_STEP      = "0";
-  protected static final String FIELD_SUFFIX_AMOUNT_RAW     = "_amount_raw";
-  protected static final String FIELD_SUFFIX_CURRENCY       = "_currency";
-
-  private IndexSchema schema;
-  protected FieldType fieldTypeCurrency;
-  protected FieldType fieldTypeAmountRaw;
-  private String exchangeRateProviderClass;
-  private String defaultCurrency;
-  private ExchangeRateProvider provider;
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  /**
-   * A wrapper arround <code>Currency.getInstance</code> that returns null
-   * instead of throwing <code>IllegalArgumentException</code>
-   * if the specified Currency does not exist in this JVM.
-   *
-   * @see Currency#getInstance(String)
-   */
-  public static Currency getCurrency(final String code) {
-    try {
-      return Currency.getInstance(code);
-    } catch (IllegalArgumentException e) {
-      /* :NOOP: */
-    }
-    return null;
-  }
+@Deprecated
+public class CurrencyField extends CurrencyFieldType implements SchemaAware, ResourceLoaderAware {
+  protected static final String FIELD_SUFFIX_AMOUNT_RAW = "_amount_raw";
+  protected static final String FIELD_SUFFIX_CURRENCY = "_currency";
+  protected static final String FIELD_TYPE_AMOUNT_RAW = "amount_raw_type_long";
+  protected static final String FIELD_TYPE_CURRENCY = "currency_type_string";
+  protected static final String PARAM_PRECISION_STEP = "precisionStep";
+  protected static final String DEFAULT_PRECISION_STEP = "0";
 
   @Override
   protected void init(IndexSchema schema, Map<String, String> args) {
-    super.init(schema, args);
-    if (this.isMultiValued()) { 
-      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, 
-                              "CurrencyField types can not be multiValued: " + 
-                              this.typeName);
-    }
-    this.schema = schema;
-    this.exchangeRateProviderClass = args.get(PARAM_RATE_PROVIDER_CLASS);
-    this.defaultCurrency = args.get(PARAM_DEFAULT_CURRENCY);
-
-    if (this.defaultCurrency == null) {
-      this.defaultCurrency = DEFAULT_DEFAULT_CURRENCY;
-    }
     
-    if (this.exchangeRateProviderClass == null) {
-      this.exchangeRateProviderClass = DEFAULT_RATE_PROVIDER_CLASS;
+    // Fail if amountLongSuffix or codeStrSuffix are specified
+    List<String> unknownParams = new ArrayList<>();
+    fieldSuffixAmountRaw = args.get(PARAM_FIELD_SUFFIX_AMOUNT_RAW);
+    if (fieldSuffixAmountRaw != null) {
+      unknownParams.add(PARAM_FIELD_SUFFIX_AMOUNT_RAW); 
     }
-
-    if (null == getCurrency(this.defaultCurrency)) {
-      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Default currency code is not supported by this JVM: " + this.defaultCurrency);
+    fieldSuffixCurrency = args.get(PARAM_FIELD_SUFFIX_CURRENCY);
+    if (fieldSuffixCurrency != null) {
+      unknownParams.add(PARAM_FIELD_SUFFIX_CURRENCY);
     }
-
+    if ( ! unknownParams.isEmpty()) {
+      throw new SolrException(ErrorCode.SERVER_ERROR, "Unknown parameter(s): " + unknownParams);
+    }
+    
     String precisionStepString = args.get(PARAM_PRECISION_STEP);
     if (precisionStepString == null) {
       precisionStepString = DEFAULT_PRECISION_STEP;
+    } else {
+      args.remove(PARAM_PRECISION_STEP);
     }
 
     // Initialize field type for amount
     fieldTypeAmountRaw = new TrieLongField();
-    fieldTypeAmountRaw.setTypeName("amount_raw_type_tlong");
+    fieldTypeAmountRaw.setTypeName(FIELD_TYPE_AMOUNT_RAW);
     Map<String,String> map = new HashMap<>(1);
     map.put("precisionStep", precisionStepString);
     fieldTypeAmountRaw.init(schema, map);
-    
+    fieldSuffixAmountRaw = FIELD_SUFFIX_AMOUNT_RAW;
+
     // Initialize field type for currency string
     fieldTypeCurrency = new StrField();
-    fieldTypeCurrency.setTypeName("currency_type_string");
-    fieldTypeCurrency.init(schema, new HashMap<String,String>());
-    
-    args.remove(PARAM_RATE_PROVIDER_CLASS);
-    args.remove(PARAM_DEFAULT_CURRENCY);
-    args.remove(PARAM_PRECISION_STEP);
-
-    try {
-      Class<? extends ExchangeRateProvider> c = schema.getResourceLoader().findClass(exchangeRateProviderClass, ExchangeRateProvider.class);
-      provider = c.newInstance();
-      provider.init(args);
-    } catch (Exception e) {
-      throw new SolrException(ErrorCode.BAD_REQUEST, "Error instantiating exchange rate provider "+exchangeRateProviderClass+": " + e.getMessage(), e);
-    }
-  }
-
-  @Override
-  public boolean isPolyField() {
-    return true;
-  }
-
-  @Override
-  public void checkSchemaField(final SchemaField field) throws SolrException {
-    super.checkSchemaField(field);
-    if (field.multiValued()) {
-      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, 
-                              "CurrencyFields can not be multiValued: " + 
-                              field.getName());
-    }
-  }
-
-  @Override
-  public List<IndexableField> createFields(SchemaField field, Object externalVal, float boost) {
-    CurrencyValue value = CurrencyValue.parse(externalVal.toString(), defaultCurrency);
-
-    List<IndexableField> f = new ArrayList<>();
-    SchemaField amountField = getAmountField(field);
-    f.add(amountField.createField(String.valueOf(value.getAmount()), amountField.indexed() && !amountField.omitNorms() ? boost : 1F));
-    SchemaField currencyField = getCurrencyField(field);
-    f.add(currencyField.createField(value.getCurrencyCode(), currencyField.indexed() && !currencyField.omitNorms() ? boost : 1F));
-
-    if (field.stored()) {
-      org.apache.lucene.document.FieldType customType = new org.apache.lucene.document.FieldType();
-      assert !customType.omitNorms();
-      customType.setStored(true);
-      String storedValue = externalVal.toString().trim();
-      if (storedValue.indexOf(",") < 0) {
-        storedValue += "," + defaultCurrency;
-      }
-      f.add(createField(field.getName(), storedValue, customType, 1F));
-    }
-
-    return f;
-  }
-
-  private SchemaField getAmountField(SchemaField field) {
-    return schema.getField(field.getName() + POLY_FIELD_SEPARATOR + FIELD_SUFFIX_AMOUNT_RAW);
-  }
+    fieldTypeCurrency.setTypeName(FIELD_TYPE_CURRENCY);
+    fieldTypeCurrency.init(schema, Collections.emptyMap());
+    fieldSuffixCurrency = FIELD_SUFFIX_CURRENCY;
 
-  private SchemaField getCurrencyField(SchemaField field) {
-    return schema.getField(field.getName() + POLY_FIELD_SEPARATOR + FIELD_SUFFIX_CURRENCY);
+    super.init(schema, args); // Must be called last so that field types are not doubly created
   }
 
   private void createDynamicCurrencyField(String suffix, FieldType type) {
@@ -214,825 +97,15 @@ public class CurrencyField extends FieldType implements SchemaAware, ResourceLoa
 
   /**
    * When index schema is informed, add dynamic fields "*____currency" and "*____amount_raw". 
-   * 
+   *
    * {@inheritDoc}
-   * 
+   *
    * @param schema {@inheritDoc}
    */
   @Override
   public void inform(IndexSchema schema) {
-    this.schema = schema;
     createDynamicCurrencyField(FIELD_SUFFIX_CURRENCY,   fieldTypeCurrency);
     createDynamicCurrencyField(FIELD_SUFFIX_AMOUNT_RAW, fieldTypeAmountRaw);
-  }
-
-  /**
-   * Load the currency config when resource loader initialized.
-   *
-   * @param resourceLoader The resource loader.
-   */
-  @Override
-  public void inform(ResourceLoader resourceLoader) {
-    provider.inform(resourceLoader);
-    boolean reloaded = provider.reload();
-    if(!reloaded) {
-      log.warn("Failed reloading currencies");
-    }
-  }
-
-  @Override
-  public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) {
-    CurrencyValue value = CurrencyValue.parse(externalVal, defaultCurrency);
-    CurrencyValue valueDefault;
-    valueDefault = value.convertTo(provider, defaultCurrency);
-
-    return getRangeQuery(parser, field, valueDefault, valueDefault, true, true);
-  }
-
-  /**
-   * <p>
-   * Returns a ValueSource over this field in which the numeric value for 
-   * each document represents the indexed value as converted to the default 
-   * currency for the field, normalized to its most granular form based 
-   * on the default fractional digits.
-   * </p>
-   * <p>
-   * For example: If the default Currency specified for a field is 
-   * <code>USD</code>, then the values returned by this value source would 
-   * represent the equivilent number of "cents" (ie: value in dollars * 100) 
-   * after converting each document's native currency to USD -- because the 
-   * default fractional digits for <code>USD</code> is "<code>2</code>".  
-   * So for a document whose indexed value was currently equivilent to 
-   * "<code>5.43,USD</code>" using the the exchange provider for this field, 
-   * this ValueSource would return a value of "<code>543</code>"
-   * </p>
-   *
-   * @see #PARAM_DEFAULT_CURRENCY
-   * @see #DEFAULT_DEFAULT_CURRENCY
-   * @see Currency#getDefaultFractionDigits
-   * @see #getConvertedValueSource
-   */
-  public RawCurrencyValueSource getValueSource(SchemaField field, 
-                                               QParser parser) {
-    getAmountField(field).checkFieldCacheSource();
-    getCurrencyField(field).checkFieldCacheSource();
-    return new RawCurrencyValueSource(field, defaultCurrency, parser);
-  }
-
-  /**
-   * <p>
-   * Returns a ValueSource over this field in which the numeric value for 
-   * each document represents the value from the underlying 
-   * <code>RawCurrencyValueSource</code> as converted to the specified target 
-   * Currency.
-   * </p>
-   * <p>
-   * For example: If the <code>targetCurrencyCode</code> param is set to
-   * <code>USD</code>, then the values returned by this value source would 
-   * represent the equivilent number of dollars after converting each 
-   * document's raw value to <code>USD</code>.  So for a document whose 
-   * indexed value was currently equivilent to "<code>5.43,USD</code>" 
-   * using the the exchange provider for this field, this ValueSource would 
-   * return a value of "<code>5.43</code>"
-   * </p>
-   *
-   * @param targetCurrencyCode The target currency for the resulting value source, if null the defaultCurrency for this field type will be used
-   * @param source the raw ValueSource to wrap
-   * @see #PARAM_DEFAULT_CURRENCY
-   * @see #DEFAULT_DEFAULT_CURRENCY
-   * @see #getValueSource
-   */
-  public ValueSource getConvertedValueSource(String targetCurrencyCode, 
-                                             RawCurrencyValueSource source) {
-    if (null == targetCurrencyCode) { 
-      targetCurrencyCode = defaultCurrency; 
-    }
-    return new ConvertedCurrencyValueSource(targetCurrencyCode, 
-                                            source);
-  }
-
-  @Override
-  public Query getRangeQuery(QParser parser, SchemaField field, String part1, String part2, final boolean minInclusive, final boolean maxInclusive) {
-      final CurrencyValue p1 = CurrencyValue.parse(part1, defaultCurrency);
-      final CurrencyValue p2 = CurrencyValue.parse(part2, defaultCurrency);
-
-      if (p1 != null && p2 != null && !p1.getCurrencyCode().equals(p2.getCurrencyCode())) {
-          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
-                  "Cannot parse range query " + part1 + " to " + part2 +
-                          ": range queries only supported when upper and lower bound have same currency.");
-      }
-
-      return getRangeQuery(parser, field, p1, p2, minInclusive, maxInclusive);
-  }
-
-  public Query getRangeQuery(QParser parser, SchemaField field, final CurrencyValue p1, final CurrencyValue p2, final boolean minInclusive, final boolean maxInclusive) {
-    String currencyCode = (p1 != null) ? p1.getCurrencyCode() :
-                          (p2 != null) ? p2.getCurrencyCode() : defaultCurrency;
-
-    // ValueSourceRangeFilter doesn't check exists(), so we have to
-    final Filter docsWithValues = new QueryWrapperFilter(new FieldValueQuery(getAmountField(field).getName()));
-    final Filter vsRangeFilter = new ValueSourceRangeFilter
-      (new RawCurrencyValueSource(field, currencyCode, parser),
-       p1 == null ? null : p1.getAmount() + "", 
-       p2 == null ? null : p2.getAmount() + "",
-       minInclusive, maxInclusive);
-    final BooleanQuery.Builder docsInRange = new BooleanQuery.Builder();
-    docsInRange.add(docsWithValues, Occur.FILTER);
-    docsInRange.add(vsRangeFilter, Occur.FILTER);
-
-    return new SolrConstantScoreQuery(new QueryWrapperFilter(docsInRange.build()));
-  }
-
-  @Override
-  public SortField getSortField(SchemaField field, boolean reverse) {
-    // Convert all values to default currency for sorting.
-    return (new RawCurrencyValueSource(field, defaultCurrency, null)).getSortField(reverse);
-  }
-  
-  @Override
-  public Type getUninversionType(SchemaField sf) {
-    return null;
-  }
-
-  @Override
-  public void write(TextResponseWriter writer, String name, IndexableField field) throws IOException {
-    writer.writeStr(name, field.stringValue(), true);
-  }
-
-  public ExchangeRateProvider getProvider() {
-    return provider;
-  }
-
-  /**
-   * <p>
-   * A value source whose values represent the "normal" values
-   * in the specified target currency.
-   * </p>
-   * @see RawCurrencyValueSource
-   */
-  class ConvertedCurrencyValueSource extends ValueSource {
-    private final Currency targetCurrency;
-    private final RawCurrencyValueSource source;
-    private final double rate;
-    public ConvertedCurrencyValueSource(String targetCurrencyCode, 
-                                        RawCurrencyValueSource source) {
-      this.source = source;
-      this.targetCurrency = getCurrency(targetCurrencyCode);
-      if (null == targetCurrency) {
-        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Currency code not supported by this JVM: " + targetCurrencyCode);
-      }
-      // the target digits & currency of our source, 
-      // become the source digits & currency of ourselves
-      this.rate = provider.getExchangeRate
-        (source.getTargetCurrency().getCurrencyCode(), 
-         targetCurrency.getCurrencyCode());
-    }
-
-    @Override
-    public FunctionValues getValues(Map context, LeafReaderContext reader) 
-      throws IOException {
-      final FunctionValues amounts = source.getValues(context, reader);
-      // the target digits & currency of our source, 
-      // become the source digits & currency of ourselves
-      final String sourceCurrencyCode = source.getTargetCurrency().getCurrencyCode();
-      final int sourceFractionDigits = source.getTargetCurrency().getDefaultFractionDigits();
-      final double divisor = Math.pow(10D, targetCurrency.getDefaultFractionDigits());
-      return new FunctionValues() {
-        @Override
-        public boolean exists(int doc) {
-          return amounts.exists(doc);
-        }
-        @Override
-        public long longVal(int doc) {
-          return (long) doubleVal(doc);
-        }
-        @Override
-        public int intVal(int doc) {
-          return (int) doubleVal(doc);
-        }
-
-        @Override
-        public double doubleVal(int doc) {
-          return CurrencyValue.convertAmount(rate, sourceCurrencyCode, amounts.longVal(doc), targetCurrency.getCurrencyCode()) / divisor;
-        }
-
-        @Override
-        public float floatVal(int doc) {
-          return CurrencyValue.convertAmount(rate, sourceCurrencyCode, amounts.longVal(doc), targetCurrency.getCurrencyCode()) / ((float)divisor);
-        }
-
-        @Override
-        public String strVal(int doc) {
-          return Double.toString(doubleVal(doc));
-        }
-
-        @Override
-        public String toString(int doc) {
-          return name() + '(' + strVal(doc) + ')';
-        }
-      };
-    }
-    public String name() {
-      return "currency";
-    }
-
-    @Override
-    public String description() {
-      return name() + "(" + source.getField().getName() + "," + targetCurrency.getCurrencyCode()+")";
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) return true;
-      if (o == null || getClass() != o.getClass()) return false;
-
-      ConvertedCurrencyValueSource that = (ConvertedCurrencyValueSource) o;
-
-      return !(source != null ? !source.equals(that.source) : that.source != null) &&
-        (rate == that.rate) && 
-        !(targetCurrency != null ? !targetCurrency.equals(that.targetCurrency) : that.targetCurrency != null);
-
-    }
-
-    @Override
-    public int hashCode() {
-      int result = targetCurrency != null ? targetCurrency.hashCode() : 0;
-      result = 31 * result + (source != null ? source.hashCode() : 0);
-      result = 31 * (int) Double.doubleToLongBits(rate);
-      return result;
-    }
-  }
-
-  /**
-   * <p>
-   * A value source whose values represent the "raw" (ie: normalized using 
-   * the number of default fractional digits) values in the specified 
-   * target currency).
-   * </p>
-   * <p>
-   * For example: if the specified target currency is "<code>USD</code>" 
-   * then the numeric values are the number of pennies in the value 
-   * (ie: <code>$n * 100</code>) since the number of defalt fractional 
-   * digits for <code>USD</code> is "<code>2</code>")
-   * </p>
-   * @see ConvertedCurrencyValueSource
-   */
-  class RawCurrencyValueSource extends ValueSource {
-    private static final long serialVersionUID = 1L;
-    private final Currency targetCurrency;
-    private ValueSource currencyValues;
-    private ValueSource amountValues;
-    private final SchemaField sf;
-
-    public RawCurrencyValueSource(SchemaField sfield, String targetCurrencyCode, QParser parser) {
-      this.sf = sfield;
-      this.targetCurrency = getCurrency(targetCurrencyCode);
-      if (null == targetCurrency) {
-        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Currency code not supported by this JVM: " + targetCurrencyCode);
-      }
-
-      SchemaField amountField = getAmountField(sf);
-      SchemaField currencyField = getCurrencyField(sf);
-
-      currencyValues = currencyField.getType().getValueSource(currencyField, parser);
-      amountValues = amountField.getType().getValueSource(amountField, parser);
-    }
-    
-    public SchemaField getField() { return sf; }
-    public Currency getTargetCurrency() { return targetCurrency; }
-
-    @Override
-    public FunctionValues getValues(Map context, LeafReaderContext reader) throws IOException {
-      final FunctionValues amounts = amountValues.getValues(context, reader);
-      final FunctionValues currencies = currencyValues.getValues(context, reader);
-
-      return new FunctionValues() {
-        private final int MAX_CURRENCIES_TO_CACHE = 256;
-        private final int[] fractionDigitCache = new int[MAX_CURRENCIES_TO_CACHE];
-        private final String[] currencyOrdToCurrencyCache = new String[MAX_CURRENCIES_TO_CACHE];
-        private final double[] exchangeRateCache = new double[MAX_CURRENCIES_TO_CACHE];
-        private int targetFractionDigits = -1;
-        private int targetCurrencyOrd = -1;
-        private boolean initializedCache;
-
-        private String getDocCurrencyCode(int doc, int currencyOrd) {
-          if (currencyOrd < MAX_CURRENCIES_TO_CACHE) {
-            String currency = currencyOrdToCurrencyCache[currencyOrd];
-
-            if (currency == null) {
-              currencyOrdToCurrencyCache[currencyOrd] = currency = currencies.strVal(doc);
-            }
-            
-            if (currency == null) {
-              currency = defaultCurrency;
-            }
-
-            if (targetCurrencyOrd == -1 && 
-                currency.equals(targetCurrency.getCurrencyCode() )) {
-              targetCurrencyOrd = currencyOrd;
-            }
-
-            return currency;
-          } else {
-            return currencies.strVal(doc);
-          }
-        }
-        /** throws a (Server Error) SolrException if the code is not valid */
-        private Currency getDocCurrency(int doc, int currencyOrd) {
-          String code = getDocCurrencyCode(doc, currencyOrd);
-          Currency c = getCurrency(code);
-          if (null == c) {
-            throw new SolrException
-              (SolrException.ErrorCode.SERVER_ERROR, 
-               "Currency code of document is not supported by this JVM: "+code);
-          }
-          return c;
-        }
-
-        @Override
-        public boolean exists(int doc) {
-          return amounts.exists(doc);
-        }
-        
-        @Override
-        public long longVal(int doc) {
-          long amount = amounts.longVal(doc);
-          // bail fast using whatever amounts defaults to if no value
-          // (if we don't do this early, currencyOrd may be < 0, 
-          // causing index bounds exception
-          if ( ! exists(doc) ) {
-            return amount;
-          }
-
-          if (!initializedCache) {
-            for (int i = 0; i < fractionDigitCache.length; i++) {
-              fractionDigitCache[i] = -1;
-            }
-
-            initializedCache = true;
-          }
-
-          int currencyOrd = currencies.ordVal(doc);
-
-          if (currencyOrd == targetCurrencyOrd) {
-            return amount;
-          }
-
-          double exchangeRate;
-          int sourceFractionDigits;
-
-          if (targetFractionDigits == -1) {
-            targetFractionDigits = targetCurrency.getDefaultFractionDigits();
-          }
-
-          if (currencyOrd < MAX_CURRENCIES_TO_CACHE) {
-            exchangeRate = exchangeRateCache[currencyOrd];
-
-            if (exchangeRate <= 0.0) {
-              String sourceCurrencyCode = getDocCurrencyCode(doc, currencyOrd);
-              exchangeRate = exchangeRateCache[currencyOrd] = provider.getExchangeRate(sourceCurrencyCode, targetCurrency.getCurrencyCode());
-            }
-
-            sourceFractionDigits = fractionDigitCache[currencyOrd];
-
-            if (sourceFractionDigits == -1) {
-              sourceFractionDigits = fractionDigitCache[currencyOrd] = getDocCurrency(doc, currencyOrd).getDefaultFractionDigits();
-            }
-          } else {
-            Currency source = getDocCurrency(doc, currencyOrd);
-            exchangeRate = provider.getExchangeRate(source.getCurrencyCode(), targetCurrency.getCurrencyCode());
-            sourceFractionDigits = source.getDefaultFractionDigits();
-          }
-
-          return CurrencyValue.convertAmount(exchangeRate, sourceFractionDigits, amount, targetFractionDigits);
-        }
-
-        @Override
-        public int intVal(int doc) {
-          return (int) longVal(doc);
-        }
-
-        @Override
-        public double doubleVal(int doc) {
-          return (double) longVal(doc);
-        }
-
-        @Override
-        public float floatVal(int doc) {
-          return (float) longVal(doc);
-        }
-
-        @Override
-        public String strVal(int doc) {
-          return Long.toString(longVal(doc));
-        }
-
-        @Override
-        public String toString(int doc) {
-          return name() + '(' + amounts.toString(doc) + ',' + currencies.toString(doc) + ')';
-        }
-      };
-    }
-
-    public String name() {
-      return "rawcurrency";
-    }
-
-    @Override
-    public String description() {
-      return name() + "(" + sf.getName() + 
-        ",target="+targetCurrency.getCurrencyCode()+")";
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) return true;
-      if (o == null || getClass() != o.getClass()) return false;
-
-      RawCurrencyValueSource that = (RawCurrencyValueSource) o;
-
-      return !(amountValues != null ? !amountValues.equals(that.amountValues) : that.amountValues != null) &&
-              !(currencyValues != null ? !currencyValues.equals(that.currencyValues) : that.currencyValues != null) &&
-              !(targetCurrency != null ? !targetCurrency.equals(that.targetCurrency) : that.targetCurrency != null);
-
-    }
-
-    @Override
-    public int hashCode() {
-      int result = targetCurrency != null ? targetCurrency.hashCode() : 0;
-      result = 31 * result + (currencyValues != null ? currencyValues.hashCode() : 0);
-      result = 31 * result + (amountValues != null ? amountValues.hashCode() : 0);
-      return result;
-    }
-  }
-}
-
-/**
- * Configuration for currency. Provides currency exchange rates.
- */
-class FileExchangeRateProvider implements ExchangeRateProvider {
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-  protected static final String PARAM_CURRENCY_CONFIG       = "currencyConfig";
-
-  // Exchange rate map, maps Currency Code -> Currency Code -> Rate
-  private Map<String, Map<String, Double>> rates = new HashMap<>();
-
-  private String currencyConfigFile;
-  private ResourceLoader loader;
-
-  /**
-   * Returns the currently known exchange rate between two currencies. If a direct rate has been loaded,
-   * it is used. Otherwise, if a rate is known to convert the target currency to the source, the inverse
-   * exchange rate is computed.
-   *
-   * @param sourceCurrencyCode The source currency being converted from.
-   * @param targetCurrencyCode The target currency being converted to.
-   * @return The exchange rate.
-   * @throws SolrException if the requested currency pair cannot be found
-   */
-  @Override
-  public double getExchangeRate(String sourceCurrencyCode, String targetCurrencyCode) {
-    if (sourceCurrencyCode == null || targetCurrencyCode == null) {
-      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Cannot get exchange rate; currency was null.");
-    }
-    
-    if (sourceCurrencyCode.equals(targetCurrencyCode)) {
-      return 1.0;
-    }
-
-    Double directRate = lookupRate(sourceCurrencyCode, targetCurrencyCode);
-
-    if (directRate != null) {
-      return directRate;
-    }
-
-    Double symmetricRate = lookupRate(targetCurrencyCode, sourceCurrencyCode);
-
-    if (symmetricRate != null) {
-      return 1.0 / symmetricRate;
-    }
-
-    throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No available conversion rate between " + sourceCurrencyCode + " to " + targetCurrencyCode);
-  }
-
-  /**
-   * Looks up the current known rate, if any, between the source and target currencies.
-   *
-   * @param sourceCurrencyCode The source currency being converted from.
-   * @param targetCurrencyCode The target currency being converted to.
-   * @return The exchange rate, or null if no rate has been registered.
-   */
-  private Double lookupRate(String sourceCurrencyCode, String targetCurrencyCode) {
-    Map<String, Double> rhs = rates.get(sourceCurrencyCode);
-
-    if (rhs != null) {
-      return rhs.get(targetCurrencyCode);
-    }
-
-    return null;
-  }
-
-  /**
-   * Registers the specified exchange rate.
-   *
-   * @param ratesMap           The map to add rate to
-   * @param sourceCurrencyCode The source currency.
-   * @param targetCurrencyCode The target currency.
-   * @param rate               The known exchange rate.
-   */
-  private void addRate(Map<String, Map<String, Double>> ratesMap, String sourceCurrencyCode, String targetCurrencyCode, double rate) {
-    Map<String, Double> rhs = ratesMap.get(sourceCurrencyCode);
-
-    if (rhs == null) {
-      rhs = new HashMap<>();
-      ratesMap.put(sourceCurrencyCode, rhs);
-    }
-
-    rhs.put(targetCurrencyCode, rate);
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-
-    FileExchangeRateProvider that = (FileExchangeRateProvider) o;
-
-    return !(rates != null ? !rates.equals(that.rates) : that.rates != null);
-  }
-
-  @Override
-  public int hashCode() {
-    return rates != null ? rates.hashCode() : 0;
-  }
-
-  @Override
-  public String toString() {
-    return "["+this.getClass().getName()+" : " + rates.size() + " rates.]";
-  }
-
-  @Override
-  public Set<String> listAvailableCurrencies() {
-    Set<String> currencies = new HashSet<>();
-    for(String from : rates.keySet()) {
-      currencies.add(from);
-      for(String to : rates.get(from).keySet()) {
-        currencies.add(to);
-      }
-    }
-    return currencies;
-  }
-
-  @Override
-  public boolean reload() throws SolrException {
-    InputStream is = null;
-    Map<String, Map<String, Double>> tmpRates = new HashMap<>();
-    try {
-      log.debug("Reloading exchange rates from file "+this.currencyConfigFile);
-
-      is = loader.openResource(currencyConfigFile);
-      javax.xml.parsers.DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
-      try {
-        dbf.setXIncludeAware(true);
-        dbf.setNamespaceAware(true);
-      } catch (UnsupportedOperationException e) {
-        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "XML parser doesn't support XInclude option", e);
-      }
-      
-      try {
-        Document doc = dbf.newDocumentBuilder().parse(is);
-        XPathFactory xpathFactory = XPathFactory.newInstance();
-        XPath xpath = xpathFactory.newXPath();
-        
-        // Parse exchange rates.
-        NodeList nodes = (NodeList) xpath.evaluate("/currencyConfig/rates/rate", doc, XPathConstants.NODESET);
-        
-        for (int i = 0; i < nodes.getLength(); i++) {
-          Node rateNode = nodes.item(i);
-          NamedNodeMap attributes = rateNode.getAttributes();
-          Node from = attributes.getNamedItem("from");
-          Node to = attributes.getNamedItem("to");
-          Node rate = attributes.getNamedItem("rate");
-          
-          if (from == null || to == null || rate == null) {
-            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Exchange rate missing attributes (required: from, to, rate) " + rateNode);
-          }
-          
-          String fromCurrency = from.getNodeValue();
-          String toCurrency = to.getNodeValue();
-          Double exchangeRate;
-          
-          if (null == CurrencyField.getCurrency(fromCurrency)) {
-            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Specified 'from' currency not supported in this JVM: " + fromCurrency);
-          }
-          if (null == CurrencyField.getCurrency(toCurrency)) {
-            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Specified 'to' currency not supported in this JVM: " + toCurrency);
-          }
-          
-          try {
-            exchangeRate = Double.parseDouble(rate.getNodeValue());
-          } catch (NumberFormatException e) {
-            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Could not parse exchange rate: " + rateNode, e);
-          }
-          
-          addRate(tmpRates, fromCurrency, toCurrency, exchangeRate);
-        }
-      } catch (SAXException | XPathExpressionException | ParserConfigurationException | IOException e) {
-        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error parsing currency config.", e);
-      }
-    } catch (IOException e) {
-      throw new SolrException(ErrorCode.SERVER_ERROR, "Error while opening Currency configuration file "+currencyConfigFile, e);
-    } finally {
-      try {
-        if (is != null) {
-          is.close();
-        }
-      } catch (IOException e) {
-        e.printStackTrace();
-      }
-    }
-    // Atomically swap in the new rates map, if it loaded successfully
-    this.rates = tmpRates;
-    return true;
-  }
-
-  @Override
-  public void init(Map<String,String> params) throws SolrException {
-    this.currencyConfigFile = params.get(PARAM_CURRENCY_CONFIG);
-    if(currencyConfigFile == null) {
-      throw new SolrException(ErrorCode.NOT_FOUND, "Missing required configuration "+PARAM_CURRENCY_CONFIG);
-    }
-    
-    // Removing config params custom to us
-    params.remove(PARAM_CURRENCY_CONFIG);
-  }
-
-  @Override
-  public void inform(ResourceLoader loader) throws SolrException {
-    if(loader == null) {
-      throw new SolrException(ErrorCode.SERVER_ERROR, "Needs ResourceLoader in order to load config file");
-    }
-    this.loader = loader;
-    reload();
-  }
-}
-
-/**
- * Represents a Currency field value, which includes a long amount and ISO currency code.
- */
-class CurrencyValue {
-  private long amount;
-  private String currencyCode;
-
-  /**
-   * Constructs a new currency value.
-   *
-   * @param amount       The amount.
-   * @param currencyCode The currency code.
-   */
-  public CurrencyValue(long amount, String currencyCode) {
-    this.amount = amount;
-    this.currencyCode = currencyCode;
-  }
-
-  /**
-   * Constructs a new currency value by parsing the specific input.
-   * <p/>
-   * Currency values are expected to be in the format &lt;amount&gt;,&lt;currency code&gt;,
-   * for example, "500,USD" would represent 5 U.S. Dollars.
-   * <p/>
-   * If no currency code is specified, the default is assumed.
-   *
-   * @param externalVal The value to parse.
-   * @param defaultCurrency The default currency.
-   * @return The parsed CurrencyValue.
-   */
-  public static CurrencyValue parse(String externalVal, String defaultCurrency) {
-    if (externalVal == null) {
-      return null;
-    }
-    String amount = externalVal;
-    String code = defaultCurrency;
-
-    if (externalVal.contains(",")) {
-      String[] amountAndCode = externalVal.split(",");
-      amount = amountAndCode[0];
-      code = amountAndCode[1];
-    }
-
-    if (amount.equals("*")) {
-      return null;
-    }
-    
-    Currency currency = CurrencyField.getCurrency(code);
-
-    if (currency == null) {
-      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Currency code not supported by this JVM: " + code);
-    }
-
-    try {
-      double value = Double.parseDouble(amount);
-      long currencyValue = Math.round(value * Math.pow(10.0, currency.getDefaultFractionDigits()));
-
-      return new CurrencyValue(currencyValue, code);
-    } catch (NumberFormatException e) {
-      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
-    }
-  }
-
-  /**
-   * The amount of the CurrencyValue.
-   *
-   * @return The amount.
-   */
-  public long getAmount() {
-    return amount;
-  }
-
-  /**
-   * The ISO currency code of the CurrencyValue.
-   *
-   * @return The currency code.
-   */
-  public String getCurrencyCode() {
-    return currencyCode;
-  }
-
-  /**
-   * Performs a currency conversion & unit conversion.
-   *
-   * @param exchangeRates      Exchange rates to apply.
-   * @param sourceCurrencyCode The source currency code.
-   * @param sourceAmount       The source amount.
-   * @param targetCurrencyCode The target currency code.
-   * @return The converted indexable units after the exchange rate and currency fraction digits are applied.
-   */
-  public static long convertAmount(ExchangeRateProvider exchangeRates, String sourceCurrencyCode, long sourceAmount, String targetCurrencyCode) {
-    double exchangeRate = exchangeRates.getExchangeRate(sourceCurrencyCode, targetCurrencyCode);
-    return convertAmount(exchangeRate, sourceCurrencyCode, sourceAmount, targetCurrencyCode);
-  }
-
-  /**
-   * Performs a currency conversion & unit conversion.
-   *
-   * @param exchangeRate         Exchange rate to apply.
-   * @param sourceFractionDigits The fraction digits of the source.
-   * @param sourceAmount         The source amount.
-   * @param targetFractionDigits The fraction digits of the target.
-   * @return The converted indexable units after the exchange rate and currency fraction digits are applied.
-   */
-  public static long convertAmount(final double exchangeRate, final int sourceFractionDigits, final long sourceAmount, final int targetFractionDigits) {
-    int digitDelta = targetFractionDigits - sourceFractionDigits;
-    double value = ((double) sourceAmount * exchangeRate);
-
-    if (digitDelta != 0) {
-      if (digitDelta < 0) {
-        for (int i = 0; i < -digitDelta; i++) {
-          value *= 0.1;
-        }
-      } else {
-        for (int i = 0; i < digitDelta; i++) {
-          value *= 10.0;
-        }
-      }
-    }
-
-    return (long) value;
-  }
-
-  /**
-   * Performs a currency conversion & unit conversion.
-   *
-   * @param exchangeRate       Exchange rate to apply.
-   * @param sourceCurrencyCode The source currency code.
-   * @param sourceAmount       The source amount.
-   * @param targetCurrencyCode The target currency code.
-   * @return The converted indexable units after the exchange rate and currency fraction digits are applied.
-   */
-  public static long convertAmount(double exchangeRate, String sourceCurrencyCode, long sourceAmount, String targetCurrencyCode) {
-    if (targetCurrencyCode.equals(sourceCurrencyCode)) {
-      return sourceAmount;
-    }
-
-    int sourceFractionDigits = Currency.getInstance(sourceCurrencyCode).getDefaultFractionDigits();
-    Currency targetCurrency = Currency.getInstance(targetCurrencyCode);
-    int targetFractionDigits = targetCurrency.getDefaultFractionDigits();
-    return convertAmount(exchangeRate, sourceFractionDigits, sourceAmount, targetFractionDigits);
-  }
-
-  /**
-   * Returns a new CurrencyValue that is the conversion of this CurrencyValue to the specified currency.
-   *
-   * @param exchangeRates      The exchange rate provider.
-   * @param targetCurrencyCode The target currency code to convert this CurrencyValue to.
-   * @return The converted CurrencyValue.
-   */
-  public CurrencyValue convertTo(ExchangeRateProvider exchangeRates, String targetCurrencyCode) {
-    return new CurrencyValue(convertAmount(exchangeRates, this.getCurrencyCode(), this.getAmount(), targetCurrencyCode), targetCurrencyCode);
-  }
-
-  @Override
-  public String toString() {
-    return String.valueOf(amount) + "," + currencyCode;
+    super.inform(schema);
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/java/org/apache/solr/schema/CurrencyFieldType.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/CurrencyFieldType.java b/solr/core/src/java/org/apache/solr/schema/CurrencyFieldType.java
new file mode 100644
index 0000000..4bfffc9
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/schema/CurrencyFieldType.java
@@ -0,0 +1,831 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.schema;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Currency;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lucene.analysis.util.ResourceLoader;
+import org.apache.lucene.analysis.util.ResourceLoaderAware;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.queries.function.FunctionValues;
+import org.apache.lucene.queries.function.ValueSource;
+import org.apache.lucene.search.BooleanClause.Occur;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.FieldValueQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.SortField;
+import org.apache.solr.uninverting.UninvertingReader.Type;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.response.TextResponseWriter;
+import org.apache.solr.search.Filter;
+import org.apache.solr.search.QParser;
+import org.apache.solr.search.QueryWrapperFilter;
+import org.apache.solr.search.SolrConstantScoreQuery;
+import org.apache.solr.search.function.ValueSourceRangeFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Field type for support of monetary values.
+ * <p>
+ * See <a href="http://wiki.apache.org/solr/CurrencyField">http://wiki.apache.org/solr/CurrencyField</a>
+ */
+public class CurrencyFieldType extends FieldType implements SchemaAware, ResourceLoaderAware {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+  
+  protected static final String PARAM_DEFAULT_CURRENCY = "defaultCurrency";
+  protected static final String DEFAULT_DEFAULT_CURRENCY = "USD";
+  protected static final String PARAM_RATE_PROVIDER_CLASS = "providerClass";
+  protected static final String DEFAULT_RATE_PROVIDER_CLASS = "solr.FileExchangeRateProvider";
+  protected static final String PARAM_FIELD_SUFFIX_AMOUNT_RAW = "amountLongSuffix";
+  protected static final String PARAM_FIELD_SUFFIX_CURRENCY = "codeStrSuffix";
+
+  protected IndexSchema schema;
+  protected FieldType fieldTypeCurrency;
+  protected FieldType fieldTypeAmountRaw;
+  protected String fieldSuffixAmountRaw;
+  protected String fieldSuffixCurrency;
+  
+  private String exchangeRateProviderClass;
+  private String defaultCurrency;
+  private ExchangeRateProvider provider;
+
+  /**
+   * A wrapper around <code>Currency.getInstance</code> that returns null
+   * instead of throwing <code>IllegalArgumentException</code>
+   * if the specified Currency does not exist in this JVM.
+   *
+   * @see Currency#getInstance(String)
+   */
+  public static Currency getCurrency(final String code) {
+    try {
+      return Currency.getInstance(code);
+    } catch (IllegalArgumentException e) {
+      /* :NOOP: */
+    }
+    return null;
+  }
+
+  @Override
+  protected void init(IndexSchema schema, Map<String, String> args) {
+    super.init(schema, args);
+    if (this.isMultiValued()) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
+          getClass().getSimpleName() + " types can not be multiValued: " + this.typeName);
+    }
+    this.schema = schema;
+
+    this.defaultCurrency = args.get(PARAM_DEFAULT_CURRENCY);
+    if (this.defaultCurrency == null) {
+      this.defaultCurrency = DEFAULT_DEFAULT_CURRENCY;
+    } else {
+      args.remove(PARAM_DEFAULT_CURRENCY);
+    }
+    if (null == getCurrency(this.defaultCurrency)) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+          "Default currency code is not supported by this JVM: " + this.defaultCurrency);
+    }
+
+    this.exchangeRateProviderClass = args.get(PARAM_RATE_PROVIDER_CLASS);
+    if (this.exchangeRateProviderClass == null) {
+      this.exchangeRateProviderClass = DEFAULT_RATE_PROVIDER_CLASS;
+    } else {
+      args.remove(PARAM_RATE_PROVIDER_CLASS);
+    }
+    try {
+      Class<? extends ExchangeRateProvider> c
+          = schema.getResourceLoader().findClass(exchangeRateProviderClass, ExchangeRateProvider.class);
+      provider = c.newInstance();
+      provider.init(args);
+    } catch (Exception e) {
+      throw new SolrException(ErrorCode.SERVER_ERROR,
+          "Error instantiating exchange rate provider " + exchangeRateProviderClass + ": " + e.getMessage(), e);
+    }
+
+    if (fieldTypeAmountRaw == null) {      // Don't initialize if subclass already has done so
+      fieldSuffixAmountRaw = args.get(PARAM_FIELD_SUFFIX_AMOUNT_RAW);
+      if (fieldSuffixAmountRaw == null) {
+        throw new SolrException(ErrorCode.SERVER_ERROR, "Missing required param " + PARAM_FIELD_SUFFIX_AMOUNT_RAW);
+      } else {
+        args.remove(PARAM_FIELD_SUFFIX_AMOUNT_RAW);
+      }
+    }
+    
+    if (fieldTypeCurrency == null) {       // Don't initialize if subclass already has done so
+      fieldSuffixCurrency = args.get(PARAM_FIELD_SUFFIX_CURRENCY);
+      if (fieldSuffixCurrency == null) {
+        throw new SolrException(ErrorCode.SERVER_ERROR, "Missing required param " + PARAM_FIELD_SUFFIX_CURRENCY);
+      } else {
+        args.remove(PARAM_FIELD_SUFFIX_CURRENCY);
+      }
+    }
+  }
+
+  @Override
+  public boolean isPolyField() {
+    return true;
+  }
+
+  @Override
+  public void checkSchemaField(final SchemaField field) throws SolrException {
+    super.checkSchemaField(field);
+    if (field.multiValued()) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
+          getClass().getSimpleName() + " fields can not be multiValued: " + field.getName());
+    }
+  }
+
+  @Override
+  public List<IndexableField> createFields(SchemaField field, Object externalVal, float boost) {
+    CurrencyValue value = CurrencyValue.parse(externalVal.toString(), defaultCurrency);
+
+    List<IndexableField> f = new ArrayList<>();
+    SchemaField amountField = getAmountField(field);
+    f.add(amountField.createField(String.valueOf(value.getAmount()), amountField.indexed() && !amountField.omitNorms() ? boost : 1F));
+    SchemaField currencyField = getCurrencyField(field);
+    f.add(currencyField.createField(value.getCurrencyCode(), currencyField.indexed() && !currencyField.omitNorms() ? boost : 1F));
+
+    if (field.stored()) {
+      org.apache.lucene.document.FieldType customType = new org.apache.lucene.document.FieldType();
+      assert !customType.omitNorms();
+      customType.setStored(true);
+      String storedValue = externalVal.toString().trim();
+      if (storedValue.indexOf(",") < 0) {
+        storedValue += "," + defaultCurrency;
+      }
+      f.add(createField(field.getName(), storedValue, customType, 1F));
+    }
+
+    return f;
+  }
+
+  private SchemaField getAmountField(SchemaField field) {
+    return schema.getField(field.getName() + POLY_FIELD_SEPARATOR + fieldSuffixAmountRaw);
+  }
+
+  private SchemaField getCurrencyField(SchemaField field) {
+    return schema.getField(field.getName() + POLY_FIELD_SEPARATOR + fieldSuffixCurrency);
+  }
+
+  /**
+   * When index schema is informed, get field types for the configured dynamic sub-fields 
+   *
+   * {@inheritDoc}
+   *
+   * @param schema {@inheritDoc}
+   */
+  @Override
+  public void inform(IndexSchema schema) {
+    this.schema = schema;
+    if (null == fieldTypeAmountRaw) {
+      assert null != fieldSuffixAmountRaw : "How did we get here?";
+      SchemaField field = schema.getFieldOrNull(POLY_FIELD_SEPARATOR + fieldSuffixAmountRaw);
+      if (field == null) {
+        throw new SolrException(ErrorCode.SERVER_ERROR, "Field type \"" + this.getTypeName()
+            + "\": Undefined dynamic field for " + PARAM_FIELD_SUFFIX_AMOUNT_RAW + "=\"" + fieldSuffixAmountRaw + "\"");
+      }
+      fieldTypeAmountRaw = field.getType();
+      if (!(fieldTypeAmountRaw instanceof LongValueFieldType)) {
+        throw new SolrException(ErrorCode.SERVER_ERROR, "Field type \"" + this.getTypeName()
+            + "\": Dynamic field for " + PARAM_FIELD_SUFFIX_AMOUNT_RAW + "=\"" + fieldSuffixAmountRaw
+            + "\" must have type class extending LongValueFieldType");
+      }
+    }
+    if (null == fieldTypeCurrency) {
+      assert null != fieldSuffixCurrency : "How did we get here?";
+      SchemaField field = schema.getFieldOrNull(POLY_FIELD_SEPARATOR + fieldSuffixCurrency);
+      if (field == null) {
+        throw new SolrException(ErrorCode.SERVER_ERROR, "Field type \"" + this.getTypeName()
+            + "\": Undefined dynamic field for " + PARAM_FIELD_SUFFIX_CURRENCY + "=\"" + fieldSuffixCurrency + "\"");
+      }
+      fieldTypeCurrency = field.getType();
+      if (!(fieldTypeCurrency instanceof StrField)) {
+        throw new SolrException(ErrorCode.SERVER_ERROR, "Field type \"" + this.getTypeName()
+            + "\": Dynamic field for " + PARAM_FIELD_SUFFIX_CURRENCY + "=\"" + fieldSuffixCurrency
+            + "\" must have type class of (or extending) StrField");
+      }
+    }
+  }
+
+  /**
+   * Load the currency config when resource loader initialized.
+   *
+   * @param resourceLoader The resource loader.
+   */
+  @Override
+  public void inform(ResourceLoader resourceLoader) {
+    provider.inform(resourceLoader);
+    boolean reloaded = provider.reload();
+    if(!reloaded) {
+      log.warn("Failed reloading currencies");
+    }
+  }
+
+  @Override
+  public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) {
+    CurrencyValue value = CurrencyValue.parse(externalVal, defaultCurrency);
+    CurrencyValue valueDefault;
+    valueDefault = value.convertTo(provider, defaultCurrency);
+
+    return getRangeQuery(parser, field, valueDefault, valueDefault, true, true);
+  }
+
+  /**
+   * <p>
+   * Returns a ValueSource over this field in which the numeric value for 
+   * each document represents the indexed value as converted to the default 
+   * currency for the field, normalized to its most granular form based 
+   * on the default fractional digits.
+   * </p>
+   * <p>
+   * For example: If the default Currency specified for a field is 
+   * <code>USD</code>, then the values returned by this value source would 
+   * represent the equivilent number of "cents" (ie: value in dollars * 100) 
+   * after converting each document's native currency to USD -- because the 
+   * default fractional digits for <code>USD</code> is "<code>2</code>".  
+   * So for a document whose indexed value was currently equivilent to 
+   * "<code>5.43,USD</code>" using the the exchange provider for this field, 
+   * this ValueSource would return a value of "<code>543</code>"
+   * </p>
+   *
+   * @see #PARAM_DEFAULT_CURRENCY
+   * @see #DEFAULT_DEFAULT_CURRENCY
+   * @see Currency#getDefaultFractionDigits
+   * @see #getConvertedValueSource
+   */
+  public RawCurrencyValueSource getValueSource(SchemaField field,
+                                               QParser parser) {
+    getAmountField(field).checkFieldCacheSource();
+    getCurrencyField(field).checkFieldCacheSource();
+    return new RawCurrencyValueSource(field, defaultCurrency, parser);
+  }
+
+  /**
+   * <p>
+   * Returns a ValueSource over this field in which the numeric value for 
+   * each document represents the value from the underlying 
+   * <code>RawCurrencyValueSource</code> as converted to the specified target 
+   * Currency.
+   * </p>
+   * <p>
+   * For example: If the <code>targetCurrencyCode</code> param is set to
+   * <code>USD</code>, then the values returned by this value source would 
+   * represent the equivilent number of dollars after converting each 
+   * document's raw value to <code>USD</code>.  So for a document whose 
+   * indexed value was currently equivilent to "<code>5.43,USD</code>" 
+   * using the the exchange provider for this field, this ValueSource would 
+   * return a value of "<code>5.43</code>"
+   * </p>
+   *
+   * @param targetCurrencyCode The target currency for the resulting value source, if null the defaultCurrency for this field type will be used
+   * @param source the raw ValueSource to wrap
+   * @see #PARAM_DEFAULT_CURRENCY
+   * @see #DEFAULT_DEFAULT_CURRENCY
+   * @see #getValueSource
+   */
+  public ValueSource getConvertedValueSource(String targetCurrencyCode,
+                                             RawCurrencyValueSource source) {
+    if (null == targetCurrencyCode) {
+      targetCurrencyCode = defaultCurrency;
+    }
+    return new ConvertedCurrencyValueSource(targetCurrencyCode,
+        source);
+  }
+
+  @Override
+  public Query getRangeQuery(QParser parser, SchemaField field, String part1, String part2, final boolean minInclusive, final boolean maxInclusive) {
+    final CurrencyValue p1 = CurrencyValue.parse(part1, defaultCurrency);
+    final CurrencyValue p2 = CurrencyValue.parse(part2, defaultCurrency);
+
+    if (p1 != null && p2 != null && !p1.getCurrencyCode().equals(p2.getCurrencyCode())) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+          "Cannot parse range query " + part1 + " to " + part2 +
+              ": range queries only supported when upper and lower bound have same currency.");
+    }
+
+    return getRangeQuery(parser, field, p1, p2, minInclusive, maxInclusive);
+  }
+
+  public Query getRangeQuery(QParser parser, SchemaField field, final CurrencyValue p1, final CurrencyValue p2, final boolean minInclusive, final boolean maxInclusive) {
+    String currencyCode = (p1 != null) ? p1.getCurrencyCode() :
+        (p2 != null) ? p2.getCurrencyCode() : defaultCurrency;
+
+    // ValueSourceRangeFilter doesn't check exists(), so we have to
+    final Filter docsWithValues = new QueryWrapperFilter(new FieldValueQuery(getAmountField(field).getName()));
+    final Filter vsRangeFilter = new ValueSourceRangeFilter
+        (new RawCurrencyValueSource(field, currencyCode, parser),
+            p1 == null ? null : p1.getAmount() + "",
+            p2 == null ? null : p2.getAmount() + "",
+            minInclusive, maxInclusive);
+    final BooleanQuery.Builder docsInRange = new BooleanQuery.Builder();
+    docsInRange.add(docsWithValues, Occur.FILTER);
+    docsInRange.add(vsRangeFilter, Occur.FILTER);
+
+    return new SolrConstantScoreQuery(new QueryWrapperFilter(docsInRange.build()));
+  }
+
+  @Override
+  public SortField getSortField(SchemaField field, boolean reverse) {
+    // Convert all values to default currency for sorting.
+    return (new RawCurrencyValueSource(field, defaultCurrency, null)).getSortField(reverse);
+  }
+
+  @Override
+  public Type getUninversionType(SchemaField sf) {
+    return null;
+  }
+
+  @Override
+  public void write(TextResponseWriter writer, String name, IndexableField field) throws IOException {
+    writer.writeStr(name, field.stringValue(), true);
+  }
+
+  public ExchangeRateProvider getProvider() {
+    return provider;
+  }
+
+  /**
+   * <p>
+   * A value source whose values represent the "normal" values
+   * in the specified target currency.
+   * </p>
+   * @see RawCurrencyValueSource
+   */
+  class ConvertedCurrencyValueSource extends ValueSource {
+    private final Currency targetCurrency;
+    private final RawCurrencyValueSource source;
+    private final double rate;
+    public ConvertedCurrencyValueSource(String targetCurrencyCode,
+                                        RawCurrencyValueSource source) {
+      this.source = source;
+      this.targetCurrency = getCurrency(targetCurrencyCode);
+      if (null == targetCurrency) {
+        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Currency code not supported by this JVM: " + targetCurrencyCode);
+      }
+      // the target digits & currency of our source, 
+      // become the source digits & currency of ourselves
+      this.rate = provider.getExchangeRate
+          (source.getTargetCurrency().getCurrencyCode(),
+              targetCurrency.getCurrencyCode());
+    }
+
+    @Override
+    public FunctionValues getValues(Map context, LeafReaderContext reader)
+        throws IOException {
+      final FunctionValues amounts = source.getValues(context, reader);
+      // the target digits & currency of our source, 
+      // become the source digits & currency of ourselves
+      final String sourceCurrencyCode = source.getTargetCurrency().getCurrencyCode();
+      final double divisor = Math.pow(10D, targetCurrency.getDefaultFractionDigits());
+      return new FunctionValues() {
+        @Override
+        public boolean exists(int doc) {
+          return amounts.exists(doc);
+        }
+        @Override
+        public long longVal(int doc) {
+          return (long) doubleVal(doc);
+        }
+        @Override
+        public int intVal(int doc) {
+          return (int) doubleVal(doc);
+        }
+
+        @Override
+        public double doubleVal(int doc) {
+          return CurrencyValue.convertAmount(rate, sourceCurrencyCode, amounts.longVal(doc), targetCurrency.getCurrencyCode()) / divisor;
+        }
+
+        @Override
+        public float floatVal(int doc) {
+          return CurrencyValue.convertAmount(rate, sourceCurrencyCode, amounts.longVal(doc), targetCurrency.getCurrencyCode()) / ((float)divisor);
+        }
+
+        @Override
+        public String strVal(int doc) {
+          return Double.toString(doubleVal(doc));
+        }
+
+        @Override
+        public String toString(int doc) {
+          return name() + '(' + strVal(doc) + ')';
+        }
+      };
+    }
+    public String name() {
+      return "currency";
+    }
+
+    @Override
+    public String description() {
+      return name() + "(" + source.getField().getName() + "," + targetCurrency.getCurrencyCode()+")";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+
+      ConvertedCurrencyValueSource that = (ConvertedCurrencyValueSource) o;
+
+      return !(source != null ? !source.equals(that.source) : that.source != null) &&
+          (rate == that.rate) &&
+          !(targetCurrency != null ? !targetCurrency.equals(that.targetCurrency) : that.targetCurrency != null);
+
+    }
+
+    @Override
+    public int hashCode() {
+      int result = targetCurrency != null ? targetCurrency.hashCode() : 0;
+      result = 31 * result + (source != null ? source.hashCode() : 0);
+      result = 31 * (int) Double.doubleToLongBits(rate);
+      return result;
+    }
+  }
+
+  /**
+   * <p>
+   * A value source whose values represent the "raw" (ie: normalized using 
+   * the number of default fractional digits) values in the specified 
+   * target currency).
+   * </p>
+   * <p>
+   * For example: if the specified target currency is "<code>USD</code>" 
+   * then the numeric values are the number of pennies in the value 
+   * (ie: <code>$n * 100</code>) since the number of default fractional 
+   * digits for <code>USD</code> is "<code>2</code>")
+   * </p>
+   * @see ConvertedCurrencyValueSource
+   */
+  class RawCurrencyValueSource extends ValueSource {
+    private static final long serialVersionUID = 1L;
+    private final Currency targetCurrency;
+    private ValueSource currencyValues;
+    private ValueSource amountValues;
+    private final SchemaField sf;
+
+    public RawCurrencyValueSource(SchemaField sfield, String targetCurrencyCode, QParser parser) {
+      this.sf = sfield;
+      this.targetCurrency = getCurrency(targetCurrencyCode);
+      if (null == targetCurrency) {
+        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Currency code not supported by this JVM: " + targetCurrencyCode);
+      }
+
+      SchemaField amountField = getAmountField(sf);
+      SchemaField currencyField = getCurrencyField(sf);
+
+      currencyValues = currencyField.getType().getValueSource(currencyField, parser);
+      amountValues = amountField.getType().getValueSource(amountField, parser);
+    }
+
+    public SchemaField getField() { return sf; }
+    public Currency getTargetCurrency() { return targetCurrency; }
+
+    @Override
+    public FunctionValues getValues(Map context, LeafReaderContext reader) throws IOException {
+      final FunctionValues amounts = amountValues.getValues(context, reader);
+      final FunctionValues currencies = currencyValues.getValues(context, reader);
+
+      return new FunctionValues() {
+        private static final int MAX_CURRENCIES_TO_CACHE = 256;
+        private final int[] fractionDigitCache = new int[MAX_CURRENCIES_TO_CACHE];
+        private final String[] currencyOrdToCurrencyCache = new String[MAX_CURRENCIES_TO_CACHE];
+        private final double[] exchangeRateCache = new double[MAX_CURRENCIES_TO_CACHE];
+        private int targetFractionDigits = -1;
+        private int targetCurrencyOrd = -1;
+        private boolean initializedCache;
+
+        private String getDocCurrencyCode(int doc, int currencyOrd) {
+          if (currencyOrd < MAX_CURRENCIES_TO_CACHE) {
+            String currency = currencyOrdToCurrencyCache[currencyOrd];
+
+            if (currency == null) {
+              currencyOrdToCurrencyCache[currencyOrd] = currency = currencies.strVal(doc);
+            }
+
+            if (currency == null) {
+              currency = defaultCurrency;
+            }
+
+            if (targetCurrencyOrd == -1 &&
+                currency.equals(targetCurrency.getCurrencyCode() )) {
+              targetCurrencyOrd = currencyOrd;
+            }
+
+            return currency;
+          } else {
+            return currencies.strVal(doc);
+          }
+        }
+        /** throws a (Server Error) SolrException if the code is not valid */
+        private Currency getDocCurrency(int doc, int currencyOrd) {
+          String code = getDocCurrencyCode(doc, currencyOrd);
+          Currency c = getCurrency(code);
+          if (null == c) {
+            throw new SolrException
+                (SolrException.ErrorCode.SERVER_ERROR,
+                    "Currency code of document is not supported by this JVM: "+code);
+          }
+          return c;
+        }
+
+        @Override
+        public boolean exists(int doc) {
+          return amounts.exists(doc);
+        }
+
+        @Override
+        public long longVal(int doc) {
+          long amount = amounts.longVal(doc);
+          // bail fast using whatever amounts defaults to if no value
+          // (if we don't do this early, currencyOrd may be < 0, 
+          // causing index bounds exception
+          if ( ! exists(doc) ) {
+            return amount;
+          }
+
+          if (!initializedCache) {
+            for (int i = 0; i < fractionDigitCache.length; i++) {
+              fractionDigitCache[i] = -1;
+            }
+
+            initializedCache = true;
+          }
+
+          int currencyOrd = currencies.ordVal(doc);
+
+          if (currencyOrd == targetCurrencyOrd) {
+            return amount;
+          }
+
+          double exchangeRate;
+          int sourceFractionDigits;
+
+          if (targetFractionDigits == -1) {
+            targetFractionDigits = targetCurrency.getDefaultFractionDigits();
+          }
+
+          if (currencyOrd < MAX_CURRENCIES_TO_CACHE) {
+            exchangeRate = exchangeRateCache[currencyOrd];
+
+            if (exchangeRate <= 0.0) {
+              String sourceCurrencyCode = getDocCurrencyCode(doc, currencyOrd);
+              exchangeRate = exchangeRateCache[currencyOrd] = provider.getExchangeRate(sourceCurrencyCode, targetCurrency.getCurrencyCode());
+            }
+
+            sourceFractionDigits = fractionDigitCache[currencyOrd];
+
+            if (sourceFractionDigits == -1) {
+              sourceFractionDigits = fractionDigitCache[currencyOrd] = getDocCurrency(doc, currencyOrd).getDefaultFractionDigits();
+            }
+          } else {
+            Currency source = getDocCurrency(doc, currencyOrd);
+            exchangeRate = provider.getExchangeRate(source.getCurrencyCode(), targetCurrency.getCurrencyCode());
+            sourceFractionDigits = source.getDefaultFractionDigits();
+          }
+
+          return CurrencyValue.convertAmount(exchangeRate, sourceFractionDigits, amount, targetFractionDigits);
+        }
+
+        @Override
+        public int intVal(int doc) {
+          return (int) longVal(doc);
+        }
+
+        @Override
+        public double doubleVal(int doc) {
+          return (double) longVal(doc);
+        }
+
+        @Override
+        public float floatVal(int doc) {
+          return (float) longVal(doc);
+        }
+
+        @Override
+        public String strVal(int doc) {
+          return Long.toString(longVal(doc));
+        }
+
+        @Override
+        public String toString(int doc) {
+          return name() + '(' + amounts.toString(doc) + ',' + currencies.toString(doc) + ')';
+        }
+      };
+    }
+
+    public String name() {
+      return "rawcurrency";
+    }
+
+    @Override
+    public String description() {
+      return name() + "(" + sf.getName() +
+          ",target="+targetCurrency.getCurrencyCode()+")";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+
+      RawCurrencyValueSource that = (RawCurrencyValueSource) o;
+
+      return !(amountValues != null ? !amountValues.equals(that.amountValues) : that.amountValues != null) &&
+          !(currencyValues != null ? !currencyValues.equals(that.currencyValues) : that.currencyValues != null) &&
+          !(targetCurrency != null ? !targetCurrency.equals(that.targetCurrency) : that.targetCurrency != null);
+
+    }
+
+    @Override
+    public int hashCode() {
+      int result = targetCurrency != null ? targetCurrency.hashCode() : 0;
+      result = 31 * result + (currencyValues != null ? currencyValues.hashCode() : 0);
+      result = 31 * result + (amountValues != null ? amountValues.hashCode() : 0);
+      return result;
+    }
+  }
+
+  /**
+   * Represents a Currency field value, which includes a long amount and ISO currency code.
+   */
+  static class CurrencyValue {
+    private long amount;
+    private String currencyCode;
+  
+    /**
+     * Constructs a new currency value.
+     *
+     * @param amount       The amount.
+     * @param currencyCode The currency code.
+     */
+    public CurrencyValue(long amount, String currencyCode) {
+      this.amount = amount;
+      this.currencyCode = currencyCode;
+    }
+  
+    /**
+     * Constructs a new currency value by parsing the specific input.
+     * <p/>
+     * Currency values are expected to be in the format &lt;amount&gt;,&lt;currency code&gt;,
+     * for example, "500,USD" would represent 5 U.S. Dollars.
+     * <p/>
+     * If no currency code is specified, the default is assumed.
+     *
+     * @param externalVal The value to parse.
+     * @param defaultCurrency The default currency.
+     * @return The parsed CurrencyValue.
+     */
+    public static CurrencyValue parse(String externalVal, String defaultCurrency) {
+      if (externalVal == null) {
+        return null;
+      }
+      String amount = externalVal;
+      String code = defaultCurrency;
+  
+      if (externalVal.contains(",")) {
+        String[] amountAndCode = externalVal.split(",");
+        amount = amountAndCode[0];
+        code = amountAndCode[1];
+      }
+  
+      if (amount.equals("*")) {
+        return null;
+      }
+      
+      Currency currency = getCurrency(code);
+  
+      if (currency == null) {
+        throw new SolrException(ErrorCode.BAD_REQUEST, "Currency code not supported by this JVM: " + code);
+      }
+  
+      try {
+        double value = Double.parseDouble(amount);
+        long currencyValue = Math.round(value * Math.pow(10.0, currency.getDefaultFractionDigits()));
+  
+        return new CurrencyValue(currencyValue, code);
+      } catch (NumberFormatException e) {
+        throw new SolrException(ErrorCode.BAD_REQUEST, e);
+      }
+    }
+  
+    /**
+     * The amount of the CurrencyValue.
+     *
+     * @return The amount.
+     */
+    public long getAmount() {
+      return amount;
+    }
+  
+    /**
+     * The ISO currency code of the CurrencyValue.
+     *
+     * @return The currency code.
+     */
+    public String getCurrencyCode() {
+      return currencyCode;
+    }
+  
+    /**
+     * Performs a currency conversion & unit conversion.
+     *
+     * @param exchangeRates      Exchange rates to apply.
+     * @param sourceCurrencyCode The source currency code.
+     * @param sourceAmount       The source amount.
+     * @param targetCurrencyCode The target currency code.
+     * @return The converted indexable units after the exchange rate and currency fraction digits are applied.
+     */
+    public static long convertAmount(ExchangeRateProvider exchangeRates, String sourceCurrencyCode, long sourceAmount, String targetCurrencyCode) {
+      double exchangeRate = exchangeRates.getExchangeRate(sourceCurrencyCode, targetCurrencyCode);
+      return convertAmount(exchangeRate, sourceCurrencyCode, sourceAmount, targetCurrencyCode);
+    }
+  
+    /**
+     * Performs a currency conversion & unit conversion.
+     *
+     * @param exchangeRate         Exchange rate to apply.
+     * @param sourceFractionDigits The fraction digits of the source.
+     * @param sourceAmount         The source amount.
+     * @param targetFractionDigits The fraction digits of the target.
+     * @return The converted indexable units after the exchange rate and currency fraction digits are applied.
+     */
+    public static long convertAmount(final double exchangeRate, final int sourceFractionDigits, final long sourceAmount, final int targetFractionDigits) {
+      int digitDelta = targetFractionDigits - sourceFractionDigits;
+      double value = ((double) sourceAmount * exchangeRate);
+  
+      if (digitDelta != 0) {
+        if (digitDelta < 0) {
+          for (int i = 0; i < -digitDelta; i++) {
+            value *= 0.1;
+          }
+        } else {
+          for (int i = 0; i < digitDelta; i++) {
+            value *= 10.0;
+          }
+        }
+      }
+  
+      return (long) value;
+    }
+  
+    /**
+     * Performs a currency conversion & unit conversion.
+     *
+     * @param exchangeRate       Exchange rate to apply.
+     * @param sourceCurrencyCode The source currency code.
+     * @param sourceAmount       The source amount.
+     * @param targetCurrencyCode The target currency code.
+     * @return The converted indexable units after the exchange rate and currency fraction digits are applied.
+     */
+    public static long convertAmount(double exchangeRate, String sourceCurrencyCode, long sourceAmount, String targetCurrencyCode) {
+      if (targetCurrencyCode.equals(sourceCurrencyCode)) {
+        return sourceAmount;
+      }
+  
+      int sourceFractionDigits = Currency.getInstance(sourceCurrencyCode).getDefaultFractionDigits();
+      Currency targetCurrency = Currency.getInstance(targetCurrencyCode);
+      int targetFractionDigits = targetCurrency.getDefaultFractionDigits();
+      return convertAmount(exchangeRate, sourceFractionDigits, sourceAmount, targetFractionDigits);
+    }
+  
+    /**
+     * Returns a new CurrencyValue that is the conversion of this CurrencyValue to the specified currency.
+     *
+     * @param exchangeRates      The exchange rate provider.
+     * @param targetCurrencyCode The target currency code to convert this CurrencyValue to.
+     * @return The converted CurrencyValue.
+     */
+    public CurrencyValue convertTo(ExchangeRateProvider exchangeRates, String targetCurrencyCode) {
+      return new CurrencyValue(convertAmount(exchangeRates, this.getCurrencyCode(), this.getAmount(), targetCurrencyCode), targetCurrencyCode);
+    }
+  
+    @Override
+    public String toString() {
+      return String.valueOf(amount) + "," + currencyCode;
+    }
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5232b2ce/solr/core/src/java/org/apache/solr/schema/FileExchangeRateProvider.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/FileExchangeRateProvider.java b/solr/core/src/java/org/apache/solr/schema/FileExchangeRateProvider.java
new file mode 100644
index 0000000..0148617
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/schema/FileExchangeRateProvider.java
@@ -0,0 +1,252 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.schema;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.invoke.MethodHandles;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.lucene.analysis.util.ResourceLoader;
+import org.apache.solr.common.SolrException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ * Configuration for currency. Provides currency exchange rates.
+ */
+class FileExchangeRateProvider implements ExchangeRateProvider {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+  protected static final String PARAM_CURRENCY_CONFIG       = "currencyConfig";
+
+  // Exchange rate map, maps Currency Code -> Currency Code -> Rate
+  private Map<String, Map<String, Double>> rates = new HashMap<>();
+
+  private String currencyConfigFile;
+  private ResourceLoader loader;
+
+  /**
+   * Returns the currently known exchange rate between two currencies. If a direct rate has been loaded,
+   * it is used. Otherwise, if a rate is known to convert the target currency to the source, the inverse
+   * exchange rate is computed.
+   *
+   * @param sourceCurrencyCode The source currency being converted from.
+   * @param targetCurrencyCode The target currency being converted to.
+   * @return The exchange rate.
+   * @throws SolrException if the requested currency pair cannot be found
+   */
+  @Override
+  public double getExchangeRate(String sourceCurrencyCode, String targetCurrencyCode) {
+    if (sourceCurrencyCode == null || targetCurrencyCode == null) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Cannot get exchange rate; currency was null.");
+    }
+    
+    if (sourceCurrencyCode.equals(targetCurrencyCode)) {
+      return 1.0;
+    }
+
+    Double directRate = lookupRate(sourceCurrencyCode, targetCurrencyCode);
+
+    if (directRate != null) {
+      return directRate;
+    }
+
+    Double symmetricRate = lookupRate(targetCurrencyCode, sourceCurrencyCode);
+
+    if (symmetricRate != null) {
+      return 1.0 / symmetricRate;
+    }
+
+    throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No available conversion rate between " + sourceCurrencyCode + " to " + targetCurrencyCode);
+  }
+
+  /**
+   * Looks up the current known rate, if any, between the source and target currencies.
+   *
+   * @param sourceCurrencyCode The source currency being converted from.
+   * @param targetCurrencyCode The target currency being converted to.
+   * @return The exchange rate, or null if no rate has been registered.
+   */
+  private Double lookupRate(String sourceCurrencyCode, String targetCurrencyCode) {
+    Map<String, Double> rhs = rates.get(sourceCurrencyCode);
+
+    if (rhs != null) {
+      return rhs.get(targetCurrencyCode);
+    }
+
+    return null;
+  }
+
+  /**
+   * Registers the specified exchange rate.
+   *
+   * @param ratesMap           The map to add rate to
+   * @param sourceCurrencyCode The source currency.
+   * @param targetCurrencyCode The target currency.
+   * @param rate               The known exchange rate.
+   */
+  private void addRate(Map<String, Map<String, Double>> ratesMap, String sourceCurrencyCode, String targetCurrencyCode, double rate) {
+    Map<String, Double> rhs = ratesMap.get(sourceCurrencyCode);
+
+    if (rhs == null) {
+      rhs = new HashMap<>();
+      ratesMap.put(sourceCurrencyCode, rhs);
+    }
+
+    rhs.put(targetCurrencyCode, rate);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    FileExchangeRateProvider that = (FileExchangeRateProvider) o;
+
+    return !(rates != null ? !rates.equals(that.rates) : that.rates != null);
+  }
+
+  @Override
+  public int hashCode() {
+    return rates != null ? rates.hashCode() : 0;
+  }
+
+  @Override
+  public String toString() {
+    return "["+this.getClass().getName()+" : " + rates.size() + " rates.]";
+  }
+
+  @Override
+  public Set<String> listAvailableCurrencies() {
+    Set<String> currencies = new HashSet<>();
+    for(String from : rates.keySet()) {
+      currencies.add(from);
+      for(String to : rates.get(from).keySet()) {
+        currencies.add(to);
+      }
+    }
+    return currencies;
+  }
+
+  @Override
+  public boolean reload() throws SolrException {
+    InputStream is = null;
+    Map<String, Map<String, Double>> tmpRates = new HashMap<>();
+    try {
+      log.debug("Reloading exchange rates from file "+this.currencyConfigFile);
+
+      is = loader.openResource(currencyConfigFile);
+      javax.xml.parsers.DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+      try {
+        dbf.setXIncludeAware(true);
+        dbf.setNamespaceAware(true);
+      } catch (UnsupportedOperationException e) {
+        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "XML parser doesn't support XInclude option", e);
+      }
+      
+      try {
+        Document doc = dbf.newDocumentBuilder().parse(is);
+        XPathFactory xpathFactory = XPathFactory.newInstance();
+        XPath xpath = xpathFactory.newXPath();
+        
+        // Parse exchange rates.
+        NodeList nodes = (NodeList) xpath.evaluate("/currencyConfig/rates/rate", doc, XPathConstants.NODESET);
+        
+        for (int i = 0; i < nodes.getLength(); i++) {
+          Node rateNode = nodes.item(i);
+          NamedNodeMap attributes = rateNode.getAttributes();
+          Node from = attributes.getNamedItem("from");
+          Node to = attributes.getNamedItem("to");
+          Node rate = attributes.getNamedItem("rate");
+          
+          if (from == null || to == null || rate == null) {
+            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Exchange rate missing attributes (required: from, to, rate) " + rateNode);
+          }
+          
+          String fromCurrency = from.getNodeValue();
+          String toCurrency = to.getNodeValue();
+          Double exchangeRate;
+          
+          if (null == CurrencyFieldType.getCurrency(fromCurrency)) {
+            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Specified 'from' currency not supported in this JVM: " + fromCurrency);
+          }
+          if (null == CurrencyFieldType.getCurrency(toCurrency)) {
+            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Specified 'to' currency not supported in this JVM: " + toCurrency);
+          }
+          
+          try {
+            exchangeRate = Double.parseDouble(rate.getNodeValue());
+          } catch (NumberFormatException e) {
+            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Could not parse exchange rate: " + rateNode, e);
+          }
+          
+          addRate(tmpRates, fromCurrency, toCurrency, exchangeRate);
+        }
+      } catch (SAXException | XPathExpressionException | ParserConfigurationException | IOException e) {
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error parsing currency config.", e);
+      }
+    } catch (IOException e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error while opening Currency configuration file "+currencyConfigFile, e);
+    } finally {
+      try {
+        if (is != null) {
+          is.close();
+        }
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+    }
+    // Atomically swap in the new rates map, if it loaded successfully
+    this.rates = tmpRates;
+    return true;
+  }
+
+  @Override
+  public void init(Map<String,String> params) throws SolrException {
+    this.currencyConfigFile = params.get(PARAM_CURRENCY_CONFIG);
+    if(currencyConfigFile == null) {
+      throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "Missing required configuration "+PARAM_CURRENCY_CONFIG);
+    }
+    
+    // Removing config params custom to us
+    params.remove(PARAM_CURRENCY_CONFIG);
+  }
+
+  @Override
+  public void inform(ResourceLoader loader) throws SolrException {
+    if(loader == null) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Needs ResourceLoader in order to load config file");
+    }
+    this.loader = loader;
+    reload();
+  }
+}