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