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