001/* 002 * Copyright 2016-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2016-2017 UnboundID Corp. 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 = 115 new ArrayList<Modification>(changes.length); 116 for (final byte[] changeBytes : changes) 117 { 118 int colonPos = -1; 119 for (int i=0; i < changeBytes.length; i++) 120 { 121 if (changeBytes[i] == ':') 122 { 123 colonPos = i; 124 break; 125 } 126 } 127 128 if (colonPos < 0) 129 { 130 throw new LDAPException(ResultCode.DECODING_ERROR, 131 ERR_LOGSCHEMA_DECODE_MODIFY_CHANGE_MISSING_COLON.get(entry.getDN(), 132 ATTR_ATTRIBUTE_CHANGES, 133 StaticUtils.toUTF8String(changeBytes))); 134 } 135 else if (colonPos == 0) 136 { 137 throw new LDAPException(ResultCode.DECODING_ERROR, 138 ERR_LOGSCHEMA_DECODE_MODIFY_CHANGE_MISSING_ATTR.get(entry.getDN(), 139 ATTR_ATTRIBUTE_CHANGES, 140 StaticUtils.toUTF8String(changeBytes))); 141 } 142 143 final String attrName = 144 StaticUtils.toUTF8String(changeBytes, 0, colonPos); 145 146 if (colonPos == (changeBytes.length - 1)) 147 { 148 throw new LDAPException(ResultCode.DECODING_ERROR, 149 ERR_LOGSCHEMA_DECODE_MODIFY_CHANGE_MISSING_CHANGE_TYPE.get( 150 entry.getDN(), ATTR_ATTRIBUTE_CHANGES, 151 StaticUtils.toUTF8String(changeBytes))); 152 } 153 154 final boolean needValue; 155 final ModificationType modType; 156 switch (changeBytes[colonPos+1]) 157 { 158 case '+': 159 modType = ModificationType.ADD; 160 needValue = true; 161 break; 162 case '-': 163 modType = ModificationType.DELETE; 164 needValue = false; 165 break; 166 case '=': 167 modType = ModificationType.REPLACE; 168 needValue = false; 169 break; 170 case '#': 171 modType = ModificationType.INCREMENT; 172 needValue = true; 173 break; 174 default: 175 throw new LDAPException(ResultCode.DECODING_ERROR, 176 ERR_LOGSCHEMA_DECODE_MODIFY_CHANGE_INVALID_CHANGE_TYPE.get( 177 entry.getDN(), ATTR_ATTRIBUTE_CHANGES, 178 StaticUtils.toUTF8String(changeBytes))); 179 } 180 181 if (changeBytes.length == (colonPos+2)) 182 { 183 if (needValue) 184 { 185 throw new LDAPException(ResultCode.DECODING_ERROR, 186 ERR_LOGSCHEMA_DECODE_MODIFY_CHANGE_MISSING_VALUE.get( 187 entry.getDN(), ATTR_ATTRIBUTE_CHANGES, 188 StaticUtils.toUTF8String(changeBytes), 189 modType.getName())); 190 } 191 else 192 { 193 mods.add(new Modification(modType, attrName)); 194 continue; 195 } 196 } 197 198 if ((changeBytes.length == (colonPos+3)) || 199 (changeBytes[colonPos+2] != ' ')) 200 { 201 throw new LDAPException(ResultCode.DECODING_ERROR, 202 ERR_LOGSCHEMA_DECODE_MODIFY_CHANGE_MISSING_SPACE.get( 203 entry.getDN(), ATTR_ATTRIBUTE_CHANGES, 204 StaticUtils.toUTF8String(changeBytes), 205 modType.getName())); 206 } 207 208 final byte[] attrValue = new byte[changeBytes.length - colonPos - 3]; 209 if (attrValue.length > 0) 210 { 211 System.arraycopy(changeBytes, (colonPos+3), attrValue, 0, 212 attrValue.length); 213 } 214 215 if (mods.isEmpty()) 216 { 217 mods.add(new Modification(modType, attrName, attrValue)); 218 continue; 219 } 220 221 final Modification lastMod = mods.get(mods.size() - 1); 222 if ((lastMod.getModificationType() == modType) && 223 (lastMod.getAttributeName().equalsIgnoreCase(attrName))) 224 { 225 final byte[][] lastModValues = lastMod.getValueByteArrays(); 226 final byte[][] newValues = new byte[lastModValues.length+1][]; 227 System.arraycopy(lastModValues, 0, newValues, 0, lastModValues.length); 228 newValues[lastModValues.length] = attrValue; 229 mods.set((mods.size()-1), 230 new Modification(modType, lastMod.getAttributeName(), newValues)); 231 } 232 else 233 { 234 mods.add(new Modification(modType, attrName, attrValue)); 235 } 236 } 237 238 modifications = Collections.unmodifiableList(mods); 239 240 241 // Get the former attribute values, if present. 242 final byte[][] formerAttrBytes = 243 entry.getAttributeValueByteArrays(ATTR_FORMER_ATTRIBUTE); 244 if ((formerAttrBytes == null) || (formerAttrBytes.length == 0)) 245 { 246 formerAttributes = Collections.emptyList(); 247 return; 248 } 249 250 final LinkedHashMap<String,List<Attribute>> attrMap = 251 new LinkedHashMap<String,List<Attribute>>(formerAttrBytes.length); 252 for (final byte[] attrBytes : formerAttrBytes) 253 { 254 int colonPos = -1; 255 for (int i=0; i < attrBytes.length; i++) 256 { 257 if (attrBytes[i] == ':') 258 { 259 colonPos = i; 260 break; 261 } 262 } 263 264 if (colonPos < 0) 265 { 266 throw new LDAPException(ResultCode.DECODING_ERROR, 267 ERR_LOGSCHEMA_DECODE_MODIFY_OLD_ATTR_MISSING_COLON.get( 268 entry.getDN(), ATTR_FORMER_ATTRIBUTE, 269 StaticUtils.toUTF8String(attrBytes))); 270 } 271 else if (colonPos == 0) 272 { 273 throw new LDAPException(ResultCode.DECODING_ERROR, 274 ERR_LOGSCHEMA_DECODE_MODIFY_OLD_ATTR_MISSING_ATTR.get( 275 entry.getDN(), ATTR_FORMER_ATTRIBUTE, 276 StaticUtils.toUTF8String(attrBytes))); 277 } 278 279 if ((colonPos == (attrBytes.length - 1)) || 280 (attrBytes[colonPos+1] != ' ')) 281 { 282 throw new LDAPException(ResultCode.DECODING_ERROR, 283 ERR_LOGSCHEMA_DECODE_MODIFY_OLD_ATTR_MISSING_SPACE.get( 284 entry.getDN(), ATTR_FORMER_ATTRIBUTE, 285 StaticUtils.toUTF8String(attrBytes))); 286 } 287 288 final String attrName = 289 StaticUtils.toUTF8String(attrBytes, 0, colonPos); 290 final String lowerName = StaticUtils.toLowerCase(attrName); 291 292 List<Attribute> attrList = attrMap.get(lowerName); 293 if (attrList == null) 294 { 295 attrList = new ArrayList<Attribute>(10); 296 attrMap.put(lowerName, attrList); 297 } 298 299 final byte[] attrValue = new byte[attrBytes.length - colonPos - 2]; 300 if (attrValue.length > 0) 301 { 302 System.arraycopy(attrBytes, colonPos + 2, attrValue, 0, 303 attrValue.length); 304 } 305 306 attrList.add(new Attribute(attrName, attrValue)); 307 } 308 309 final ArrayList<Attribute> oldAttributes = 310 new ArrayList<Attribute>(attrMap.size()); 311 for (final List<Attribute> attrList : attrMap.values()) 312 { 313 if (attrList.size() == 1) 314 { 315 oldAttributes.addAll(attrList); 316 } 317 else 318 { 319 final byte[][] valueArray = new byte[attrList.size()][]; 320 for (int i=0; i < attrList.size(); i++) 321 { 322 valueArray[i] = attrList.get(i).getValueByteArray(); 323 } 324 oldAttributes.add(new Attribute(attrList.get(0).getName(), valueArray)); 325 } 326 } 327 328 formerAttributes = Collections.unmodifiableList(oldAttributes); 329 } 330 331 332 333 /** 334 * Retrieves the modifications for the modify request described by this modify 335 * access log entry. 336 * 337 * @return The modifications for the modify request described by this modify 338 * access log entry. 339 */ 340 public List<Modification> getModifications() 341 { 342 return modifications; 343 } 344 345 346 347 /** 348 * Retrieves a list of former versions of modified attributes described by 349 * this modify access log entry, if available. 350 * 351 * @return A list of former versions of modified attributes, or an empty list 352 * if no former attribute information was included in the access log 353 * entry. 354 */ 355 public List<Attribute> getFormerAttributes() 356 { 357 return formerAttributes; 358 } 359 360 361 362 /** 363 * Retrieves a {@code ModifyRequest} created from this modify access log 364 * entry. 365 * 366 * @return The {@code ModifyRequest} created from this modify access log 367 * entry. 368 */ 369 public ModifyRequest toModifyRequest() 370 { 371 return new ModifyRequest(getTargetEntryDN(), modifications, 372 getRequestControlArray()); 373 } 374}