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