001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2005-2010 Pentaho
008// All Rights Reserved.
009*/
010package mondrian.i18n;
011
012import mondrian.olap.MondrianProperties;
013import mondrian.olap.Util;
014import mondrian.spi.DynamicSchemaProcessor;
015import mondrian.spi.impl.FilterDynamicSchemaProcessor;
016
017import org.apache.log4j.Logger;
018
019import java.io.InputStream;
020import java.util.MissingResourceException;
021import java.util.ResourceBundle;
022import java.util.regex.Matcher;
023import 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 */
031public class LocalizingDynamicSchemaProcessor
032    extends FilterDynamicSchemaProcessor
033    implements DynamicSchemaProcessor
034{
035    private static final Logger LOGGER =
036        Logger.getLogger(LocalizingDynamicSchemaProcessor.class);
037
038    /** Creates a new instance of LocalizingDynamicSchemaProcessor */
039    public LocalizingDynamicSchemaProcessor() {
040    }
041
042    private ResourceBundle bundle;
043
044    /**
045     * Regular expression for variables.
046     */
047    private static final Pattern pattern = Pattern.compile("(%\\{.*?\\})");
048
049    /**
050     * Populates the bundle with the given resource.
051     *
052     * <p>The name of the property file is typically the name of a class, as
053     * per {@link ResourceBundle#getBundle(String)}. However, for backwards
054     * compatibility, the name can contain slashes (which are converted to
055     * dots) and end with ".properties" (which is removed). Therefore
056     * "com/acme/MyResource.properties" is equivalent to
057     * "com.acme.MyResource".
058     *
059     * @see MondrianProperties#LocalePropFile
060     *
061     * @param propFile The name of the property file
062     */
063    void populate(String propFile) {
064        if (propFile.endsWith(".properties")) {
065            propFile =
066                propFile.substring(
067                    0,
068                    propFile.length() - ".properties".length());
069        }
070        try {
071            bundle = ResourceBundle.getBundle(
072                propFile,
073                Util.parseLocale(locale),
074                getClass().getClassLoader());
075        } catch (Exception e) {
076            LOGGER.warn(
077                "Mondrian: Warning: no suitable locale file found for locale '"
078                    + locale
079                    + "'",
080                e);
081        }
082    }
083
084    private void loadProperties() {
085        String propFile = MondrianProperties.instance().LocalePropFile.get();
086        if (propFile != null) {
087            populate(propFile);
088        }
089    }
090
091    public String filter(
092        String schemaUrl,
093        Util.PropertyList connectInfo,
094        InputStream stream) throws Exception
095    {
096        setLocale(connectInfo.get("Locale"));
097
098        loadProperties();
099
100        String schema = super.filter(schemaUrl, connectInfo, stream);
101        if (bundle != null) {
102            schema = doRegExReplacements(schema);
103        }
104        LOGGER.debug(schema);
105        return schema;
106    }
107
108    private String doRegExReplacements(String schema) {
109        // As of JDK 1.5, cannot use StringBuilder - appendReplacement requires
110        // the antediluvian StringBuffer.
111        StringBuffer intlSchema = new StringBuffer();
112        Matcher match = pattern.matcher(schema);
113        String key;
114        while (match.find()) {
115            key = extractKey(match.group());
116            int start = match.start();
117            int end = match.end();
118
119            try {
120                String intlProperty = bundle.getString(key);
121                if (intlProperty != null) {
122                    match.appendReplacement(intlSchema, intlProperty);
123                }
124            } catch (MissingResourceException e) {
125                LOGGER.error("Missing resource for key [" + key + "]", e);
126            } catch (NullPointerException e) {
127                LOGGER.error(
128                    "missing resource key at substring(" + start + "," + end
129                    + ")",
130                    e);
131            }
132        }
133        match.appendTail(intlSchema);
134        return intlSchema.toString();
135    }
136
137    private String extractKey(String group) {
138        // removes leading '%{' and tailing '%' from the matched string
139        // to obtain the required key
140        return group.substring(2, group.length() - 1);
141    }
142
143    /**
144     * Property locale.
145     */
146    private String locale;
147
148    /**
149     * Returns the property locale.
150     *
151     * @return Value of property locale.
152     */
153    public String getLocale() {
154        return this.locale;
155    }
156
157    /**
158     * Sets the property locale.
159     *
160     * @param locale New value of property locale.
161     */
162    public void setLocale(String locale) {
163        this.locale = locale;
164    }
165}
166
167// End LocalizingDynamicSchemaProcessor.java