001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2008-2009 Sun Microsystems, Inc. 025 * Portions Copyright 2012-2015 ForgeRock AS 026 */ 027package org.opends.server.schema; 028 029import java.util.Collection; 030import java.util.Collections; 031import java.util.HashMap; 032import java.util.List; 033import java.util.Locale; 034import java.util.Map; 035import java.util.Set; 036 037import org.forgerock.i18n.LocalizableMessage; 038import org.forgerock.i18n.slf4j.LocalizedLogger; 039import org.forgerock.opendj.config.server.ConfigChangeResult; 040import org.forgerock.opendj.config.server.ConfigException; 041import org.forgerock.opendj.ldap.schema.CoreSchema; 042import org.forgerock.opendj.ldap.schema.MatchingRule; 043import org.forgerock.opendj.ldap.schema.Schema; 044import org.opends.server.admin.server.ConfigurationChangeListener; 045import org.opends.server.admin.std.server.CollationMatchingRuleCfg; 046import org.opends.server.api.MatchingRuleFactory; 047import org.opends.server.core.DirectoryServer; 048import org.opends.server.types.DirectoryException; 049import org.opends.server.types.InitializationException; 050import org.opends.server.util.CollectionUtils; 051 052import static org.opends.messages.ConfigMessages.*; 053import static org.opends.messages.SchemaMessages.*; 054 055/** 056 * This class is a factory class for Collation matching rules. It 057 * creates different matching rules based on the configuration entries. 058 */ 059public final class CollationMatchingRuleFactory extends 060 MatchingRuleFactory<CollationMatchingRuleCfg> implements 061 ConfigurationChangeListener<CollationMatchingRuleCfg> 062{ 063 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 064 065 /** Stores the list of available locales on this JVM. */ 066 private static final Set<Locale> supportedLocales = CollectionUtils.newHashSet(Locale.getAvailableLocales()); 067 068 /** Current Configuration. */ 069 private CollationMatchingRuleCfg currentConfig; 070 /** Map of OID and the Matching Rule. */ 071 private final Map<String, MatchingRule> matchingRules = new HashMap<>(); 072 073 074 /** Creates a new instance of CollationMatchingRuleFactory. */ 075 public CollationMatchingRuleFactory() 076 { 077 super(); 078 } 079 080 /** {@inheritDoc} */ 081 @Override 082 public final Collection<org.forgerock.opendj.ldap.schema.MatchingRule> getMatchingRules() 083 { 084 return Collections.unmodifiableCollection(matchingRules.values()); 085 } 086 087 /** 088 * Adds a new mapping of OID and MatchingRule. 089 * 090 * @param oid 091 * OID of the matching rule 092 * @param matchingRule 093 * instance of a MatchingRule. 094 */ 095 private void addMatchingRule(String oid, MatchingRule matchingRule) 096 { 097 matchingRules.put(oid, matchingRule); 098 } 099 100 /** 101 * Clears the Map containing matching Rules. 102 */ 103 private void resetRules() 104 { 105 matchingRules.clear(); 106 } 107 108 /** {@inheritDoc} */ 109 @Override 110 public void initializeMatchingRule(CollationMatchingRuleCfg configuration) 111 throws ConfigException, InitializationException 112 { 113 final Schema coreSchema = CoreSchema.getInstance(); 114 for (String collation : configuration.getCollation()) 115 { 116 CollationMapper mapper = new CollationMapper(collation); 117 118 String nOID = mapper.getNumericOID(); 119 String languageTag = mapper.getLanguageTag(); 120 if (nOID == null || languageTag == null) 121 { 122 logger.error(WARN_ATTR_INVALID_COLLATION_MATCHING_RULE_FORMAT, collation); 123 continue; 124 } 125 126 Locale locale = getLocale(languageTag); 127 if (locale != null) 128 { 129 try 130 { 131 final int[] numericSuffixes = { 1, 2, 3, 4, 5, 6 }; 132 for (int suffix : numericSuffixes) 133 { 134 final String oid = nOID + "." + suffix; 135 addMatchingRule(oid, coreSchema.getMatchingRule(oid)); 136 } 137 // the default (equality) matching rule 138 addMatchingRule(nOID, coreSchema.getMatchingRule(nOID)); 139 } 140 catch (Exception e) 141 { 142 logger.error(LocalizableMessage.raw("Error when adding a collation matching rule with oid %s, tag %s: %s", 143 nOID, languageTag, e.getMessage())); 144 } 145 } 146 else 147 { 148 // This locale is not supported by JVM. 149 logger.error(WARN_ATTR_INVALID_COLLATION_MATCHING_RULE_LOCALE, 150 collation, configuration.dn().toString(), languageTag); 151 } 152 } 153 154 // Save this configuration. 155 currentConfig = configuration; 156 157 // Register for change events. 158 currentConfig.addCollationChangeListener(this); 159 } 160 161 /** {@inheritDoc} */ 162 @Override 163 public void finalizeMatchingRule() 164 { 165 // De-register the listener. 166 currentConfig.removeCollationChangeListener(this); 167 } 168 169 /** {@inheritDoc} */ 170 @Override 171 public ConfigChangeResult applyConfigurationChange( 172 CollationMatchingRuleCfg configuration) 173 { 174 final ConfigChangeResult ccr = new ConfigChangeResult(); 175 176 if (!configuration.isEnabled() 177 || currentConfig.isEnabled() != configuration.isEnabled()) 178 { 179 // Don't do anything if: 180 // 1. The configuration is disabled. 181 // 2. There is a change in the enable status 182 // i.e. (disable->enable or enable->disable). In this case, the 183 // ConfigManager will have already created the new Factory object. 184 return ccr; 185 } 186 187 // Since we have come here it means that this Factory is enabled and 188 // there is a change in the CollationMatchingRuleFactory's 189 // configuration. 190 // Deregister all the Matching Rule corresponding to this factory. 191 for (MatchingRule rule : getMatchingRules()) 192 { 193 DirectoryServer.deregisterMatchingRule(rule); 194 } 195 196 // Clear the associated matching rules. 197 resetRules(); 198 199 final Schema coreSchema = CoreSchema.getInstance(); 200 for (String collation : configuration.getCollation()) 201 { 202 // validation has already been performed in isConfigurationChangeAcceptable() 203 CollationMapper mapper = new CollationMapper(collation); 204 String nOID = mapper.getNumericOID(); 205 addMatchingRule(nOID, coreSchema.getMatchingRule(nOID)); 206 } 207 208 try 209 { 210 for (MatchingRule matchingRule : getMatchingRules()) 211 { 212 DirectoryServer.registerMatchingRule(matchingRule, false); 213 } 214 } 215 catch (DirectoryException de) 216 { 217 LocalizableMessage message = 218 WARN_CONFIG_SCHEMA_MR_CONFLICTING_MR.get(configuration.dn(), de.getMessageObject()); 219 ccr.setAdminActionRequired(true); 220 ccr.addMessage(message); 221 } 222 currentConfig = configuration; 223 return ccr; 224 } 225 226 /** {@inheritDoc} */ 227 @Override 228 public boolean isConfigurationChangeAcceptable( 229 CollationMatchingRuleCfg configuration, 230 List<LocalizableMessage> unacceptableReasons) 231 { 232 boolean configAcceptable = true; 233 234 // If the new configuration disables this factory, don't do 235 // anything. 236 if (!configuration.isEnabled()) 237 { 238 return configAcceptable; 239 } 240 241 // If it comes here we don't need to verify MatchingRuleType; it 242 // should be okay as its syntax is verified by the admin framework. 243 // Iterate over the collations and verify if the format is okay. 244 // Also, verify if the locale is allowed by the JVM. 245 for (String collation : configuration.getCollation()) 246 { 247 CollationMapper mapper = new CollationMapper(collation); 248 249 String nOID = mapper.getNumericOID(); 250 String languageTag = mapper.getLanguageTag(); 251 if (nOID == null || languageTag == null) 252 { 253 configAcceptable = false; 254 LocalizableMessage msg = WARN_ATTR_INVALID_COLLATION_MATCHING_RULE_FORMAT.get(collation); 255 unacceptableReasons.add(msg); 256 continue; 257 } 258 259 Locale locale = getLocale(languageTag); 260 if (locale == null) 261 { 262 LocalizableMessage msg = WARN_ATTR_INVALID_COLLATION_MATCHING_RULE_LOCALE.get( 263 collation, configuration.dn(), languageTag); 264 unacceptableReasons.add(msg); 265 configAcceptable = false; 266 continue; 267 } 268 } 269 return configAcceptable; 270 } 271 272 273 /** 274 * Verifies if the locale is supported by the JVM. 275 * 276 * @param lTag 277 * The language tag specified in the configuration. 278 * @return Locale The locale corresponding to the languageTag. 279 */ 280 private Locale getLocale(String lTag) 281 { 282 // Separates the language and the country from the locale. 283 Locale locale; 284 285 int countryIndex = lTag.indexOf("-"); 286 int variantIndex = lTag.lastIndexOf("-"); 287 288 if (countryIndex > 0) 289 { 290 String lang = lTag.substring(0, countryIndex); 291 String country; 292 293 if (variantIndex > countryIndex) 294 { 295 country = lTag.substring(countryIndex + 1, variantIndex); 296 String variant = lTag.substring(variantIndex + 1, lTag.length()); 297 locale = new Locale(lang, country, variant); 298 } 299 else 300 { 301 country = lTag.substring(countryIndex + 1, lTag.length()); 302 locale = new Locale(lang, country); 303 } 304 } 305 else 306 { 307 locale = new Locale(lTag); 308 } 309 310 if (!supportedLocales.contains(locale)) 311 { 312 // This locale is not supported by this JVM. 313 locale = null; 314 } 315 return locale; 316 } 317 318 /** 319 * A utility class for extracting the OID and Language Tag from the 320 * configuration entry. 321 */ 322 private final class CollationMapper 323 { 324 /** OID of the collation rule. */ 325 private String oid; 326 327 /** Language Tag. */ 328 private String lTag; 329 330 /** 331 * Creates a new instance of CollationMapper. 332 * 333 * @param collation 334 * The collation text in the LOCALE:OID format. 335 */ 336 private CollationMapper(String collation) 337 { 338 int index = collation.indexOf(":"); 339 if (index > 0) 340 { 341 oid = collation.substring(index + 1, collation.length()); 342 lTag = collation.substring(0, index); 343 } 344 } 345 346 /** 347 * Returns the OID part of the collation text. 348 * 349 * @return OID part of the collation text. 350 */ 351 private String getNumericOID() 352 { 353 return oid; 354 } 355 356 /** 357 * Returns the language Tag of collation text. 358 * 359 * @return Language Tag part of the collation text. 360 */ 361 private String getLanguageTag() 362 { 363 return lTag; 364 } 365 } 366}