001package com.pingidentity.ds.plugin; 002 003import com.unboundid.directory.sdk.common.operation.AddRequest; 004import com.unboundid.directory.sdk.common.operation.DeleteRequest; 005import com.unboundid.directory.sdk.common.operation.ModifyDNRequest; 006import com.unboundid.directory.sdk.common.operation.ModifyRequest; 007import com.unboundid.directory.sdk.common.operation.*; 008import com.unboundid.directory.sdk.common.types.CompletedOperationContext; 009import com.unboundid.directory.sdk.common.types.LogSeverity; 010import com.unboundid.directory.sdk.common.types.RegisteredMonitorProvider; 011import com.unboundid.directory.sdk.ds.api.Plugin; 012import com.unboundid.directory.sdk.ds.config.PluginConfig; 013import com.unboundid.directory.sdk.ds.types.DirectoryServerContext; 014import com.unboundid.directory.sdk.ds.types.PostResponsePluginResult; 015import com.unboundid.ldap.sdk.*; 016import com.unboundid.util.StaticUtils; 017import com.unboundid.util.args.*; 018 019import java.io.File; 020import java.io.FileWriter; 021import java.io.IOException; 022import java.text.SimpleDateFormat; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Date; 026import java.util.List; 027 028public class SnapshotChangelog extends Plugin 029{ 030 public static final String OBJECT_CLASS = "objectClass"; 031 public static final String CHANGELOG_PREFIX = "snapshot-changelog-"; 032 public static final String CHANGE_NUMBER_ATTRIBUTE_TYPE = "scl-change-number"; 033 private static final String ARG_NAME_BASE_DN = "base-dn"; 034 private static final String ARG_BASE_DN_DEFAULT = "cn=snapshot-changelog"; 035 private static final String ARG_NAME_SPECIAL_ATTRIBUTE = "do-not-log-if-modify-only-attributes"; 036 private static final String ARG_NAME_FILTER_ENTRY_BEFORE = "filter-entry-before"; 037 private static final String ARG_NAME_FILTER_ENTRY_AFTER = "filter-entry-after"; 038 private static final String ARG_NAME_INCLUDE_ATTRIBUTE = "include-attribute"; 039 private static final String ARG_NAME_EXCLUDE_ATTRIBUTE = "exclude-attribute"; 040 private static final String ATTRIBUTE_ENTRY_AFTER = "scl-entry-after"; 041 private static final String ATTRIBUTE_ENTRY_BEFORE = "scl-entry-before"; 042 private static final List<File> DEFAULT_DIRECTORY = Arrays.asList(new File("logs/snapshot-changelog")); 043 private static final String ARG_FAILED_PERSISTENCE_FOLDER = "failed-persistence-folder"; 044 private static final String ARG_FAILED_PERSISTENCE_FORMAT = "failed-persistence-date-format"; 045 private static final String ARG_FAILED_PERSISTENCE_USE_FORMATTED_DATE = "failed-persistence-use-formatted-date"; 046 private List<String> specialAttributes; 047 private List<String> includeAttributes; 048 private List<String> excludeAttributes; 049 private List<Filter> entryBeforeFilters; 050 private List<Filter> entryAfterFilters; 051 private List<DN> publicBackends; 052 private SnapshotChangelogMonitorProvider monitor; 053 private RegisteredMonitorProvider registeredMonitorProvider; 054 private Thread persisterThread; 055 private String ARG_PERSISTENCE_FAILED_FORMAT_DEFAULT = "yyyyMMdd-HHmmss.SSS"; 056 DirectoryServerContext serverContext; 057 DN baseDN; 058 SnapshotChangeNumberPersister persister; 059 PluginConfig config; 060 private File failedPersistPath; 061 private SimpleDateFormat failedPersistDateFormat; 062 private boolean failedPersistUseDate; 063 064 public SnapshotChangelog() { 065 } 066 067 public void defineConfigArguments(ArgumentParser parser) throws ArgumentException { 068 try { 069 parser.addArgument(new DNArgument((Character)null, "base-dn", true, 1, "{DN}", "The base DN where the enhanced changelog should be written (Default: cn=snapshot-changelog)", new DN("cn=snapshot-changelog"))); 070 } catch (LDAPException var9) { 071 var9.printStackTrace(); 072 } 073 074 StringArgument includeAttributesArgument = new StringArgument(null, ARG_NAME_INCLUDE_ATTRIBUTE, false, 0, 075 "{attribute-name}", "zero or more attributes to explicitly whitelist. All other attributes will be " + 076 "removed from the entries committed in this changelog."); 077 parser.addArgument(includeAttributesArgument); 078 079 StringArgument excludeAttributesArgument = new StringArgument(null, ARG_NAME_EXCLUDE_ATTRIBUTE, false, 0, 080 "{attribute-name}", "zero or more attributes to explicitly blacklist. These attributes will be " + 081 "removed from the entries committed to this changelog"); 082 parser.addArgument(excludeAttributesArgument); 083 084 StringArgument specialAttributesArgument = new StringArgument(null, ARG_NAME_SPECIAL_ATTRIBUTE, false, 0, 085 "{attribute-name}", "zero or more special attributes. Special attributes are attributes that may be " + 086 "included in the changelog only if other attributes of interest are present. This is only evaluated " + 087 "for modify operations."); 088 parser.addArgument(specialAttributesArgument); 089 090 FilterArgument preEntryIncludeFilterArgument = new FilterArgument(null, ARG_NAME_FILTER_ENTRY_BEFORE, false, 091 0, "{ldap-filter}", "zero or more filters that the entry prior to the modification(s) must match for " + 092 "the change to be committed to the changelog. This is not evaluated for add operations."); 093 parser.addArgument(preEntryIncludeFilterArgument); 094 095 FilterArgument postEntryFilterArgument = new FilterArgument(null, ARG_NAME_FILTER_ENTRY_AFTER, false, 0, 096 "{ldap-filter}", "zero or more filters that the entry after the modification(s) must match for the " + 097 "change to be committed to the changelog. This is not evaluated for delete operations."); 098 parser.addArgument(postEntryFilterArgument); 099 FileArgument failedPersistenceFolder = new FileArgument(null, ARG_FAILED_PERSISTENCE_FOLDER, false, 1, "{path}", "The path to write snapshot entries in case there is an issue writing to the changelog", true, true, false, false, DEFAULT_DIRECTORY); 100 parser.addArgument(failedPersistenceFolder); 101 StringArgument failedPersistenceFormatArgument = new StringArgument(null, ARG_FAILED_PERSISTENCE_FORMAT, false, 1, "{SimpleDataFormat}", "The Date format for the persistence failed file name suffix", ARG_PERSISTENCE_FAILED_FORMAT_DEFAULT); 102 failedPersistenceFormatArgument.addValueValidator(new ArgumentValueValidator() { 103 public void validateArgumentValue(Argument argument, String argumentValue) throws ArgumentException { 104 try { 105 SimpleDateFormat simpleDateFormat = new SimpleDateFormat(argumentValue); 106 simpleDateFormat.format(new Date()); 107 } catch (IllegalArgumentException iae) { 108 throw new ArgumentException(iae.getMessage()); 109 } 110 } 111 }); 112 parser.addArgument(failedPersistenceFormatArgument); 113 parser.addArgument(new BooleanArgument(null, ARG_FAILED_PERSISTENCE_USE_FORMATTED_DATE, "By default the failed persistence file will be appended with a suffix equal to the changelog number. This option allows to override this behaviour to append a formatted timestamp instead")); 114 } 115 116 /** 117 * Performs the necessary processing to ensure that configuration is properly applied to the instance of the 118 * extension 119 * This is used to update configuration without restarting the plugin if possible 120 * 121 * @param config the configuration object of the instance of the extension 122 * @param parser the argument parser 123 * @param adminActionsRequired a list of messages describing reason administrative actions necessary to apply 124 * configuration 125 * @param messages a list of message providing details about the application of the configuration to 126 * the instance 127 * @return SUCCESS if the configuration could be applied 128 */ 129 @Override 130 public ResultCode applyConfiguration(PluginConfig config, ArgumentParser parser, List<String> 131 adminActionsRequired, List<String> messages) 132 { 133 specialAttributes = parser.getStringArgument(ARG_NAME_SPECIAL_ATTRIBUTE).getValues(); 134 includeAttributes = parser.getStringArgument(ARG_NAME_INCLUDE_ATTRIBUTE).getValues(); 135 excludeAttributes = parser.getStringArgument(ARG_NAME_EXCLUDE_ATTRIBUTE).getValues(); 136 137 entryBeforeFilters = parser.getFilterArgument(ARG_NAME_FILTER_ENTRY_BEFORE).getValues(); 138 entryAfterFilters = parser.getFilterArgument(ARG_NAME_FILTER_ENTRY_AFTER).getValues(); 139 failedPersistPath = parser.getFileArgument(ARG_FAILED_PERSISTENCE_FOLDER).getValue(); 140 failedPersistDateFormat = new SimpleDateFormat(parser.getStringArgument(ARG_FAILED_PERSISTENCE_FORMAT).getValue()); 141 failedPersistUseDate = parser.getBooleanArgument(ARG_FAILED_PERSISTENCE_USE_FORMATTED_DATE).isPresent(); 142 143 baseDN = parser.getDNArgument(ARG_NAME_BASE_DN).getValue(); 144 return ResultCode.SUCCESS; 145 } 146 147 /** 148 * Performs the necessary processing to initialize the instance of the extension 149 * 150 * @param serverContext the server context 151 * @param config the configuration object of the instance of the extension 152 * @param parser the argument parser 153 * @throws LDAPException if initialization was not successful 154 */ 155 @Override 156 public void initializePlugin(DirectoryServerContext serverContext, PluginConfig config, ArgumentParser parser) 157 throws LDAPException 158 { 159 this.serverContext = serverContext; 160 this.config = config; 161 162 publicBackends = new ArrayList<>(); 163 for (String namingContext : serverContext.getInternalRootConnection().getRootDSE().getNamingContextDNs()) 164 { 165 publicBackends.add(new DN(namingContext)); 166 } 167 168 // Use the applyConfiguration method to set up the plugin. 169 final ArrayList<String> adminActionsRequired = new ArrayList<>(5); 170 final ArrayList<String> messages = new ArrayList<>(5); 171 final ResultCode resultCode = applyConfiguration(config, parser, adminActionsRequired, messages); 172 if (resultCode != ResultCode.SUCCESS) 173 { 174 throw new LDAPException(resultCode, 175 "One or more errors occurred while trying to initialize " + getExtensionName() + " extension" + 176 " in '" 177 + config.getConfigObjectDN() + ": " + 178 StaticUtils.concatenateStrings(messages)); 179 } 180 181 this.monitor = new SnapshotChangelogMonitorProvider(this); 182 registeredMonitorProvider = serverContext.registerMonitorProvider(monitor, config); 183 184 185 this.persister = new SnapshotChangeNumberPersister(this); 186 persisterThread = serverContext.createThread(persister, "Snapshot Changelog " + 187 "Persister Thread"); 188 persisterThread.start(); 189 } 190 191 /** 192 * Performs the necessary processing to gracefully shutdown the instance of the extension by committing state to the 193 * enhanced changelog backend 194 */ 195 @Override 196 public void finalizePlugin() 197 { 198 persister.shutdown(); 199 serverContext.deregisterMonitorProvider(registeredMonitorProvider); 200 } 201 202 /** 203 * Performs the necessary processing to generate the extension name 204 * 205 * @return the extension name 206 */ 207 @Override 208 public String getExtensionName() 209 { 210 return "Snapshot Changelog"; 211 } 212 213 /** 214 * Performs the necessary 215 * 216 * @return an array of descriptive strings about the extension, each of which appears as a paragraph 217 */ 218 @Override 219 public String[] getExtensionDescription() 220 { 221 return new String[]{"In a nutshell, this changelog provides the full entry before and after change."}; 222 } 223 224 /** 225 * Performs the necessary processing to evaluate if a changelog entry should be committed to the snapshot changelog 226 * for an add operation 227 * 228 * @param operationContext the operation context 229 * @param request the request to evaluate 230 * @param result the result that was sent to the client 231 * @return SUCCESS 232 */ 233 @Override 234 public PostResponsePluginResult doPostResponse(CompletedOperationContext operationContext, AddRequest request, 235 AddResult result) 236 { 237 if (accept(result) && notInSnapshotChangelog(request.getEntry().getDN())) 238 { 239 SearchResultEntry entry = null; 240 try 241 { 242 entry = serverContext.getInternalRootConnection().getEntry(request.getEntry().getDN(),"*","+"); 243 } catch (LDAPException e) 244 { 245 debug(e); 246 } 247 Entry candidate = ( entry != null ? entry : request.getEntry().toLDAPSDKEntry() ); 248 if (entryMatchesAny(candidate, entryAfterFilters)) 249 { 250 processChangelogEntry(null, candidate, ChangeType.ADD); 251 } 252 } 253 return PostResponsePluginResult.SUCCESS; 254 } 255 256 /** 257 * This method catches ADD received via replication 258 * 259 * @param operationContext the operation context 260 * @param request the request to evaluate 261 * @param result the result of the ADD 262 */ 263 @Override 264 public void doPostReplication(CompletedOperationContext operationContext, AddRequest request, AddResult result) 265 { 266 doPostResponse(operationContext, request, result); 267 } 268 269 /** 270 * Performs the necessary processing to evaluate if a changelog entry should be committed to the snapshot changelog 271 * for a delete operation 272 * 273 * @param operationContext the operation context 274 * @param request the request to evaluate 275 * @param result the result that was sent to the client 276 * @return SUCCESS 277 */ 278 @Override 279 public PostResponsePluginResult doPostResponse(CompletedOperationContext operationContext, DeleteRequest 280 request, 281 DeleteResult result) 282 { 283 if (accept(result) && notInSnapshotChangelog(request.getDN())) 284 { 285 Entry candidate = result.getDeletedEntry().toLDAPSDKEntry(); 286 if (entryMatchesAny(candidate, entryBeforeFilters)) 287 { 288 processChangelogEntry(candidate, null, ChangeType.DELETE); 289 } 290 } 291 return PostResponsePluginResult.SUCCESS; 292 } 293 294 /** 295 * This method catches DELETE requests received via replication 296 * 297 * @param operationContext the operation context 298 * @param request the replicated DELETE request 299 * @param result the result 300 */ 301 @Override 302 public void doPostReplication(CompletedOperationContext operationContext, DeleteRequest request, DeleteResult 303 result) 304 { 305 doPostResponse(operationContext, request, result); 306 } 307 308 /** 309 * Performs the necessary processing to evaluate if a changelog entry should be committed to the snapshot changelog 310 * for a modify operation 311 * 312 * @param operationContext the operation context 313 * @param request the request to evaluate 314 * @param result the result that was sent to the client 315 * @return SUCCESS 316 */ 317 @Override 318 public PostResponsePluginResult doPostResponse(CompletedOperationContext operationContext, ModifyRequest 319 request, ModifyResult result) 320 { 321 if (accept(result) && notInSnapshotChangelog(request.getDN()) && !onlySpecialAttributesLeft(request.getModifications())) 322 { 323 Entry candidateBefore = result.getOldEntry().toLDAPSDKEntry(); 324 Entry candidateAfter = null; 325 try { 326 candidateAfter = serverContext.getInternalRootConnection().getEntry(result.getNewEntry().getDN()); 327 } catch (LDAPException e) { 328 e.printStackTrace(); 329 } 330 if (filterMatchForMod(candidateBefore,candidateAfter)) 331 { 332 processChangelogEntry(candidateBefore, candidateAfter, ChangeType.MODIFY); 333 } 334 } 335 return PostResponsePluginResult.SUCCESS; 336 } 337 338 /** 339 * This method catches MODIFY operations received via replication 340 * 341 * @param operationContext the operation context 342 * @param request the replicated MODIFY request 343 * @param result the result 344 */ 345 @Override 346 public void doPostReplication(CompletedOperationContext operationContext, ModifyRequest request, ModifyResult 347 result) 348 { 349 doPostResponse(operationContext, request, result); 350 } 351 352 /** 353 * Performs the necessary processing to evaluate if a changelog entry should be committed to the snapshot changelog 354 * for a modify dn operation 355 * 356 * @param operationContext the operation context 357 * @param request the request to evaluate 358 * @param result the result that was sent to the client 359 * @return SUCCESS 360 */ 361 @Override 362 public PostResponsePluginResult doPostResponse(CompletedOperationContext operationContext, ModifyDNRequest 363 request, ModifyDNResult result) 364 { 365 if (accept(result) && notInSnapshotChangelog(request.getDN())) 366 { 367 Entry candidateBefore = result.getOldEntry().toLDAPSDKEntry(); 368 Entry candidateAfter = result.getNewEntry().toLDAPSDKEntry(); 369 if (filterMatchForMod(candidateBefore,candidateAfter)) 370 { 371 processChangelogEntry(candidateBefore, candidateAfter, ChangeType.MODIFY_DN); 372 } 373 } 374 return PostResponsePluginResult.SUCCESS; 375 } 376 377 @Override 378 public void doPostReplication(CompletedOperationContext operationContext, ModifyDNRequest request, ModifyDNResult 379 result) 380 { 381 doPostResponse(operationContext, request, result); 382 } 383 384 /** 385 * This method handles the special case of modifications where unlike for other operations, both filters before and 386 * after might be use to select whether to commit the entry to the snapshot changelog 387 * 388 * If only the before or after is defined, then either can match -- or rather whichever is define must match 389 * If both the before and after are defined, then both must match 390 * @param entryBefore the candidate entry before 391 * @param entryAfter the candidate entry after 392 * @return true if the operation should result in an entry being committed to the changelog backend 393 */ 394 private boolean filterMatchForMod(Entry entryBefore, Entry entryAfter) 395 { 396 boolean evaluateBeforeFilters = entryBeforeFilters != null && entryBeforeFilters.size() > 0; 397 boolean evaluateAfterFilters = entryAfterFilters != null && entryAfterFilters.size() > 0; 398 if (!(evaluateBeforeFilters || evaluateAfterFilters)) 399 { 400 // neither filters are defined 401 return true; 402 } else if ( evaluateBeforeFilters && evaluateAfterFilters ) 403 { 404 // both filters are defined 405 return entryMatchesAny(entryBefore, entryBeforeFilters) && entryMatchesAny(entryAfter, entryAfterFilters); 406 } else if ( evaluateBeforeFilters ) 407 { 408 //only the before filters are defined 409 return entryMatchesAny(entryBefore, entryBeforeFilters); 410 } else if ( evaluateAfterFilters ) 411 { 412 // only the after filters are defined 413 return entryMatchesAny(entryAfter, entryAfterFilters); 414 } 415 // never here 416 return true; 417 } 418 419 /** 420 * creates the changelog entry 421 * 422 * @param entryBefore the contents of the entry prior to the operation 423 * @param entryAfter the contents of the entry after the operation 424 * @param changeType the operation type 425 */ 426 private void processChangelogEntry(final Entry entryBefore, final Entry entryAfter, final ChangeType 427 changeType) 428 { 429 if (entryBefore == null && entryAfter == null) 430 { 431 return; 432 } 433 434 Entry changelogEntry = new Entry(DN.NULL_DN); 435 changelogEntry.addAttribute(OBJECT_CLASS, CHANGELOG_PREFIX + changeType); 436 if (entryBefore != null) 437 { 438 Entry entry = sanitizeEntry(entryBefore); 439 changelogEntry.addAttribute(ATTRIBUTE_ENTRY_BEFORE, String.join("\n", entry.toLDIFString())); 440 } 441 if (entryAfter != null) 442 { 443 Entry entry = sanitizeEntry(entryAfter); 444 changelogEntry.addAttribute(ATTRIBUTE_ENTRY_AFTER, String.join("\n", entry.toLDIFString())); 445 } 446 commitEntry(changelogEntry); 447 } 448 449 /** 450 * This method checks whether the entry only has attributes remaining that are special attribute 451 * if that is the case then the entry should not result in a commit to the changelog 452 * 453 * @param modifications the list of modifications in the operation 454 * @return true if after removing all special attributes the operation has affected no attributes of interest 455 */ 456 private boolean onlySpecialAttributesLeft(final List<Modification> modifications) 457 { 458 final List<String> attributes = new ArrayList<>(); 459 for (Modification m : modifications) 460 { 461 // there are include attributes white listed 462 if (includeAttributes != null && includeAttributes.size() > 0 && includeAttributes.stream().noneMatch(m 463 .getAttributeName() 464 ::equalsIgnoreCase)) 465 { 466 // it is not one of them 467 return false; 468 } 469 470 // there are exclude attribute black listed 471 if (excludeAttributes != null && excludeAttributes.size() > 0 && excludeAttributes.stream().anyMatch(m 472 .getAttributeName() 473 ::equalsIgnoreCase)) 474 { 475 // it is one of them 476 continue; 477 } 478 479 // there attributes in which we are only interested if something else of interest was altered 480 if (specialAttributes != null && specialAttributes.size() > 0 && specialAttributes.stream().anyMatch(m 481 .getAttributeName() 482 ::equalsIgnoreCase)) 483 { 484 // it is one of them 485 continue; 486 } 487 attributes.add(m.getAttributeName()); 488 } 489 return (attributes.size() == 0); 490 } 491 492 /** 493 * Performs the necessary processing to ensure that an entry should be evaluated 494 * 495 * @param entry the entry to check 496 * @return true if it is under a public backend 497 */ 498 private boolean entryInPublicBackend(final Entry entry) 499 { 500 for (DN publicBackend : publicBackends) 501 { 502 try 503 { 504 if (publicBackend.isAncestorOf(entry.getDN(), true)) 505 { 506 return true; 507 } 508 } catch (LDAPException e) 509 { 510 serverContext.debugCaught(e); 511 } 512 } 513 return false; 514 } 515 516 /** 517 * Performs a check to avoid infinite looping by invoking the plugin for operations targeting itself 518 * @param dn the distinguished name to evaluate 519 * @return true if the entry is not in the snapshot changelog 520 */ 521 private boolean notInSnapshotChangelog(final String dn) 522 { 523 try 524 { 525 if ( baseDN != null && baseDN.isAncestorOf(dn,true) ) 526 { 527 return false; 528 } 529 } catch (LDAPException e) 530 { 531 debug(e); 532 } 533 return true; 534 } 535 536 /** 537 * Performs the necessary processing to verify that an entry matches any of the provided filters 538 * If no filters are provided, we consider it to be a match 539 * 540 * @param entry the entry to evaluate 541 * @param filters the list of filters to assert against the entry 542 * @return true if no filters were provided or any of the provided filters match 543 */ 544 private boolean entryMatchesAny(final Entry entry, final List<Filter> filters) 545 { 546 boolean result = true; 547 if (!entryInPublicBackend(entry)) 548 { 549 return false; 550 } 551 if (filters != null && filters.size() > 0) 552 { 553 for (Filter filter : filters) 554 { 555 try 556 { 557 if (filter.matchesEntry(entry)) 558 { 559 return true; 560 } 561 } catch (LDAPException e) 562 { 563 debug(e); 564 log(e.getDiagnosticMessage(),LogSeverity.SEVERE_ERROR); 565 } 566 } 567 result = false; 568 } 569 return result; 570 } 571 572 /** 573 * Performs the necessary processing to compute the DN of a changelog entry 574 * @param changeNumber the change number of the entry for which to build the DN 575 * @return the DN string 576 */ 577 public String getChangelogEntryDN(final Long changeNumber) 578 { 579 return CHANGE_NUMBER_ATTRIBUTE_TYPE + "=" + changeNumber + "," +baseDN; 580 } 581 582 /** 583 * Commits the entry to the snapshot changelog backend and handles the possible exceptions 584 * 585 * @param changelogEntry the changelog entry to commit 586 */ 587 private synchronized void commitEntry(final Entry changelogEntry) 588 { 589 Long changeNumber = persister.getNextChangeNumber(); 590 try 591 { 592 changelogEntry.setDN(getChangelogEntryDN(changeNumber)); 593 serverContext.getInternalRootConnection().add(changelogEntry); 594 } catch (LDAPException e) 595 { 596 File persisterFile = new File(failedPersistPath.getPath()+"."+(failedPersistUseDate ? failedPersistDateFormat.format(new Date()) : changeNumber.toString())); 597 try { 598 FileWriter fw = new FileWriter(persisterFile); 599 fw.write(changelogEntry.toLDIFString()); 600 fw.flush(); 601 fw.close(); 602 } catch (IOException ioe) { 603 out("Could not persist changelog entry " + changelogEntry.getDN()); 604 out(changelogEntry.toLDIFString()); 605 serverContext.debugCaught(ioe); 606 } 607 } 608 } 609 610 /** 611 * Performs the necessary processing to sanitize an entry to only include attributes of interest 612 * 613 * @param entry the original entry 614 * @return the resulting entry with the pared down list of attributes 615 */ 616 private Entry sanitizeEntry(Entry entry) 617 { 618 Entry result = entry.duplicate(); 619 boolean evaluateIncludes = includeAttributes != null && includeAttributes.size() > 0; 620 boolean evaluateExcludes = excludeAttributes != null && excludeAttributes.size() > 0; 621 622 if (evaluateIncludes) 623 { 624 for (Attribute attribute : entry.getAttributes()) 625 { 626 if (includeAttributes.stream().noneMatch(attribute.getBaseName()::equalsIgnoreCase)) 627 { 628 result.removeAttribute(attribute.getName()); 629 } 630 } 631 } 632 if (evaluateExcludes) 633 { 634 for (String attributeType : excludeAttributes) 635 { 636 result.removeAttribute(attributeType); 637 } 638 } 639 640 return result; 641 } 642 643 644 /** 645 * Performs the necessary processing to determine whether to accept processing the operation 646 * This is a check to try and bail early before most processing is done 647 * @param operationResult the operation to evaluate 648 * @return true if the operation should be processed 649 * @throws LDAPException 650 */ 651 private boolean accept(final GenericResult operationResult) 652 { 653 boolean result = false; 654 if (ResultCode.SUCCESS.equals(operationResult.getResultCode())) 655 { 656 return true; 657 } 658 return result; 659 } 660 661 /** 662 * Shorthand convenience method to log to server context 663 * @param message the message to log 664 * @param logSeverity the log severity to log with 665 */ 666 private void log(String message, LogSeverity logSeverity) 667 { 668 if ( serverContext == null ) 669 { 670 out(message); 671 } else { 672 serverContext.logMessage(logSeverity,message); 673 } 674 } 675 676 /** 677 * Shorthand convenience method to catch throwables 678 * @param throwable the throwable caught 679 */ 680 private void debug(Throwable throwable) 681 { 682 if ( serverContext == null ) 683 { 684 out(throwable.getMessage()); 685 } else { 686 serverContext.debugCaught(throwable); 687 } 688 } 689 690 /** 691 * Shorthand utility method to print to STD OUT 692 * @param message the message to print 693 */ 694 private void out(final String message) 695 { 696 System.out.println(message); 697 } 698}