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 2013-2015 ForgeRock AS. 026 */ 027package org.opends.server.backends.jeb; 028 029import java.io.IOException; 030import java.util.Collection; 031import java.util.LinkedList; 032import java.util.List; 033 034import org.forgerock.i18n.LocalizableMessage; 035import org.forgerock.i18n.slf4j.LocalizedLogger; 036import org.forgerock.opendj.io.ASN1; 037import org.forgerock.opendj.io.ASN1Reader; 038import org.forgerock.opendj.io.ASN1Writer; 039import org.forgerock.opendj.ldap.ByteStringBuilder; 040import org.opends.server.api.CompressedSchema; 041import org.opends.server.core.DirectoryServer; 042import org.opends.server.types.DirectoryException; 043import org.opends.server.types.InitializationException; 044import org.opends.server.util.StaticUtils; 045 046import com.sleepycat.je.Cursor; 047import com.sleepycat.je.Database; 048import com.sleepycat.je.DatabaseConfig; 049import com.sleepycat.je.DatabaseEntry; 050import com.sleepycat.je.DatabaseException; 051import com.sleepycat.je.Environment; 052import com.sleepycat.je.LockConflictException; 053import com.sleepycat.je.OperationStatus; 054 055import static com.sleepycat.je.LockMode.*; 056import static com.sleepycat.je.OperationStatus.*; 057 058import static org.opends.messages.BackendMessages.*; 059 060/** 061 * This class provides a compressed schema implementation whose definitions are 062 * stored in a Berkeley DB JE database. 063 */ 064public final class JECompressedSchema extends CompressedSchema 065{ 066 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 067 068 /** The name of the database used to store compressed attribute description definitions. */ 069 private static final String DB_NAME_AD = "compressed_attributes"; 070 /** The name of the database used to store compressed object class set definitions. */ 071 private static final String DB_NAME_OC = "compressed_object_classes"; 072 073 /** The compressed attribute description schema database. */ 074 private Database adDatabase; 075 /** The environment in which the databases are held. */ 076 private Environment environment; 077 /** The compressed object class set schema database. */ 078 private Database ocDatabase; 079 080 private final ByteStringBuilder storeAttributeWriterBuffer = new ByteStringBuilder(); 081 private final ASN1Writer storeAttributeWriter = ASN1.getWriter(storeAttributeWriterBuffer); 082 private final ByteStringBuilder storeObjectClassesWriterBuffer = new ByteStringBuilder(); 083 private final ASN1Writer storeObjectClassesWriter = ASN1.getWriter(storeObjectClassesWriterBuffer); 084 085 086 087 /** 088 * Creates a new instance of this JE compressed schema manager. 089 * 090 * @param environment 091 * A reference to the database environment in which the databases 092 * will be held. 093 * @throws DatabaseException 094 * If a database problem occurs while loading the compressed schema 095 * definitions from the database. 096 * @throws InitializationException 097 * If an error occurs while loading and processing the compressed 098 * schema definitions. 099 */ 100 public JECompressedSchema(final Environment environment) 101 throws DatabaseException, InitializationException 102 { 103 this.environment = environment; 104 load(); 105 } 106 107 108 109 /** 110 * Closes the databases and releases any resources held by this compressed 111 * schema manager. 112 */ 113 public void close() 114 { 115 close0(adDatabase); 116 close0(ocDatabase); 117 118 adDatabase = null; 119 ocDatabase = null; 120 environment = null; 121 } 122 123 private void close0(Database database) 124 { 125 try 126 { 127 database.sync(); 128 } 129 catch (final Exception e) 130 { 131 // Ignore. 132 } 133 StaticUtils.close(database); 134 } 135 136 137 138 /** {@inheritDoc} */ 139 @Override 140 protected void storeAttribute(final byte[] encodedAttribute, 141 final String attributeName, final Collection<String> attributeOptions) 142 throws DirectoryException 143 { 144 try 145 { 146 storeAttributeWriterBuffer.clear(); 147 storeAttributeWriter.writeStartSequence(); 148 storeAttributeWriter.writeOctetString(attributeName); 149 for (final String option : attributeOptions) 150 { 151 storeAttributeWriter.writeOctetString(option); 152 } 153 storeAttributeWriter.writeEndSequence(); 154 store(adDatabase, encodedAttribute, storeAttributeWriterBuffer); 155 } 156 catch (final IOException e) 157 { 158 // TODO: Shouldn't happen but should log a message 159 } 160 } 161 162 163 164 /** {@inheritDoc} */ 165 @Override 166 protected void storeObjectClasses(final byte[] encodedObjectClasses, 167 final Collection<String> objectClassNames) throws DirectoryException 168 { 169 try 170 { 171 storeObjectClassesWriterBuffer.clear(); 172 storeObjectClassesWriter.writeStartSequence(); 173 for (final String ocName : objectClassNames) 174 { 175 storeObjectClassesWriter.writeOctetString(ocName); 176 } 177 storeObjectClassesWriter.writeEndSequence(); 178 store(ocDatabase, encodedObjectClasses, storeObjectClassesWriterBuffer); 179 } 180 catch (final IOException e) 181 { 182 // TODO: Shouldn't happen but should log a message 183 } 184 } 185 186 187 188 /** 189 * Loads the compressed schema information from the database. 190 * 191 * @throws DatabaseException 192 * If a database error occurs while loading the definitions from the 193 * database. 194 * @throws InitializationException 195 * If an error occurs while loading and processing the definitions. 196 */ 197 private void load() throws DatabaseException, InitializationException 198 { 199 final DatabaseConfig dbConfig = JEBUtils.toDatabaseConfigNoDuplicates(environment); 200 201 adDatabase = environment.openDatabase(null, DB_NAME_AD, dbConfig); 202 ocDatabase = environment.openDatabase(null, DB_NAME_OC, dbConfig); 203 204 // Cursor through the object class database and load the object class set 205 // definitions. At the same time, figure out the highest token value and 206 // initialize the object class counter to one greater than that. 207 final Cursor ocCursor = ocDatabase.openCursor(null, null); 208 try 209 { 210 final DatabaseEntry keyEntry = new DatabaseEntry(); 211 final DatabaseEntry valueEntry = new DatabaseEntry(); 212 OperationStatus status = ocCursor.getFirst(keyEntry, valueEntry, READ_UNCOMMITTED); 213 while (status == SUCCESS) 214 { 215 final byte[] encodedObjectClasses = keyEntry.getData(); 216 final ASN1Reader reader = ASN1.getReader(valueEntry.getData()); 217 reader.readStartSequence(); 218 final List<String> objectClassNames = new LinkedList<>(); 219 while (reader.hasNextElement()) 220 { 221 objectClassNames.add(reader.readOctetStringAsString()); 222 } 223 reader.readEndSequence(); 224 loadObjectClasses(encodedObjectClasses, objectClassNames); 225 status = ocCursor.getNext(keyEntry, valueEntry, READ_UNCOMMITTED); 226 } 227 } 228 catch (final IOException e) 229 { 230 logger.traceException(e); 231 throw new InitializationException( 232 ERR_COMPSCHEMA_CANNOT_DECODE_OC_TOKEN.get(e.getMessage()), e); 233 } 234 finally 235 { 236 ocCursor.close(); 237 } 238 239 // Cursor through the attribute description database and load the attribute 240 // set definitions. 241 final Cursor adCursor = adDatabase.openCursor(null, null); 242 try 243 { 244 final DatabaseEntry keyEntry = new DatabaseEntry(); 245 final DatabaseEntry valueEntry = new DatabaseEntry(); 246 OperationStatus status = adCursor.getFirst(keyEntry, valueEntry, READ_UNCOMMITTED); 247 while (status == SUCCESS) 248 { 249 final byte[] encodedAttribute = keyEntry.getData(); 250 final ASN1Reader reader = ASN1.getReader(valueEntry.getData()); 251 reader.readStartSequence(); 252 final String attributeName = reader.readOctetStringAsString(); 253 final List<String> attributeOptions = new LinkedList<>(); 254 while (reader.hasNextElement()) 255 { 256 attributeOptions.add(reader.readOctetStringAsString()); 257 } 258 reader.readEndSequence(); 259 loadAttribute(encodedAttribute, attributeName, attributeOptions); 260 status = adCursor.getNext(keyEntry, valueEntry, READ_UNCOMMITTED); 261 } 262 } 263 catch (final IOException e) 264 { 265 logger.traceException(e); 266 throw new InitializationException( 267 ERR_COMPSCHEMA_CANNOT_DECODE_AD_TOKEN.get(e.getMessage()), e); 268 } 269 finally 270 { 271 adCursor.close(); 272 } 273 } 274 275 276 277 private void store(final Database database, final byte[] key, final ByteStringBuilder value) throws DirectoryException 278 { 279 if (!putNoOverwrite(database, key, value)) 280 { 281 final LocalizableMessage m = ERR_JEB_COMPSCHEMA_CANNOT_STORE_MULTIPLE_FAILURES.get(); 282 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), m); 283 } 284 } 285 286 private boolean putNoOverwrite(final Database database, final byte[] key, final ByteStringBuilder value) 287 throws DirectoryException 288 { 289 final DatabaseEntry keyEntry = new DatabaseEntry(key); 290 final DatabaseEntry valueEntry = new DatabaseEntry(value.getBackingArray(), 0, value.length()); 291 for (int i = 0; i < 3; i++) 292 { 293 try 294 { 295 final OperationStatus status = database.putNoOverwrite(null, keyEntry, valueEntry); 296 if (status != SUCCESS) 297 { 298 final LocalizableMessage m = ERR_JEB_COMPSCHEMA_CANNOT_STORE_STATUS.get(status); 299 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), m); 300 } 301 return true; 302 } 303 catch (final LockConflictException ce) 304 { 305 continue; 306 } 307 catch (final DatabaseException de) 308 { 309 throw new DirectoryException( 310 DirectoryServer.getServerErrorResultCode(), ERR_COMPSCHEMA_CANNOT_STORE_EX.get(de.getMessage()), de); 311 } 312 } 313 return false; 314 } 315 316}