| | |
| | | import org.opends.server.api.plugin.LDIFPluginResult; |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.core.PluginConfigManager; |
| | | import org.opends.server.protocols.asn1.ASN1Element; |
| | | import org.opends.server.protocols.asn1.ASN1OctetString; |
| | | import org.opends.server.util.LDIFException; |
| | | |
| | |
| | | |
| | | |
| | | /** |
| | | * Encodes this entry into a form that is suitable for long-term |
| | | * persistent storage. The encoding will have a version number so |
| | | * that if the way we store entries changes in the future we will |
| | | * still be able to read entries encoded in an older format. |
| | | * |
| | | * @return The entry encoded in a form that is suitable for |
| | | * long-term persistent storage. |
| | | */ |
| | | public byte[] encode() |
| | | { |
| | | // The version number will be one byte. We'll add that later. |
| | | |
| | | |
| | | // The DN will be encoded as a one-to-five byte length followed |
| | | // byte the UTF-8 byte representation. |
| | | byte[] dnBytes = getBytes(dn.toString()); |
| | | byte[] dnLength = ASN1Element.encodeLength(dnBytes.length); |
| | | int totalBytes = 1 + dnBytes.length + dnLength.length; |
| | | |
| | | |
| | | // The object classes will be encoded as one-to-five byte length |
| | | // followed by a zero-delimited UTF-8 byte representation of the |
| | | // names (e.g., top\0person\0organizationalPerson\0inetOrgPerson). |
| | | int i=0; |
| | | int totalOCBytes = objectClasses.size() - 1; |
| | | byte[][] ocBytes = new byte[objectClasses.size()][]; |
| | | for (String ocName : objectClasses.values()) |
| | | { |
| | | ocBytes[i] = getBytes(ocName); |
| | | totalOCBytes += ocBytes[i++].length; |
| | | } |
| | | byte[] ocLength = ASN1Element.encodeLength(totalOCBytes); |
| | | totalBytes += totalOCBytes + ocLength.length; |
| | | |
| | | |
| | | // The user attributes will be encoded as a one-to-five byte |
| | | // number of attributes followed by a sequence of: |
| | | // - A UTF-8 byte representation of the attribute name. |
| | | // - A zero delimiter |
| | | // - A one-to-five byte number of values for the attribute |
| | | // - A sequence of: |
| | | // - A one-to-five byte length for the value |
| | | // - A UTF-8 byte representation for the value |
| | | i=0; |
| | | int numUserAttributes = 0; |
| | | int totalUserAttrBytes = 0; |
| | | LinkedList<byte[]> userAttrBytes = new LinkedList<byte[]>(); |
| | | for (List<Attribute> attrList : userAttributes.values()) |
| | | { |
| | | for (Attribute a : attrList) |
| | | { |
| | | if (a.isVirtual() || (! a.hasValue())) |
| | | { |
| | | continue; |
| | | } |
| | | |
| | | numUserAttributes++; |
| | | |
| | | byte[] nameBytes = getBytes(a.getNameWithOptions()); |
| | | |
| | | int numValues = 0; |
| | | int totalValueBytes = 0; |
| | | LinkedList<byte[]> valueBytes = new LinkedList<byte[]>(); |
| | | for (AttributeValue v : a.getValues()) |
| | | { |
| | | numValues++; |
| | | byte[] vBytes = v.getValueBytes(); |
| | | byte[] vLength = ASN1Element.encodeLength(vBytes.length); |
| | | valueBytes.add(vLength); |
| | | valueBytes.add(vBytes); |
| | | totalValueBytes += vLength.length + vBytes.length; |
| | | } |
| | | byte[] numValuesBytes = ASN1Element.encodeLength(numValues); |
| | | |
| | | byte[] attrBytes = new byte[nameBytes.length + |
| | | numValuesBytes.length + |
| | | totalValueBytes + 1]; |
| | | System.arraycopy(nameBytes, 0, attrBytes, 0, |
| | | nameBytes.length); |
| | | |
| | | int pos = nameBytes.length+1; |
| | | System.arraycopy(numValuesBytes, 0, attrBytes, pos, |
| | | numValuesBytes.length); |
| | | pos += numValuesBytes.length; |
| | | for (byte[] b : valueBytes) |
| | | { |
| | | System.arraycopy(b, 0, attrBytes, pos, b.length); |
| | | pos += b.length; |
| | | } |
| | | |
| | | userAttrBytes.add(attrBytes); |
| | | totalUserAttrBytes += attrBytes.length; |
| | | } |
| | | } |
| | | byte[] userAttrCount = |
| | | ASN1OctetString.encodeLength(numUserAttributes); |
| | | totalBytes += totalUserAttrBytes + userAttrCount.length; |
| | | |
| | | |
| | | // The operational attributes will be encoded in the same way as |
| | | // the user attributes. |
| | | i=0; |
| | | int numOperationalAttributes = 0; |
| | | int totalOperationalAttrBytes = 0; |
| | | LinkedList<byte[]> operationalAttrBytes = |
| | | new LinkedList<byte[]>(); |
| | | for (List<Attribute> attrList : operationalAttributes.values()) |
| | | { |
| | | for (Attribute a : attrList) |
| | | { |
| | | if (a.isVirtual() || (! a.hasValue())) |
| | | { |
| | | continue; |
| | | } |
| | | |
| | | numOperationalAttributes++; |
| | | |
| | | byte[] nameBytes = getBytes(a.getNameWithOptions()); |
| | | |
| | | int numValues = 0; |
| | | int totalValueBytes = 0; |
| | | LinkedList<byte[]> valueBytes = new LinkedList<byte[]>(); |
| | | for (AttributeValue v : a.getValues()) |
| | | { |
| | | numValues++; |
| | | byte[] vBytes = v.getValueBytes(); |
| | | byte[] vLength = ASN1Element.encodeLength(vBytes.length); |
| | | valueBytes.add(vLength); |
| | | valueBytes.add(vBytes); |
| | | totalValueBytes += vLength.length + vBytes.length; |
| | | } |
| | | byte[] numValuesBytes = ASN1Element.encodeLength(numValues); |
| | | |
| | | byte[] attrBytes = new byte[nameBytes.length + |
| | | numValuesBytes.length + |
| | | totalValueBytes + 1]; |
| | | System.arraycopy(nameBytes, 0, attrBytes, 0, |
| | | nameBytes.length); |
| | | |
| | | int pos = nameBytes.length+1; |
| | | System.arraycopy(numValuesBytes, 0, attrBytes, pos, |
| | | numValuesBytes.length); |
| | | pos += numValuesBytes.length; |
| | | for (byte[] b : valueBytes) |
| | | { |
| | | System.arraycopy(b, 0, attrBytes, pos, b.length); |
| | | pos += b.length; |
| | | } |
| | | |
| | | operationalAttrBytes.add(attrBytes); |
| | | totalOperationalAttrBytes += attrBytes.length; |
| | | } |
| | | } |
| | | byte[] operationalAttrCount = |
| | | ASN1OctetString.encodeLength(numOperationalAttributes); |
| | | totalBytes += totalOperationalAttrBytes + |
| | | operationalAttrCount.length; |
| | | |
| | | |
| | | // Now we've got all the data that we need. Create a big byte |
| | | // array to hold it all and pack it in. |
| | | byte[] entryBytes = new byte[totalBytes]; |
| | | |
| | | |
| | | // Add the entry version number as the first byte. |
| | | entryBytes[0] = 0x01; |
| | | |
| | | |
| | | // Next, add the DN length and value. |
| | | System.arraycopy(dnLength, 0, entryBytes, 1, dnLength.length); |
| | | int pos = 1 + dnLength.length; |
| | | System.arraycopy(dnBytes, 0, entryBytes, pos, dnBytes.length); |
| | | pos += dnBytes.length; |
| | | |
| | | |
| | | // Next, add the object classes length and values. |
| | | System.arraycopy(ocLength, 0, entryBytes, pos, ocLength.length); |
| | | pos += ocLength.length; |
| | | for (byte[] b : ocBytes) |
| | | { |
| | | System.arraycopy(b, 0, entryBytes, pos, b.length); |
| | | pos += b.length + 1; |
| | | } |
| | | |
| | | // We need to back up one because there's no zero-teriminator |
| | | // after the last object class name. |
| | | pos--; |
| | | |
| | | |
| | | // Next, add the user attribute count and the user attribute |
| | | // data. |
| | | System.arraycopy(userAttrCount, 0, entryBytes, pos, |
| | | userAttrCount.length); |
| | | pos += userAttrCount.length; |
| | | for (byte[] b : userAttrBytes) |
| | | { |
| | | System.arraycopy(b, 0, entryBytes, pos, b.length); |
| | | pos += b.length; |
| | | } |
| | | |
| | | |
| | | // Finally, add the operational attribute count and the |
| | | // operational attribute data. |
| | | System.arraycopy(operationalAttrCount, 0, entryBytes, pos, |
| | | operationalAttrCount.length); |
| | | pos += operationalAttrCount.length; |
| | | for (byte[] b : operationalAttrBytes) |
| | | { |
| | | System.arraycopy(b, 0, entryBytes, pos, b.length); |
| | | pos += b.length; |
| | | } |
| | | |
| | | return entryBytes; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Decodes the provided byte array as an entry. |
| | | * |
| | | * @param entryBytes The byte array containing the data to be |
| | | * decoded. |
| | | * |
| | | * @return The decoded entry. |
| | | * |
| | | * @throws DirectoryException If the provided byte array cannot be |
| | | * decoded as an entry. |
| | | */ |
| | | public static Entry decode(byte[] entryBytes) |
| | | throws DirectoryException |
| | | { |
| | | try |
| | | { |
| | | // The first byte must be the entry version. If it's not one |
| | | // we recognize, then that's an error. |
| | | // NOTE: If we ever change the encoding, then we'll need a |
| | | // new method for each one. |
| | | if (entryBytes[0] != 0x01) |
| | | { |
| | | int msgID = MSGID_ENTRY_DECODE_UNRECOGNIZED_VERSION; |
| | | String message = getMessage(msgID, |
| | | byteToHex(entryBytes[0])); |
| | | throw new DirectoryException( |
| | | DirectoryServer.getServerErrorResultCode(), |
| | | message, msgID); |
| | | } |
| | | |
| | | |
| | | // Next is the length of the DN. It may be a single byte or |
| | | // multiple bytes. |
| | | int pos = 1; |
| | | int dnLength = entryBytes[pos] & 0x7F; |
| | | if (entryBytes[pos++] != dnLength) |
| | | { |
| | | int numLengthBytes = dnLength; |
| | | dnLength = 0; |
| | | for (int i=0; i < numLengthBytes; i++, pos++) |
| | | { |
| | | dnLength = (dnLength << 8) | (entryBytes[pos] & 0xFF); |
| | | } |
| | | } |
| | | |
| | | |
| | | // Next is the DN itself. |
| | | byte[] dnBytes = new byte[dnLength]; |
| | | System.arraycopy(entryBytes, pos, dnBytes, 0, dnLength); |
| | | pos += dnLength; |
| | | DN dn = DN.decode(new ASN1OctetString(dnBytes)); |
| | | |
| | | |
| | | // Next is the length of the object classes. It may be a single |
| | | // byte or multiple bytes. |
| | | int ocLength = entryBytes[pos] & 0x7F; |
| | | if (entryBytes[pos++] != ocLength) |
| | | { |
| | | int numLengthBytes = ocLength; |
| | | ocLength = 0; |
| | | for (int i=0; i < numLengthBytes; i++, pos++) |
| | | { |
| | | ocLength = (ocLength << 8) | (entryBytes[pos] & 0xFF); |
| | | } |
| | | } |
| | | |
| | | |
| | | // Next is the encoded set of object classes. It will be a |
| | | // single string with the object class names separated by zeros. |
| | | LinkedHashMap<ObjectClass,String> objectClasses = |
| | | new LinkedHashMap<ObjectClass,String>(); |
| | | int startPos = pos; |
| | | for (int i=0; i < ocLength; i++,pos++) |
| | | { |
| | | if (entryBytes[pos] == 0x00) |
| | | { |
| | | String name = new String(entryBytes, startPos, pos-startPos, |
| | | "UTF-8"); |
| | | String lowerName = toLowerCase(name); |
| | | ObjectClass oc = |
| | | DirectoryServer.getObjectClass(lowerName, true); |
| | | objectClasses.put(oc, name); |
| | | startPos = pos+1; |
| | | } |
| | | } |
| | | String name = new String(entryBytes, startPos, pos-startPos, |
| | | "UTF-8"); |
| | | String lowerName = toLowerCase(name); |
| | | ObjectClass oc = |
| | | DirectoryServer.getObjectClass(lowerName, true); |
| | | objectClasses.put(oc, name); |
| | | |
| | | |
| | | // Next is the total number of user attributes. It may be a |
| | | // single byte or multiple bytes. |
| | | int numUserAttrs = entryBytes[pos] & 0x7F; |
| | | if (entryBytes[pos++] != numUserAttrs) |
| | | { |
| | | int numLengthBytes = numUserAttrs; |
| | | numUserAttrs = 0; |
| | | for (int i=0; i < numLengthBytes; i++, pos++) |
| | | { |
| | | numUserAttrs = (numUserAttrs << 8) | |
| | | (entryBytes[pos] & 0xFF); |
| | | } |
| | | } |
| | | |
| | | |
| | | // Now, we should iterate through the user attributes and decode |
| | | // each one. |
| | | LinkedHashMap<AttributeType,List<Attribute>> userAttributes = |
| | | new LinkedHashMap<AttributeType,List<Attribute>>(); |
| | | for (int i=0; i < numUserAttrs; i++) |
| | | { |
| | | // First, we have the zero-terminated attribute name. |
| | | startPos = pos; |
| | | while (entryBytes[pos] != 0x00) |
| | | { |
| | | pos++; |
| | | } |
| | | name = new String(entryBytes, startPos, pos-startPos, |
| | | "UTF-8"); |
| | | LinkedHashSet<String> options; |
| | | int semicolonPos = name.indexOf(';'); |
| | | if (semicolonPos > 0) |
| | | { |
| | | String baseName = name.substring(0, semicolonPos); |
| | | lowerName = toLowerCase(baseName); |
| | | options = new LinkedHashSet<String>(); |
| | | |
| | | int nextPos = name.indexOf(';', semicolonPos+1); |
| | | while (nextPos > 0) |
| | | { |
| | | String option = name.substring(semicolonPos+1, nextPos); |
| | | if (option.length() > 0) |
| | | { |
| | | options.add(option); |
| | | } |
| | | |
| | | semicolonPos = nextPos; |
| | | nextPos = name.indexOf(';', semicolonPos+1); |
| | | } |
| | | |
| | | String option = name.substring(semicolonPos+1); |
| | | if (option.length() > 0) |
| | | { |
| | | options.add(option); |
| | | } |
| | | |
| | | name = baseName; |
| | | } |
| | | else |
| | | { |
| | | lowerName = toLowerCase(name); |
| | | options = new LinkedHashSet<String>(0); |
| | | } |
| | | AttributeType attributeType = |
| | | DirectoryServer.getAttributeType(lowerName, true); |
| | | |
| | | |
| | | |
| | | // Next, we have the number of values. |
| | | int numValues = entryBytes[++pos] & 0x7F; |
| | | if (entryBytes[pos++] != numValues) |
| | | { |
| | | int numLengthBytes = numValues; |
| | | numValues = 0; |
| | | for (int j=0; j < numLengthBytes; j++, pos++) |
| | | { |
| | | numValues = (numValues << 8) | (entryBytes[pos] & 0xFF); |
| | | } |
| | | } |
| | | |
| | | // Next, we have the sequence of length-value pairs. |
| | | LinkedHashSet<AttributeValue> values = |
| | | new LinkedHashSet<AttributeValue>(numValues); |
| | | for (int j=0; j < numValues; j++) |
| | | { |
| | | int valueLength = entryBytes[pos] & 0x7F; |
| | | if (entryBytes[pos++] != valueLength) |
| | | { |
| | | int numLengthBytes = valueLength; |
| | | valueLength = 0; |
| | | for (int k=0; k < numLengthBytes; k++, pos++) |
| | | { |
| | | valueLength = (valueLength << 8) | |
| | | (entryBytes[pos] & 0xFF); |
| | | } |
| | | } |
| | | |
| | | byte[] valueBytes = new byte[valueLength]; |
| | | System.arraycopy(entryBytes, pos, valueBytes, 0, |
| | | valueLength); |
| | | values.add(new AttributeValue(attributeType, |
| | | new ASN1OctetString(valueBytes))); |
| | | pos += valueLength; |
| | | } |
| | | |
| | | |
| | | // Create the attribute and add it to the set of user |
| | | // attributes. |
| | | Attribute a = new Attribute(attributeType, name, options, |
| | | values); |
| | | List<Attribute> attrList = userAttributes.get(attributeType); |
| | | if (attrList == null) |
| | | { |
| | | attrList = new ArrayList<Attribute>(1); |
| | | attrList.add(a); |
| | | userAttributes.put(attributeType, attrList); |
| | | } |
| | | else |
| | | { |
| | | attrList.add(a); |
| | | } |
| | | } |
| | | |
| | | |
| | | // Next is the total number of operational attributes. It may |
| | | // be a single byte or multiple bytes. |
| | | int numOperationalAttrs = entryBytes[pos] & 0x7F; |
| | | if (entryBytes[pos++] != numOperationalAttrs) |
| | | { |
| | | int numLengthBytes = numOperationalAttrs; |
| | | numOperationalAttrs = 0; |
| | | for (int i=0; i < numLengthBytes; i++, pos++) |
| | | { |
| | | numOperationalAttrs = |
| | | (numOperationalAttrs << 8) | (entryBytes[pos] & 0xFF); |
| | | } |
| | | } |
| | | |
| | | |
| | | // Now, we should iterate through the operational attributes and |
| | | // decode each one. |
| | | LinkedHashMap<AttributeType,List<Attribute>> |
| | | operationalAttributes = |
| | | new LinkedHashMap<AttributeType,List<Attribute>>(); |
| | | for (int i=0; i < numOperationalAttrs; i++) |
| | | { |
| | | // First, we have the zero-terminated attribute name. |
| | | startPos = pos; |
| | | while (entryBytes[pos] != 0x00) |
| | | { |
| | | pos++; |
| | | } |
| | | name = new String(entryBytes, startPos, pos-startPos, |
| | | "UTF-8"); |
| | | LinkedHashSet<String> options; |
| | | int semicolonPos = name.indexOf(';'); |
| | | if (semicolonPos > 0) |
| | | { |
| | | String baseName = name.substring(0, semicolonPos); |
| | | lowerName = toLowerCase(baseName); |
| | | options = new LinkedHashSet<String>(); |
| | | |
| | | int nextPos = name.indexOf(';', semicolonPos+1); |
| | | while (nextPos > 0) |
| | | { |
| | | String option = name.substring(semicolonPos+1, nextPos); |
| | | if (option.length() > 0) |
| | | { |
| | | options.add(option); |
| | | } |
| | | |
| | | semicolonPos = nextPos; |
| | | nextPos = name.indexOf(';', semicolonPos+1); |
| | | } |
| | | |
| | | String option = name.substring(semicolonPos+1); |
| | | if (option.length() > 0) |
| | | { |
| | | options.add(option); |
| | | } |
| | | |
| | | name = baseName; |
| | | } |
| | | else |
| | | { |
| | | lowerName = toLowerCase(name); |
| | | options = new LinkedHashSet<String>(0); |
| | | } |
| | | AttributeType attributeType = |
| | | DirectoryServer.getAttributeType(lowerName, true); |
| | | |
| | | |
| | | // Next, we have the number of values. |
| | | int numValues = entryBytes[++pos] & 0x7F; |
| | | if (entryBytes[pos++] != numValues) |
| | | { |
| | | int numLengthBytes = numValues; |
| | | numValues = 0; |
| | | for (int j=0; j < numLengthBytes; j++, pos++) |
| | | { |
| | | numValues = (numValues << 8) | (entryBytes[pos] & 0xFF); |
| | | } |
| | | } |
| | | |
| | | // Next, we have the sequence of length-value pairs. |
| | | LinkedHashSet<AttributeValue> values = |
| | | new LinkedHashSet<AttributeValue>(numValues); |
| | | for (int j=0; j < numValues; j++) |
| | | { |
| | | int valueLength = entryBytes[pos] & 0x7F; |
| | | if (entryBytes[pos++] != valueLength) |
| | | { |
| | | int numLengthBytes = valueLength; |
| | | valueLength = 0; |
| | | for (int k=0; k < numLengthBytes; k++, pos++) |
| | | { |
| | | valueLength = (valueLength << 8) | |
| | | (entryBytes[pos] & 0xFF); |
| | | } |
| | | } |
| | | |
| | | byte[] valueBytes = new byte[valueLength]; |
| | | System.arraycopy(entryBytes, pos, valueBytes, 0, |
| | | valueLength); |
| | | values.add(new AttributeValue(attributeType, |
| | | new ASN1OctetString(valueBytes))); |
| | | pos += valueLength; |
| | | } |
| | | |
| | | |
| | | // Create the attribute and add it to the set of operational |
| | | // attributes. |
| | | Attribute a = new Attribute(attributeType, name, options, |
| | | values); |
| | | List<Attribute> attrList = |
| | | operationalAttributes.get(attributeType); |
| | | if (attrList == null) |
| | | { |
| | | attrList = new ArrayList<Attribute>(1); |
| | | attrList.add(a); |
| | | operationalAttributes.put(attributeType, attrList); |
| | | } |
| | | else |
| | | { |
| | | attrList.add(a); |
| | | } |
| | | } |
| | | |
| | | |
| | | // We've got everything that we need, so create and return the |
| | | // entry. |
| | | return new Entry(dn, objectClasses, userAttributes, |
| | | operationalAttributes); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | throw de; |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | int msgID = MSGID_ENTRY_DECODE_EXCEPTION; |
| | | String message = getMessage(msgID, |
| | | stackTraceToSingleLineString(e)); |
| | | throw new DirectoryException( |
| | | DirectoryServer.getServerErrorResultCode(), |
| | | message, msgID, e); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Retrieves a list of the lines for this entry in LDIF form. Long |
| | | * lines will not be wrapped automatically. |
| | | * |