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