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