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 2014-2015 ForgeRock AS 025 */ 026package org.opends.server.core; 027 028import static org.forgerock.util.Utils.*; 029import static org.opends.messages.ConfigMessages.*; 030import static org.opends.server.util.StaticUtils.*; 031 032import java.io.File; 033import java.io.FileReader; 034import java.io.FilenameFilter; 035import java.io.IOException; 036import java.util.ArrayList; 037import java.util.Collections; 038import java.util.List; 039 040import org.forgerock.i18n.LocalizableMessage; 041import org.forgerock.i18n.slf4j.LocalizedLogger; 042import org.forgerock.opendj.config.ClassPropertyDefinition; 043import org.forgerock.opendj.config.server.ConfigException; 044import org.forgerock.opendj.ldap.Entry; 045import org.forgerock.opendj.ldap.schema.Schema; 046import org.forgerock.opendj.ldap.schema.SchemaBuilder; 047import org.forgerock.opendj.ldif.EntryReader; 048import org.forgerock.opendj.ldif.LDIFEntryReader; 049import org.forgerock.opendj.server.config.meta.SchemaProviderCfgDefn; 050import org.forgerock.opendj.server.config.server.RootCfg; 051import org.forgerock.opendj.server.config.server.SchemaProviderCfg; 052import org.forgerock.util.Utils; 053import org.opends.server.schema.SchemaProvider; 054import org.opends.server.schema.SchemaUpdater; 055import org.opends.server.types.InitializationException; 056 057/** 058 * Responsible for loading the server schema. 059 * <p> 060 * The schema is loaded in three steps : 061 * <ul> 062 * <li>Start from the core schema.</li> 063 * <li>Load schema elements from the schema providers defined in configuration.</li> 064 * <li>Load all schema files located in the schema directory.</li> 065 * </ul> 066 */ 067public final class SchemaHandler 068{ 069 private static final String CORE_SCHEMA_PROVIDER_NAME = "Core Schema"; 070 071 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 072 073 private ServerContext serverContext; 074 075 private long oldestModificationTime = -1L; 076 077 private long youngestModificationTime = -1L; 078 079 /** 080 * Creates a new instance. 081 */ 082 public SchemaHandler() 083 { 084 // no implementation. 085 } 086 087 /** 088 * Initialize this schema handler. 089 * 090 * @param serverContext 091 * The server context. 092 * @throws ConfigException 093 * If a configuration problem arises in the process of performing 094 * the initialization. 095 * @throws InitializationException 096 * If a problem that is not configuration-related occurs during 097 * initialization. 098 */ 099 public void initialize(final ServerContext serverContext) throws InitializationException, ConfigException 100 { 101 this.serverContext = serverContext; 102 103 final RootCfg rootConfiguration = serverContext.getServerManagementContext().getRootConfiguration(); 104 final SchemaUpdater schemaUpdater = serverContext.getSchemaUpdater(); 105 106 // Start from the core schema (TODO: or start with empty schema and add core schema in core schema provider ?) 107 final SchemaBuilder schemaBuilder = new SchemaBuilder(Schema.getCoreSchema()); 108 109 // Take providers into account. 110 loadSchemaFromProviders(rootConfiguration, schemaBuilder, schemaUpdater); 111 112 // Take schema files into account (TODO : or load files using provider mechanism ?) 113 completeSchemaFromFiles(schemaBuilder); 114 115 schemaUpdater.updateSchema(schemaBuilder.toSchema()); 116 } 117 118 /** 119 * Load the schema from provided root configuration. 120 * 121 * @param rootConfiguration 122 * The root to retrieve schema provider configurations. 123 * @param schemaBuilder 124 * The schema builder that providers should update. 125 * @param schemaUpdater 126 * The updater that providers should use when applying a 127 * configuration change. 128 */ 129 private void loadSchemaFromProviders(final RootCfg rootConfiguration, final SchemaBuilder schemaBuilder, 130 final SchemaUpdater schemaUpdater) throws ConfigException, InitializationException { 131 for (final String name : rootConfiguration.listSchemaProviders()) 132 { 133 final SchemaProviderCfg config = rootConfiguration.getSchemaProvider(name); 134 if (config.isEnabled()) 135 { 136 loadSchemaProvider(config.getJavaClass(), config, schemaBuilder, schemaUpdater, true); 137 } 138 else if (name.equals(CORE_SCHEMA_PROVIDER_NAME)) { 139 // TODO : use correct message ERR_CORE_SCHEMA_NOT_ENABLED 140 LocalizableMessage message = LocalizableMessage.raw("Core Schema can't be disabled"); 141 throw new ConfigException(message); 142 } 143 } 144 } 145 146 /** 147 * Load the schema provider from the provided class name. 148 * <p> 149 * If {@code} initialize} is {@code true}, then the provider is initialized, 150 * and the provided schema builder is updated with schema elements fropm the 151 * provider. 152 */ 153 private <T extends SchemaProviderCfg> SchemaProvider<T> loadSchemaProvider(final String className, 154 final T config, final SchemaBuilder schemaBuilder, final SchemaUpdater schemaUpdater, final boolean initialize) 155 throws InitializationException 156 { 157 try 158 { 159 final ClassPropertyDefinition propertyDef = SchemaProviderCfgDefn.getInstance().getJavaClassPropertyDefinition(); 160 final Class<? extends SchemaProvider> providerClass = propertyDef.loadClass(className, SchemaProvider.class); 161 final SchemaProvider<T> provider = providerClass.newInstance(); 162 163 if (initialize) { 164 provider.initialize(config, schemaBuilder, schemaUpdater); 165 } 166 else { 167 final List<LocalizableMessage> unacceptableReasons = new ArrayList<>(); 168 final boolean isAcceptable = provider.isConfigurationAcceptable(config, unacceptableReasons); 169 if (!isAcceptable) 170 { 171 final String reasons = Utils.joinAsString(". ", unacceptableReasons); 172 // TODO : fix message, eg CONFIG SCHEMA PROVIDER CONFIG NOT ACCEPTABLE 173 throw new InitializationException(ERR_CONFIG_ALERTHANDLER_CONFIG_NOT_ACCEPTABLE.get(config.dn(), reasons)); 174 } 175 } 176 return provider; 177 } 178 catch (Exception e) 179 { 180 // TODO : fix message 181 throw new InitializationException(ERR_CONFIG_SCHEMA_SYNTAX_CANNOT_INITIALIZE. 182 get(className, config.dn(), stackTraceToSingleLineString(e)), e); 183 } 184 } 185 186 /** 187 * Retrieves the path to the directory containing the server schema files. 188 * 189 * @return The path to the directory containing the server schema files. 190 */ 191 private File getSchemaDirectoryPath() throws InitializationException 192 { 193 final File dir = serverContext.getEnvironment().getSchemaDirectory(); 194 if (dir == null) 195 { 196 throw new InitializationException(ERR_CONFIG_SCHEMA_NO_SCHEMA_DIR.get(null)); 197 } 198 if (!dir.exists()) 199 { 200 throw new InitializationException(ERR_CONFIG_SCHEMA_NO_SCHEMA_DIR.get(dir.getPath())); 201 } 202 if (!dir.isDirectory()) 203 { 204 throw new InitializationException(ERR_CONFIG_SCHEMA_DIR_NOT_DIRECTORY.get(dir.getPath())); 205 } 206 return dir; 207 } 208 209 /** Returns the LDIF reader on provided LDIF file. The caller must ensure the reader is closed. */ 210 private EntryReader getLDIFReader(final File ldifFile, final Schema schema) 211 throws InitializationException 212 { 213 try 214 { 215 final LDIFEntryReader reader = new LDIFEntryReader(new FileReader(ldifFile)); 216 reader.setSchema(schema); 217 return reader; 218 } 219 catch (Exception e) 220 { 221 // TODO : fix message 222 throw new InitializationException(ERR_CONFIG_FILE_CANNOT_OPEN_FOR_READ.get(ldifFile.getAbsolutePath(), e), e); 223 } 224 } 225 226 /** 227 * Complete the schema with schema files. 228 * 229 * @param schemaBuilder 230 * The schema builder to update with the content of the schema files. 231 * @throws ConfigException 232 * If a configuration problem causes the schema element 233 * initialization to fail. 234 * @throws InitializationException 235 * If a problem occurs while initializing the schema elements that 236 * is not related to the server configuration. 237 */ 238 private void completeSchemaFromFiles(final SchemaBuilder schemaBuilder) 239 throws ConfigException, InitializationException 240 { 241 final File schemaDirectory = getSchemaDirectoryPath(); 242 for (String schemaFile : getSchemaFileNames(schemaDirectory)) 243 { 244 loadSchemaFile(schemaFile, schemaBuilder, Schema.getDefaultSchema()); 245 } 246 } 247 248 /** Returns the list of names of schema files contained in the provided directory. */ 249 private List<String> getSchemaFileNames(final File schemaDirectory) throws InitializationException { 250 try 251 { 252 final File[] schemaFiles = schemaDirectory.listFiles(new SchemaFileFilter()); 253 final List<String> schemaFileNames = new ArrayList<>(schemaFiles.length); 254 255 for (final File f : schemaFiles) 256 { 257 if (f.isFile()) 258 { 259 schemaFileNames.add(f.getName()); 260 } 261 262 final long modificationTime = f.lastModified(); 263 if (oldestModificationTime <= 0L 264 || modificationTime < oldestModificationTime) 265 { 266 oldestModificationTime = modificationTime; 267 } 268 269 if (youngestModificationTime <= 0 270 || modificationTime > youngestModificationTime) 271 { 272 youngestModificationTime = modificationTime; 273 } 274 } 275 // If the oldest and youngest modification timestamps didn't get set 276 // then set them to the current time. 277 if (oldestModificationTime <= 0) 278 { 279 oldestModificationTime = System.currentTimeMillis(); 280 } 281 282 if (youngestModificationTime <= 0) 283 { 284 youngestModificationTime = oldestModificationTime; 285 } 286 Collections.sort(schemaFileNames); 287 return schemaFileNames; 288 } 289 catch (Exception e) 290 { 291 throw new InitializationException(ERR_CONFIG_SCHEMA_CANNOT_LIST_FILES 292 .get(schemaDirectory, getExceptionMessage(e)), e); 293 } 294 } 295 296 /** Returns the schema entry from the provided reader. */ 297 private Entry readSchemaEntry(final EntryReader reader, final File schemaFile) throws InitializationException { 298 try 299 { 300 Entry entry = null; 301 if (reader.hasNext()) 302 { 303 entry = reader.readEntry(); 304 if (reader.hasNext()) 305 { 306 // TODO : fix message 307 logger.warn(WARN_CONFIG_SCHEMA_MULTIPLE_ENTRIES_IN_FILE, schemaFile, ""); 308 } 309 return entry; 310 } 311 else 312 { 313 // TODO : fix message - should be SCHEMA NO LDIF ENTRY 314 throw new InitializationException(WARN_CONFIG_SCHEMA_CANNOT_READ_LDIF_ENTRY.get( 315 schemaFile, "", "")); 316 } 317 } 318 catch (IOException e) 319 { 320 // TODO : fix message 321 throw new InitializationException(WARN_CONFIG_SCHEMA_CANNOT_READ_LDIF_ENTRY.get( 322 schemaFile, "", getExceptionMessage(e)), e); 323 } 324 finally 325 { 326 closeSilently(reader); 327 } 328 } 329 330 /** 331 * Add the schema from the provided schema file to the provided schema 332 * builder. 333 * 334 * @param schemaFileName 335 * The name of the schema file to be loaded 336 * @param schemaBuilder 337 * The schema builder in which the contents of the schema file are to 338 * be loaded. 339 * @param readSchema 340 * The schema used to read the file. 341 * @throws InitializationException 342 * If a problem occurs while initializing the schema elements. 343 */ 344 private void loadSchemaFile(final String schemaFileName, final SchemaBuilder schemaBuilder, final Schema readSchema) 345 throws InitializationException 346 { 347 EntryReader reader = null; 348 try 349 { 350 File schemaFile = new File(getSchemaDirectoryPath(), schemaFileName); 351 reader = getLDIFReader(schemaFile, readSchema); 352 final Entry entry = readSchemaEntry(reader, schemaFile); 353 // TODO : there is no more file information attached to schema elements - we should add support for this 354 // in order to be able to redirect schema elements in the correct file when doing backups 355 schemaBuilder.addSchema(entry, true); 356 } 357 finally { 358 Utils.closeSilently(reader); 359 } 360 } 361 362 /** A file filter implementation that accepts only LDIF files. */ 363 private static class SchemaFileFilter implements FilenameFilter 364 { 365 private static final String LDIF_SUFFIX = ".ldif"; 366 367 @Override 368 public boolean accept(File directory, String filename) 369 { 370 return filename.endsWith(LDIF_SUFFIX); 371 } 372 } 373}