001// Copyright 2006-2025 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// You may obtain a copy of the License at 006// 007// http://www.apache.org/licenses/LICENSE-2.0 008// 009// Unless required by applicable law or agreed to in writing, software 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014 015package org.apache.tapestry5.ioc.internal.util; 016 017import org.apache.tapestry5.commons.*; 018import org.apache.tapestry5.commons.internal.util.InternalCommonsUtils; 019import org.apache.tapestry5.commons.services.Coercion; 020import org.apache.tapestry5.commons.services.PlasticProxyFactory; 021import org.apache.tapestry5.commons.util.CollectionFactory; 022import org.apache.tapestry5.commons.util.CommonsUtils; 023import org.apache.tapestry5.commons.util.ExceptionUtils; 024import org.apache.tapestry5.func.F; 025import org.apache.tapestry5.func.Mapper; 026import org.apache.tapestry5.func.Predicate; 027import org.apache.tapestry5.internal.plastic.PlasticInternalUtils; 028import org.apache.tapestry5.ioc.AdvisorDef; 029import org.apache.tapestry5.ioc.AdvisorDef2; 030import org.apache.tapestry5.ioc.IOCConstants; 031import org.apache.tapestry5.ioc.Invokable; 032import org.apache.tapestry5.ioc.ModuleBuilderSource; 033import org.apache.tapestry5.ioc.OperationTracker; 034import org.apache.tapestry5.ioc.ServiceAdvisor; 035import org.apache.tapestry5.ioc.ServiceBuilderResources; 036import org.apache.tapestry5.ioc.ServiceDecorator; 037import org.apache.tapestry5.ioc.ServiceLifecycle; 038import org.apache.tapestry5.ioc.ServiceLifecycle2; 039import org.apache.tapestry5.ioc.ServiceResources; 040import org.apache.tapestry5.ioc.annotations.*; 041import org.apache.tapestry5.ioc.def.*; 042import org.apache.tapestry5.ioc.internal.ServiceDefImpl; 043import org.apache.tapestry5.plastic.PlasticUtils; 044import org.slf4j.Logger; 045 046import javax.annotation.PostConstruct; 047import javax.inject.Named; 048 049import java.io.Closeable; 050import java.io.IOException; 051import java.lang.annotation.Annotation; 052import java.lang.annotation.Retention; 053import java.lang.annotation.RetentionPolicy; 054import java.lang.reflect.*; 055import java.net.URL; 056import java.util.*; 057import java.util.concurrent.atomic.AtomicLong; 058import java.util.regex.Matcher; 059import java.util.regex.Pattern; 060 061/** 062 * Utilities used within various internal implementations of the tapestry-ioc module. 063 */ 064@SuppressWarnings("all") 065public class InternalUtils 066{ 067 /** 068 * @since 5.2.2 069 */ 070 public static final boolean SERVICE_CLASS_RELOADING_ENABLED = Boolean.parseBoolean(System.getProperty( 071 IOCConstants.SERVICE_CLASS_RELOADING_ENABLED, "true")); 072 073 /** 074 * Converts a method to a user presentable string using a {@link PlasticProxyFactory} to obtain a {@link Location} 075 * (where possible). {@link #asString(Method)} is used under the covers, to present a detailed, but not excessive, 076 * description of the class, method and parameters. 077 * 078 * @param method 079 * method to convert to a string 080 * @param proxyFactory 081 * used to obtain the {@link Location} 082 * @return the method formatted for presentation to the user 083 */ 084 public static String asString(Method method, PlasticProxyFactory proxyFactory) 085 { 086 Location location = proxyFactory.getMethodLocation(method); 087 088 return location != null ? location.toString() : asString(method); 089 } 090 091 /** 092 * Converts a method to a user presentable string consisting of the containing class name, the method name, and the 093 * short form of the parameter list (the class name of each parameter type, shorn of the package name portion). 094 * 095 * @param method 096 * @return short string representation 097 */ 098 public static String asString(Method method) 099 { 100 return InternalCommonsUtils.asString(method); 101 } 102 103 /** 104 * Returns the size of an object array, or null if the array is empty. 105 */ 106 107 public static int size(Object[] array) 108 { 109 return array == null ? 0 : array.length; 110 } 111 112 public static int size(Collection collection) 113 { 114 return collection == null ? 0 : collection.size(); 115 } 116 117 /** 118 * Strips leading "_" and "$" and trailing "_" from the name. 119 */ 120 public static String stripMemberName(String memberName) 121 { 122 return InternalCommonsUtils.stripMemberName(memberName); 123 } 124 125 /** 126 * Converts an enumeration (of Strings) into a sorted list of Strings. 127 */ 128 public static List<String> toList(Enumeration e) 129 { 130 List<String> result = CollectionFactory.newList(); 131 132 while (e.hasMoreElements()) 133 { 134 String name = (String) e.nextElement(); 135 136 result.add(name); 137 } 138 139 Collections.sort(result); 140 141 return result; 142 } 143 144 /** 145 * Finds a specific annotation type within an array of annotations. 146 * 147 * @param <T> 148 * @param annotations 149 * to search 150 * @param annotationClass 151 * to match 152 * @return the annotation instance, if found, or null otherwise 153 */ 154 public static <T extends Annotation> T findAnnotation(Annotation[] annotations, Class<T> annotationClass) 155 { 156 for (Annotation a : annotations) 157 { 158 if (annotationClass.isInstance(a)) 159 return annotationClass.cast(a); 160 } 161 162 return null; 163 } 164 165 private static ObjectCreator<Object> asObjectCreator(final Object fixedValue) 166 { 167 return new ObjectCreator<Object>() 168 { 169 @Override 170 public Object createObject() 171 { 172 return fixedValue; 173 } 174 }; 175 } 176 177 private static ObjectCreator calculateInjection(final Class injectionType, Type genericType, final Annotation[] annotations, 178 final ObjectLocator locator, InjectionResources resources) 179 { 180 final AnnotationProvider provider = new AnnotationProvider() 181 { 182 @Override 183 public <T extends Annotation> T getAnnotation(Class<T> annotationClass) 184 { 185 return findAnnotation(annotations, annotationClass); 186 } 187 }; 188 189 // At some point, it would be nice to eliminate InjectService, and rely 190 // entirely on service interface type and point-of-injection markers. 191 192 InjectService is = provider.getAnnotation(InjectService.class); 193 194 if (is != null) 195 { 196 String serviceId = is.value(); 197 198 return asObjectCreator(locator.getService(serviceId, injectionType)); 199 } 200 201 Named named = provider.getAnnotation(Named.class); 202 203 if (named != null) 204 { 205 return asObjectCreator(locator.getService(named.value(), injectionType)); 206 } 207 208 209 // TAP5-2758: An @Symbol is treated as an explicit signal that the injected value 210 // should not be autowired, but resolved using the MasterObjectProvider instead. 211 // The check needs to be done before the @Inject one, or the value is solely treated 212 // by its type. 213 // Without this check, if any configuration is used, no List/Collection/Map value as 214 // Symbol can be used without both @Inject and @Symbol, as @Symbol has no explicit 215 // meaning on how to inject the value, and the configuration detector strikes before 216 // the final fallback to the MasterObjectProvider. 217 218 Symbol symbol = provider.getAnnotation(Symbol.class); 219 220 if (symbol != null) 221 { 222 return asObjectCreator(locator.getObject(injectionType, provider)); 223 } 224 225 // In the absence of @InjectService, try some autowiring. First, does the 226 // parameter type match one of the resources (the parameter defaults)? 227 228 if (provider.getAnnotation(Inject.class) == null) 229 { 230 Object result = resources.findResource(injectionType, genericType); 231 232 if (result != null) 233 { 234 return asObjectCreator(result); 235 } 236 } 237 238 // TAP5-1765: For @Autobuild, special case where we always compute a fresh value 239 // for the injection on every use. Elsewhere, we compute once when generating the 240 // construction plan and just use the singleton value repeatedly. 241 242 if (provider.getAnnotation(Autobuild.class) != null) 243 { 244 return new ObjectCreator() 245 { 246 @Override 247 public Object createObject() 248 { 249 return locator.getObject(injectionType, provider); 250 } 251 }; 252 } 253 254 // Otherwise, make use of the MasterObjectProvider service to resolve this type (plus 255 // any other information gleaned from additional annotation) into the correct object. 256 257 return asObjectCreator(locator.getObject(injectionType, provider)); 258 } 259 260 public static ObjectCreator[] calculateParametersForMethod(Method method, ObjectLocator locator, 261 InjectionResources resources, OperationTracker tracker) 262 { 263 264 return calculateParameters(locator, resources, method.getParameterTypes(), method.getGenericParameterTypes(), 265 method.getParameterAnnotations(), tracker); 266 } 267 268 public static ObjectCreator[] calculateParameters(final ObjectLocator locator, final InjectionResources resources, 269 Class[] parameterTypes, final Type[] genericTypes, Annotation[][] parameterAnnotations, 270 OperationTracker tracker) 271 { 272 int parameterCount = parameterTypes.length; 273 274 ObjectCreator[] parameters = new ObjectCreator[parameterCount]; 275 276 for (int i = 0; i < parameterCount; i++) 277 { 278 final Class type = parameterTypes[i]; 279 final Type genericType = genericTypes[i]; 280 final Annotation[] annotations = parameterAnnotations[i]; 281 282 String description = String.format("Determining injection value for parameter #%d (%s)", i + 1, 283 PlasticUtils.toTypeName(type)); 284 285 final Invokable<ObjectCreator> operation = new Invokable<ObjectCreator>() 286 { 287 @Override 288 public ObjectCreator invoke() 289 { 290 return calculateInjection(type, genericType, annotations, locator, resources); 291 } 292 }; 293 294 parameters[i] = tracker.invoke(description, operation); 295 } 296 297 return parameters; 298 } 299 300 /** 301 * Injects into the fields (of all visibilities) when the {@link org.apache.tapestry5.ioc.annotations.Inject} or 302 * {@link org.apache.tapestry5.ioc.annotations.InjectService} annotations are present. 303 * 304 * @param object 305 * to be initialized 306 * @param locator 307 * used to resolve external dependencies 308 * @param resources 309 * provides injection resources for fields 310 * @param tracker 311 * track operations 312 */ 313 public static void injectIntoFields(final Object object, final ObjectLocator locator, 314 final InjectionResources resources, OperationTracker tracker) 315 { 316 Class clazz = object.getClass(); 317 318 while (clazz != Object.class) 319 { 320 Field[] fields = clazz.getDeclaredFields(); 321 322 for (final Field f : fields) 323 { 324 // Ignore all static and final fields. 325 326 int fieldModifiers = f.getModifiers(); 327 328 if (Modifier.isStatic(fieldModifiers) || Modifier.isFinal(fieldModifiers)) 329 continue; 330 331 final AnnotationProvider ap = new AnnotationProvider() 332 { 333 @Override 334 public <T extends Annotation> T getAnnotation(Class<T> annotationClass) 335 { 336 return f.getAnnotation(annotationClass); 337 } 338 }; 339 340 String description = String.format("Calculating possible injection value for field %s.%s (%s)", 341 clazz.getName(), f.getName(), 342 PlasticUtils.toTypeName(f.getType())); 343 344 tracker.run(description, new Runnable() 345 { 346 @Override 347 public void run() 348 { 349 final Class<?> fieldType = f.getType(); 350 351 InjectService is = ap.getAnnotation(InjectService.class); 352 if (is != null) 353 { 354 inject(object, f, locator.getService(is.value(), fieldType)); 355 return; 356 } 357 358 if (ap.getAnnotation(Inject.class) != null || ap.getAnnotation(InjectResource.class) != null) 359 { 360 Object value = resources.findResource(fieldType, f.getGenericType()); 361 362 if (value != null) 363 { 364 inject(object, f, value); 365 return; 366 } 367 368 inject(object, f, locator.getObject(fieldType, ap)); 369 return; 370 } 371 372 if (ap.getAnnotation(javax.inject.Inject.class) != null) 373 { 374 Named named = ap.getAnnotation(Named.class); 375 376 if (named == null) 377 { 378 Object value = resources.findResource(fieldType, f.getGenericType()); 379 380 if (value != null) 381 { 382 inject(object, f, value); 383 return; 384 } 385 386 inject(object, f, locator.getObject(fieldType, ap)); 387 } else 388 { 389 inject(object, f, locator.getService(named.value(), fieldType)); 390 } 391 392 return; 393 } 394 395 // Ignore fields that do not have the necessary annotation. 396 397 } 398 }); 399 } 400 401 clazz = clazz.getSuperclass(); 402 } 403 } 404 405 private synchronized static void inject(Object target, Field field, Object value) 406 { 407 try 408 { 409 if (!field.isAccessible()) 410 field.setAccessible(true); 411 412 field.set(target, value); 413 414 // Is there a need to setAccessible back to false? 415 } catch (Exception ex) 416 { 417 throw new RuntimeException(String.format("Unable to set field '%s' of %s to %s: %s", field.getName(), 418 target, value, ExceptionUtils.toMessage(ex))); 419 } 420 } 421 422 /** 423 * Joins together some number of elements to form a comma separated list. 424 */ 425 public static String join(List elements) 426 { 427 return InternalCommonsUtils.join(elements); 428 } 429 430 /** 431 * Joins together some number of elements. If a value in the list is the empty string, it is replaced with the 432 * string "(blank)". 433 * 434 * @param elements 435 * objects to be joined together 436 * @param separator 437 * used between elements when joining 438 */ 439 public static String join(List elements, String separator) 440 { 441 return InternalCommonsUtils.join(elements, separator); 442 } 443 444 /** 445 * Creates a sorted copy of the provided elements, then turns that into a comma separated list. 446 * 447 * @return the elements converted to strings, sorted, joined with comma ... or "(none)" if the elements are null or 448 * empty 449 */ 450 public static String joinSorted(Collection elements) 451 { 452 return InternalCommonsUtils.joinSorted(elements); 453 } 454 455 /** 456 * Returns true if the input is null, or is a zero length string (excluding leading/trailing whitespace). 457 */ 458 459 public static boolean isBlank(String input) 460 { 461 return CommonsUtils.isBlank(input); 462 } 463 464 /** 465 * Returns true if the input is an empty collection. 466 */ 467 468 public static boolean isEmptyCollection(Object input) 469 { 470 if (input instanceof Collection) 471 { 472 return ((Collection) input).isEmpty(); 473 } 474 475 return false; 476 } 477 478 public static boolean isNonBlank(String input) 479 { 480 return InternalCommonsUtils.isNonBlank(input); 481 } 482 483 /** 484 * Capitalizes a string, converting the first character to uppercase. 485 */ 486 public static String capitalize(String input) 487 { 488 return InternalCommonsUtils.capitalize(input); 489 } 490 491 /** 492 * Sniffs the object to see if it is a {@link Location} or {@link Locatable}. Returns null if null or not 493 * convertable to a location. 494 */ 495 496 public static Location locationOf(Object location) 497 { 498 return InternalCommonsUtils.locationOf(location); 499 } 500 501 public static <K, V> Set<K> keys(Map<K, V> map) 502 { 503 if (map == null) 504 return Collections.emptySet(); 505 506 return map.keySet(); 507 } 508 509 /** 510 * Gets a value from a map (which may be null). 511 * 512 * @param <K> 513 * @param <V> 514 * @param map 515 * the map to extract from (may be null) 516 * @param key 517 * @return the value from the map, or null if the map is null 518 */ 519 520 public static <K, V> V get(Map<K, V> map, K key) 521 { 522 if (map == null) 523 return null; 524 525 return map.get(key); 526 } 527 528 /** 529 * Returns true if the method provided is a static method. 530 */ 531 public static boolean isStatic(Method method) 532 { 533 return Modifier.isStatic(method.getModifiers()); 534 } 535 536 public static <T> Iterator<T> reverseIterator(final List<T> list) 537 { 538 final ListIterator<T> normal = list.listIterator(list.size()); 539 540 return new Iterator<T>() 541 { 542 @Override 543 public boolean hasNext() 544 { 545 return normal.hasPrevious(); 546 } 547 548 @Override 549 public T next() 550 { 551 return normal.previous(); 552 } 553 554 @Override 555 public void remove() 556 { 557 throw new UnsupportedOperationException(); 558 } 559 }; 560 } 561 562 /** 563 * Return true if the input string contains the marker for symbols that must be expanded. 564 */ 565 public static boolean containsSymbols(String input) 566 { 567 return InternalCommonsUtils.containsSymbols(input); 568 } 569 570 /** 571 * Searches the string for the final period ('.') character and returns everything after that. The input string is 572 * generally a fully qualified class name, though tapestry-core also uses this method for the occasional property 573 * expression (which is also dot separated). Returns the input string unchanged if it does not contain a period 574 * character. 575 */ 576 public static String lastTerm(String input) 577 { 578 return InternalCommonsUtils.lastTerm(input); 579 } 580 581 /** 582 * Searches a class for the "best" constructor, the public constructor with the most parameters. Returns null if 583 * there are no public constructors. If there is more than one constructor with the maximum number of parameters, it 584 * is not determined which will be returned (don't build a class like that!). In addition, if a constructor is 585 * annotated with {@link org.apache.tapestry5.ioc.annotations.Inject}, it will be used (no check for multiple such 586 * constructors is made, only at most a single constructor should have the annotation). 587 * 588 * @param clazz 589 * to search for a constructor for 590 * @return the constructor to be used to instantiate the class, or null if no appropriate constructor was found 591 */ 592 public static Constructor findAutobuildConstructor(Class clazz) 593 { 594 Constructor[] constructors = clazz.getConstructors(); 595 596 switch (constructors.length) 597 { 598 case 1: 599 600 return constructors[0]; 601 602 case 0: 603 604 return null; 605 606 default: 607 break; 608 } 609 610 Constructor standardConstructor = findConstructorByAnnotation(constructors, Inject.class); 611 Constructor javaxConstructor = findConstructorByAnnotation(constructors, javax.inject.Inject.class); 612 613 if (standardConstructor != null && javaxConstructor != null) 614 throw new IllegalArgumentException( 615 String.format( 616 "Too many autobuild constructors found: use either @%s or @%s annotation to mark a single constructor for autobuilding.", 617 Inject.class.getName(), javax.inject.Inject.class.getName())); 618 619 if (standardConstructor != null) 620 { 621 return standardConstructor; 622 } 623 624 if (javaxConstructor != null) 625 { 626 return javaxConstructor; 627 } 628 629 // Choose a constructor with the most parameters. 630 631 Comparator<Constructor> comparator = new Comparator<Constructor>() 632 { 633 @Override 634 public int compare(Constructor o1, Constructor o2) 635 { 636 return o2.getParameterTypes().length - o1.getParameterTypes().length; 637 } 638 }; 639 640 Arrays.sort(constructors, comparator); 641 642 return constructors[0]; 643 } 644 645 private static <T extends Annotation> Constructor findConstructorByAnnotation(Constructor[] constructors, 646 Class<T> annotationClass) 647 { 648 for (Constructor c : constructors) 649 { 650 if (c.getAnnotation(annotationClass) != null) 651 return c; 652 } 653 654 return null; 655 } 656 657 /** 658 * Adds a value to a specially organized map where the values are lists of objects. This somewhat simulates a map 659 * that allows multiple values for the same key. 660 * 661 * @param map 662 * to store value into 663 * @param key 664 * for which a value is added 665 * @param value 666 * to add 667 * @param <K> 668 * the type of key 669 * @param <V> 670 * the type of the list 671 */ 672 public static <K, V> void addToMapList(Map<K, List<V>> map, K key, V value) 673 { 674 InternalCommonsUtils.addToMapList(map, key, value); 675 } 676 677 /** 678 * Validates that the marker annotation class had a retention policy of runtime. 679 * 680 * @param markerClass 681 * the marker annotation class 682 */ 683 public static void validateMarkerAnnotation(Class markerClass) 684 { 685 Retention policy = (Retention) markerClass.getAnnotation(Retention.class); 686 687 if (policy != null && policy.value() == RetentionPolicy.RUNTIME) 688 return; 689 690 throw new IllegalArgumentException(UtilMessages.badMarkerAnnotation(markerClass)); 691 } 692 693 public static void validateMarkerAnnotations(Class[] markerClasses) 694 { 695 for (Class markerClass : markerClasses) 696 validateMarkerAnnotation(markerClass); 697 } 698 699 public static void close(Closeable stream) 700 { 701 if (stream != null) 702 try 703 { 704 stream.close(); 705 } catch (IOException ex) 706 { 707 // Ignore. 708 } 709 } 710 711 /** 712 * Extracts the message from an exception. If the exception's message is null, returns the exceptions class name. 713 * 714 * @param exception 715 * to extract message from 716 * @return message or class name 717 * @deprecated Deprecated in 5.4; use {@link ExceptionUtils#toMessage(Throwable)} instead. 718 */ 719 // Cause it gets used a lot outside of Tapestry proper even though it is internal. 720 public static String toMessage(Throwable exception) 721 { 722 return ExceptionUtils.toMessage(exception); 723 } 724 725 public static void validateConstructorForAutobuild(Constructor constructor) 726 { 727 Class clazz = constructor.getDeclaringClass(); 728 729 if (!Modifier.isPublic(clazz.getModifiers())) 730 throw new IllegalArgumentException(String.format( 731 "Class %s is not a public class and may not be autobuilt.", clazz.getName())); 732 733 if (!Modifier.isPublic(constructor.getModifiers())) 734 throw new IllegalArgumentException( 735 String.format( 736 "Constructor %s is not public and may not be used for autobuilding an instance of the class. " 737 + "You should make the constructor public, or mark an alternate public constructor with the @Inject annotation.", 738 constructor)); 739 } 740 741 /** 742 * @since 5.3 743 */ 744 public static final Mapper<Class, AnnotationProvider> CLASS_TO_AP_MAPPER = new Mapper<Class, AnnotationProvider>() 745 { 746 @Override 747 public AnnotationProvider map(final Class element) 748 { 749 return toAnnotationProvider(element); 750 } 751 752 }; 753 754 /** 755 * @since 5.3 756 */ 757 public static AnnotationProvider toAnnotationProvider(final Class element) 758 { 759 return InternalCommonsUtils.toAnnotationProvider(element); 760 } 761 762 /** 763 * @since 5.3 764 */ 765 public static final Mapper<Method, AnnotationProvider> METHOD_TO_AP_MAPPER = new Mapper<Method, AnnotationProvider>() 766 { 767 @Override 768 public AnnotationProvider map(final Method element) 769 { 770 return toAnnotationProvider(element); 771 } 772 }; 773 774 public static final Method findMethod(Class containingClass, String methodName, Class... parameterTypes) 775 { 776 if (containingClass == null) 777 return null; 778 779 try 780 { 781 return containingClass.getMethod(methodName, parameterTypes); 782 } catch (SecurityException ex) 783 { 784 throw new RuntimeException(ex); 785 } catch (NoSuchMethodException ex) 786 { 787 return null; 788 } 789 } 790 791 /** 792 * @since 5.3 793 */ 794 public static ServiceDef3 toServiceDef3(ServiceDef sd) 795 { 796 if (sd instanceof ServiceDef3) 797 return (ServiceDef3) sd; 798 799 final ServiceDef2 sd2 = toServiceDef2(sd); 800 801 return new ServiceDef3() 802 { 803 // ServiceDef3 methods: 804 805 @Override 806 public AnnotationProvider getClassAnnotationProvider() 807 { 808 return toAnnotationProvider(getServiceInterface()); 809 } 810 811 @Override 812 public AnnotationProvider getMethodAnnotationProvider(final String methodName, final Class... argumentTypes) 813 { 814 return toAnnotationProvider(findMethod(getServiceInterface(), methodName, argumentTypes)); 815 } 816 817 @Override 818 public Class getServiceImplementation() 819 { 820 return null; 821 } 822 823 // ServiceDef2 methods: 824 825 @Override 826 public boolean isPreventDecoration() 827 { 828 return sd2.isPreventDecoration(); 829 } 830 831 @Override 832 public ObjectCreator createServiceCreator(ServiceBuilderResources resources) 833 { 834 return sd2.createServiceCreator(resources); 835 } 836 837 @Override 838 public String getServiceId() 839 { 840 return sd2.getServiceId(); 841 } 842 843 @Override 844 public Set<Class> getMarkers() 845 { 846 return sd2.getMarkers(); 847 } 848 849 @Override 850 public Class getServiceInterface() 851 { 852 return sd2.getServiceInterface(); 853 } 854 855 @Override 856 public String getServiceScope() 857 { 858 return sd2.getServiceScope(); 859 } 860 861 @Override 862 public boolean isEagerLoad() 863 { 864 return sd2.isEagerLoad(); 865 } 866 867 }; 868 } 869 870 public static ServiceDef2 toServiceDef2(final ServiceDef sd) 871 { 872 if (sd instanceof ServiceDef2) 873 return (ServiceDef2) sd; 874 875 return new ServiceDef2() 876 { 877 // ServiceDef2 methods: 878 879 @Override 880 public boolean isPreventDecoration() 881 { 882 return false; 883 } 884 885 // ServiceDef methods: 886 887 @Override 888 public ObjectCreator createServiceCreator(ServiceBuilderResources resources) 889 { 890 return sd.createServiceCreator(resources); 891 } 892 893 @Override 894 public String getServiceId() 895 { 896 return sd.getServiceId(); 897 } 898 899 @Override 900 public Set<Class> getMarkers() 901 { 902 return sd.getMarkers(); 903 } 904 905 @Override 906 public Class getServiceInterface() 907 { 908 return sd.getServiceInterface(); 909 } 910 911 @Override 912 public String getServiceScope() 913 { 914 return sd.getServiceScope(); 915 } 916 917 @Override 918 public boolean isEagerLoad() 919 { 920 return sd.isEagerLoad(); 921 } 922 923 @Override 924 public String toString() 925 { 926 return sd.toString(); 927 } 928 929 @Override 930 public int hashCode() 931 { 932 final int prime = 31; 933 int result = 1; 934 result = prime * result + ((getServiceId() == null) ? 0 : getServiceId().hashCode()); 935 return result; 936 } 937 938 @Override 939 public boolean equals(Object obj) 940 { 941 if (this == obj) { return true; } 942 if (obj == null) { return false; } 943 if (!(obj instanceof ServiceDefImpl)) { return false; } 944 ServiceDef other = (ServiceDef) obj; 945 if (getServiceId() == null) 946 { 947 if (other.getServiceId() != null) { return false; } 948 } 949 else if (!getServiceId().equals(other.getServiceId())) { return false; } 950 return true; 951 } 952 953 }; 954 } 955 956 public static ModuleDef2 toModuleDef2(final ModuleDef md) 957 { 958 if (md instanceof ModuleDef2) 959 return (ModuleDef2) md; 960 961 return new ModuleDef2() 962 { 963 @Override 964 public Set<AdvisorDef> getAdvisorDefs() 965 { 966 return Collections.emptySet(); 967 } 968 969 @Override 970 public Class getBuilderClass() 971 { 972 return md.getBuilderClass(); 973 } 974 975 @Override 976 public Set<ContributionDef> getContributionDefs() 977 { 978 return md.getContributionDefs(); 979 } 980 981 @Override 982 public Set<DecoratorDef> getDecoratorDefs() 983 { 984 return md.getDecoratorDefs(); 985 } 986 987 @Override 988 public String getLoggerName() 989 { 990 return md.getLoggerName(); 991 } 992 993 @Override 994 public ServiceDef getServiceDef(String serviceId) 995 { 996 return md.getServiceDef(serviceId); 997 } 998 999 @Override 1000 public Set<String> getServiceIds() 1001 { 1002 return md.getServiceIds(); 1003 } 1004 1005 @Override 1006 public Set<StartupDef> getStartups() 1007 { 1008 return Collections.emptySet(); 1009 } 1010 }; 1011 } 1012 1013 /** 1014 * @since 5.1.0.2 1015 */ 1016 public static ServiceLifecycle2 toServiceLifecycle2(final ServiceLifecycle lifecycle) 1017 { 1018 if (lifecycle instanceof ServiceLifecycle2) 1019 return (ServiceLifecycle2) lifecycle; 1020 1021 return new ServiceLifecycle2() 1022 { 1023 @Override 1024 public boolean requiresProxy() 1025 { 1026 return true; 1027 } 1028 1029 @Override 1030 public Object createService(ServiceResources resources, ObjectCreator creator) 1031 { 1032 return lifecycle.createService(resources, creator); 1033 } 1034 1035 @Override 1036 public boolean isSingleton() 1037 { 1038 return lifecycle.isSingleton(); 1039 } 1040 }; 1041 } 1042 1043 /** 1044 * @since 5.2.0 1045 */ 1046 public static <T extends Comparable<T>> List<T> matchAndSort(Collection<? extends T> collection, 1047 Predicate<T> predicate) 1048 { 1049 assert predicate != null; 1050 1051 List<T> result = CollectionFactory.newList(); 1052 1053 for (T object : collection) 1054 { 1055 if (predicate.accept(object)) 1056 result.add(object); 1057 } 1058 1059 Collections.sort(result); 1060 1061 return result; 1062 } 1063 1064 /** 1065 * @since 5.2.0 1066 */ 1067 public static ContributionDef2 toContributionDef2(final ContributionDef contribution) 1068 { 1069 if (contribution instanceof ContributionDef2) 1070 return (ContributionDef2) contribution; 1071 1072 return new ContributionDef2() 1073 { 1074 1075 @Override 1076 public Set<Class> getMarkers() 1077 { 1078 return Collections.emptySet(); 1079 } 1080 1081 @Override 1082 public Class getServiceInterface() 1083 { 1084 return null; 1085 } 1086 1087 @Override 1088 public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, 1089 Configuration configuration) 1090 { 1091 contribution.contribute(moduleSource, resources, configuration); 1092 } 1093 1094 @Override 1095 public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, 1096 OrderedConfiguration configuration) 1097 { 1098 contribution.contribute(moduleSource, resources, configuration); 1099 } 1100 1101 @Override 1102 public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, 1103 MappedConfiguration configuration) 1104 { 1105 contribution.contribute(moduleSource, resources, configuration); 1106 } 1107 1108 @Override 1109 public String getServiceId() 1110 { 1111 return contribution.getServiceId(); 1112 } 1113 1114 @Override 1115 public String toString() 1116 { 1117 return contribution.toString(); 1118 } 1119 }; 1120 } 1121 1122 public static ContributionDef3 toContributionDef3(ContributionDef contribution) 1123 { 1124 1125 if (contribution instanceof ContributionDef2) 1126 { 1127 return (ContributionDef3) contribution; 1128 } 1129 1130 final ContributionDef2 cd2 = toContributionDef2(contribution); 1131 1132 return new ContributionDef3() 1133 { 1134 @Override 1135 public boolean isOptional() 1136 { 1137 return false; 1138 } 1139 1140 @Override 1141 public String getServiceId() 1142 { 1143 return cd2.getServiceId(); 1144 } 1145 1146 @Override 1147 public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, Configuration configuration) 1148 { 1149 cd2.contribute(moduleSource, resources, configuration); 1150 } 1151 1152 @Override 1153 public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, OrderedConfiguration configuration) 1154 { 1155 cd2.contribute(moduleSource, resources, configuration); 1156 } 1157 1158 @Override 1159 public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, MappedConfiguration configuration) 1160 { 1161 cd2.contribute(moduleSource, resources, configuration); 1162 } 1163 1164 @Override 1165 public Set<Class> getMarkers() 1166 { 1167 return cd2.getMarkers(); 1168 } 1169 1170 @Override 1171 public Class getServiceInterface() 1172 { 1173 return cd2.getServiceInterface(); 1174 } 1175 1176 @Override 1177 public String toString() 1178 { 1179 return cd2.toString(); 1180 } 1181 }; 1182 } 1183 1184 /** 1185 * @since 5.2.2 1186 */ 1187 public static AdvisorDef2 toAdvisorDef2(final AdvisorDef advisor) 1188 { 1189 if (advisor instanceof AdvisorDef2) 1190 return (AdvisorDef2) advisor; 1191 1192 return new AdvisorDef2() 1193 { 1194 1195 @Override 1196 public ServiceAdvisor createAdvisor(ModuleBuilderSource moduleSource, ServiceResources resources) 1197 { 1198 return advisor.createAdvisor(moduleSource, resources); 1199 } 1200 1201 @Override 1202 public String getAdvisorId() 1203 { 1204 return advisor.getAdvisorId(); 1205 } 1206 1207 @Override 1208 public String[] getConstraints() 1209 { 1210 return advisor.getConstraints(); 1211 } 1212 1213 @Override 1214 public boolean matches(ServiceDef serviceDef) 1215 { 1216 return advisor.matches(serviceDef); 1217 } 1218 1219 @Override 1220 public Set<Class> getMarkers() 1221 { 1222 return Collections.emptySet(); 1223 } 1224 1225 @Override 1226 public Class getServiceInterface() 1227 { 1228 return null; 1229 } 1230 1231 @Override 1232 public String toString() 1233 { 1234 return advisor.toString(); 1235 } 1236 }; 1237 } 1238 1239 /** 1240 * @since 5.2.2 1241 */ 1242 public static DecoratorDef2 toDecoratorDef2(final DecoratorDef decorator) 1243 { 1244 if (decorator instanceof DecoratorDef2) 1245 return (DecoratorDef2) decorator; 1246 1247 return new DecoratorDef2() 1248 { 1249 1250 @Override 1251 public ServiceDecorator createDecorator(ModuleBuilderSource moduleSource, ServiceResources resources) 1252 { 1253 return decorator.createDecorator(moduleSource, resources); 1254 } 1255 1256 @Override 1257 public String[] getConstraints() 1258 { 1259 return decorator.getConstraints(); 1260 } 1261 1262 @Override 1263 public String getDecoratorId() 1264 { 1265 return decorator.getDecoratorId(); 1266 } 1267 1268 @Override 1269 public boolean matches(ServiceDef serviceDef) 1270 { 1271 return decorator.matches(serviceDef); 1272 } 1273 1274 @Override 1275 public Set<Class> getMarkers() 1276 { 1277 return Collections.emptySet(); 1278 } 1279 1280 @Override 1281 public Class getServiceInterface() 1282 { 1283 return null; 1284 } 1285 1286 @Override 1287 public String toString() 1288 { 1289 return decorator.toString(); 1290 } 1291 }; 1292 } 1293 1294 /** 1295 * Determines if the indicated class is stored as a locally accessible file 1296 * (and not, typically, as a file inside a JAR). This is related to automatic 1297 * reloading of services. 1298 * 1299 * @since 5.2.0 1300 */ 1301 public static boolean isLocalFile(Class clazz) 1302 { 1303 String path = PlasticInternalUtils.toClassPath(clazz.getName()); 1304 1305 ClassLoader loader = clazz.getClassLoader(); 1306 1307 // System classes have no visible class loader, and are not local files. 1308 1309 if (loader == null) 1310 return false; 1311 1312 URL classFileURL = loader.getResource(path); 1313 1314 return classFileURL != null && classFileURL.getProtocol().equals("file"); 1315 } 1316 1317 /** 1318 * Wraps a {@link Coercion} as a {@link Mapper}. 1319 * 1320 * @since 5.2.0 1321 */ 1322 public static <S, T> Mapper<S, T> toMapper(final Coercion<S, T> coercion) 1323 { 1324 assert coercion != null; 1325 1326 return new Mapper<S, T>() 1327 { 1328 @Override 1329 public T map(S value) 1330 { 1331 return coercion.coerce(value); 1332 } 1333 }; 1334 } 1335 1336 private static final AtomicLong uuidGenerator = new AtomicLong(System.nanoTime()); 1337 1338 /** 1339 * Generates a unique value for the current execution of the application. This initial UUID value 1340 * is not easily predictable; subsequent UUIDs are allocated in ascending series. 1341 * 1342 * @since 5.2.0 1343 */ 1344 public static long nextUUID() 1345 { 1346 return uuidGenerator.incrementAndGet(); 1347 } 1348 1349 /** 1350 * Extracts the service id from the passed annotated element. First the {@link ServiceId} annotation is checked. 1351 * If present, its value is returned. Otherwise {@link Named} annotation is checked. If present, its value is 1352 * returned. 1353 * If neither of the annotations is present, <code>null</code> value is returned 1354 * 1355 * @param annotated 1356 * annotated element to get annotations from 1357 * @since 5.3 1358 */ 1359 public static String getServiceId(AnnotatedElement annotated) 1360 { 1361 ServiceId serviceIdAnnotation = annotated.getAnnotation(ServiceId.class); 1362 1363 if (serviceIdAnnotation != null) 1364 { 1365 return serviceIdAnnotation.value(); 1366 } 1367 1368 Named namedAnnotation = annotated.getAnnotation(Named.class); 1369 1370 if (namedAnnotation != null) 1371 { 1372 String value = namedAnnotation.value(); 1373 1374 if (InternalCommonsUtils.isNonBlank(value)) 1375 { 1376 return value; 1377 } 1378 } 1379 1380 return null; 1381 } 1382 1383 1384 public static AnnotationProvider toAnnotationProvider(final Method element) 1385 { 1386 return InternalCommonsUtils.toAnnotationProvider(element); 1387 } 1388 1389 public static <T> ObjectCreator<T> createConstructorConstructionPlan(final OperationTracker tracker, final ObjectLocator locator, 1390 final InjectionResources resources, 1391 final Logger logger, 1392 final String description, 1393 final Constructor<T> constructor) 1394 { 1395 return tracker.invoke(String.format("Creating plan to instantiate %s via %s", 1396 constructor.getDeclaringClass().getName(), 1397 constructor), new Invokable<ObjectCreator<T>>() 1398 { 1399 @Override 1400 public ObjectCreator<T> invoke() 1401 { 1402 validateConstructorForAutobuild(constructor); 1403 1404 ObjectCreator[] constructorParameters = calculateParameters(locator, resources, constructor.getParameterTypes(), constructor.getGenericParameterTypes(), constructor.getParameterAnnotations(), tracker); 1405 1406 Invokable<T> core = new ConstructorInvoker<T>(constructor, constructorParameters); 1407 1408 Invokable<T> wrapped = logger == null ? core : new LoggingInvokableWrapper<T>(logger, description, core); 1409 1410 ConstructionPlan<T> plan = new ConstructionPlan(tracker, description, wrapped); 1411 1412 extendPlanForInjectedFields(plan, tracker, locator, resources, constructor.getDeclaringClass()); 1413 1414 extendPlanForPostInjectionMethods(plan, tracker, locator, resources, constructor.getDeclaringClass()); 1415 1416 return plan; 1417 } 1418 }); 1419 } 1420 1421 private static <T> void extendPlanForInjectedFields(final ConstructionPlan<T> plan, OperationTracker tracker, final ObjectLocator locator, final InjectionResources resources, Class<T> instantiatedClass) 1422 { 1423 Class clazz = instantiatedClass; 1424 1425 while (clazz != Object.class) 1426 { 1427 Field[] fields = clazz.getDeclaredFields(); 1428 1429 for (final Field f : fields) 1430 { 1431 // Ignore all static and final fields. 1432 1433 int fieldModifiers = f.getModifiers(); 1434 1435 if (Modifier.isStatic(fieldModifiers) || Modifier.isFinal(fieldModifiers)) 1436 continue; 1437 1438 final AnnotationProvider ap = new AnnotationProvider() 1439 { 1440 @Override 1441 public <T extends Annotation> T getAnnotation(Class<T> annotationClass) 1442 { 1443 return f.getAnnotation(annotationClass); 1444 } 1445 }; 1446 1447 String description = String.format("Calculating possible injection value for field %s.%s (%s)", 1448 clazz.getName(), f.getName(), 1449 PlasticUtils.toTypeName(f.getType())); 1450 1451 tracker.run(description, new Runnable() 1452 { 1453 @Override 1454 public void run() 1455 { 1456 final Class<?> fieldType = f.getType(); 1457 1458 InjectService is = ap.getAnnotation(InjectService.class); 1459 if (is != null) 1460 { 1461 addInjectPlan(plan, f, locator.getService(is.value(), fieldType)); 1462 return; 1463 } 1464 1465 if (ap.getAnnotation(Inject.class) != null || ap.getAnnotation(InjectResource.class) != null) 1466 { 1467 Object value = resources.findResource(fieldType, f.getGenericType()); 1468 1469 if (value != null) 1470 { 1471 addInjectPlan(plan, f, value); 1472 return; 1473 } 1474 1475 addInjectPlan(plan, f, locator.getObject(fieldType, ap)); 1476 return; 1477 } 1478 1479 if (ap.getAnnotation(javax.inject.Inject.class) != null) 1480 { 1481 Named named = ap.getAnnotation(Named.class); 1482 1483 if (named == null) 1484 { 1485 addInjectPlan(plan, f, locator.getObject(fieldType, ap)); 1486 } else 1487 { 1488 addInjectPlan(plan, f, locator.getService(named.value(), fieldType)); 1489 } 1490 1491 return; 1492 } 1493 1494 // Ignore fields that do not have the necessary annotation. 1495 1496 } 1497 }); 1498 } 1499 1500 clazz = clazz.getSuperclass(); 1501 } 1502 } 1503 1504 private static <T> void addInjectPlan(ConstructionPlan<T> plan, final Field field, final Object injectedValue) 1505 { 1506 plan.add(new InitializationPlan<T>() 1507 { 1508 @Override 1509 public String getDescription() 1510 { 1511 return String.format("Injecting %s into field %s of class %s.", 1512 injectedValue, 1513 field.getName(), 1514 field.getDeclaringClass().getName()); 1515 } 1516 1517 @Override 1518 public void initialize(T instance) 1519 { 1520 inject(instance, field, injectedValue); 1521 } 1522 }); 1523 } 1524 1525 private static boolean hasAnnotation(AccessibleObject member, Class<? extends Annotation> annotationType) 1526 { 1527 return member.getAnnotation(annotationType) != null; 1528 } 1529 1530 private static <T> void extendPlanForPostInjectionMethods(ConstructionPlan<T> plan, OperationTracker tracker, ObjectLocator locator, InjectionResources resources, Class<T> instantiatedClass) 1531 { 1532 for (Method m : instantiatedClass.getMethods()) 1533 { 1534 if (hasAnnotation(m, PostInjection.class) || hasAnnotation(m, PostConstruct.class)) 1535 { 1536 extendPlanForPostInjectionMethod(plan, tracker, locator, resources, m); 1537 } 1538 } 1539 } 1540 1541 private static void extendPlanForPostInjectionMethod(final ConstructionPlan<?> plan, final OperationTracker tracker, final ObjectLocator locator, final InjectionResources resources, final Method method) 1542 { 1543 tracker.run("Computing parameters for post-injection method " + method, 1544 new Runnable() 1545 { 1546 @Override 1547 public void run() 1548 { 1549 final ObjectCreator[] parameters = calculateParametersForMethod(method, locator, 1550 resources, tracker); 1551 1552 plan.add(new InitializationPlan<Object>() 1553 { 1554 @Override 1555 public String getDescription() 1556 { 1557 return "Invoking " + method; 1558 } 1559 1560 @Override 1561 public void initialize(Object instance) 1562 { 1563 Throwable fail = null; 1564 1565 Object[] realized = realizeObjects(parameters); 1566 1567 try 1568 { 1569 method.invoke(instance, realized); 1570 } catch (InvocationTargetException ex) 1571 { 1572 fail = ex.getTargetException(); 1573 } catch (Exception ex) 1574 { 1575 fail = ex; 1576 } 1577 1578 if (fail != null) 1579 { 1580 throw new RuntimeException(String 1581 .format("Exception invoking method %s: %s", method, ExceptionUtils.toMessage(fail)), fail); 1582 } 1583 } 1584 }); 1585 } 1586 }); 1587 } 1588 1589 1590 public static <T> ObjectCreator<T> createMethodInvocationPlan(final OperationTracker tracker, final ObjectLocator locator, 1591 final InjectionResources resources, 1592 final Logger logger, 1593 final String description, 1594 final Object instance, 1595 final Method method) 1596 { 1597 1598 return tracker.invoke("Creating plan to invoke " + method, new Invokable<ObjectCreator<T>>() 1599 { 1600 @Override 1601 public ObjectCreator<T> invoke() 1602 { 1603 ObjectCreator[] methodParameters = calculateParametersForMethod(method, locator, resources, tracker); 1604 1605 Invokable<T> core = new MethodInvoker<T>(instance, method, methodParameters); 1606 1607 Invokable<T> wrapped = logger == null ? core : new LoggingInvokableWrapper<T>(logger, description, core); 1608 1609 return new ConstructionPlan(tracker, description, wrapped); 1610 } 1611 }); 1612 } 1613 1614 /** 1615 * @since 5.3.1, 5.4 1616 */ 1617 public final static Mapper<ObjectCreator, Object> CREATE_OBJECT = new Mapper<ObjectCreator, Object>() 1618 { 1619 @Override 1620 public Object map(ObjectCreator element) 1621 { 1622 return element.createObject(); 1623 } 1624 }; 1625 1626 /** 1627 * @since 5.3.1, 5.4 1628 */ 1629 public static Object[] realizeObjects(ObjectCreator[] creators) 1630 { 1631 return F.flow(creators).map(CREATE_OBJECT).toArray(Object.class); 1632 } 1633 1634 /** 1635 * Extracts the string keys from a map and returns them in sorted order. The keys are converted to strings. 1636 * 1637 * @param map 1638 * the map to extract keys from (may be null) 1639 * @return the sorted keys, or the empty set if map is null 1640 */ 1641 1642 public static List<String> sortedKeys(Map map) 1643 { 1644 return InternalCommonsUtils.sortedKeys(map); 1645 } 1646 1647 /** 1648 * Capitalizes the string, and inserts a space before each upper case character (or sequence of upper case 1649 * characters). Thus "userId" becomes "User Id", etc. Also, converts underscore into space (and capitalizes the 1650 * following word), thus "user_id" also becomes "User Id". 1651 */ 1652 public static String toUserPresentable(String id) 1653 { 1654 return InternalCommonsUtils.toUserPresentable(id); 1655 } 1656 1657 /** 1658 * Used to convert a property expression into a key that can be used to locate various resources (Blocks, messages, 1659 * etc.). Strips out any punctuation characters, leaving just words characters (letters, number and the 1660 * underscore). 1661 * 1662 * @param expression a property expression 1663 * @return the expression with punctuation removed 1664 */ 1665 public static String extractIdFromPropertyExpression(String expression) 1666 { 1667 return InternalCommonsUtils.extractIdFromPropertyExpression(expression); 1668 } 1669 1670 /** 1671 * Looks for a label within the messages based on the id. If found, it is used, otherwise the name is converted to a 1672 * user presentable form. 1673 */ 1674 public static String defaultLabel(String id, Messages messages, String propertyExpression) 1675 { 1676 return InternalCommonsUtils.defaultLabel(id, messages, propertyExpression); 1677 } 1678 1679 public static String replace(String input, Pattern pattern, String replacement) 1680 { 1681 return InternalCommonsUtils.replace(input, pattern, replacement); 1682 } 1683 1684}