svn commit: r603024 - in /ofbiz/trunk/framework: base/dtd/ofbiz-properties.xsd base/src/base/org/ofbiz/base/util/UtilProperties.java example/config/ExampleXmlUiLabels.xml webtools/src/org/ofbiz/webtools/WebToolsServices.java

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

svn commit: r603024 - in /ofbiz/trunk/framework: base/dtd/ofbiz-properties.xsd base/src/base/org/ofbiz/base/util/UtilProperties.java example/config/ExampleXmlUiLabels.xml webtools/src/org/ofbiz/webtools/WebToolsServices.java

adrianc
Author: adrianc
Date: Mon Dec 10 12:04:01 2007
New Revision: 603024

URL: http://svn.apache.org/viewvc?rev=603024&view=rev
Log:
UtilProperties bug fix and enhancements.

This commit fixes the bug reported in https://issues.apache.org/jira/browse/OFBIZ-1488 and it includes a few of the improvements mentioned in https://issues.apache.org/jira/browse/OFBIZ-1485. This commit also lays the groundwork for improved properties file support in the future.

This commit contains a new xsd file - it needs to be included on the apache ofbiz website. XML properties files will generate console error messages until it is.

Added:
    ofbiz/trunk/framework/base/dtd/ofbiz-properties.xsd
Modified:
    ofbiz/trunk/framework/base/src/base/org/ofbiz/base/util/UtilProperties.java
    ofbiz/trunk/framework/example/config/ExampleXmlUiLabels.xml
    ofbiz/trunk/framework/webtools/src/org/ofbiz/webtools/WebToolsServices.java

Added: ofbiz/trunk/framework/base/dtd/ofbiz-properties.xsd
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/base/dtd/ofbiz-properties.xsd?rev=603024&view=auto
==============================================================================
--- ofbiz/trunk/framework/base/dtd/ofbiz-properties.xsd (added)
+++ ofbiz/trunk/framework/base/dtd/ofbiz-properties.xsd Mon Dec 10 12:04:01 2007
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
+    <xs:element name="resource">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element minOccurs="0" maxOccurs="unbounded" ref="property"/>
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="property">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element minOccurs="0" maxOccurs="unbounded" ref="value"/>
+                <xs:attributeGroup ref="attlist.property"/>
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+    <xs:attributeGroup name="attlist.property">
+        <xs:attribute type="xs:string" name="key" use="required"/>
+    </xs:attributeGroup>
+</xs:schema>

Modified: ofbiz/trunk/framework/base/src/base/org/ofbiz/base/util/UtilProperties.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/base/src/base/org/ofbiz/base/util/UtilProperties.java?rev=603024&r1=603023&r2=603024&view=diff
==============================================================================
--- ofbiz/trunk/framework/base/src/base/org/ofbiz/base/util/UtilProperties.java (original)
+++ ofbiz/trunk/framework/base/src/base/org/ofbiz/base/util/UtilProperties.java Mon Dec 10 12:04:01 2007
@@ -21,6 +21,7 @@
 import java.net.URL;
 import java.text.MessageFormat;
 import java.util.Enumeration;
+import java.util.InvalidPropertiesFormatException;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
@@ -29,14 +30,16 @@
 import java.util.Properties;
 import java.util.ResourceBundle;
 import java.util.Set;
+import java.io.BufferedInputStream;
 import java.io.FileOutputStream;
 import java.io.FileNotFoundException;
+import java.io.InputStream;
 import java.io.IOException;
 
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
-import javolution.util.FastMap;
+import javolution.util.FastList;
 import javolution.util.FastSet;
 
 import org.ofbiz.base.location.FlexibleLocation;
@@ -49,6 +52,7 @@
  * Generic Property Accessor with Cache - Utilities for working with properties files
  *
  */
