001/* 002 * Copyright 2016-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2016-2018 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.sdk.experimental; 022 023 024 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.LinkedHashMap; 028import java.util.List; 029 030import com.unboundid.ldap.sdk.Attribute; 031import com.unboundid.ldap.sdk.ModifyRequest; 032import com.unboundid.ldap.sdk.Entry; 033import com.unboundid.ldap.sdk.LDAPException; 034import com.unboundid.ldap.sdk.Modification; 035import com.unboundid.ldap.sdk.ModificationType; 036import com.unboundid.ldap.sdk.OperationType; 037import com.unboundid.ldap.sdk.ResultCode; 038import com.unboundid.util.NotMutable; 039import com.unboundid.util.StaticUtils; 040import com.unboundid.util.ThreadSafety; 041import com.unboundid.util.ThreadSafetyLevel; 042 043import static com.unboundid.ldap.sdk.experimental.ExperimentalMessages.*; 044 045 046 047/** 048 * This class represents an entry that holds information about a modify 049 * operation processed by an LDAP server, as per the specification described in 050 * draft-chu-ldap-logschema-00. 051 */ 052@NotMutable() 053@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 054public final class DraftChuLDAPLogSchema00ModifyEntry 055 extends DraftChuLDAPLogSchema00Entry 056{ 057 /** 058 * The name of the attribute used to hold the attribute changes contained in 059 * the modify operation. 060 */ 061 public static final String ATTR_ATTRIBUTE_CHANGES = "reqMod"; 062 063 064 065 /** 066 * The name of the attribute used to hold the former values of entries changed 067 * by the modify operation. 068 */ 069 public static final String ATTR_FORMER_ATTRIBUTE = "reqOld"; 070 071 072 073 /** 074 * The serial version UID for this serializable class. 075 */ 076 private static final long serialVersionUID = 5787071409404025072L; 077 078 079 080 // A list of the former versions of modified attributes. 081 private final List<Attribute> formerAttributes; 082 083 // A list of the modifications contained in the request. 084 private final List<Modification> modifications; 085 086 087 088 /** 089 * Creates a new instance of this modify access log entry from the provided 090 * entry. 091 * 092 * @param entry The entry used to create this modify access log entry. 093 * 094 * @throws LDAPException If the provided entry cannot be decoded as a valid 095 * modify access log entry as per the specification 096 * contained in draft-chu-ldap-logschema-00. 097 */ 098 public DraftChuLDAPLogSchema00ModifyEntry(final Entry entry) 099 throws LDAPException 100 { 101 super(entry, OperationType.MODIFY); 102 103 104 // Process the set of modifications. 105 final byte[][] changes = 106 entry.getAttributeValueByteArrays(ATTR_ATTRIBUTE_CHANGES); 107 if ((changes == null) || (changes.length == 0)) 108 { 109 throw new LDAPException(ResultCode.DECODING_ERROR, 110 ERR_LOGSCHEMA_DECODE_MISSING_REQUIRED_ATTR.get(entry.getDN(), 111 ATTR_ATTRIBUTE_CHANGES)); 112 } 113 114 final ArrayList<Modification> mods = new ArrayList<>(changes.length); 115 for (final byte[] changeBytes : changes) 116 { 117 int colonPos = -1; 118 for (int i=0; i < changeBytes.length; i++) 119 { 120 if (changeBytes[i] == ':') 121 { 122 colonPos = i; 123 break; 124 } 125 } 126 127 if (colonPos < 0) 128 { 129 throw new LDAPException(ResultCode.DECODING_ERROR, 130 ERR_LOGSCHEMA_DECODE_MODIFY_CHANGE_MISSING_COLON.get(entry.getDN(), 131 ATTR_ATTRIBUTE_CHANGES, 132 StaticUtils.toUTF8String(changeBytes))); 133 } 134 else if (colonPos == 0) 135 { 136 throw new LDAPException(ResultCode.DECODING_ERROR, 137 ERR_LOGSCHEMA_DECODE_MODIFY_CHANGE_MISSING_ATTR.get(entry.getDN(), 138 ATTR_ATTRIBUTE_CHANGES, 139 StaticUtils.toUTF8String(changeBytes))); 140 } 141 142 final String attrName = 143 StaticUtils.toUTF8String(changeBytes, 0, colonPos); 144 145 if (colonPos == (changeBytes.length - 1)) 146 { 147 throw new LDAPException(ResultCode.DECODING_ERROR, 148 ERR_LOGSCHEMA_DECODE_MODIFY_CHANGE_MISSING_CHANGE_TYPE.get( 149 entry.getDN(), ATTR_ATTRIBUTE_CHANGES, 150 StaticUtils.toUTF8String(changeBytes))); 151 } 152 153 final boolean needValue; 154 final ModificationType modType; 155 switch (changeBytes[colonPos+1]) 156 { 157 case '+': 158 modType = ModificationType.ADD; 159 needValue = true; 160 break; 161 case '-': 162 modType = ModificationType.DELETE; 163 needValue = false; 164 break; 165 case '=': 166 modType = ModificationType.REPLACE; 167 needValue = false; 168 break; 169 case '#': 170 modType = ModificationType.INCREMENT; 171 needValue = true; 172 break; 173 default: 174 throw new LDAPException(ResultCode.DECODING_ERROR, 175 ERR_LOGSCHEMA_DECODE_MODIFY_CHANGE_INVALID_CHANGE_TYPE.get( 176 entry.getDN(), ATTR_ATTRIBUTE_CHANGES, 177 StaticUtils.toUTF8String(changeBytes))); 178 } 179 180 if (changeBytes.length == (colonPos+2)) 181 { 182 if (needValue) 183 { 184 throw new LDAPException(ResultCode.DECODING_ERROR, 185 ERR_LOGSCHEMA_DECODE_MODIFY_CHANGE_MISSING_VALUE.get( 186 entry.getDN(), ATTR_ATTRIBUTE_CHANGES, 187 StaticUtils.toUTF8String(changeBytes), 188 modType.getName())); 189 } 190 else 191 { 192 mods.add(new Modification(modType, attrName)); 193 continue; 194 } 195 } 196 197 if ((changeBytes.length == (colonPos+3)) || 198 (changeBytes[colonPos+2] != ' ')) 199 { 200 throw new LDAPException(ResultCode.DECODING_ERROR, 201 ERR_LOGSCHEMA_DECODE_MODIFY_CHANGE_MISSING_SPACE.get( 202 entry.getDN(), ATTR_ATTRIBUTE_CHANGES, 203 StaticUtils.toUTF8String(changeBytes), 204 modType.getName())); 205 } 206 207 final byte[] attrValue = new byte[changeBytes.length - colonPos - 3]; 208 if (attrValue.length > 0) 209 { 210 System.arraycopy(changeBytes, (colonPos+3), attrValue, 0, 211 attrValue.length); 212 } 213 214 if (mods.isEmpty()) 215 { 216 mods.add(new Modification(modType, attrName, attrValue)); 217 continue; 218 } 219 220 final Modification lastMod = mods.get(mods.size() - 1); 221 if ((lastMod.getModificationType() == modType) && 222 (lastMod.getAttributeName().equalsIgnoreCase(attrName))) 223 { 224 final byte[][] lastModValues = lastMod.getValueByteArrays(); 225 final byte[][] newValues = new byte[lastModValues.length+1][]; 226 System.arraycopy(lastModValues, 0, newValues, 0, lastModValues.length); 227 newValues[lastModValues.length] = attrValue; 228 mods.set((mods.size()-1), 229 new Modification(modType, lastMod.getAttributeName(), newValues)); 230 } 231 else 232 { 233 mods.add(new Modification(modType, attrName, attrValue)); 234 } 235 } 236 237 modifications = Collections.unmodifiableList(mods); 238 239 240 // Get the former attribute values, if present. 241 final byte[][] formerAttrBytes = 242 entry.getAttributeValueByteArrays(ATTR_FORMER_ATTRIBUTE); 243 if ((formerAttrBytes == null) || (formerAttrBytes.length == 0)) 244 { 245 formerAttributes = Collections.emptyList(); 246 return; 247 } 248 249 final LinkedHashMap<String,List<Attribute>> attrMap = 250 new LinkedHashMap<>(formerAttrBytes.length); 251 for (final byte[] attrBytes : formerAttrBytes) 252 { 253 int colonPos = -1; 254 for (int i=0; i < attrBytes.length; i++) 255 { 256 if (attrBytes[i] == ':') 257 { 258 colonPos = i; 259 break; 260 } 261 } 262 263 if (colonPos < 0) 264 { 265 throw new LDAPException(ResultCode.DECODING_ERROR, 266 ERR_LOGSCHEMA_DECODE_MODIFY_OLD_ATTR_MISSING_COLON.get( 267 entry.getDN(), ATTR_FORMER_ATTRIBUTE, 268 StaticUtils.toUTF8String(attrBytes))); 269 } 270 else if (colonPos == 0) 271 { 272 throw new LDAPException(ResultCode.DECODING_ERROR, 273 ERR_LOGSCHEMA_DECODE_MODIFY_OLD_ATTR_MISSING_ATTR.get( 274 entry.getDN(), ATTR_FORMER_ATTRIBUTE, 275 StaticUtils.toUTF8String(attrBytes))); 276 } 277 278 if ((colonPos == (attrBytes.length - 1)) || 279 (attrBytes[colonPos+1] != ' ')) 280 { 281 throw new LDAPException(ResultCode.DECODING_ERROR, 282 ERR_LOGSCHEMA_DECODE_MODIFY_OLD_ATTR_MISSING_SPACE.get( 283 entry.getDN(), ATTR_FORMER_ATTRIBUTE, 284 StaticUtils.toUTF8String(attrBytes))); 285 } 286 287 final String attrName = 288 StaticUtils.toUTF8String(attrBytes, 0, colonPos); 289 final String lowerName = StaticUtils.toLowerCase(attrName); 290 291 List<Attribute> attrList = attrMap.get(lowerName); 292 if (attrList == null) 293 { 294 attrList = new ArrayList<>(10); 295 attrMap.put(lowerName, attrList); 296 } 297 298 final byte[] attrValue = new byte[attrBytes.length - colonPos - 2]; 299 if (attrValue.length > 0) 300 { 301 System.arraycopy(attrBytes, colonPos + 2, attrValue, 0, 302 attrValue.length); 303 } 304 305 attrList.add(new Attribute(attrName, attrValue)); 306 } 307 308 final ArrayList<Attribute> oldAttributes = new ArrayList<>(attrMap.size()); 309 for (final List<Attribute> attrList : attrMap.values()) 310 { 311 if (attrList.size() == 1) 312 { 313 oldAttributes.addAll(attrList); 314 } 315 else 316 { 317 final byte[][] valueArray = new byte[attrList.size()][]; 318 for (int i=0; i < attrList.size(); i++) 319 { 320 valueArray[i] = attrList.get(i).getValueByteArray(); 321 } 322 oldAttributes.add(new Attribute(attrList.get(0).getName(), valueArray)); 323 } 324 } 325 326 formerAttributes = Collections.unmodifiableList(oldAttributes); 327 } 328 329 330 331 /** 332 * Retrieves the modifications for the modify request described by this modify 333 * access log entry. 334 * 335 * @return The modifications for the modify request described by this modify 336 * access log entry. 337 */ 338 public List<Modification> getModifications() 339 { 340 return modifications; 341 } 342 343 344 345 /** 346 * Retrieves a list of former versions of modified attributes described by 347 * this modify access log entry, if available. 348 * 349 * @return A list of former versions of modified attributes, or an empty list 350 * if no former attribute information was included in the access log 351 * entry. 352 */ 353 public List<Attribute> getFormerAttributes() 354 { 355 return formerAttributes; 356 } 357 358 359 360 /** 361 * Retrieves a {@code ModifyRequest} created from this modify access log 362 * entry. 363 * 364 * @return The {@code ModifyRequest} created from this modify access log 365 * entry. 366 */ 367 public ModifyRequest toModifyRequest() 368 { 369 return new ModifyRequest(getTargetEntryDN(), modifications, 370 getRequestControlArray()); 371 } 372}