001    /*
002    // $Id: //open/mondrian/src/main/mondrian/i18n/LocalizingDynamicSchemaProcessor.java#7 $
003    // This software is subject to the terms of the Common Public License
004    // Agreement, available at the following URL:
005    // http://www.opensource.org/licenses/cpl.html.
006    // Copyright (C) 2005-2008 Julian Hyde
007    // All Rights Reserved.
008    // You must accept the terms of that agreement to use this software.
009    */
010    
011    package mondrian.i18n;
012    import mondrian.olap.MondrianProperties;
013    import mondrian.olap.Util;
014    import mondrian.spi.DynamicSchemaProcessor;
015    import mondrian.spi.impl.FilterDynamicSchemaProcessor;
016    import org.apache.log4j.Logger;
017    
018    import java.io.*;
019    import java.net.MalformedURLException;
020    import java.net.URL;
021    import java.util.*;
022    import java.util.regex.Matcher;
023    import java.util.regex.Pattern;
024    
025    /**
026     * Schema processor which helps localize data and metadata.
027     *
028     * @author arosselet
029     * @since August 26, 2005
030     * @version $Id: //open/mondrian/src/main/mondrian/i18n/LocalizingDynamicSchemaProcessor.java#7 $
031     */
032    public class LocalizingDynamicSchemaProcessor
033        extends FilterDynamicSchemaProcessor
034        implements DynamicSchemaProcessor
035    {
036        private static final Logger LOGGER =
037                Logger.getLogger(LocalizingDynamicSchemaProcessor.class);
038    
039        /** Creates a new instance of LocalizingDynamicSchemaProcessor */
040        public LocalizingDynamicSchemaProcessor() {
041        }
042    
043        private PropertyResourceBundle i8n;
044    
045        /**
046         * Regular expression for variables.
047         */
048        private static final Pattern pattern = Pattern.compile("(%\\{.*?\\})");
049        private static final int INVALID_LOCALE = 1;
050        private static final int FULL_LOCALE = 3;
051        private static final int LANG_LOCALE = 2;
052        private static final Set<String> countries = Collections.unmodifiableSet(
053                new HashSet<String>(Arrays.asList(Locale.getISOCountries())));
054        private static final Set<String> languages = Collections.unmodifiableSet(
055                new HashSet<String>(Arrays.asList(Locale.getISOLanguages())));
056        private int localeType = INVALID_LOCALE;
057    
058        void populate(String propFile) {
059            StringBuilder localizedPropFileBase = new StringBuilder();
060            String [] tokens = propFile.split("\\.");
061    
062            for (int i = 0; i < tokens.length - 1; i++) {
063                if (localizedPropFileBase.length() > 0) {
064                    localizedPropFileBase.append(".");
065                }
066                localizedPropFileBase.append(tokens[i]);
067            }
068    
069            String [] localePropFilename = new String[localeType];
070            String [] localeTokens = locale.split("\\_");
071            int index = localeType;
072            for (int i = 0; i < localeType; i++) {
073                //"en_GB" -> [en][GB]  first
074                String catName = "";
075                /*
076                 * if en_GB, then append [0]=_en_GB [1]=_en
077                 * if en, then append [0]=_en
078                 * if null/bad then append nothing;
079                 */
080                for (int j = 0;j <= i - 1; j++) {
081                    catName += "_" + localeTokens[j];
082                }
083                localePropFilename[--index] = localizedPropFileBase + catName +
084                        "." + tokens[tokens.length - 1];
085            }
086            boolean fileExists = false;
087            File file = null;
088            for (int i = 0;i < localeType && !fileExists; i++) {
089                file = new File(localePropFilename[i]);
090                if (LOGGER.isDebugEnabled()) {
091                    LOGGER.debug("populate: file=" +
092                            file.getAbsolutePath() +
093                            " exists=" +
094                            file.exists());
095                }
096                if (!file.exists()) {
097                    LOGGER.warn("Mondrian: Warning: file '"
098                            + file.getAbsolutePath()
099                            + "' not found - trying next default locale");
100                }
101                fileExists = file.exists();
102            }
103    
104            if (fileExists) {
105                try {
106                    URL url = Util.toURL(file);
107                    i8n = new PropertyResourceBundle(url.openStream());
108                    LOGGER.info("Mondrian: locale file '"
109                            + file.getAbsolutePath()
110                            + "' loaded");
111    
112                } catch (MalformedURLException e) {
113                    LOGGER.error("Mondrian: locale file '"
114                            + file.getAbsolutePath()
115                            + "' could not be loaded ("
116                            + e
117                            + ")");
118                } catch (java.io.IOException e) {
119                    LOGGER.error("Mondrian: locale file '"
120                            + file.getAbsolutePath()
121                            + "' could not be loaded ("
122                            + e
123                            + ")");
124                }
125            } else {
126                LOGGER.warn("Mondrian: Warning: no suitable locale file found for locale '"
127                        + locale
128                        + "'");
129            }
130        }
131    
132        private void loadProperties() {
133            String propFile = MondrianProperties.instance().LocalePropFile.get();
134            if (propFile != null) {
135                populate(propFile);
136            }
137        }
138    
139        public String filter(
140            String schemaUrl,
141            Util.PropertyList connectInfo,
142            InputStream stream) throws Exception
143        {
144            setLocale(connectInfo.get("Locale"));
145    
146            loadProperties();
147    
148            String schema = super.filter(schemaUrl, connectInfo, stream);
149            if (i8n != null) {
150                schema = doRegExReplacements(schema);
151            }
152            LOGGER.debug(schema);
153            return schema;
154        }
155    
156        private String doRegExReplacements(String schema) {
157            // As of JDK 1.5, cannot use StringBuilder - appendReplacement requires
158            // the antediluvian StringBuffer.
159            StringBuffer intlSchema = new StringBuffer();
160            Matcher match = pattern.matcher(schema);
161            String key;
162            while (match.find()) {
163                key = extractKey(match.group());
164                int start = match.start();
165                int end = match.end();
166    
167                try {
168                    String intlProperty = i8n.getString(key);
169                    if (intlProperty != null) {
170                        match.appendReplacement(intlSchema, intlProperty);
171                    }
172                } catch (java.util.MissingResourceException e) {
173                    LOGGER.error("Missing resource for key [" + key + "]",e);
174                } catch (java.lang.NullPointerException e) {
175                    LOGGER.error("missing resource key at substring(" + start + "," + end + ")", e);
176                }
177            }
178            match.appendTail(intlSchema);
179            return intlSchema.toString();
180        }
181    
182        private String extractKey(String group) {
183            // removes leading '%{' and tailing '%' from the matched string
184            // to obtain the required key
185            return group.substring(2, group.length() - 1);
186        }
187    
188        /**
189         * Property locale.
190         */
191        private String locale;
192    
193        /**
194         * Returns the property locale.
195         *
196         * @return Value of property locale.
197         */
198        public String getLocale() {
199            return this.locale;
200        }
201    
202        /**
203         * Sets the property locale.
204         *
205         * @param locale New value of property locale.
206         */
207        public void setLocale(String locale) {
208            this.locale = locale;
209            localeType = INVALID_LOCALE;  // if invalid/missing, default localefile will be tried.
210            // make sure that both language and country fields are valid
211            if (locale.indexOf("_") != -1 && locale.length() == 5) {
212                if (languages.contains(locale.substring(0, 2)) &&
213                        countries.contains(locale.substring(3, 5))) {
214                    localeType = FULL_LOCALE;
215                }
216            } else {
217                if (locale.length() == 2) {
218                //make sure that the language field is valid since that is all that was provided
219                    if (languages.contains(locale.substring(0, 2))) {
220                        localeType = LANG_LOCALE;
221                    }
222                }
223            }
224        }
225    }
226    
227    // End LocalizingDynamicSchemaProcessor.java