+@SuppressWarnings("serial")
 public class UtilProperties implements java.io.Serializable {
 
     public static final String module = UtilProperties.class.getName();
@@ -67,7 +71,7 @@
     /** An instance of the generic cache for storing the ResourceBundle
      *  corresponding to each properties file keyed by a String for the resource location and the locale
      */
-    protected static UtilCache<String, ResourceBundleMapWrapper.InternalRbmWrapper> bundleLocaleCache = new UtilCache<String, ResourceBundleMapWrapper.InternalRbmWrapper>("properties.UtilPropertiesBundleLocaleCache");
+    protected static UtilCache<String, ResourceBundle> bundleLocaleCache = new UtilCache<String, ResourceBundle>("properties.UtilPropertiesBundleLocaleCache");
 
 
     /** Compares the specified property to the compareString, returns true if they are the same, false otherwise
@@ -492,26 +496,23 @@
 
     public static ResourceBundleMapWrapper.InternalRbmWrapper getInternalRbmWrapper(String resource, Locale locale) {
         String resourceCacheKey = resource + "_" + locale.toString();
-        ResourceBundleMapWrapper.InternalRbmWrapper bundleMap = bundleLocaleCache.get(resourceCacheKey);
-        if (bundleMap == null) {
-            synchronized (UtilProperties.class) {
-                bundleMap = bundleLocaleCache.get(resourceCacheKey);
-                if (bundleMap == null) {
-                    ResourceBundle bundle = getBaseResourceBundle(resource, locale);
+        ResourceBundle bundle = bundleLocaleCache.get(resourceCacheKey);
+        if (bundle == null) {
+            synchronized (bundleLocaleCache) {
+                bundle = bundleLocaleCache.get(resourceCacheKey);
+                if (bundle == null) {
+                    bundle = getBaseResourceBundle(resource, locale);
                     if (bundle == null) {
                         throw new IllegalArgumentException("Could not find resource bundle [" + resource + "] in the locale [" + locale + "]");
                     }
-                    bundleMap = new ResourceBundleMapWrapper.InternalRbmWrapper(bundle);
-                    if (bundleMap != null) {
-                        // TODO: Make this smarter by checking the Locale of the ResourceBundle -
-                        // there might be an instance of this bundle already in the cache.
-                        // See http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html#getLocale()
-                        bundleLocaleCache.put(resourceCacheKey, bundleMap);
-                    }
+                    // TODO: Make this smarter by checking the Locale of the ResourceBundle -
+                    // there might be an instance of this bundle already in the cache.
+                    // See http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html#getLocale()
+                    bundleLocaleCache.put(resourceCacheKey, bundle);
                 }
             }
         }
-        return bundleMap;
+        return new ResourceBundleMapWrapper.InternalRbmWrapper(bundle);
     }
 
     protected static Set<String> resourceNotFoundMessagesShown = FastSet.newInstance();
@@ -522,7 +523,7 @@
         ClassLoader loader = Thread.currentThread().getContextClassLoader();
         ResourceBundle bundle = null;
         try {
-            bundle = XmlResourceBundle.getBundle(resource, locale, loader);
+            bundle = UtilResourceBundle.getBundle(resource, locale, loader);
         } catch (MissingResourceException e) {
             String resourceFullName = resource + "_" + locale.toString();
             if (!resourceNotFoundMessagesShown.contains(resourceFullName)) {
@@ -543,75 +544,220 @@
         return bundle;
     }
 
-    /** Returns the specified resource/properties file
-     *
-     * NOTE: This is NOT fully implemented yet to fulfill all of the requirements
-     *  for i18n messages. Do NOT use.
-     *
-     * To be used in an i18n context this still needs to be extended quite
-     *  a bit. The behavior needed is that for each getMessage the most specific
-     *  locale (with fname_en_US for instance) is searched first, then the next
-     *  less specific (fname_en for instance), then without the locale if it is
-     *  still not found (plain fname for example, not that these examples would
-     *  have .properties appended to them).
-     * This would be accomplished by returning the following structure:
-     *    1. Get "fname" FlexibleProperties object
-     *    2. Get the "fname_en" FlexibleProperties object and if the "fname" one
-     *      is not null, set it as the default/parent of the "fname_en" object
-     *    3. Get the "fname_en_US" FlexibleProperties object and if the
-     *      "fname_en" one is not null, set it as the default/parent of the
-     *      "fname_en_US" object; if the "fname_en" one is null, but the "fname"
-     *      one is not, set the "fname" object as the default/parent of the
-     *      "fname_en_US" object
-     * Then return the fname_en_US object if not null, else the fname_en, else the fname.
-     *
-     * To make this all more fun, the default locale should be the parent of
-     *  the "fname" object in this example so that there is an even higher
-     *  chance of finding something for each request.
-     *
-     * For efficiency all of these should be cached indendependently so the same
-     *  instance can be shared, speeding up loading time/efficiency.
-     *
-     * All of this should work with the setDefaultProperties method of the
-     *  FlexibleProperties class, but it should be tested and updated as
-     *  necessary. It's a bit tricky, so chances are it won't work as desired...
-     *
+    /** Returns the specified resource/properties file.<p>Note that this method
+     * will return a Properties instance for the specified locale <em>only</em> -
+     * if you need <a href="http://www.w3.org/International/">I18n</a> properties, then use
+     * <a href="#getResourceBundle(java.lang.String,%20java.util.Locale)">
+     * getResourceBundle(String resource, Locale locale)</a>. This method is
+     * intended to be used primarily by the UtilProperties class.</p>
      * @param resource The name of the resource - can be a file, class, or URL
-     * @param locale The locale that the given resource will correspond to
-     * @return The Properties class
+     * @param locale The desired locale
+     * @return The Properties instance, or null if no matching properties are found
      */
     public static Properties getProperties(String resource, Locale locale) {
-        if (resource == null || resource.length() <= 0) return null;
-        if (locale == null) locale = Locale.getDefault();
+        if (UtilValidate.isEmpty(resource)) {
+            throw new IllegalArgumentException("resource cannot be null or empty");
+        }
+        if (locale == null) {
+            throw new IllegalArgumentException("locale cannot be null");
+        }
+        Properties properties = null;
+        URL url = resolvePropertiesUrl(resource, locale);
+        if (url != null) {
+            try {
+                properties = new ExtendedProperties(url, locale);
+            } catch (Exception e) {
+                if (UtilValidate.isNotEmpty(e.getMessage())) {
+                    Debug.log(e.getMessage(), module);
+                }
+                properties = null;
+            }
+        }
+        if (UtilValidate.isNotEmpty(properties)) {
+            Debug.logInfo("Loaded " + properties.size() + " properties for: " + resource + " (" + locale + ")", module);
+        }
+        return properties;
+    }
 
-        String localeString = locale.toString();
-        String resourceLocale = resource + "_" + localeString;
-        FlexibleProperties properties = resourceCache.get(resourceLocale);
+    // ========= Classes and Methods for expanded Properties file support ========== //
 
-        if (properties == null) {
-            try {
-                URL url = UtilURL.fromResource(resourceLocale);
-                if (url == null) {
-                    properties = (FlexibleProperties) getProperties(resource);
-                } else {
-                    properties = FlexibleProperties.makeFlexibleProperties(url);
+    public static final Locale LOCALE_ROOT = new Locale("", "", "");
+
+    protected static Locale fallbackLocale = null;
+    /** Returns the configured fallback locale. UtilProperties uses this locale
+     * to resolve locale-specific XML properties.<p>The fallback locale can be
+     * configured using the <code>locale.properties.fallback</code> property in
+     * <code>general.properties</code>.
+     * @return The configured fallback locale
+     */
+    public static Locale getFallbackLocale() {
+        if (fallbackLocale == null) {
+            synchronized (UtilResourceBundle.class) {
+                if (fallbackLocale == null) {
+                    String locale = getPropertyValue("general", "locale.properties.fallback");
+                    if (UtilValidate.isNotEmpty(locale)) {
+                        fallbackLocale = UtilMisc.parseLocale(locale);
+                    }
+                    if (fallbackLocale == null) {
+                        fallbackLocale = UtilMisc.parseLocale("en");
+                    }
                 }
-            } catch (MissingResourceException e) {
-                Debug.log(e.getMessage(), module);
             }
-            resourceCache.put(resourceLocale, properties);
         }
+        return fallbackLocale;
+    }
 
-        if (properties == null)
-            Debug.logInfo("[UtilProperties.getProperties] could not find resource: " + resource + ", locale: " + locale, module);
+    /** Converts a Locale instance to a candidate Locale list. The list
+     * is ordered most-specific to least-specific. Example:
+     * <code>localeToCandidateList(Locale.US)</code> would return
+     * a list containing <code>en_US</code> and <code>en</code>.
+     * @return A list of default candidate locales.
+     */
+    public static List<Locale> localeToCandidateList(Locale locale) {
+        List<Locale> localeList = FastList.newInstance();
+        localeList.add(locale);
+        String localeString = locale.toString();
+        int pos = localeString.lastIndexOf("_", localeString.length());
+        while (pos != -1) {
+            localeString = localeString.substring(0, pos);
+            localeList.add(new Locale(localeString));
+            pos = localeString.lastIndexOf("_", localeString.length());
+        }
+        return localeList;
+    }
+
+    protected static Set<Locale> defaultCandidateLocales = null;
+    /** Returns the default candidate Locale list. The list is populated
+     * with the JVM's default locale, the OFBiz fallback locale, and
+     * the <code>LOCALE_ROOT</code> (empty) locale - in that order.
+     * @return A list of default candidate locales.
+     */
+    public static Set<Locale> getDefaultCandidateLocales() {
+        if (defaultCandidateLocales == null) {
+            synchronized (UtilProperties.class) {
+                if (defaultCandidateLocales == null) {
+                    defaultCandidateLocales = FastSet.newInstance();
+                    defaultCandidateLocales.addAll(localeToCandidateList(Locale.getDefault()));
+                    // Change to Locale.ROOT in Java 6
+                    defaultCandidateLocales.add(LOCALE_ROOT);
+                    defaultCandidateLocales.addAll(localeToCandidateList(getFallbackLocale()));
+                }
+            }
+        }
+        return defaultCandidateLocales;
+    }
 
-        return properties;
+    /** Returns a list of candidate locales based on a supplied locale.
+     * The returned list consists of the supplied locale and the
+     * <a href="#getDefaultCandidateLocales()">default candidate locales</a>
+     * - in that order.
+     * @param locale The desired locale
+     * @return A list of candidate locales
+     */
+    public static List<Locale> getCandidateLocales(Locale locale) {
+        // Java 6 conformance
+        if (LOCALE_ROOT.equals(locale)) {
+            return UtilMisc.toList(locale);
+        }
+        Set<Locale> localeSet = FastSet.newInstance();
+        localeSet.addAll(localeToCandidateList(locale));
+        localeSet.addAll(getDefaultCandidateLocales());
+        List<Locale> localeList = FastList.newInstance();
+        localeList.addAll(localeSet);
+        return localeList;
     }
-    
-    /**
-     * Custom ResourceBundle class. This class reads XML properties files and supports
-     * the OFBiz <code>component://</code> location protocol.<p>The format of the XML
-     * properties file is:<br /><br /><code>
+
+    /** Create a localized resource name based on a resource name and
+     * a locale.
+     * @param resource The desired resource
+     * @param locale The desired locale
+     * @return Localized resource name
+     */
+    public static String createResourceName(String resource, Locale locale) {
+        if (locale == null) {
+            return resource;
+        }
+        String resourceName = resource;
+        if (UtilValidate.isNotEmpty(locale.toString())) {
+            resourceName = resourceName + "_" + locale;
+        }
+        return resourceName;
+    }
+
+    protected static Set<String> propertiesNotFound = FastSet.newInstance();
+    /** Resolve a properties file URL.
+     * <p>This method uses the following strategy:<br />
+     * <ul>
+     * <li>Locate the XML file specified in <code>resource (MyProps.xml)</code></li>
+     * <li>Locate the file that starts with the name specified in
+     * <code>resource</code> and ends with the locale's string and
+     * <code>.xml (MyProps_en.xml)</code></li>
+     * <li>Locate the file that starts with the name specified in
+     * <code>resource</code> and ends with the locale's string and
+     * <code>.properties (MyProps_en.properties)</code></li>
+     * <li>Locate the file that starts with the name specified in
+     * <code>resource and ends with the locale's string (MyProps_en)</code></li>
+     * </ul>
+     * <br />
+     * The <code>component://</code> protocol is supported in the
+     * <code>resource</code> parameter.
+     * </p>
+     *
+     * @param resource The resource to resolve
+     * @param locale The desired locale
+     * @return A URL instance or null if not found.
+     */
+    public static URL resolvePropertiesUrl(String resource, Locale locale) {
+        if (UtilValidate.isEmpty(resource)) {
+            throw new IllegalArgumentException("resource cannot be null or empty");
+        }
+        if (locale == null) {
+            throw new IllegalArgumentException("locale cannot be null");
+        }
+        String resourceName = createResourceName(resource, locale);
+        if (propertiesNotFound.contains(resourceName)) {
+            return null;
+        }
+        URL url = null;
+        try {
+            // Check for complete URL first
+            if (resource.endsWith(".xml")) {
+                url = FlexibleLocation.resolveLocation(resource);
+                if (url != null) {
+                    return url;
+                }
+            }
+            // Check for XML properties file next
+            url = FlexibleLocation.resolveLocation(resourceName + ".xml");
+            if (url != null) {
+                return url;
+            }
+            // Check for *.properties file
+            url = FlexibleLocation.resolveLocation(resourceName + ".properties");
+            if (url != null) {
+                return url;
+            }
+            url = FlexibleLocation.resolveLocation(resourceName);
+            if (url != null) {
+                return url;
+            }
+        } catch (Exception e) {
+            Debug.logInfo("Properties resolver: invalid URL - " + e.getMessage(), module);
+        }
+        if (propertiesNotFound.size() <= 300) {
+            // Sanity check - list could get quite large
+            propertiesNotFound.add(resourceName);
+        }
+        return null;
+    }
+
+    /** Convert XML property file to Properties instance. This method will convert
+     * both the Java XML properties file format and the OFBiz custom XML
+     * properties file format.
+     * <p>
+     * The format of the custom XML properties file is:<br />
+     * <br />
+     * <code>
      * &lt;resource&gt;<br />
      * &nbsp;&lt;property key="key"&gt;<br />
      * &nbsp;&nbsp;&lt;value xml:lang="locale 1"&gt;Some value&lt;/value&gt<br />
@@ -619,29 +765,80 @@
      * &nbsp;&nbsp;...<br />
      * &nbsp;&lt;/property&gt;<br />
      * &nbsp;...<br />
-     * &lt;/resource&gt;<br /><br /></code>
-     * where <em>"locale 1", "locale 2"</em> are valid Locale strings.</p>
+     * &lt;/resource&gt;<br /><br /></code> where <em>"locale 1", "locale 2"</em> are valid Locale strings.
+     * </p>
+     *
+     * @param in XML file InputStream
+     * @param locale The desired locale
+     * @param properties Optional Properties object to populate
+     * @return Properties instance or null if not found
+     */
+    public static Properties xmlToProperties(InputStream in, Locale locale, Properties properties) throws IOException, InvalidPropertiesFormatException {
+        if (in == null) {
+            throw new IllegalArgumentException("InputStream cannot be null");
+        }
+        Document doc = null;
+        try {
+            // set validation true when we have a DTD for the custom XML format
+            doc = UtilXml.readXmlDocument(in, false, "XML Properties file");
+            in.close();
+        } catch (Exception e) {
+            in.close();
+            return null;
+        }
+        Element resourceElement = doc.getDocumentElement();
+        List<? extends Element> propertyList = UtilXml.childElementList(resourceElement, "property");
+        if (UtilValidate.isNotEmpty(propertyList)) {
+            // Custom XML properties file format
+            if (locale == null) {
+                throw new IllegalArgumentException("locale cannot be null");
+            }
+            String localeString = locale.toString();
+            for (Iterator<? extends Element> p = propertyList.iterator(); p.hasNext();) {
+                Element property = p.next();
+                Element value = UtilXml.firstChildElement(property, "value", "xml:lang", localeString);
+                if (value != null) {
+                    if (properties == null) {
+                        properties = new Properties();
+                    }
+                    properties.put(property.getAttribute("key"), UtilXml.elementValue(value));
+                }
+            }
+            return properties;
+        }
+        propertyList = UtilXml.childElementList(resourceElement, "entry");
+        if (UtilValidate.isEmpty(propertyList)) {
+            throw new InvalidPropertiesFormatException("XML properties file invalid or empty");
+        }
+        // Java XML properties file format
+        for (Iterator<? extends Element> p = propertyList.iterator(); p.hasNext();) {
+            Element property = p.next();
+            String value = UtilXml.elementValue(property);
+            if (value != null) {
+                if (properties == null) {
+                    properties = new Properties();
+                }
+                properties.put(property.getAttribute("key"), value);
+            }
+        }
+        return properties;
+    }
+
+    /** Custom ResourceBundle class. This class extends ResourceBundle
+     * to add support for the OFBiz custom XML properties file format.
      */
-    public static class XmlResourceBundle extends ResourceBundle {
-        protected Map<String, Object> propertyMap = null;
-        protected String locale = null;
+    public static class UtilResourceBundle extends ResourceBundle {
+        protected Properties properties = null;
+        protected Locale locale = null;
 
-        protected XmlResourceBundle() {}
+        protected UtilResourceBundle() {}
         
-        public XmlResourceBundle(Map<String, Object> propertyMap, String locale, ResourceBundle parent) {
-            this.propertyMap = propertyMap;
+        public UtilResourceBundle(Properties properties, Locale locale, ResourceBundle parent) {
+            this.properties = properties;
             this.locale = locale;
-            this.parent = parent;
+            setParent(parent);
         }
 
-        /**
-         * Get ResourceBundle. <p>This method override behaves differently than the
-         * ResourceBundle method. The method calls ResourceBundle.getBundle(...) first
-         * - to preserve the original behavior. If null is returned, then the method
-         * attempts to get an XML resource. The resource search starts with the
-         * specified locale, then the system locale, and finally the locale specified
-         * in the general.properties <code>locale.properties.fallback</code> property.</p>
-         */
         public static ResourceBundle getBundle(String resource, Locale locale, ClassLoader loader) throws MissingResourceException {
             ResourceBundle bundle = null;
             try {
@@ -652,107 +849,81 @@
             if (bundle != null) {
                 return bundle;
             }
-            try {
-                URL url = FlexibleLocation.resolveLocation(resource, loader);
-                if (url == null) {
-                    url = FlexibleLocation.resolveLocation(resource + ".xml");
-                }
-                if (url != null) {
-                    Document doc = UtilXml.readXmlDocument(url);
-                    bundle = createBundle(doc, locale.toString());
-                    if (bundle == null && !Locale.getDefault().toString().equals(locale.toString())) {
-                        bundle = createBundle(doc, Locale.getDefault().toString());
-                    }
-                    if (bundle == null && !getFallbackLocale().toString().equals(locale.toString())) {
-                        bundle = createBundle(doc, getFallbackLocale().toString());
+            double startTime = System.currentTimeMillis();
+            FastList<Locale> candidateLocales = (FastList<Locale>) getCandidateLocales(locale);
+            ResourceBundle parentBundle = null;
+            synchronized (bundleLocaleCache) {
+                while (candidateLocales.size() > 0) {
+                    Locale candidateLocale = candidateLocales.removeLast();
+                    // ResourceBundles are connected together as a singly-linked list
+                    String parentName = createResourceName(resource, candidateLocale);
+                    ResourceBundle lookupBundle = bundleLocaleCache.get(parentName);
+                    if (lookupBundle == null) {
+                        Properties newProps = getProperties(resource, candidateLocale);
+                        if (UtilValidate.isNotEmpty(newProps)) {
+                            bundle = new UtilResourceBundle(newProps, candidateLocale, parentBundle);
+                            bundleLocaleCache.put(parentName, bundle);
+                            parentBundle = bundle;
+                        }
+                    } else {
+                        parentBundle = bundle;
+                        bundle = lookupBundle;
                     }
                 }
-            } catch (Exception e) {
-                throw new MissingResourceException(e.getMessage(), null, null);
             }
+            double totalTime = System.currentTimeMillis() - startTime;
             if (bundle == null) {
-                throw new MissingResourceException("Resource " + resource + " not found", null, null);
+                throw new MissingResourceException("Resource " + resource + ", locale " + locale + " not found", null, null);
             }
+            Debug.logInfo("ResourceBundle " + resource + " (" + locale + ") created in " + totalTime + " mS", module);
             return bundle;
         }
         
-        /**
-         * Creates a new XmlResourceBundle.
-         * @param doc The XML Document instance to search
-         * @param localeString
-         * @return new ResourceBundle, or null if no matching resource found
-         * @throws Exception Invalid properties XML file
-         */
-        protected static ResourceBundle createBundle(Document doc, String localeString) throws Exception {
-            Map<String, Object> propertyMap = null;
-            Element resourceElement = doc.getDocumentElement();
-            List propertyList = UtilXml.childElementList(resourceElement, "property");
-            if (propertyList == null) {
-                throw new Exception("XML properties file invalid or empty");
-            }
-            int pos = 0;
-            while (pos != -1) {
-                for (Iterator p = propertyList.iterator(); p.hasNext();) {
-                    Element property = (Element) p.next();
-                    Element value = UtilXml.firstChildElement(property, "value", "xml:lang", localeString);
-                    if (value != null) {
-                        if (propertyMap == null) {
-                            propertyMap = FastMap.newInstance();
-                        }
-                        propertyMap.put(property.getAttribute("key"), UtilXml.elementValue(value));
-                    }
-                }
-                if (propertyMap != null) {
-                    return new XmlResourceBundle(propertyMap, localeString, null);
-                }
-                pos = localeString.lastIndexOf("_", localeString.length());
-                if (pos != -1) {
-                    localeString = localeString.substring(0, pos);
-                }
-            }
-            return null;
-        }
-        
         public Locale getLocale() {
-            return UtilMisc.ensureLocale(this.locale);
+            return this.locale;
         }
         
         protected Object handleGetObject(String key) {
-            Object obj = propertyMap.get(key);
-            if (obj == null && this.parent != null) {
-                obj = parent.getObject(key);
-            }
-            return obj;
+            return properties.get(key);
         }
         
-        public Enumeration getKeys() {
-            return new Enumeration() {
-                Iterator i = propertyMap.keySet().iterator();
+        public Enumeration<String> getKeys() {
+            return new Enumeration<String>() {
+                Iterator i = properties.keySet().iterator();
                 public boolean hasMoreElements() {
                     return (i.hasNext());
                 }
-                public Object nextElement() {
-                    return i.next();
+                public String nextElement() {
+                    return (String) i.next();
                 }
             };
         }
 
-        protected static Locale fallbackLocale = null;
-        public static Locale getFallbackLocale() {
-            if (fallbackLocale == null) {
-                synchronized (XmlResourceBundle.class) {
-                    if (fallbackLocale == null) {
-                        String locale = UtilProperties.getPropertyValue("general", "locale.properties.fallback");
-                        if (UtilValidate.isNotEmpty(locale)) {
-                            fallbackLocale = UtilMisc.parseLocale(locale);
-                        }
-                        if (fallbackLocale == null) {
-                            fallbackLocale = UtilMisc.parseLocale("en");
-                        }
-                    }
-                }
-            }
-            return fallbackLocale;
+    }
+
+    /** Custom Properties class. Extended from Properties to add support
+     * for the OFBiz custom XML file format.
+     */
+    @SuppressWarnings("serial")
+    public static class ExtendedProperties extends Properties {
+        public ExtendedProperties() {
+            super();
+        }
+        public ExtendedProperties(Properties defaults) {
+            super(defaults);
+        }
+        public ExtendedProperties(URL url, Locale locale) throws IOException, InvalidPropertiesFormatException {
+            InputStream in = new BufferedInputStream(url.openStream());
+            if (url.getFile().endsWith(".xml")) {
+                xmlToProperties(in, locale, this);
+            } else {
+                load(in);
+            }
+            in.close();
+        }
+        public void loadFromXML(InputStream in) throws IOException, InvalidPropertiesFormatException {
+            xmlToProperties(in, null, this);
+            in.close();
         }
     }
 }

Modified: ofbiz/trunk/framework/example/config/ExampleXmlUiLabels.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/example/config/ExampleXmlUiLabels.xml?rev=603024&r1=603023&r2=603024&view=diff
==============================================================================
--- ofbiz/trunk/framework/example/config/ExampleXmlUiLabels.xml (original)
+++ ofbiz/trunk/framework/example/config/ExampleXmlUiLabels.xml Mon Dec 10 12:04:01 2007
@@ -31,8 +31,6 @@
     </property>
     <property key="ExampleCompanySubtitle">
         <value xml:lang="en">Part of the Open For Business Family of Open Source Software</value>
-        <value xml:lang="it">Part of the Open For Business Family of Open Source Software</value>
-        <value xml:lang="ro">Part of the Open For Business Family of Open Source Software</value>
     </property>
     <property key="ExampleExampleId">
         <value xml:lang="en">Example ID</value>
@@ -77,17 +75,13 @@
     <property key="ExampleExplicitOption">
         <value xml:lang="en">Explicit Option</value>
         <value xml:lang="it">Opzione Esplicita</value>
-        <value xml:lang="ro">Explicit Option</value>
     </property>
     <property key="ExampleFormWidgetExamples">
         <value xml:lang="en">Form Widget Examples</value>
-        <value xml:lang="it">Form Widget Examples</value>
-        <value xml:lang="ro">Form Widget Examples</value>
     </property>
     <property key="ExampleMainPage">
         <value xml:lang="en">Example Main Page</value>
         <value xml:lang="it">Pagina Principale Esempi</value>
-        <value xml:lang="ro">Example Main Page</value>
     </property>
     <property key="ExampleMessage">
         <value xml:lang="en">For something interesting make sure you are logged in, try username:admin, password:ofbiz.</value>
@@ -106,8 +100,6 @@
     </property>
     <property key="ExampleOriginalExample">
         <value xml:lang="en">Original Example</value>
-        <value xml:lang="it">Original Example</value>
-        <value xml:lang="ro">Original Example</value>
     </property>
     <property key="ExampleErrorNoExampleStatusValidChange">
         <value xml:lang="en">Error: status change from [${lookedUpValue.statusId}] to [${parameters.statusId}] is not allowed.</value>
@@ -138,8 +130,6 @@
     </property>
     <property key="PageTitleFormWidgetExamples">
         <value xml:lang="en">Form Widget Examples</value>
-        <value xml:lang="it">Form Widget Examples</value>
-        <value xml:lang="ro">Form Widget Examples</value>
     </property>
     <property key="PageTitleEditExample">
         <value xml:lang="en">Edit Example</value>
@@ -211,11 +201,9 @@
     <property key="FormFieldTitle_anotherDate">
         <value xml:lang="en">Another Date</value>
         <value xml:lang="it">Altra Data</value>
-        <value xml:lang="ro">Another Date</value>
     </property>  
     <property key="FormFieldTitle_anotherText">
         <value xml:lang="en">Another Text</value>
         <value xml:lang="it">Altro Testo</value>
-        <value xml:lang="ro">Another Text</value>
     </property>
 </resource>

Modified: ofbiz/trunk/framework/webtools/src/org/ofbiz/webtools/WebToolsServices.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/webtools/src/org/ofbiz/webtools/WebToolsServices.java?rev=603024&r1=603023&r2=603024&view=diff
==============================================================================
--- ofbiz/trunk/framework/webtools/src/org/ofbiz/webtools/WebToolsServices.java (original)
+++ ofbiz/trunk/framework/webtools/src/org/ofbiz/webtools/WebToolsServices.java Mon Dec 10 12:04:01 2007
@@ -51,7 +51,7 @@
 import org.ofbiz.base.util.StringUtil;
 import org.ofbiz.base.util.UtilMisc;
 import org.ofbiz.base.util.UtilProperties;
-import org.ofbiz.base.util.UtilProperties.XmlResourceBundle;
+import org.ofbiz.base.util.UtilProperties.UtilResourceBundle;
 import org.ofbiz.base.util.UtilURL;
 import org.ofbiz.base.util.UtilValidate;
 import org.ofbiz.base.util.UtilDateTime;
@@ -661,7 +661,7 @@
                         ResourceBundle bundle = null;
                         if (UtilValidate.isNotEmpty(entity.getDefaultResourceName())) {
                             try {
-                                bundle = XmlResourceBundle.getBundle(entity.getDefaultResourceName(), locale, loader);
+                                bundle = UtilResourceBundle.getBundle(entity.getDefaultResourceName(), locale, loader);
                             } catch (Exception exception) {
                                 Debug.logInfo(exception.getMessage(), module);
                             }