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 Sun Microsystems, Inc. 025 * Portions Copyright 2013-2015 ForgeRock AS. 026 */ 027package org.opends.server.core; 028 029import java.io.File; 030import java.io.FileInputStream; 031import java.io.FileOutputStream; 032import java.util.Collection; 033import java.util.LinkedList; 034import java.util.List; 035import java.util.Map.Entry; 036 037import org.forgerock.i18n.LocalizableMessage; 038import org.forgerock.i18n.slf4j.LocalizedLogger; 039import org.forgerock.opendj.io.ASN1; 040import org.forgerock.opendj.io.ASN1Reader; 041import org.forgerock.opendj.io.ASN1Writer; 042import org.forgerock.opendj.ldap.ByteString; 043import org.opends.server.api.CompressedSchema; 044import org.opends.server.types.DirectoryException; 045 046import static org.opends.messages.CoreMessages.*; 047import static org.opends.server.config.ConfigConstants.*; 048import static org.opends.server.util.StaticUtils.*; 049 050/** 051 * This class provides a default implementation of a compressed schema manager 052 * that will store the schema definitions in a binary file 053 * (config/schematokens.dat). 054 */ 055public final class DefaultCompressedSchema extends CompressedSchema 056{ 057 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 058 059 /** Synchronizes calls to save. */ 060 private final Object saveLock = new Object(); 061 062 063 064 /** 065 * Creates a new instance of this compressed schema manager. 066 */ 067 public DefaultCompressedSchema() 068 { 069 load(); 070 } 071 072 073 074 /** {@inheritDoc} */ 075 @Override 076 protected void storeAttribute(final byte[] encodedAttribute, 077 final String attributeName, final Collection<String> attributeOptions) 078 throws DirectoryException 079 { 080 save(); 081 } 082 083 084 085 /** {@inheritDoc} */ 086 @Override 087 protected void storeObjectClasses(final byte[] encodedObjectClasses, 088 final Collection<String> objectClassNames) throws DirectoryException 089 { 090 save(); 091 } 092 093 094 095 /** 096 * Loads the compressed schema information from disk. 097 */ 098 private void load() 099 { 100 FileInputStream inputStream = null; 101 102 try 103 { 104 // Determine the location of the compressed schema data file. It should 105 // be in the config directory with a name of "schematokens.dat". If that 106 // file doesn't exist, then don't do anything. 107 final String path = DirectoryServer.getInstanceRoot() + File.separator 108 + CONFIG_DIR_NAME + File.separator + COMPRESSED_SCHEMA_FILE_NAME; 109 if (!new File(path).exists()) 110 { 111 return; 112 } 113 inputStream = new FileInputStream(path); 114 final ASN1Reader reader = ASN1.getReader(inputStream); 115 116 // The first element in the file should be a sequence of object class 117 // sets. Each object class set will itself be a sequence of octet 118 // strings, where the first one is the token and the remaining elements 119 // are the names of the associated object classes. 120 reader.readStartSequence(); 121 while (reader.hasNextElement()) 122 { 123 reader.readStartSequence(); 124 final byte[] encodedObjectClasses = reader.readOctetString() 125 .toByteArray(); 126 final List<String> objectClassNames = new LinkedList<>(); 127 while (reader.hasNextElement()) 128 { 129 objectClassNames.add(reader.readOctetStringAsString()); 130 } 131 reader.readEndSequence(); 132 loadObjectClasses(encodedObjectClasses, objectClassNames); 133 } 134 reader.readEndSequence(); 135 136 // The second element in the file should be an integer element that holds 137 // the value to use to initialize the object class counter. 138 reader.readInteger(); // No longer used. 139 140 // The third element in the file should be a sequence of attribute 141 // description components. Each attribute description component will 142 // itself be a sequence of octet strings, where the first one is the 143 // token, the second is the attribute name, and all remaining elements are 144 // the attribute options. 145 reader.readStartSequence(); 146 while (reader.hasNextElement()) 147 { 148 reader.readStartSequence(); 149 final byte[] encodedAttribute = reader.readOctetString().toByteArray(); 150 final String attributeName = reader.readOctetStringAsString(); 151 final List<String> attributeOptions = new LinkedList<>(); 152 while (reader.hasNextElement()) 153 { 154 attributeOptions.add(reader.readOctetStringAsString()); 155 } 156 reader.readEndSequence(); 157 loadAttribute(encodedAttribute, attributeName, attributeOptions); 158 } 159 reader.readEndSequence(); 160 161 // The fourth element in the file should be an integer element that holds 162 // the value to use to initialize the attribute description counter. 163 reader.readInteger(); // No longer used. 164 } 165 catch (final Exception e) 166 { 167 logger.traceException(e); 168 169 // FIXME -- Should we do something else here? 170 throw new RuntimeException(e); 171 } 172 finally 173 { 174 close(inputStream); 175 } 176 } 177 178 179 180 /** 181 * Writes the compressed schema information to disk. 182 * 183 * @throws DirectoryException 184 * If a problem occurs while writing the updated information. 185 */ 186 private void save() throws DirectoryException 187 { 188 synchronized (saveLock) 189 { 190 FileOutputStream outputStream = null; 191 try 192 { 193 // Determine the location of the "live" compressed schema data file, and 194 // then append ".tmp" to get the name of the temporary file that we will 195 // use. 196 final String path = DirectoryServer.getInstanceRoot() + File.separator 197 + CONFIG_DIR_NAME + File.separator + COMPRESSED_SCHEMA_FILE_NAME; 198 final String tempPath = path + ".tmp"; 199 200 outputStream = new FileOutputStream(tempPath); 201 final ASN1Writer writer = ASN1.getWriter(outputStream); 202 203 // The first element in the file should be a sequence of object class 204 // sets. Each object class set will itself be a sequence of octet 205 // strings, where the first one is the token and the remaining elements 206 // are the names of the associated object classes. 207 writer.writeStartSequence(); 208 int ocCounter = 1; 209 for (final Entry<byte[], Collection<String>> mapEntry : 210 getAllObjectClasses()) 211 { 212 writer.writeStartSequence(); 213 writer.writeOctetString(ByteString.wrap(mapEntry.getKey())); 214 final Collection<String> objectClassNames = mapEntry.getValue(); 215 for (final String ocName : objectClassNames) 216 { 217 writer.writeOctetString(ocName); 218 } 219 writer.writeEndSequence(); 220 ocCounter++; 221 } 222 writer.writeEndSequence(); 223 224 // The second element in the file should be an integer element that 225 // holds the value to use to initialize the object class counter. 226 writer.writeInteger(ocCounter); // No longer used. 227 228 // The third element in the file should be a sequence of attribute 229 // description components. Each attribute description component will 230 // itself be a sequence of octet strings, where the first one is the 231 // token, the second is the attribute name, and all remaining elements 232 // are the attribute options. 233 writer.writeStartSequence(); 234 int adCounter = 1; 235 for (final Entry<byte[], Entry<String, Collection<String>>> mapEntry : 236 getAllAttributes()) 237 { 238 writer.writeStartSequence(); 239 writer.writeOctetString(ByteString.wrap(mapEntry.getKey())); 240 writer.writeOctetString(mapEntry.getValue().getKey()); 241 for (final String option : mapEntry.getValue().getValue()) 242 { 243 writer.writeOctetString(option); 244 } 245 writer.writeEndSequence(); 246 adCounter++; 247 } 248 writer.writeEndSequence(); 249 250 // The fourth element in the file should be an integer element that 251 // holds the value to use to initialize the attribute description 252 // counter. 253 writer.writeInteger(adCounter); // No longer used. 254 255 // Close the writer and swing the temp file into place. 256 outputStream.close(); 257 final File liveFile = new File(path); 258 final File tempFile = new File(tempPath); 259 260 if (liveFile.exists()) 261 { 262 final File saveFile = new File(liveFile.getAbsolutePath() + ".save"); 263 if (saveFile.exists()) 264 { 265 saveFile.delete(); 266 } 267 liveFile.renameTo(saveFile); 268 } 269 tempFile.renameTo(liveFile); 270 } 271 catch (final Exception e) 272 { 273 logger.traceException(e); 274 275 final LocalizableMessage message = ERR_COMPRESSEDSCHEMA_CANNOT_WRITE_UPDATED_DATA 276 .get(stackTraceToSingleLineString(e)); 277 throw new DirectoryException( 278 DirectoryServer.getServerErrorResultCode(), message, e); 279 } 280 finally 281 { 282 close(outputStream); 283 } 284 } 285 } 286 287}