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