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