001package com.identityworksllc.iiq.common; 002 003import bsh.EvalError; 004import bsh.This; 005import com.fasterxml.jackson.core.JsonProcessingException; 006import com.fasterxml.jackson.databind.ObjectMapper; 007import com.identityworksllc.iiq.common.logging.SLogger; 008import org.apache.commons.beanutils.PropertyUtils; 009import org.apache.commons.logging.Log; 010import org.apache.commons.logging.LogFactory; 011import sailpoint.api.IdentityService; 012import sailpoint.api.IncrementalObjectIterator; 013import sailpoint.api.IncrementalProjectionIterator; 014import sailpoint.api.SailPointContext; 015import sailpoint.api.SailPointFactory; 016import sailpoint.object.*; 017import sailpoint.search.MapMatcher; 018import sailpoint.tools.GeneralException; 019import sailpoint.tools.MapUtil; 020import sailpoint.tools.Util; 021import sailpoint.tools.xml.AbstractXmlObject; 022 023import java.lang.reflect.Method; 024import java.lang.reflect.Modifier; 025import java.lang.reflect.Parameter; 026import java.sql.Connection; 027import java.sql.ResultSet; 028import java.sql.SQLException; 029import java.text.ParseException; 030import java.text.SimpleDateFormat; 031import java.util.*; 032import java.util.function.BiConsumer; 033import java.util.function.BiFunction; 034import java.util.function.Consumer; 035import java.util.function.Function; 036import java.util.function.Predicate; 037import java.util.function.Supplier; 038import java.util.regex.Matcher; 039import java.util.regex.Pattern; 040import java.util.stream.Collectors; 041import java.util.stream.Stream; 042import java.util.stream.StreamSupport; 043 044import static com.identityworksllc.iiq.common.Utilities.getProperty; 045 046/** 047 * This class implements a whole slew of `java.util.function` implementations, which 048 * can be used as a hack to get streams working in Beanshell. This can simplify code 049 * enormously. 050 * 051 * For example, you could do something like: 052 * 053 * ``` 054 * someMethod(Object item) { 055 * // Beanshell function that does something interesting with the item 056 * } 057 * 058 * // Creates a {@link Consumer} that invokes the specified Beanshell method 059 * someMethodFunction = Functions.c(this, "someMethod"); 060 * 061 * list.forEach(someMethodFunction); 062 * ``` 063 * 064 * @author Devin Rosenbauer 065 * @author Instrumental Identity 066 */ 067@SuppressWarnings({"rawtypes", "unused"}) 068public class Functions { 069 /** 070 * A generic callback for doing connection things 071 */ 072 @FunctionalInterface 073 public interface ConnectionHandler { 074 /** 075 * Does something with the connection 076 * @param connection The connection 077 * @throws SQLException on SQL errors 078 * @throws GeneralException on IIQ errors 079 */ 080 void accept(Connection connection) throws SQLException, GeneralException; 081 } 082 083 /** 084 * A generic callback for doing result set row handling things 085 */ 086 @FunctionalInterface 087 public interface RowHandler { 088 /** 089 * Does something with the current row of the ResultSet provided 090 * 091 * @param resultSet The ResultSet from which the current row should be extracted 092 * @throws SQLException on JDBC errors 093 * @throws GeneralException on IIQ errors 094 */ 095 void accept(ResultSet resultSet) throws SQLException, GeneralException; 096 } 097 098 /** 099 * A generic callback implementation, essentially a runnable with an exception. This 100 * class also implements ConsumerWithError, making it easier to use via Beanshell. 101 */ 102 @FunctionalInterface 103 public interface GenericCallback extends ConsumerWithError<SailPointContext> { 104 @Override 105 default void acceptWithError(SailPointContext context) throws GeneralException { 106 run(context); 107 } 108 109 void run(SailPointContext context) throws GeneralException; 110 } 111 112 /** 113 * An extension of Predicate that allows predicate code to throw 114 * an exception. If used in a context not expecting this class, 115 * the error will be caught, logged, and re-thrown. 116 */ 117 public interface PredicateWithError<A> extends Predicate<A> { 118 /** 119 * Wraps the default {@link Predicate#and(Predicate)} method, allowing it 120 * to return a PredicateWithError that can throw an exception. 121 * 122 * @param other a predicate that will be logically-ANDed with this predicate 123 * @return a PredicateWithError that combines the two predicates 124 */ 125 default PredicateWithError<A> and(Predicate<? super A> other) { 126 return (a) -> { 127 boolean result = testWithError(a); 128 if (result) { 129 if (other instanceof PredicateWithError) { 130 @SuppressWarnings("unchecked") 131 PredicateWithError<? super A> errorHandler = (PredicateWithError<? super A>) other; 132 result = errorHandler.testWithError(a); 133 } else { 134 result = other.test(a); 135 } 136 } 137 return result; 138 }; 139 } 140 141 default PredicateWithError<A> negate() { 142 return (a) -> !testWithError(a); 143 } 144 145 default PredicateWithError<A> or(Predicate<? super A> other) { 146 return (a) -> { 147 boolean result = testWithError(a); 148 if (!result) { 149 if (other instanceof PredicateWithError) { 150 @SuppressWarnings("unchecked") 151 PredicateWithError<? super A> errorHandler = (PredicateWithError<? super A>) other; 152 result = errorHandler.testWithError(a); 153 } else { 154 result = other.test(a); 155 } 156 } 157 return result; 158 }; 159 } 160 161 @Override 162 default boolean test(A a) { 163 try { 164 return testWithError(a); 165 } catch (Throwable t) { 166 log.error("Caught an error in a function"); 167 if (t instanceof RuntimeException) { 168 throw ((RuntimeException) t); 169 } 170 throw new IllegalStateException(t); 171 } 172 } 173 174 boolean testWithError(A object) throws Throwable; 175 } 176 177 /** 178 * An extension of BiFunction that allows functional code to throw 179 * an exception. If used in a context not expecting this class, 180 * the error will be caught, logged, and re-thrown. 181 */ 182 public interface BiFunctionWithError<A, B, R> extends BiFunction<A, B, R> { 183 @SuppressWarnings("unchecked") 184 default <V> BiFunctionWithError<A, B, V> andThen(Function<? super R, ? extends V> after) { 185 return (a, b) -> { 186 R result = applyWithError(a, b); 187 if (after instanceof FunctionWithError) { 188 return ((FunctionWithError<? super R, ? extends V>) after).applyWithError(result); 189 } 190 return after.apply(result); 191 }; 192 } 193 194 @Override 195 default R apply(A a, B b) { 196 try { 197 return applyWithError(a, b); 198 } catch (Throwable t) { 199 log.error("Caught an error in a function"); 200 if (t instanceof RuntimeException) { 201 throw ((RuntimeException) t); 202 } 203 throw new IllegalStateException(t); 204 } 205 } 206 207 R applyWithError(A obj1, B obj2) throws Throwable; 208 } 209 210 /** 211 * An extension of Consumer that allows functional code to throw 212 * an exception. If used in a context not expecting this class, 213 * the error will be caught, logged, and re-thrown. 214 */ 215 public interface ConsumerWithError<T> extends Consumer<T> { 216 @Override 217 default void accept(T a) { 218 try { 219 acceptWithError(a); 220 } catch (Throwable t) { 221 log.error("Caught an error in a function"); 222 if (t instanceof RuntimeException) { 223 throw ((RuntimeException) t); 224 } 225 throw new IllegalStateException(t); 226 } 227 } 228 229 void acceptWithError(T t) throws Throwable; 230 231 @Override 232 default ConsumerWithError<T> andThen(Consumer<? super T> after) { 233 return (a) -> { 234 acceptWithError(a); 235 if (after instanceof ConsumerWithError) { 236 @SuppressWarnings("unchecked") 237 ConsumerWithError<T> errorHandler = (ConsumerWithError<T>) after; 238 errorHandler.acceptWithError(a); 239 } else { 240 after.accept(a); 241 } 242 }; 243 } 244 } 245 246 /** 247 * An extension of Function that allows functional code to throw 248 * an exception. If used in a context not expecting this class, 249 * the error will be caught, logged, and re-thrown. 250 */ 251 @SuppressWarnings("unchecked") 252 public interface FunctionWithError<A, B> extends Function<A, B> { 253 default <V> FunctionWithError<A, V> andThen(Function<? super B, ? extends V> after) { 254 return o -> { 255 B result = applyWithError(o); 256 if (after instanceof FunctionWithError) { 257 return ((FunctionWithError<? super B, ? extends V>) after).applyWithError(result); 258 } 259 return after.apply(result); 260 }; 261 } 262 263 /** 264 * Handles the case where this object is used in a regular Stream API, which 265 * cannot handle errors. Logs the error and throws a runtime exception. 266 * @param a The input object of type A 267 * @return The output object of type B 268 */ 269 @Override 270 default B apply(A a) { 271 try { 272 return applyWithError(a); 273 } catch (Throwable t) { 274 log.error("Caught an error in a function"); 275 if (t instanceof RuntimeException) { 276 throw ((RuntimeException) t); 277 } 278 throw new IllegalStateException(t); 279 } 280 } 281 282 /** 283 * Implements a function transforming an object of type A to an object of 284 * type B. This function can throw an arbitrary error. 285 * 286 * @param object The input object 287 * @return The output object 288 * @throws Throwable if any errors occur 289 */ 290 B applyWithError(A object) throws Throwable; 291 292 /** 293 * Creates an error-friendly function composition that first translates from 294 * input type V to intermediate type A, then translates from A to B. 295 * 296 * B = f2(f1(V)) 297 * 298 * @param before The function to invoke before invoking this function 299 * @param <V> The input type of the 'before' function 300 * @return The composed FunctionWithError 301 */ 302 @Override 303 default <V> FunctionWithError<V, B> compose(Function<? super V, ? extends A> before) { 304 if (before instanceof FunctionWithError) { 305 return (v) -> applyWithError(((FunctionWithError<? super V, ? extends A>) before).applyWithError(v)); 306 } else { 307 return (v) -> applyWithError(before.apply(v)); 308 } 309 } 310 311 } 312 313 /** 314 * An extension of Supplier that allows functional code to throw 315 * an exception. If used in a context not expecting this class, 316 * the error will be caught, logged, and re-thrown. 317 */ 318 public interface SupplierWithError<T> extends Supplier<T> { 319 @Override 320 default T get() { 321 try { 322 return getWithError(); 323 } catch (Throwable t) { 324 log.error("Caught an error in a function"); 325 if (t instanceof RuntimeException) { 326 throw ((RuntimeException) t); 327 } 328 throw new IllegalStateException(t); 329 } 330 } 331 332 T getWithError() throws Throwable; 333 } 334 335 /** 336 * A dual Consumer and BiConsumer that just does nothing, eating 337 * the object 338 */ 339 public static class NullConsumer implements Consumer<Object>, BiConsumer<Object, Object> { 340 341 @Override 342 public void accept(Object o, Object o2) { 343 344 } 345 346 @Override 347 public void accept(Object o) { 348 349 } 350 } 351 352 /** 353 * Wrapper class so that Functions.otob() can be used as both a 354 * function and a predicate 355 */ 356 public static class OtobWrapper implements Function<Object, Boolean>, Predicate<Object> { 357 358 /** 359 * A holder object causing the singleton to be initialized on first use. 360 * The JVM automatically synchronizes it. 361 */ 362 private static final class OtobWrapperSingletonHolder { 363 /** 364 * The singleton OtobWrapper object 365 */ 366 static final OtobWrapper _singleton = new OtobWrapper(); 367 } 368 369 private OtobWrapper() { 370 /* do not instantiate */ 371 } 372 373 /** 374 * Gets an instance of OtobWrapper. At this time, this is a Singleton 375 * object, but you should not depend on that behavior. 376 * 377 * @return an {@link OtobWrapper} object 378 */ 379 public static OtobWrapper get() { 380 return OtobWrapperSingletonHolder._singleton; 381 } 382 383 @Override 384 public Boolean apply(Object o) { 385 return Util.otob(o); 386 } 387 388 @Override 389 public boolean test(Object o) { 390 return Util.otob(o); 391 } 392 } 393 394 /** 395 * Logger shared among various functions 396 */ 397 private static final Log log = LogFactory.getLog(Functions.class); 398 399 /** 400 * Private utility constructor 401 */ 402 private Functions() { 403 404 } 405 406 /** 407 * A flatMap() function to extract the account requests from a plan 408 */ 409 public static Function<ProvisioningPlan, Stream<ProvisioningPlan.AccountRequest>> accountRequests() { 410 return plan -> plan == null ? Stream.empty() : Utilities.safeStream(plan.getAccountRequests()); 411 } 412 413 /** 414 * Returns a predicate that is always false 415 * @param <T> The arbitrary type 416 * @return The predicate 417 */ 418 public static <T> Predicate<T> alwaysFalse() { 419 return (t) -> false; 420 } 421 422 /** 423 * Returns a predicate that is always true 424 * @param <T> The arbitrary type 425 * @return The predicate 426 */ 427 public static <T> Predicate<T> alwaysTrue() { 428 return (t) -> true; 429 } 430 431 /** 432 * Returns a Consumer that will append all inputs to the given List. 433 * If the list is not modifiable, the error will occur at runtime. 434 * 435 * @param values The List to which things should be added 436 * @return The function to add items to that list 437 * @param <T> The type of the items 438 */ 439 public static <T> Consumer<T> appendTo(List<? super T> values) { 440 return values::add; 441 } 442 443 /** 444 * Returns a Consumer that will append all inputs to the given List. 445 * If the set is not modifiable, the error will occur at runtime. 446 * 447 * @param values The Set to which things should be added 448 * @return The function to add items to that set 449 * @param <T> The type of the items 450 */ 451 public static <T> Consumer<T> appendTo(Set<? super T> values) { 452 return values::add; 453 } 454 455 /** 456 * Creates a Predicate that resolves to true if the given attribute on the argument 457 * resolves to the given value. 458 */ 459 public static Predicate<? extends SailPointObject> attributeEquals(final String attributeName, final Object testValue) { 460 return spo -> { 461 Attributes<String, Object> attributes = Utilities.getAttributes(spo); 462 if (attributes != null) { 463 Object val = attributes.get(attributeName); 464 return Util.nullSafeEq(val, testValue); 465 } 466 return (testValue == null); 467 }; 468 } 469 470 /** 471 * Creates a flatMap() stream to extract attribute requests from an account request 472 */ 473 public static Function<ProvisioningPlan.AccountRequest, Stream<ProvisioningPlan.AttributeRequest>> attributeRequests() { 474 return accountRequest -> accountRequest == null ? Stream.empty() : Utilities.safeStream(accountRequest.getAttributeRequests()); 475 } 476 477 /** 478 * Create a Predicate that resolves to true if the given attribute value is the same 479 * (per Sameness) as the given test value. 480 */ 481 public static Predicate<? extends SailPointObject> attributeSame(final String attributeName, final Object testValue) { 482 return spo -> { 483 Attributes<String, Object> attributes = Utilities.getAttributes(spo); 484 if (attributes != null) { 485 Object val = attributes.get(attributeName); 486 return Sameness.isSame(val, testValue, false); 487 } 488 return (testValue == null); 489 }; 490 } 491 492 /** 493 * Creates a BiConsumer that invokes the given Beanshell method, 494 * passing the inputs to the consumer. 495 * 496 * @param bshThis The Beanshell 'this' context 497 * @param methodName The method name to invoke 498 * @return The BiConsumer 499 */ 500 public static BiConsumer<?, ?> bc(bsh.This bshThis, String methodName) { 501 return (a, b) -> { 502 Object[] params = new Object[]{a, b}; 503 try { 504 bshThis.invokeMethod(methodName, params); 505 } catch (EvalError evalError) { 506 throw new RuntimeException(evalError); 507 } 508 }; 509 } 510 511 /** 512 * Returns the 'boxed' class corresponding to the given primitive. 513 * 514 * @param prim The primitive class, e.g. Long.TYPE 515 * @return The boxed class, e.g., java.lang.Long 516 */ 517 public static Class<?> box(Class<?> prim) { 518 Objects.requireNonNull(prim, "The class to box must not be null"); 519 if (prim.equals(Long.TYPE)) { 520 return Long.class; 521 } else if (prim.equals(Integer.TYPE)) { 522 return Integer.class; 523 } else if (prim.equals(Short.TYPE)) { 524 return Short.class; 525 } else if (prim.equals(Character.TYPE)) { 526 return Character.class; 527 } else if (prim.equals(Byte.TYPE)) { 528 return Byte.class; 529 } else if (prim.equals(Boolean.TYPE)) { 530 return Boolean.class; 531 } else if (prim.equals(Float.TYPE)) { 532 return Float.class; 533 } else if (prim.equals(Double.TYPE)) { 534 return Double.class; 535 } 536 throw new IllegalArgumentException("Unrecognized primitive type: " + prim.getName()); 537 } 538 539 /** 540 * Creates a Consumer that passes each input to the given beanshell method in the 541 * given namespace. 542 */ 543 public static ConsumerWithError<Object> c(bsh.This bshThis, String methodName) { 544 return object -> { 545 Object[] params = new Object[]{object}; 546 bshThis.invokeMethod(methodName, params); 547 }; 548 } 549 550 /** 551 * Creates a Consumer to invoke a method on each input. 552 */ 553 public static ConsumerWithError<Object> c(String methodName) { 554 return object -> { 555 Method method = object.getClass().getMethod(methodName); 556 method.invoke(object); 557 }; 558 } 559 560 /** 561 * Creates a Consumer to invoke a method on an object passed to it. The 562 * remaining inputs arguments will be provided as arguments to the method. 563 */ 564 public static ConsumerWithError<Object> c(String methodName, Object... inputs) { 565 return object -> { 566 Method method = findMethod(object.getClass(), methodName, false, inputs); 567 if (method != null) { 568 method.invoke(object, inputs); 569 } 570 }; 571 } 572 573 /** 574 * Returns a function that will cast the input object to the given type 575 * or throw a ClassCastException. 576 */ 577 public static <T> Function<Object, T> cast(Class<T> target) { 578 return target::cast; 579 } 580 581 /** 582 * Returns a Comparator that extracts the given property (by path) from the two 583 * input objects, then compares them. The property values must be Comparable to 584 * each other. 585 */ 586 @SuppressWarnings("rawtypes") 587 public static Comparator<Object> comparator(String property) { 588 return (a, b) -> { 589 try { 590 Object o1 = Utilities.getProperty(a, property); 591 Object o2 = Utilities.getProperty(b, property); 592 if (o1 instanceof Comparable && o2 instanceof Comparable) { 593 return Util.nullSafeCompareTo((Comparable) o1, (Comparable) o2); 594 } else { 595 throw new IllegalArgumentException("Property values must both implement Comparable; these are " + safeType(o1) + " and " + safeType(o2)); 596 } 597 } catch (Exception e) { 598 throw new IllegalStateException(e); 599 } 600 }; 601 } 602 603 /** 604 * Creates a Comparator that extracts the given property from the two input objects, 605 * then passes them both to the given Beanshell method, which must return an appropriate 606 * Comparator integer result. 607 */ 608 public static Comparator<Object> comparator(String property, bsh.This bshThis, String methodName) { 609 return (a, b) -> { 610 try { 611 Object o1 = PropertyUtils.getProperty(a, property); 612 Object o2 = PropertyUtils.getProperty(b, property); 613 Object[] params = new Object[]{o1, o2}; 614 return Util.otoi(bshThis.invokeMethod(methodName, params)); 615 } catch (Exception e) { 616 throw new IllegalStateException(e); 617 } 618 }; 619 } 620 621 /** 622 * Creates a Comparator that directly passes the two input objects to the given 623 * Beanshell method. The method must return an appropriate Comparator integer 624 * result. 625 */ 626 public static Comparator<Object> comparator(bsh.This bshThis, String methodName) { 627 return (a, b) -> { 628 Object[] params = new Object[]{a, b}; 629 try { 630 return Util.otoi(bshThis.invokeMethod(methodName, params)); 631 } catch (EvalError evalError) { 632 throw new IllegalStateException(evalError); 633 } 634 }; 635 } 636 637 /** 638 * Creates a Consumer that invokes the given static method on the given 639 * class for each input object. (The "cs" stands for consumer static.) 640 */ 641 public static ConsumerWithError<Object> cs(Class<?> sourceType, String methodName) { 642 return object -> { 643 Object[] params = new Object[]{object}; 644 Method method = findMethod(sourceType, methodName, true, params); 645 if (method != null) { 646 method.invoke(null, params); 647 } 648 }; 649 } 650 651 /** 652 * Creates a Consumer that invokes the given static method on the given 653 * class for each input object, passing the object as the first method 654 * parameter and the given param1 as the second. (The "cs" stands for 655 * consumer static.) 656 */ 657 public static ConsumerWithError<Object> cs(Class<?> sourceType, String methodName, Object param1) { 658 return object -> { 659 Object[] params = new Object[]{object, param1}; 660 Method method = findMethod(sourceType, methodName, true, params); 661 if (method != null) { 662 method.invoke(null, params); 663 } 664 }; 665 } 666 667 /** 668 * Creates a Consumer that invokes the given static method on the given 669 * class for each input object, passing the object as the first method 670 * parameter, the given param1 as the second, and the given param2 as the 671 * third. 672 */ 673 public static ConsumerWithError<Object> cs(Class<?> sourceType, String methodName, Object param1, Object param2) { 674 return object -> { 675 Object[] params = new Object[]{object, param1, param2}; 676 Method method = findMethod(sourceType, methodName, true, params); 677 if (method != null) { 678 method.invoke(null, params); 679 } 680 }; 681 } 682 683 /** 684 * Logs whatever is passed in at debug level, if the logger has debug enabled 685 * @param <T> The type of the object 686 * @return The consumer 687 */ 688 public static <T> Consumer<T> debug() { 689 return debug(log); 690 } 691 692 /** 693 * Logs whatever is passed in at debug level, if the logger has debug enabled 694 * @param logger The logger to which the value should be logged 695 * @param <T> The type of the object 696 * @return The consumer 697 */ 698 public static <T> Consumer<T> debug(Log logger) { 699 return obj -> { 700 if (logger.isDebugEnabled()) { 701 logger.debug(obj); 702 } 703 }; 704 } 705 706 /** 707 * Logs the input as XML, useful with peek() in a stream. Since no logger 708 * is specified, uses this class's own logger. 709 * 710 * @param <T> the input type 711 * @return The consumer 712 */ 713 public static <T extends AbstractXmlObject> Consumer<T> debugXml() { 714 return debugXml(log); 715 } 716 717 /** 718 * Logs the input as XML, useful with peek() in a stream 719 * @param logger The logger to which the object should be logged 720 * @param <T> the input type 721 * @return The consumer 722 */ 723 public static <T extends AbstractXmlObject> Consumer<T> debugXml(Log logger) { 724 return obj -> { 725 try { 726 if (logger.isDebugEnabled() && obj instanceof AbstractXmlObject) { 727 logger.debug(obj.toXml()); 728 } 729 } catch (GeneralException e) { 730 logger.error("Caught an error attempting to debug-log an object XML", e); 731 } 732 }; 733 } 734 735 /** 736 * Creates a Predicate that returns true if the input string ends with the given suffix 737 * @param suffix The suffix 738 * @return True if the input string ends with the suffix 739 */ 740 public static Predicate<String> endsWith(String suffix) { 741 return (s) -> s.endsWith(suffix); 742 } 743 744 /** 745 * Returns a Predicate that returns true if the two values are null-safe equals. 746 * Two null values will be considered equal. 747 * 748 * {@link Util#nullSafeEq(Object, Object)} is used under the hood. 749 * 750 * @param value The value to which each input should be compared 751 * @param <T> The type of the input expected 752 * @return The predicate 753 */ 754 public static <T> Predicate<? extends T> eq(final T value) { 755 return o -> Util.nullSafeEq(o, value, true); 756 } 757 758 /** 759 * Returns a Predicate that returns true if the extracted value from the input is equal 760 * (in a null-safe way) to the test value. If your test value is itself a Predicate, 761 * it will be invoked to test the extracted value. 762 * 763 * @param valueExtractor A function to extract a value for comparison from the actual input object 764 * @param testValue The text value 765 */ 766 public static <K, T> Predicate<K> eq(Function<K, T> valueExtractor, Object testValue) { 767 return (input) -> { 768 T value = valueExtractor.apply(input); 769 if (testValue instanceof Predicate) { 770 return ((Predicate<T>) testValue).test(value); 771 } else { 772 return Util.nullSafeEq(value, testValue, true); 773 } 774 }; 775 } 776 777 /** 778 * Returns a Predicate that resolves to true when tested against an object that is 779 * equal to the input to this method. 780 * 781 * @param value The value to test for equality with the Predicate input 782 * @return The predicate 783 */ 784 public static Predicate<String> eqIgnoreCase(final String value) { 785 return o -> Util.nullSafeCaseInsensitiveEq(o, value); 786 } 787 788 /** 789 * Returns a function that extracts the Nth matching group from applying the 790 * regular expression to the input string. 791 * 792 * The return value will be an empty Optional if the input does not match the 793 * regular expression or if the group was not matched. Otherwise, it will be 794 * an Optional containing the contents of the matched group. 795 * 796 * @param regex The regular expression 797 * @param matchGroup Which match group to return (starting with 1) 798 * @return A function with the above behavior 799 */ 800 public static Function<String, Optional<String>> extractRegexMatchGroup(final String regex, final int matchGroup) { 801 Pattern pattern = Pattern.compile(regex); 802 803 return string -> { 804 Matcher matcher = pattern.matcher(string); 805 if (matcher.find()) { 806 String group = matcher.group(matchGroup); 807 return Optional.ofNullable(group); 808 } else { 809 return Optional.empty(); 810 } 811 }; 812 } 813 814 /** 815 * Type-free version of {@link Functions#f(String, Class)} 816 */ 817 public static <K> Function<K, Object> f(String methodName) { 818 return f(methodName, Object.class); 819 } 820 821 /** 822 * Type-free version of {@link Functions#f(String, Class, Object...)} 823 */ 824 public static <K> Function<K, Object> f(String methodName, Object... parameters) { 825 return f(methodName, Object.class, parameters); 826 } 827 828 /** 829 * Creates a Function that invokes the given method on the input object, and 830 * returns the output of that method. The given parameters will be passed to 831 * the method. 832 * 833 * NOTE: Beanshell will be confused if you don't pass at least one argument. 834 * 835 * @param methodName The method name to invoke 836 * @param expectedType The expected type of the output 837 * @param parameters The remaining parameters to pass to the method 838 * @param <T> The value 839 * @return The function 840 */ 841 public static <K, T> Function<K, T> f(String methodName, Class<T> expectedType, Object... parameters) { 842 return object -> { 843 try { 844 Class<?> cls = object.getClass(); 845 Method method = findMethod(cls, methodName, false, parameters); 846 if (method != null) { 847 return expectedType.cast(method.invoke(object, parameters)); 848 } 849 } catch (Exception e) { 850 /* Ignore */ 851 } 852 return null; 853 }; 854 } 855 856 /** 857 * Creates a Function that invokes the named Beanshell method, passing the input 858 * object as its parameter, and returning the method's return value. 859 * 860 * A simplified type-free version of {@link Functions#f(This, String, Class, Object...)} 861 * 862 * @param bshThis The object on which the method should be invoked 863 * @param methodName The method name to invoke 864 * @return The function 865 */ 866 public static Function<Object, Object> f(bsh.This bshThis, String methodName) { 867 return f(bshThis, methodName, Object.class); 868 } 869 870 /** 871 * Creates a Function that invokes the named Beanshell method, passing the input 872 * object as its parameter, and returning the method's return value. 873 * 874 * This is roughly equivalent to the class method syntax, ClassName::method. 875 * 876 * @param bshThis The object on which the method should be invoked 877 * @param methodName The method name to invoke 878 * @param expectedResult The expected type of the output 879 * @param parameters The other parameters to pass to the method 880 * @param <B> The type of the output 881 * @return The function 882 */ 883 public static <B> Function<Object, B> f(bsh.This bshThis, String methodName, Class<B> expectedResult, Object... parameters) { 884 return object -> { 885 try { 886 List<Object> params = new ArrayList<>(); 887 params.add(object); 888 if (parameters != null) { 889 params.addAll(Arrays.asList(parameters)); 890 } 891 return expectedResult.cast(bshThis.invokeMethod(methodName, params.toArray())); 892 } catch (Exception e) { 893 // TODO Handle this with an Either 894 throw new RuntimeException(e); 895 } 896 }; 897 } 898 899 /** 900 * Creates a Function that invokes the named method on the given target object, 901 * passing the input item as its parameter. 902 * 903 * This is roughly equivalent to the instance method syntax, obj::method, and can 904 * be used to do stuff equivalent to: 905 * 906 * list.stream().filter(map::containsKey)... 907 * 908 * @param target The object on which the method should be invoked 909 * @param methodName The method name to invoke 910 * @param expectedType The expected type of the output 911 * @param <T> The type of the output 912 * @return The function 913 */ 914 public static <T> Function<Object, T> f(Object target, String methodName, Class<T> expectedType) { 915 if (target instanceof bsh.This) { 916 // Beanshell is bad at calling this method; help it out 917 return f((bsh.This) target, methodName, expectedType, new Object[0]); 918 } 919 return object -> { 920 try { 921 Object[] params = new Object[]{object}; 922 Class<?> sourceType = target.getClass(); 923 Method method = findMethod(sourceType, methodName, false, params); 924 if (method != null) { 925 return expectedType.cast(method.invoke(target, object)); 926 } else { 927 log.warn("Could not find matching method " + methodName + " in target class " + target.getClass()); 928 } 929 } catch (Exception e) { 930 /* Ignore */ 931 } 932 return null; 933 }; 934 } 935 936 /** 937 * Creates a Function that invokes the given method on the input object, and 938 * returns the output of that method. This is essentially equivalent to the 939 * class method reference syntax Class::method. 940 * 941 * @param methodName The method name to invoke 942 * @param expectedType The expected type of the output 943 * @param <T> The value 944 * @return The function 945 * @param <K> The input type 946 */ 947 public static <K, T> Function<K, T> f(String methodName, Class<T> expectedType) { 948 return object -> { 949 try { 950 Class<?> cls = object.getClass(); 951 Method method = cls.getMethod(methodName); 952 return expectedType.cast(method.invoke(object)); 953 } catch (Exception e) { 954 /* Ignore */ 955 } 956 return null; 957 }; 958 } 959 960 /** 961 * Finds the most specific accessible Method on the given Class that accepts 962 * the parameters provided. If two methods are equally specific, which would 963 * usually result in an "ambiguous method call" compiler error, an arbitrary 964 * one will be returned. 965 * 966 * @param toSearch The class to search for the method 967 * @param name The name of the method to locate 968 * @param findStatic True if we should look at static methods, false if we should look at instance methods 969 * @param params The intended parameters to the method 970 * @return The Method discovered, or null if none match 971 */ 972 public static Method findMethod(Class<?> toSearch, String name, boolean findStatic, Object... params) { 973 boolean hasNull = false; 974 for(Object in : params) { 975 if (in == null) { 976 hasNull = true; 977 } 978 } 979 List<Method> foundMethods = new ArrayList<>(); 980 method: 981 for (Method m : toSearch.getMethods()) { 982 if (Modifier.isStatic(m.getModifiers()) != findStatic) { 983 continue; 984 } 985 if (m.getName().equals(name)) { 986 Parameter[] paramTypes = m.getParameters(); 987 boolean isVarArgs = m.isVarArgs(); 988 // Quick and easy case 989 if (params.length == 0 && paramTypes.length == 0) { 990 foundMethods.add(m); 991 } else if (params.length == paramTypes.length) { 992 int index = 0; 993 for (Parameter p : paramTypes) { 994 Object param = params[index]; 995 if (param == null && p.getType().isPrimitive()) { 996 // Can't pass a 'null' to a primitive input, will be an NPE 997 continue method; 998 } else if (param != null && !isAssignableFrom(p.getType(), params[index].getClass())) { 999 continue method; 1000 } 1001 index++; 1002 } 1003 foundMethods.add(m); 1004 } else if (params.length == paramTypes.length - 1 && isVarArgs) { 1005 for (int index = 0; index < paramTypes.length - 1; index++) { 1006 Parameter p = paramTypes[index]; 1007 if (params.length > index) { 1008 Object param = params[index]; 1009 if (param == null && p.getType().isPrimitive()) { 1010 // Can't pass a 'null' to a primitive input, will be an NPE 1011 continue method; 1012 } else if (param != null && !isAssignableFrom(p.getType(), params[index].getClass())) { 1013 continue method; 1014 } 1015 } 1016 } 1017 foundMethods.add(m); 1018 } 1019 } 1020 } 1021 if (foundMethods.size() == 0) { 1022 return null; 1023 } else if (foundMethods.size() == 1) { 1024 return foundMethods.get(0); 1025 } else if (hasNull) { 1026 // We can't proceed here because we can't narrow down what the 1027 // caller meant by a null. The compiler could do it if we cast 1028 // the null explicitly, but we can't do it at runtime. 1029 return null; 1030 } 1031 1032 Comparator<Method> methodComparator = (m1, m2) -> { 1033 int paramCount = m1.getParameterCount(); 1034 int weight = 0; 1035 for (int p = 0; p < paramCount; p++) { 1036 Class<?> c1 = m1.getParameterTypes()[p]; 1037 Class<?> c2 = m2.getParameterTypes()[p]; 1038 1039 if (!c1.equals(c2)) { 1040 if (isAssignableFrom(c2, c1)) { 1041 weight--; 1042 } else { 1043 weight++; 1044 } 1045 } 1046 } 1047 if (weight == 0) { 1048 Class<?> r1 = m1.getReturnType(); 1049 Class<?> r2 = m2.getReturnType(); 1050 1051 if (!r1.equals(r2)) { 1052 if (isAssignableFrom(r2, r1)) { 1053 weight--; 1054 } else { 1055 weight++; 1056 } 1057 } 1058 } 1059 return weight; 1060 }; 1061 1062 foundMethods.sort(methodComparator); 1063 1064 return foundMethods.get(0); 1065 } 1066 1067 /** 1068 * Transforms a provisioning plan into a list of requests for the given application 1069 * @param application The application name 1070 * @return The list of account requests 1071 */ 1072 public static Function<ProvisioningPlan, List<ProvisioningPlan.AccountRequest>> findRequests(String application) { 1073 return plan -> plan.getAccountRequests(application); 1074 } 1075 1076 /** 1077 * Transforms a provisioning plan into a list of requests with the given operation 1078 * @param operation The operation to look for 1079 * @return The list of account requests 1080 */ 1081 public static Function<ProvisioningPlan, List<ProvisioningPlan.AccountRequest>> findRequests(ProvisioningPlan.AccountRequest.Operation operation) { 1082 return plan -> Utilities.safeStream(plan.getAccountRequests()).filter(ar -> Util.nullSafeEq(ar.getOperation(), operation)).collect(Collectors.toList()); 1083 } 1084 1085 /** 1086 * Runs the given consumer for each item in the collection 1087 * @param values The values to iterate 1088 * @param consumer The consumer to apply to each 1089 * @param <T> The type of the collection 1090 */ 1091 public static <T> void forEach(Collection<T> values, Consumer<T> consumer) { 1092 values.forEach(consumer); 1093 } 1094 1095 /** 1096 * Runs the given consumer for each entry in the map 1097 * @param values The map of values 1098 * @param consumer The consumer of the map entries 1099 * @param <A> The key type 1100 * @param <B> The value type 1101 */ 1102 public static <A, B> void forEach(Map<A, B> values, Consumer<Map.Entry<A, B>> consumer) { 1103 values.entrySet().forEach(consumer); 1104 } 1105 1106 /** 1107 * Runs the given bi-consumer for all of the values in the map 1108 * @param values The values 1109 * @param consumer The bi-consumer to be passed the key and value 1110 * @param <A> The key type 1111 * @param <B> The value type 1112 */ 1113 public static <A, B> void forEach(Map<A, B> values, BiConsumer<A, B> consumer) { 1114 values.forEach(consumer); 1115 } 1116 1117 /** 1118 * Runs the given consumer for each remaining item returned by the Iterator 1119 * @param values The values to iterate 1120 * @param consumer The consumer to apply to each 1121 * @param <A> The type of the values 1122 */ 1123 public static <A> void forEach(Iterator<A> values, Consumer<A> consumer) { 1124 values.forEachRemaining(consumer); 1125 } 1126 1127 /** 1128 * Invokes the named static function on the given class, passing the input object to it. 1129 * The output will be cast to the expected type and returned. 1130 */ 1131 public static <T> Function<Object, T> fs(Class<?> targetClass, String methodName, Class<T> expectedType) { 1132 return object -> { 1133 try { 1134 Class<?> cls = object.getClass(); 1135 Method method = findMethod(targetClass, methodName, true, object); 1136 if (method == null) { 1137 log.warn("Could not find matching static method " + methodName + " in target class " + targetClass); 1138 } else { 1139 return expectedType.cast(method.invoke(null, object)); 1140 } 1141 } catch (Exception e) { 1142 /* Ignore */ 1143 } 1144 return null; 1145 }; 1146 } 1147 1148 /** 1149 * Constructs a Supplier that 'curries' the given Function, applying 1150 * it to the input object and returning the result. This allows you to 1151 * do logger-related structures like this in Beanshell: 1152 * 1153 * ftoc(someObject, getStringAttribute("hi")) 1154 * 1155 * @param inputObject The input object to curry 1156 * @param function The function to apply to the input object 1157 * @return A supplier wrapping the object and function call 1158 * @param <In> The input object type 1159 * @param <Out> The output object type 1160 */ 1161 public static <In, Out> Supplier<Out> ftoc(In inputObject, Function<? super In, ? extends Out> function) { 1162 return () -> function.apply(inputObject); 1163 } 1164 1165 /** 1166 * Functionally implements the {@link Utilities#getProperty(Object, String)} method 1167 * @param beanPath The path to the property to retrieve from the object 1168 * @return A function that retrieves the property value from the object 1169 */ 1170 public static Function<Object, Object> get(String beanPath) { 1171 return get(Object.class, beanPath); 1172 } 1173 1174 /** 1175 * Functionally implements the {@link Utilities#getProperty(Object, String)} method. 1176 * If the value is not of the expected type, returns null silently. 1177 * 1178 * @param expectedType The expected type of the property value 1179 * @param beanPath The path to the property to retrieve from the object 1180 * @return A function that retrieves the value of the given property 1181 * @param <T> The expected type of the property value 1182 */ 1183 public static <T> Function<Object, T> get(Class<T> expectedType, String beanPath) { 1184 return obj -> { 1185 try { 1186 return expectedType.cast(getProperty(obj, beanPath, true)); 1187 } catch (Exception e) { 1188 return null; 1189 } 1190 }; 1191 } 1192 1193 /** 1194 * Returns a function to get the administrator of the given Identity. 1195 * @return The function that retrieves the administrator of the Identity 1196 */ 1197 public static Function<Identity, Identity> getAdministrator() { 1198 return Identity::getAdministrator; 1199 } 1200 1201 /** 1202 * If the input object has Attributes, retrieves the attribute with the given name, 1203 * otherwise returns null. 1204 * 1205 * @param attributeName The attribute name to retrieve 1206 * @return The function 1207 */ 1208 public static Function<? extends SailPointObject, Object> getAttribute(final String attributeName) { 1209 return spo -> { 1210 Attributes<String, Object> attributes = Utilities.getAttributes(spo); 1211 if (attributes != null) { 1212 return attributes.get(attributeName); 1213 } 1214 return null; 1215 }; 1216 } 1217 1218 /** 1219 * Extracts a boolean attribute from the input object, or false if the 1220 * attribute is not present. 1221 * 1222 * @param attributeName The attribute name 1223 * @return The function 1224 */ 1225 public static Function<? extends SailPointObject, Boolean> getBooleanAttribute(final String attributeName) { 1226 return getBooleanAttribute(attributeName, false); 1227 } 1228 1229 /** 1230 * Extracts a boolean attribute from the input object, or the default value if the 1231 * attribute is not present. 1232 * 1233 * @param attributeName The attribute name 1234 * @param defaultValue The default value 1235 * @return The function 1236 */ 1237 public static Function<? extends SailPointObject, Boolean> getBooleanAttribute(final String attributeName, final boolean defaultValue) { 1238 return spo -> { 1239 Attributes<String, Object> attributes = Utilities.getAttributes(spo); 1240 if (attributes != null) { 1241 return attributes.getBoolean(attributeName, defaultValue); 1242 } 1243 return defaultValue; 1244 }; 1245 } 1246 1247 /** 1248 * Extracts the list of entitlements from the given Identity object, optionally 1249 * filtering them with the filter if provided. This is a direct query against the 1250 * database. 1251 * 1252 * @param context The context 1253 * @param optionalFilter An optional filter which will be used to decide which entitlements to return 1254 * @return The function 1255 */ 1256 public static Function<Identity, List<IdentityEntitlement>> getEntitlements(SailPointContext context, Filter optionalFilter) { 1257 return identity -> { 1258 try { 1259 QueryOptions qo = new QueryOptions(); 1260 qo.add(Filter.eq("identity.id", identity.getId())); 1261 if (optionalFilter != null) { 1262 qo.add(optionalFilter); 1263 } 1264 List<IdentityEntitlement> entitlements = context.getObjects(IdentityEntitlement.class, qo); 1265 if (entitlements == null) { 1266 entitlements = new ArrayList<>(); 1267 } 1268 return entitlements; 1269 } catch (GeneralException e) { 1270 return new ArrayList<>(); 1271 } 1272 }; 1273 } 1274 1275 /** 1276 * Gets all links associated with the given Identity. This will use IdentityService 1277 * to do a database query rather than pulling the links from the Identity object, 1278 * because the Identity object's list can be unreliable in long-running DB sessions. 1279 */ 1280 public static Function<Identity, List<Link>> getLinks() { 1281 return (identity) -> { 1282 List<Link> output = new ArrayList<>(); 1283 try { 1284 SailPointContext context = SailPointFactory.getCurrentContext(); 1285 IdentityService service = new IdentityService(context); 1286 List<Link> links = service.getLinks(identity, 0, 0); 1287 if (links != null) { 1288 output.addAll(links); 1289 } 1290 } catch (GeneralException e) { 1291 log.error("Caught an exception getting links for identity " + identity.getName() + " in a function"); 1292 } 1293 return output; 1294 }; 1295 } 1296 1297 /** 1298 * Gets all links associated with the given Identity on the given Application(s). 1299 * 1300 * @param applicationName The name of the application for which to retrieve links 1301 * @param moreApplicationNames Additional application names to retrieve links for 1302 * @return A function that retrieves the links for the Identity on the specified applications 1303 */ 1304 public static Function<Identity, List<Link>> getLinks(final String applicationName, final String... moreApplicationNames) { 1305 if (applicationName == null) { 1306 throw new IllegalArgumentException("Application name must not be null"); 1307 } 1308 1309 final List<String> names = new ArrayList<>(); 1310 names.add(applicationName); 1311 if (moreApplicationNames != null) { 1312 Collections.addAll(names, moreApplicationNames); 1313 } 1314 1315 return (identity) -> { 1316 List<Link> output = new ArrayList<>(); 1317 try { 1318 SailPointContext context = SailPointFactory.getCurrentContext(); 1319 for (String name : names) { 1320 Application application = context.getObjectByName(Application.class, name); 1321 IdentityService service = new IdentityService(context); 1322 List<Link> links = service.getLinks(identity, application); 1323 if (links != null) { 1324 output.addAll(links); 1325 } 1326 } 1327 } catch (GeneralException e) { 1328 log.error("Caught an exception getting links for identity " + identity.getName() + " in a function"); 1329 } 1330 return output; 1331 }; 1332 } 1333 1334 /** 1335 * Gets all links associated with the given Identity on the given Application 1336 */ 1337 public static Function<Identity, List<Link>> getLinks(final Application application) { 1338 if (application == null) { 1339 throw new IllegalArgumentException("Application must not be null"); 1340 } 1341 return getLinks(application.getName()); 1342 } 1343 1344 /** 1345 * Gets the given attribute as a string from the input object. If the object has no 1346 * attributes, or the attribute is not present, returns null. 1347 * 1348 * @param attributeName The name of the attribute to retrieve 1349 * @return A function that retrieves the attribute value as a string 1350 */ 1351 public static Function<? extends SailPointObject, String> getStringAttribute(final String attributeName) { 1352 return spo -> { 1353 Attributes<String, Object> attributes = Utilities.getAttributes(spo); 1354 if (attributes != null) { 1355 return attributes.getString(attributeName); 1356 } 1357 return null; 1358 }; 1359 } 1360 1361 /** 1362 * Gets the given attribute as a string list from the input object 1363 * @param attributeName The name of the attribute to retrieve 1364 * @return A function that retrieves the attribute value as a string list 1365 */ 1366 public static Function<? extends SailPointObject, List<String>> getStringListAttribute(final String attributeName) { 1367 return spo -> { 1368 Attributes<String, Object> attributes = Utilities.getAttributes(spo); 1369 if (attributes != null) { 1370 return attributes.getStringList(attributeName); 1371 } 1372 return null; 1373 }; 1374 } 1375 1376 /** 1377 * Returns a Predicate that resolves to true if the input AccountRequest has 1378 * at least one AttributeRequest corresponding to every one of the attribute names 1379 * given 1380 * 1381 * @param attributeName The list of names to match against 1382 * @return The predicate 1383 */ 1384 public static Predicate<ProvisioningPlan.AccountRequest> hasAllAttributeRequest(final String... attributeName) { 1385 return hasAttributeRequest(true, attributeName); 1386 } 1387 1388 /** 1389 * Returns a Predicate that resolves to true if the input AccountRequest has 1390 * at least one AttributeRequest corresponding to any of the attribute names given 1391 * @param attributeName The list of names to match against 1392 * @return The predicate 1393 */ 1394 public static Predicate<ProvisioningPlan.AccountRequest> hasAnyAttributeRequest(final String... attributeName) { 1395 return hasAttributeRequest(false, attributeName); 1396 } 1397 1398 /** 1399 * Resolves to true if the input object has a non-null value for the given attribute 1400 */ 1401 public static Predicate<? extends SailPointObject> hasAttribute(final String attributeName) { 1402 return spo -> { 1403 Attributes<String, Object> attributes = Utilities.getAttributes(spo); 1404 if (attributes != null) { 1405 return attributes.get(attributeName) != null; 1406 } 1407 return false; 1408 }; 1409 } 1410 1411 /** 1412 * Returns a predicate that resolves to true if the account request has the attribute(s) 1413 * in question, controlled by the 'useAnd' parameter. 1414 * 1415 * @param useAnd If true, all names must match an AttributeRequest. If false, only one match will resolve in a true. 1416 * @param attributeName The list of attribute names 1417 */ 1418 public static Predicate<ProvisioningPlan.AccountRequest> hasAttributeRequest(boolean useAnd, final String... attributeName) { 1419 List<String> attributeNames = new ArrayList<>(); 1420 if (attributeName != null) { 1421 Collections.addAll(attributeNames, attributeName); 1422 } 1423 return spo -> { 1424 Set<String> fieldNames = new HashSet<>(); 1425 for (ProvisioningPlan.AttributeRequest attributeRequest : Util.safeIterable(spo.getAttributeRequests())) { 1426 if (attributeNames.isEmpty() || attributeNames.contains(attributeRequest.getName())) { 1427 if (useAnd) { 1428 fieldNames.add(attributeRequest.getName()); 1429 } else { 1430 return true; 1431 } 1432 } 1433 } 1434 return useAnd && fieldNames.size() > attributeNames.size(); 1435 }; 1436 } 1437 1438 /** 1439 * Returns a Predicate that resolves to true if the input Identity has an account of the given application 1440 * @param applicationName The name of the application to check for 1441 * @return A predicate that resolves to true if the Identity has a link to the application 1442 */ 1443 public static Predicate<Identity> hasLink(final String applicationName) { 1444 return (identity) -> { 1445 try { 1446 SailPointContext context = SailPointFactory.getCurrentContext(); 1447 IdentityLinkUtil service = new IdentityLinkUtil(context, identity); 1448 List<Link> links = service.getLinksByApplication(applicationName); 1449 return links != null && !links.isEmpty(); 1450 } catch (GeneralException e) { 1451 log.error("Caught an exception getting links for identity " + identity.getName() + " in a function"); 1452 return false; 1453 } 1454 }; 1455 } 1456 1457 /** 1458 * Resolves to the ID of the input SailPointObject 1459 */ 1460 public static Function<? extends SailPointObject, String> id() { 1461 return SailPointObject::getId; 1462 } 1463 1464 /** 1465 * Resolves to true if the input object is in the given list 1466 */ 1467 public static Predicate<?> in(final List<String> list) { 1468 return o -> Util.nullSafeContains(list, o); 1469 } 1470 1471 /** 1472 * Resolves to true if the input object is in the given set 1473 */ 1474 public static Predicate<?> in(final Set<String> list) { 1475 return in(new ArrayList<>(list)); 1476 } 1477 1478 /** 1479 * Returns true if targetType is assignable from otherType, e.g. if the following code 1480 * would not fail to compile: 1481 * 1482 * OtherType ot = new OtherType(); 1483 * TargetType tt = ot; 1484 * 1485 * This is also equivalent to 'b instanceof A' or 'otherType extends targetType'. 1486 * 1487 * Primitive types and their boxed equivalents have special handling. 1488 * 1489 * @param targetType The first (parent-ish) class 1490 * @param otherType The second (child-ish) class 1491 * @return True if cls1 is assignable from cls2 1492 */ 1493 public static boolean isAssignableFrom(Class<?> targetType, Class<?> otherType) { 1494 return Utilities.isAssignableFrom(targetType, otherType); 1495 } 1496 1497 /** 1498 * A predicate that resolves to true if the boolean attribute of the input object 1499 * is true according to {@link Attributes#getBoolean(String)}. 1500 * 1501 * @param attributeName The attribute name to query 1502 * @return The predicate 1503 */ 1504 public static Predicate<? extends SailPointObject> isBooleanAttribute(final String attributeName) { 1505 return isBooleanAttribute(attributeName, false); 1506 } 1507 1508 /** 1509 * A predicate that resolves to true if the boolean attribute of the input object 1510 * is true according to {@link Attributes#getBoolean(String, boolean)}. 1511 * 1512 * @param attributeName The attribute name to query 1513 * @param defaultValue The default to use if the attribute is empty 1514 * @return The predicate 1515 */ 1516 public static Predicate<? extends SailPointObject> isBooleanAttribute(final String attributeName, boolean defaultValue) { 1517 return spo -> { 1518 Attributes<String, Object> attributes = Utilities.getAttributes(spo); 1519 if (attributes != null) { 1520 return attributes.getBoolean(attributeName, defaultValue); 1521 } 1522 return defaultValue; 1523 }; 1524 } 1525 1526 /** 1527 * A predicate that resolves to true if the boolean property of the input object 1528 * is true according to {@link Util#otob(Object)} 1529 * 1530 * @param attributeName The property name to query 1531 * @return The predicate 1532 */ 1533 public static Predicate<? extends SailPointObject> isBooleanProperty(final String attributeName) { 1534 return isBooleanProperty(attributeName, false); 1535 } 1536 1537 /** 1538 * A predicate that resolves to true if the boolean property of the input object 1539 * is true according to {@link Util#otob(Object)} 1540 * 1541 * @param attributeName The property name to query 1542 * @param defaultValue The default to use if the property is empty 1543 * @return The predicate 1544 */ 1545 public static Predicate<? extends SailPointObject> isBooleanProperty(final String attributeName, boolean defaultValue) { 1546 return spo -> { 1547 try { 1548 Object property = getProperty(spo, attributeName); 1549 return Util.otob(property); 1550 } catch (GeneralException e) { 1551 /* Ignore */ 1552 } 1553 return false; 1554 }; 1555 } 1556 1557 /** 1558 * A predicate that resolves to true if the input is disabled. If the input object is of type Identity, 1559 * its {@link Identity#isInactive()} will be called. Otherwise, {@link SailPointObject#isDisabled()}. 1560 * @param <T> The type of the object 1561 * @return The predicate 1562 */ 1563 public static <T extends SailPointObject> Predicate<T> isDisabled() { 1564 return spo -> { 1565 if (spo instanceof Identity) { 1566 return ((Identity) spo).isInactive(); 1567 } else { 1568 return spo.isDisabled(); 1569 } 1570 }; 1571 } 1572 1573 /** 1574 * Returns a predicate that resolves to true if the input is empty according to Sameness 1575 * @param <T> The type of the object 1576 * @return The preidcate 1577 */ 1578 public static <T> Predicate<T> isEmpty() { 1579 return Sameness::isEmpty; 1580 } 1581 1582 /** 1583 * Resolves to true if the input object is an instance of the target 1584 * class. For specific common classes, this uses a quicker instanceof, 1585 * and for everything else, it passes off to isAssignableFrom. 1586 * <p> 1587 * Null inputs will always be false. 1588 */ 1589 public static <T> Predicate<T> isInstanceOf(Class<?> target) { 1590 Objects.requireNonNull(target); 1591 // Faster versions for specific common classes 1592 if (target.equals(String.class)) { 1593 return obj -> obj instanceof String; 1594 } else if (target.equals(List.class)) { 1595 return obj -> obj instanceof List; 1596 } else if (target.equals(Map.class)) { 1597 return obj -> obj instanceof Map; 1598 } else if (target.equals(SailPointObject.class)) { 1599 return obj -> obj instanceof SailPointObject; 1600 } else if (target.equals(Identity.class)) { 1601 return obj -> obj instanceof Identity; 1602 } else if (target.equals(Link.class)) { 1603 return obj -> obj instanceof Link; 1604 } 1605 return obj -> (obj != null && isAssignableFrom(target, obj.getClass())); 1606 } 1607 1608 /** 1609 * Returns a Predicate that resolves to true if the input Link's native identity is equal to the 1610 * comparison value. 1611 */ 1612 public static Predicate<Link> isNativeIdentity(final String nativeIdentity) { 1613 return link -> Util.nullSafeEq(link.getNativeIdentity(), nativeIdentity); 1614 } 1615 1616 public static Predicate<String> isNotNullOrEmpty() { 1617 return Util::isNotNullOrEmpty; 1618 } 1619 1620 /** 1621 * Resolves to true if the object is not null 1622 */ 1623 public static <T> Predicate<T> isNull() { 1624 return Objects::isNull; 1625 } 1626 1627 /** 1628 * Resolves to true if the given property on the given object is 1629 * not null or empty. Emptiness is defined by this class's isEmpty(). 1630 */ 1631 public static <T> Predicate<T> isNullOrEmpty(String propertyPath) { 1632 return o -> { 1633 try { 1634 Object property = getProperty(o, propertyPath); 1635 if (property == null) { 1636 return true; 1637 } 1638 return isEmpty().test(property); 1639 } catch (GeneralException e) { 1640 /* Ignore this */ 1641 } 1642 return true; 1643 }; 1644 } 1645 1646 /** 1647 * Functional equivalent to Util.isNullOrEmpty 1648 */ 1649 public static Predicate<String> isNullOrEmpty() { 1650 return Util::isNullOrEmpty; 1651 } 1652 1653 /** 1654 * Extracts the key from the given map entry 1655 */ 1656 public static <T> Function<Map.Entry<T, ?>, T> key() { 1657 return Map.Entry::getKey; 1658 } 1659 1660 /** 1661 * Returns a Predicate that resolves to true if the input string matches the given pattern, as 1662 * equivalent to the Filter like() method 1663 */ 1664 public static Predicate<? extends String> like(final String pattern, final Filter.MatchMode matchMode) { 1665 if (Util.isNullOrEmpty(pattern)) { 1666 throw new IllegalArgumentException("Pattern must not be null or empty"); 1667 } 1668 if (matchMode.equals(Filter.MatchMode.EXACT)) { 1669 return eq(pattern); 1670 } 1671 return s -> { 1672 if (s == null || s.isEmpty()) { 1673 return false; 1674 } 1675 if (matchMode.equals(Filter.MatchMode.START)) { 1676 return s.startsWith(pattern); 1677 } else if (matchMode.equals(Filter.MatchMode.END)) { 1678 return s.endsWith(pattern); 1679 } else if (matchMode.equals(Filter.MatchMode.ANYWHERE)) { 1680 return s.contains(pattern); 1681 } else { 1682 return false; 1683 } 1684 }; 1685 } 1686 1687 /** 1688 * Returns the Identity for the given Link object 1689 * @return Gets the Link identity 1690 */ 1691 public static Function<Link, Identity> linkGetIdentity() { 1692 return Link::getIdentity; 1693 } 1694 1695 /** 1696 * Creates a predicate that tests whether the Link has the given application name 1697 * @param applicationName The application name 1698 * @return The predicate 1699 */ 1700 public static Predicate<Link> linkIsApplication(String applicationName) { 1701 return (link) -> Util.nullSafeEq(link.getApplicationName(), applicationName); 1702 } 1703 1704 /** 1705 * Creates a predicate that tests whether the Link has the given application name. 1706 * The ID will be cached in the closure, so you do not need to keep the Application 1707 * object in the same Hibernate scope. 1708 * 1709 * @param application The application object 1710 * @return The predicate 1711 */ 1712 public static Predicate<Link> linkIsApplication(Application application) { 1713 final String applicationId = application.getId(); 1714 return (link) -> Util.nullSafeEq(link.getApplicationId(), applicationId); 1715 } 1716 1717 /** 1718 * Returns a functional getter for a map of a given type 1719 * @param index The index to get 1720 * @param <T> The type stored in the list 1721 * @return The output 1722 */ 1723 public static <T> Function<List<T>, T> listSafeGet(int index) { 1724 return (list) -> { 1725 if (list.size() <= index) { 1726 return null; 1727 } else if (index < 0) { 1728 return null; 1729 } else { 1730 return list.get(index); 1731 } 1732 }; 1733 } 1734 1735 /** 1736 * Logs the input object at warn() level in the default Functions logger. 1737 * This is intended for use with peek() in the middle of a stream. 1738 */ 1739 public static <T> Consumer<T> log() { 1740 return (obj) -> { 1741 if (log.isWarnEnabled()) { 1742 log.warn(obj); 1743 } 1744 }; 1745 } 1746 1747 /** 1748 * Logs the input object at warn() level in the provided logger 1749 * This is intended for use with peek() in the middle of a stream. 1750 */ 1751 public static <T> Consumer<T> log(Log logger) { 1752 return (obj) -> { 1753 if (logger.isWarnEnabled()) { 1754 logger.warn(obj); 1755 } 1756 }; 1757 } 1758 1759 /** 1760 * Converts the given object to XML and logs it to the default logger as a warning 1761 * @param <T> The object type 1762 * @return A consumer that will log the object 1763 */ 1764 public static <T extends AbstractXmlObject> Consumer<T> logXml() { 1765 return logXml(log); 1766 } 1767 1768 /** 1769 * Converts the given object to XML and logs it to the given logger 1770 * @param logger The logger 1771 * @param <T> The object type 1772 * @return A consumer that will log the object 1773 */ 1774 public static <T extends AbstractXmlObject> Consumer<T> logXml(Log logger) { 1775 return obj -> { 1776 try { 1777 if (logger.isWarnEnabled() && obj instanceof AbstractXmlObject) { 1778 logger.warn(obj.toXml()); 1779 } 1780 } catch (GeneralException e) { 1781 logger.error("Caught an error attempting to log an object XML", e); 1782 } 1783 }; 1784 } 1785 1786 /** 1787 * For the given input key, returns the value of that key in the given map. 1788 * 1789 * @param map Returns the result of {@link MapUtil#get(Map, String)} 1790 * @return A function that takes a string and returns the proper value from the Map 1791 */ 1792 public static Function<String, Object> lookup(final Map<String, Object> map) { 1793 return key -> { 1794 try { 1795 return MapUtil.get(map, key); 1796 } catch (GeneralException e) { 1797 /* Ignore */ 1798 return null; 1799 } 1800 }; 1801 } 1802 1803 /** 1804 * For the given input map, returns the value at the key. The key is a 1805 * MapUtil path. 1806 * 1807 * @param key Returns the result of {@link MapUtil#get(Map, String)} 1808 * @return A function that takes a Map and returns the value of the given key in that map 1809 */ 1810 public static Function<Map<String, Object>, Object> lookup(final String key) { 1811 return map -> { 1812 try { 1813 return MapUtil.get(map, key); 1814 } catch (GeneralException e) { 1815 /* Ignore */ 1816 return null; 1817 } 1818 }; 1819 } 1820 1821 /** 1822 * Same as lookup(String), but casts the output to the expected type 1823 */ 1824 public static <B> Function<Map<String, B>, B> lookup(final String key, final Class<B> expectedType) { 1825 return map -> { 1826 try { 1827 return expectedType.cast(MapUtil.get((Map<String, Object>) map, key)); 1828 } catch (GeneralException e) { 1829 return null; 1830 } 1831 }; 1832 } 1833 1834 /** 1835 * Returns a Function converting a string to an object of the given type, 1836 * looked up using the given SailPointContext. 1837 * 1838 * In 8.4, ensure that the SailPointContext is the correct one for the object 1839 * type. Certain objects are now in the Access History context. 1840 * 1841 * @param <T> The type of SailPointObject to look up 1842 * @param context The context to use to look up the object 1843 * @param sailpointClass The object type to read 1844 * @return A function to look up objects of the given type by ID or name 1845 */ 1846 public static <T extends SailPointObject> Function<String, T> lookup(final SailPointContext context, final Class<T> sailpointClass) { 1847 return name -> { 1848 try { 1849 return context.getObject(sailpointClass, name); 1850 } catch (GeneralException e) { 1851 return null; 1852 } 1853 }; 1854 } 1855 1856 /** 1857 * Returns a functional getter for a map of a given type 1858 * @param key The key to get 1859 * @param <T> The key type of the map 1860 * @param <U> The value type of the map 1861 * @return The output 1862 */ 1863 public static <T, U> Function<Map<T, ? extends U>, U> mapGet(T key) { 1864 return (map) -> map.get(key); 1865 } 1866 1867 /** 1868 * Returns a functional predicate to compare a Map to the filter provided. 1869 * 1870 * @param filter The filter to execute against the Map 1871 * @param <T> The key type of the map 1872 * @param <U> The value type of the map 1873 * @return The output 1874 */ 1875 public static <T, U> PredicateWithError<Map<T, U>> mapMatches(Filter filter) { 1876 return (map) -> { 1877 MapMatcher matcher = new MapMatcher(filter); 1878 return matcher.matches(map); 1879 }; 1880 } 1881 1882 /** 1883 * Creates a map comparator for sorting a map against the given key(s). Additional 1884 * keys are implemented using recursive calls back to this method. 1885 * 1886 * @param key The first key for comparing 1887 * @param keys A list of additional keys for comparing 1888 * @return The comparator 1889 */ 1890 @SafeVarargs 1891 public static <K> Comparator<Map<K, Object>> mapValueComparator(K key, K... keys) { 1892 Comparator<Map<K, Object>> mapComparator = Comparator.comparing(mapGet(key).andThen(otoa())); 1893 if (keys != null) { 1894 for(K otherKey : keys) { 1895 if (otherKey != null) { 1896 mapComparator = mapComparator.thenComparing(mapValueComparator(otherKey)); 1897 } 1898 } 1899 } 1900 return mapComparator; 1901 } 1902 1903 /** 1904 * Creates a Predicate that retrieves the key of the given name from the input 1905 * Map and returns true if the value equals the given value. 1906 * 1907 * @param key The key to query in the map 1908 * @param value The value to query in the map 1909 */ 1910 public static <T, U> Predicate<Map<T, ? extends U>> mapValueEquals(T key, U value) { 1911 return eq(Functions.mapGet(key), value); 1912 } 1913 1914 /** 1915 * Resolves to true if the input object matches the filter. This ought to be thread-safe 1916 * if the SailPointFactory's current context is correct for the thread. 1917 * 1918 * {@link HybridObjectMatcher} is used to do the matching. 1919 * 1920 * @param filter The Filter to evaluate against the input object 1921 * @return A predicate returning true when the filter matches the input 1922 */ 1923 public static PredicateWithError<? extends SailPointObject> matches(Filter filter) { 1924 return spo -> { 1925 HybridObjectMatcher matcher = new HybridObjectMatcher(SailPointFactory.getCurrentContext(), filter, false); 1926 return matcher.matches(spo); 1927 }; 1928 } 1929 1930 /** 1931 * Resolves to true if the input object matches the filter. This ought to be thread-safe 1932 * if the SailPointFactory's current context is correct for the thread. 1933 * 1934 * {@link HybridObjectMatcher} is used to do the matching. 1935 * 1936 * @param filter The Filter to evaluate against the input object 1937 * @param matchType The class to match, which does not need to be a SailPointObject 1938 * @return A predicate returning true when the filter matches the input 1939 */ 1940 public static <T> PredicateWithError<T> matches(Filter filter, Class<T> matchType) { 1941 return object -> { 1942 HybridObjectMatcher matcher = new HybridObjectMatcher(SailPointFactory.getCurrentContext(), filter, false); 1943 return matcher.matches(object); 1944 }; 1945 } 1946 1947 /** 1948 * Resolves to true if the input object matches the filter. The filter will be 1949 * compiled when this method is called, and then the remainder is a simple 1950 * forward to {@link #matches(Filter)}. 1951 * 1952 * @param filterString The filter string to evaluate against the input object 1953 * @return A predicate returning true when the filter matches the input 1954 */ 1955 public static PredicateWithError<? extends SailPointObject> matches(String filterString) { 1956 Filter filter = Filter.compile(filterString); 1957 return matches(filter); 1958 } 1959 1960 /** 1961 * Maps to the name of the input object 1962 */ 1963 public static Function<? extends SailPointObject, String> name() { 1964 return SailPointObject::getName; 1965 } 1966 1967 /** 1968 * Resolves to true if the input object does not equal the value 1969 */ 1970 public static <T> Predicate<? extends T> ne(final T value) { 1971 return eq(value).negate(); 1972 } 1973 1974 /** 1975 * Returns a supplier returning a new, writeable list. This can be passed to {@link Collectors#toCollection(Supplier)} for example. 1976 * @param <T> The type of the list 1977 * @return the new ArrayList supplier 1978 */ 1979 public static <T> Supplier<List<T>> newList() { 1980 return ArrayList::new; 1981 } 1982 1983 /** 1984 * Returns a supplier returning a new, writeable set. This can be passed to {@link Collectors#toCollection(Supplier)} for example. 1985 * @param <T> The type of the list 1986 * @return the new HashSet supplier 1987 */ 1988 public static <T> Supplier<Set<T>> newSet() { 1989 return HashSet::new; 1990 } 1991 1992 /** 1993 * Returns a predicate that resolves to true if the input item is not null 1994 * @return The predicate 1995 */ 1996 public static Predicate<?> nonNull() { 1997 return Objects::nonNull; 1998 } 1999 2000 /** 2001 * A very basic normalizer function that trims spaces and lowercases the 2002 * input string, then removes all non-ASCII characters. This should be replaced 2003 * with a better normalization algorithm in most cases. 2004 * 2005 * @return The function 2006 */ 2007 public static Function<String, String> normalize() { 2008 return (s) -> s == null ? "" : s.trim().toLowerCase(Locale.getDefault()).replaceAll("[^A-Za-z0-9_]+", ""); 2009 } 2010 2011 /** 2012 * Implements a generic "black hole" or "dev null" consumer that swallows 2013 * the object. This can be used as a sane default where another consumer 2014 * is required. 2015 */ 2016 public static NullConsumer nothing() { 2017 return new NullConsumer(); 2018 } 2019 2020 /** 2021 * Returns a function that transforms a value into an Optional that will 2022 * be empty if the value matches {@link Utilities#isNothing(Object)}. 2023 * 2024 * @return A function that transforms a value into an Optional 2025 * @param <T> The type of the object 2026 */ 2027 public static <T> Function<T, Optional<T>> nothingToOptional() { 2028 return (item) -> { 2029 if (Utilities.isNothing(item)) { 2030 return Optional.empty(); 2031 } else { 2032 return Optional.of(item); 2033 } 2034 }; 2035 } 2036 2037 /** 2038 * Returns a function that transforms a value into a Stream that will 2039 * be empty if the value matches {@link Utilities#isNothing(Object)}. 2040 * 2041 * @return A function that transforms a value into an Optional 2042 * @param <T> The type of the object 2043 */ 2044 public static <T> Function<T, Stream<T>> nothingToStream() { 2045 return (item) -> { 2046 if (Utilities.isNothing(item)) { 2047 return Stream.empty(); 2048 } else { 2049 return Stream.of(item); 2050 } 2051 }; 2052 } 2053 2054 /** 2055 * Creates a BiFunction that resolves to Boolean true if the two input objects are equal, ignoring case 2056 * 2057 * @return the BiFunction 2058 */ 2059 public static BiFunction<Object, Object, Boolean> nullSafeEq() { 2060 return Util::nullSafeEq; 2061 } 2062 2063 /** 2064 * Transforms the input object into a non-null string 2065 */ 2066 public static Function<Object, String> nullToEmpty() { 2067 return Utilities::safeString; 2068 } 2069 2070 /** 2071 * Resolves to true if the given AccountRequest's operation equals the given value 2072 */ 2073 public static Predicate<ProvisioningPlan.AccountRequest> operationEquals(ProvisioningPlan.AccountRequest.Operation operation) { 2074 return accountRequest -> Util.nullSafeEq(accountRequest.getOperation(), operation); 2075 } 2076 2077 /** 2078 * Resolves to true if the given provisioning plan's operation equals the given value 2079 */ 2080 public static Predicate<ProvisioningPlan.AttributeRequest> operationEquals(ProvisioningPlan.Operation operation) { 2081 return attributeRequest -> Util.nullSafeEq(attributeRequest.getOperation(), operation); 2082 } 2083 2084 /** 2085 * Returns a predicate that resolves to true if the input Optional is empty 2086 * @return The predicate 2087 */ 2088 public static Predicate<Optional<?>> optionalEmpty() { 2089 return (o) -> !o.isPresent(); 2090 } 2091 2092 /** 2093 * Returns a predicate that resolves to true if the input Optional is present 2094 * @return The predicate 2095 */ 2096 public static Predicate<Optional<?>> optionalPresent() { 2097 return Optional::isPresent; 2098 } 2099 2100 /** 2101 * Returns a Beanshell-friendly equivalent to the JDK 9 Optional::stream 2102 * function. The stream will have zero or one elements and is intended 2103 * for use with {@link Stream#flatMap(Function)}. 2104 * 2105 * @return A function from an Optional to a Stream 2106 * @param <T> The type of the object 2107 */ 2108 public static <T> Function<Optional<T>, Stream<T>> optionalToStream() { 2109 return o -> o.map(Stream::of).orElseGet(Stream::empty); 2110 } 2111 2112 /** 2113 * Creates a function equivalent to Util::otoa 2114 * @return The function 2115 */ 2116 public static <T> Function<T, String> otoa() { 2117 return Util::otoa; 2118 } 2119 2120 /** 2121 * Returns an object that implements both Function and Predicate and returns 2122 * the result of Util::otob on the input object 2123 * @return The function/predicate 2124 */ 2125 public static OtobWrapper otob() { 2126 return OtobWrapper.get(); 2127 } 2128 2129 /** 2130 * Creates a function equivalent to Util::otoi 2131 * @return The function 2132 */ 2133 public static <T> Function<T, Integer> otoi() { 2134 return Util::otoi; 2135 } 2136 2137 /** 2138 * Creates a function equivalent to Util::otol 2139 * @return The function 2140 */ 2141 public static <T> Function<T, List<String>> otol() { 2142 return Util::otol; 2143 } 2144 2145 /** 2146 * Transforms a Function that returns a boolean into a predicate 2147 * @param f The function to transform 2148 * @return The predicate 2149 */ 2150 public static <T> Predicate<T> p(Function<T, Boolean> f) { 2151 return f::apply; 2152 } 2153 2154 /** 2155 * Invokes the given method on the input object, resolving to the 2156 * otob truthy conversion of its output. 2157 */ 2158 @SuppressWarnings("unchecked") 2159 public static <T> Predicate<T> p(String methodName) { 2160 return (Predicate<T>) p(f(methodName).andThen(Util::otob)); 2161 } 2162 2163 /** 2164 * Invokes the given method against the target object, passing the 2165 * input object as its only parameter, then resolves to true if the 2166 * output is otob truthy. 2167 */ 2168 public static Predicate<?> p(Object target, String methodName) { 2169 return p(f(target, methodName, Object.class).andThen(Util::otob)); 2170 } 2171 2172 /** 2173 * Constructs a Predicate that invokes the given method against the 2174 * input object, providing the additional inputs as needed, then returns true 2175 * if the result is 'otob' truthy. 2176 */ 2177 public static Predicate<?> p(String methodName, Object... inputs) { 2178 return p(f(methodName, inputs).andThen(Util::otob)); 2179 } 2180 2181 /** 2182 * Invokes the given Beanshell method, which will receive the input 2183 * object as its sole parameter, and then resolves to true if the 2184 * method returns an otob truthy value. 2185 */ 2186 public static Predicate<?> p(bsh.This bshThis, String methodName) { 2187 return p(f(bshThis, methodName).andThen(Util::otob)); 2188 } 2189 2190 /** 2191 * Parses each incoming string as a date according to the provided format, 2192 * returning null if there is a parse exception 2193 */ 2194 public static Function<String, Date> parseDate(String format) { 2195 if (Util.isNotNullOrEmpty(format)) { 2196 throw new IllegalArgumentException("The format string " + format + " is not valid"); 2197 } 2198 // Also throws an exception if it's a bad format 2199 SimpleDateFormat preParse = new SimpleDateFormat(format); 2200 return date -> { 2201 final SimpleDateFormat formatter = new SimpleDateFormat(format); 2202 try { 2203 return formatter.parse(date); 2204 } catch (ParseException e) { 2205 if (log.isDebugEnabled()) { 2206 log.debug("Could not parse input string " + date + " according to formatter " + format); 2207 } 2208 return null; 2209 } 2210 }; 2211 } 2212 2213 /** 2214 * Returns a Predicate that resolves to true if the given plan has the given attribute on the given 2215 * application. This can be done in a more fluent way using Plans.find 2216 * if desired. 2217 */ 2218 public static Predicate<ProvisioningPlan> planHasAttribute(String application, String attribute) { 2219 return plan -> ( 2220 Utilities.safeStream(plan.getAccountRequests()) 2221 .filter(ar -> Util.nullSafeEq(ar.getApplicationName(), application)) 2222 .flatMap(ar -> Utilities.safeStream(ar.getAttributeRequests())) 2223 .anyMatch(attr -> Util.nullSafeEq(attr.getName(), attribute))); 2224 } 2225 2226 /** 2227 * Creates a Predicate that returns true if provided a ProvisioningPlan that contains 2228 * an AccountRequest with the given Operation 2229 * 2230 * @param operation The operation to check for 2231 * @return The predicate 2232 */ 2233 public static Predicate<ProvisioningPlan> planHasOperation(ProvisioningPlan.AccountRequest.Operation operation) { 2234 return plan -> (Utilities.safeStream(plan.getAccountRequests()).anyMatch(ar -> Util.nullSafeEq(ar.getOperation(), operation))); 2235 } 2236 2237 /** 2238 * Creates a Predicate that returns true if provided a ProvisioningPlan that contains 2239 * an AccountRequest with the given application name and request operation 2240 * 2241 * @param application The name of the application 2242 * @param operation The operation to check for 2243 * @return The predicate 2244 */ 2245 public static Predicate<ProvisioningPlan> planHasOperation(String application, ProvisioningPlan.AccountRequest.Operation operation) { 2246 return plan -> (Utilities.safeStream(plan.getAccountRequests()).anyMatch(ar -> Util.nullSafeEq(ar.getApplicationName(), application) && Util.nullSafeEq(ar.getOperation(), operation))); 2247 } 2248 2249 /** 2250 * Maps to a Stream of provisioning plans (for use with flatMap) in the given project 2251 */ 2252 public static Function<ProvisioningProject, Stream<ProvisioningPlan>> plans() { 2253 return (project) -> project == null ? Stream.empty() : Utilities.safeStream(project.getPlans()); 2254 } 2255 2256 /** 2257 * Resolves to true if the given property value on the input object can be 2258 * coerced to a Date and is after the given Date. 2259 */ 2260 public static Predicate<Object> propertyAfter(final String propertyPath, final Date test) { 2261 return source -> { 2262 try { 2263 Object propertyValue = Utilities.getProperty(source, propertyPath); 2264 if (propertyValue == null) { 2265 return false; 2266 } 2267 if (propertyValue instanceof Date) { 2268 Date d = (Date) propertyValue; 2269 return d.after(test); 2270 } else if (propertyValue instanceof Number) { 2271 long date = ((Number) propertyValue).longValue(); 2272 Date d = new Date(date); 2273 return d.after(test); 2274 } else if (propertyValue instanceof String) { 2275 long date = Long.parseLong((String) propertyValue); 2276 Date d = new Date(date); 2277 return d.after(test); 2278 } 2279 } catch (GeneralException ignored) { 2280 /* Nothing */ 2281 } 2282 return false; 2283 }; 2284 } 2285 2286 /** 2287 * Resolves to true if the given property value on the input object can be 2288 * coerced to a Date and is before the given Date. 2289 */ 2290 public static Predicate<Object> propertyBefore(final String propertyPath, final Date test) { 2291 return source -> { 2292 try { 2293 Object propertyValue = Utilities.getProperty(source, propertyPath); 2294 if (propertyValue == null) { 2295 return false; 2296 } 2297 if (propertyValue instanceof Date) { 2298 Date d = (Date) propertyValue; 2299 return d.before(test); 2300 } else if (propertyValue instanceof Number) { 2301 long date = ((Number) propertyValue).longValue(); 2302 Date d = new Date(date); 2303 return d.before(test); 2304 } else if (propertyValue instanceof String) { 2305 long date = Long.parseLong((String) propertyValue); 2306 Date d = new Date(date); 2307 return d.before(test); 2308 } 2309 } catch (GeneralException ignored) { 2310 /* Nothing */ 2311 } 2312 return false; 2313 }; 2314 } 2315 2316 /** 2317 * Returns a Predicate that resolves to true if the given property on the input object equals the test value 2318 */ 2319 public static Predicate<Object> propertyEquals(final String propertyPath, final Object test) { 2320 return source -> { 2321 try { 2322 Object propertyValue = Utilities.getProperty(source, propertyPath); 2323 return Util.nullSafeEq(propertyValue, test); 2324 } catch (GeneralException e) { 2325 return false; 2326 } 2327 }; 2328 } 2329 2330 /** 2331 * Returns a Predicate that resolves to true if the property at the given path matches the given regex. If the 2332 * property is a string it will be used directly. If the property is a List containing 2333 * only one string, that string will be extracted and used. 2334 * 2335 * In all other cases, including parse errors, false will be returned. 2336 * 2337 * @param propertyPath The path to the property via {@link Utilities#getProperty(Object, String)} 2338 * @param regexString The regular expression string 2339 * @return A predicate that returns true if the property extracted matches the 2340 */ 2341 public static Predicate<Object> propertyMatchesRegex(final String propertyPath, final String regexString) { 2342 Pattern regex = Pattern.compile(regexString); 2343 2344 return object -> { 2345 try { 2346 Object propertyValue = getProperty(object, propertyPath, true); 2347 if (propertyValue instanceof String) { 2348 String stringProperty = (String) propertyValue; 2349 Matcher regexMatcher = regex.matcher(stringProperty); 2350 return regexMatcher.find(); 2351 } else if (propertyValue instanceof List) { 2352 List<?> listProperty = (List<?>) propertyValue; 2353 if (listProperty.size() == 1) { 2354 Object onlyValue = listProperty.get(0); 2355 if (onlyValue instanceof String) { 2356 String stringProperty = (String) onlyValue; 2357 Matcher regexMatcher = regex.matcher(stringProperty); 2358 return regexMatcher.find(); 2359 } 2360 } 2361 } 2362 } catch(GeneralException e) { 2363 return false; 2364 } 2365 return false; 2366 }; 2367 } 2368 2369 /** 2370 * Returns a Predicate that resolves to true if the given property on the input object is the same as the 2371 * test value, per Sameness rules 2372 */ 2373 public static Predicate<Object> propertySame(final String propertyPath, final Object test) { 2374 return source -> { 2375 try { 2376 Object propertyValue = Utilities.getProperty(source, propertyPath); 2377 return Sameness.isSame(propertyValue, test, false); 2378 } catch (GeneralException e) { 2379 return false; 2380 } 2381 }; 2382 } 2383 2384 /** 2385 * Returns a Predicate that evaluates to true if the predicate's input 2386 * string matches the given regular expression. 2387 * 2388 * @param regex The regex 2389 * @return A predicate that matches the regex 2390 */ 2391 public static Predicate<String> regexMatches(final String regex) { 2392 return string -> string.matches(regex); 2393 } 2394 2395 /** 2396 * Returns a Predicate that evaluates to true if the predicate's input 2397 * string matches the given regular expression. 2398 * 2399 * Unlike the version of {@link #regexMatches(String)} taking a String, 2400 * this one uses {@link Matcher#find()}, meaning it will match partial 2401 * strings. 2402 * 2403 * @param regex The regex 2404 * @return A predicate that matches the regex 2405 */ 2406 public static Predicate<String> regexMatches(final Pattern regex) { 2407 return string -> regex.matcher(string).find(); 2408 } 2409 2410 /** 2411 * Returns a Predicate that evaluates to true if the predicate's input 2412 * string matches the given regular expression. 2413 * 2414 * @param regex The regex 2415 * @return A predicate that matches the regex 2416 */ 2417 public static Predicate<String> regexMatchesPartial(final String regex) { 2418 Pattern pattern = Pattern.compile(regex); 2419 2420 return string -> pattern.matcher(string).find(); 2421 } 2422 2423 /** 2424 * Safely casts the value to the given class, returning null if it can't be 2425 * cast to that value. 2426 * 2427 * @param expectedClass The expected class 2428 * @param <T> The expected class type 2429 * @return The value cast to the given type, or null if not that type 2430 */ 2431 public static <T> Function<Object, T> safeCast(Class<T> expectedClass) { 2432 return o -> Utilities.safeCast(o, expectedClass); 2433 } 2434 2435 /** 2436 * Safely retrieves the given property from the input object, returning 2437 * the default value if the result is null or throws an exception. 2438 */ 2439 public static <T> Function<Object, T> safeGet(String propertyName, T defaultValue, Class<T> expectedClass) { 2440 return o -> { 2441 try { 2442 Object result = Utilities.getProperty(o, propertyName, true); 2443 if (result == null) { 2444 return defaultValue; 2445 } 2446 return expectedClass.cast(result); 2447 } catch (Exception e) { 2448 return defaultValue; 2449 } 2450 }; 2451 } 2452 2453 /** 2454 * Safely retrieves the given property from the input object, returning 2455 * null if the property value is not of the expected type. 2456 */ 2457 public static <T> Function<Object, T> safeGet(String propertyName, Class<T> expectedClass) { 2458 return o -> { 2459 try { 2460 return expectedClass.cast(Utilities.getProperty(o, propertyName)); 2461 } catch (Exception e) { 2462 return null; 2463 } 2464 }; 2465 } 2466 2467 public static Function<Object, List<String>> safeListify() { 2468 return Utilities::safeListify; 2469 } 2470 2471 private static Class<?> safeType(Object obj) { 2472 if (obj == null) { 2473 return null; 2474 } 2475 return obj.getClass(); 2476 } 2477 2478 /** 2479 * Returns a Predicate that resolves to true if the input value is the same as the given test value, 2480 * per Sameness rules 2481 */ 2482 public static Predicate<?> sameAs(final Object value) { 2483 return sameAs(value, false); 2484 } 2485 2486 /** 2487 * Returns a Predicate that resolves to true if the input value is the same as the given test value, 2488 * ignoring case, per Sameness rules 2489 */ 2490 public static Predicate<?> sameAs(final Object value, boolean ignoreCase) { 2491 return o -> Sameness.isSame(o, value, ignoreCase); 2492 } 2493 2494 /** 2495 * Supplies a value by invoking the given Beanshell method with the 2496 * given parameters. On error, returns null. 2497 */ 2498 public static SupplierWithError<Boolean> sb(final bsh.This bsh, final String methodName, final Object... params) { 2499 return () -> Util.otob(bsh.invokeMethod(methodName, params)); 2500 } 2501 2502 /** 2503 * Creates a comparator that sorts in the order specified, leaving the 2504 * input objects alone. Equivalent to calling sortListOrder and passing 2505 * {@link Function#identity()} as the translator. 2506 */ 2507 public static <ListItem> Comparator<ListItem> sortListOrder(List<ListItem> order) { 2508 return sortListOrder(order, Function.identity()); 2509 } 2510 2511 /** 2512 * Creates a comparator that sorts in the order specified, translating input 2513 * values into sort keys first via the provided translator. Values not in the 2514 * order list will be sorted to the end of the list. 2515 * 2516 * For example, you might sort a list of Links into a specific order by 2517 * application to create a precedence structure. 2518 * 2519 * If no order is specified, the resulting Comparator will laboriously 2520 * leave the list in the original order. 2521 * 2522 * @param <ListItem> The type of the item being sorted 2523 * @param <SortType> The type of item in the ordering list 2524 * @param order The ordering to apply 2525 * @param keyExtractor The translator 2526 * @return The comparator 2527 */ 2528 public static <ListItem, SortType> Comparator<ListItem> sortListOrder(List<SortType> order, Function<ListItem, SortType> keyExtractor) { 2529 Map<SortType, Integer> orderMap = new HashMap<>(); 2530 if (order != null) { 2531 int index = 0; 2532 for (SortType v : order) { 2533 orderMap.put(v, index++); 2534 } 2535 } 2536 2537 return (v1, v2) -> { 2538 SortType key1 = keyExtractor.apply(v1); 2539 SortType key2 = keyExtractor.apply(v2); 2540 2541 int o1 = orderMap.getOrDefault(key1, Integer.MAX_VALUE); 2542 int o2 = orderMap.getOrDefault(key2, Integer.MAX_VALUE); 2543 2544 return (o1 - o2); 2545 }; 2546 } 2547 2548 /** 2549 * Returns a Predicate that resolves to true if the input string starts with the given prefix 2550 */ 2551 public static Predicate<String> startsWith(String prefix) { 2552 return (s) -> s.startsWith(prefix); 2553 } 2554 2555 /** 2556 * Creates a stream out of the given SailPoint search 2557 * @param context The context 2558 * @param spoClass The SailPointObject to search 2559 * @param qo The QueryOptions 2560 * @param <A> The object type 2561 * @return The stream 2562 * @throws GeneralException if the query fails 2563 */ 2564 public static <A extends SailPointObject> Stream<A> stream(SailPointContext context, Class<A> spoClass, QueryOptions qo) throws GeneralException { 2565 IncrementalObjectIterator<A> objects = new IncrementalObjectIterator<>(context, spoClass, qo); 2566 return StreamSupport.stream(Spliterators.spliterator(objects, objects.getSize(), Spliterator.ORDERED), false).onClose(() -> { 2567 Util.flushIterator(objects); 2568 }); 2569 } 2570 2571 /** 2572 * Creates a stream out of the given SailPoint projection search 2573 * @param context The context 2574 * @param spoClass The SailPoint class 2575 * @param qo The query filters 2576 * @param props The query properties to query 2577 * @param <A> The object type to query 2578 * @return The stream 2579 * @throws GeneralException if the query fails 2580 */ 2581 public static <A extends SailPointObject> Stream<Object[]> stream(SailPointContext context, Class<A> spoClass, QueryOptions qo, List<String> props) throws GeneralException { 2582 IncrementalProjectionIterator objects = new IncrementalProjectionIterator(context, spoClass, qo, props); 2583 return StreamSupport.stream(Spliterators.spliteratorUnknownSize(objects, Spliterator.ORDERED), false).onClose(() -> { 2584 Util.flushIterator(objects); 2585 }); 2586 } 2587 2588 2589 /** 2590 * Equivalent to {@link #stream(Function, Class)} if passed Object.class 2591 */ 2592 public static <A> Function<A, Stream<Object>> stream(Function<A, ? extends Object> input) { 2593 return stream(input, Object.class); 2594 } 2595 2596 /** 2597 * Applies the given function to the input object and then makes a Stream 2598 * out of it. When used with other functions, this is handy for flatMap. 2599 * 2600 * If the result of the function is not of the expected type, or is not a 2601 * List, Map, or Set of them, returns an empty Stream. 2602 * 2603 * @param input A function to transform the input object 2604 * @param expectedType The expected type of the output 2605 * @return A function from an input object to a Stream of output objects 2606 */ 2607 @SuppressWarnings("unchecked") 2608 public static <A, T> Function<A, Stream<T>> stream(Function<A, ?> input, Class<T> expectedType) { 2609 return object -> { 2610 Object result = input.apply(object); 2611 if (result == null) { 2612 return Stream.empty(); 2613 } 2614 if (result instanceof List) { 2615 return Utilities.safeStream((List)result); 2616 } else if (result instanceof Set) { 2617 return Utilities.safeStream((Set)result); 2618 } else if (result instanceof Map) { 2619 return Utilities.safeStream(((Map)result).entrySet()); 2620 } else if (expectedType.isAssignableFrom(result.getClass())) { 2621 return (Stream<T>)Stream.of(result); 2622 } else { 2623 return Stream.empty(); 2624 } 2625 }; 2626 } 2627 2628 /** 2629 * Returns a supplier that serializes the given object to JSON. The JSON 2630 * text is lazily determined only on supplier invocation. 2631 * 2632 * If an error occurs during JSON invocation, a warning will be logged and 2633 * an empty string will be returned. 2634 * 2635 * @param obj The object to serialize when the supplier is called 2636 * @return A supplier to JSON 2637 */ 2638 public static Supplier<String> toJson(Object obj) { 2639 return () -> { 2640 ObjectMapper mapper = Utilities.getJacksonObjectMapper(); 2641 try { 2642 return mapper.writeValueAsString(obj); 2643 } catch (JsonProcessingException e) { 2644 log.warn("Error converting object to JSON", e); 2645 return ""; 2646 } 2647 }; 2648 } 2649 2650 /** 2651 * Returns a Supplier that formats the given object for logging. The returned 2652 * object is an instance of {@link com.identityworksllc.iiq.common.logging.SLogger.Formatter}. 2653 * 2654 * @param obj The object to format 2655 * @return A supplier that formats the object for logging, as {@link SLogger} would do 2656 */ 2657 public static Supplier<String> toLogFormat(Object obj) { 2658 return new SLogger.Formatter(obj); 2659 } 2660 2661 /** 2662 * Returns a Supplier that invokes toString() on the given object, or 2663 * returns an empty string if the object is null. 2664 * 2665 * If the input is a Supplier itself (e.g., if you want lazily extract 2666 * an expensive object later), the Supplier will be invoked first 2667 * and then its result will be converted to string. 2668 * 2669 * @param obj The object to convert to string 2670 * @return A supplier that invokes toString() on the object 2671 */ 2672 public static Supplier<String> toString(Object obj) { 2673 // Handle case where the input is itself a Supplier 2674 if (obj instanceof Supplier) { 2675 return () -> { 2676 Object supplied = ((Supplier<?>) obj).get(); 2677 return supplied == null ? "" : supplied.toString(); 2678 }; 2679 } 2680 2681 return () -> obj == null ? "" : obj.toString(); 2682 } 2683 2684 /** 2685 * Returns a Supplier that translates the given AbstractXmlObject to XML. 2686 * This can be used with modern log4j2 invocations, notably. The call 2687 * to {@link AbstractXmlObject#toXml()} happens on invocation. The 2688 * result is not cached, so if the object is changed, subsequent 2689 * invocations of the Supplier may produce different output. 2690 * 2691 * @param spo The SailPointObject to serialize 2692 * @return A supplier that translates the SPO to XML when invoked 2693 */ 2694 public static Supplier<String> toXml(AbstractXmlObject spo) { 2695 if (spo == null) { 2696 return () -> ""; 2697 } 2698 2699 return () -> { 2700 try { 2701 return spo.toXml(); 2702 } catch (GeneralException e) { 2703 log.debug("Unable to translate object to XML", e); 2704 return e.toString(); 2705 } 2706 }; 2707 } 2708 2709 /** 2710 * Returns a mapping function producing the value of the input map entry 2711 * @param <T> The map entry's value type 2712 * @return A function producing the resulting value 2713 */ 2714 public static <T> Function<Map.Entry<?, T>, T> value() { 2715 return Map.Entry::getValue; 2716 } 2717 2718 /** 2719 * Returns a function mapping an Identity to its manager 2720 * @return The function that retrieves the manager of the Identity 2721 */ 2722 public Function<Identity, Identity> getManager() { 2723 return Identity::getManager; 2724 } 2725}