001 /*
002 * Copyright 2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2016 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 */
021 package com.unboundid.ldap.sdk.experimental;
022
023
024
025 import java.util.ArrayList;
026 import java.util.Collections;
027 import java.util.LinkedHashMap;
028 import java.util.List;
029
030 import com.unboundid.ldap.sdk.Attribute;
031 import com.unboundid.ldap.sdk.ModifyRequest;
032 import com.unboundid.ldap.sdk.Entry;
033 import com.unboundid.ldap.sdk.LDAPException;
034 import com.unboundid.ldap.sdk.Modification;
035 import com.unboundid.ldap.sdk.ModificationType;
036 import com.unboundid.ldap.sdk.OperationType;
037 import com.unboundid.ldap.sdk.ResultCode;
038 import com.unboundid.util.NotMutable;
039 import com.unboundid.util.StaticUtils;
040 import com.unboundid.util.ThreadSafety;
041 import com.unboundid.util.ThreadSafetyLevel;
042
043 import 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)
054 public 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 }