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