001// Copyright 2022, 2023, 2024 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. 014package org.apache.tapestry5.internal.services; 015 016import java.io.BufferedReader; 017import java.io.BufferedWriter; 018import java.io.File; 019import java.io.FileReader; 020import java.io.FileWriter; 021import java.io.IOException; 022import java.lang.reflect.Field; 023import java.util.ArrayList; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.Iterator; 029import java.util.LinkedList; 030import java.util.List; 031import java.util.Locale; 032import java.util.Map; 033import java.util.Objects; 034import java.util.Set; 035import java.util.WeakHashMap; 036import java.util.function.Consumer; 037import java.util.stream.Collectors; 038 039import org.apache.tapestry5.ComponentResources; 040import org.apache.tapestry5.SymbolConstants; 041import org.apache.tapestry5.annotations.InjectComponent; 042import org.apache.tapestry5.annotations.InjectPage; 043import org.apache.tapestry5.annotations.Mixin; 044import org.apache.tapestry5.annotations.MixinClasses; 045import org.apache.tapestry5.annotations.Mixins; 046import org.apache.tapestry5.commons.Resource; 047import org.apache.tapestry5.commons.internal.util.TapestryException; 048import org.apache.tapestry5.commons.services.InvalidationEventHub; 049import org.apache.tapestry5.commons.util.UnknownValueException; 050import org.apache.tapestry5.internal.TapestryInternalUtils; 051import org.apache.tapestry5.internal.ThrowawayClassLoader; 052import org.apache.tapestry5.internal.parser.ComponentTemplate; 053import org.apache.tapestry5.internal.parser.StartComponentToken; 054import org.apache.tapestry5.internal.parser.TemplateToken; 055import org.apache.tapestry5.internal.structure.ComponentPageElement; 056import org.apache.tapestry5.ioc.Orderable; 057import org.apache.tapestry5.ioc.annotations.Symbol; 058import org.apache.tapestry5.ioc.internal.util.ClasspathResource; 059import org.apache.tapestry5.ioc.internal.util.InternalUtils; 060import org.apache.tapestry5.ioc.services.PerthreadManager; 061import org.apache.tapestry5.json.JSONArray; 062import org.apache.tapestry5.json.JSONObject; 063import org.apache.tapestry5.model.ComponentModel; 064import org.apache.tapestry5.model.EmbeddedComponentModel; 065import org.apache.tapestry5.model.MutableComponentModel; 066import org.apache.tapestry5.model.ParameterModel; 067import org.apache.tapestry5.plastic.PlasticField; 068import org.apache.tapestry5.plastic.PlasticManager; 069import org.apache.tapestry5.runtime.Component; 070import org.apache.tapestry5.services.ComponentClassResolver; 071import org.apache.tapestry5.services.pageload.PageClassLoaderContextManager; 072import org.apache.tapestry5.services.templates.ComponentTemplateLocator; 073import org.slf4j.Logger; 074import org.slf4j.LoggerFactory; 075 076@SuppressWarnings("deprecation") 077public class ComponentDependencyRegistryImpl implements ComponentDependencyRegistry 078{ 079 080 private static final List<String> EMPTY_LIST = Collections.emptyList(); 081 082 final private PageClassLoaderContextManager pageClassLoaderContextManager; 083 084 private static final String META_ATTRIBUTE = "injectedComponentDependencies"; 085 086 private static final String META_ATTRIBUTE_SEPARATOR = ","; 087 088 private static final String NO_DEPENDENCY = "NONE"; 089 090 // Key is a component, values are the components that depend on it. 091 final private Map<String, Set<Dependency>> map; 092 093 // Cache to check which classes were already processed or not. 094 final private Set<String> alreadyProcessed; 095 096 final private File storedDependencies; 097 098 final private static ThreadLocal<Integer> INVALIDATIONS_DISABLED = ThreadLocal.withInitial(() -> 0); 099 100 final private PlasticManager plasticManager; 101 102 final private ComponentClassResolver resolver; 103 104 final private TemplateParser templateParser; 105 106 final private Map<String, Boolean> isPageCache = new WeakHashMap<>(); 107 108 final private ComponentTemplateLocator componentTemplateLocator; 109 110 final private boolean storedDependencyInformationPresent; 111 112 private boolean enableEnsureClassIsAlreadyProcessed = true; 113 114 public ComponentDependencyRegistryImpl( 115 final PageClassLoaderContextManager pageClassLoaderContextManager, 116 final PlasticManager plasticManager, 117 final ComponentClassResolver componentClassResolver, 118 final TemplateParser templateParser, 119 final ComponentTemplateLocator componentTemplateLocator, 120 final @Symbol(SymbolConstants.COMPONENT_DEPENDENCY_FILE) String componentDependencyFile, 121 final @Symbol(SymbolConstants.PRODUCTION_MODE) boolean productionMode) 122 { 123 this.pageClassLoaderContextManager = pageClassLoaderContextManager; 124 map = new HashMap<>(); 125 alreadyProcessed = new HashSet<>(); 126 this.plasticManager = plasticManager; 127 this.resolver = componentClassResolver; 128 this.templateParser = templateParser; 129 this.componentTemplateLocator = componentTemplateLocator; 130 131 if (!productionMode) 132 { 133 134 Logger logger = LoggerFactory.getLogger(ComponentDependencyRegistry.class); 135 136 storedDependencies = new File(componentDependencyFile); 137 final boolean fileExists = storedDependencies.exists(); 138 139 logger.info("Component dependencies file: {} Found? {}", 140 storedDependencies.getAbsolutePath(), fileExists); 141 142 if (fileExists) 143 { 144 try (FileReader fileReader = new FileReader(storedDependencies); 145 BufferedReader reader = new BufferedReader(fileReader)) 146 { 147 StringBuilder builder = new StringBuilder(); 148 String line = reader.readLine(); 149 while (line != null) 150 { 151 builder.append(line); 152 line = reader.readLine(); 153 } 154 JSONArray jsonArray = new JSONArray(builder.toString()); 155 for (int i = 0; i < jsonArray.size(); i++) 156 { 157 final JSONObject jsonObject = jsonArray.getJSONObject(i); 158 final String className = jsonObject.getString("class"); 159 final String type = jsonObject.getString("type"); 160 if (!type.equals(NO_DEPENDENCY)) 161 { 162 final DependencyType dependencyType = DependencyType.valueOf(type); 163 final String dependency = jsonObject.getString("dependency"); 164 add(className, dependency, dependencyType); 165 alreadyProcessed.add(dependency); 166 } 167 alreadyProcessed.add(className); 168 } 169 } catch (IOException e) 170 { 171 throw new TapestryException("Exception trying to read " + storedDependencies.getAbsolutePath(), e); 172 } 173 174 } 175 176 } 177 else 178 { 179 storedDependencies = null; 180 } 181 182 storedDependencyInformationPresent = !map.isEmpty(); 183 184 } 185 186 public void setupThreadCleanup(final PerthreadManager perthreadManager) 187 { 188 perthreadManager.addThreadCleanupCallback(() -> { 189 INVALIDATIONS_DISABLED.set(0); 190 }); 191 } 192 193 @Override 194 public void register(Class<?> component) 195 { 196 register(component, component.getClassLoader()); 197 } 198 199 @Override 200 public void register(Class<?> component, ClassLoader classLoader) 201 { 202 203 final String className = component.getName(); 204 final Set<Class<?>> furtherDependencies = new HashSet<>(); 205 Consumer<Class<?>> processClass = furtherDependencies::add; 206 Consumer<String> processClassName = s -> { 207 try { 208 furtherDependencies.add(classLoader.loadClass(s)); 209 } catch (ClassNotFoundException e) { 210 throw new RuntimeException(e); 211 } 212 }; 213 214 // Components declared in the template 215 registerTemplate(component, processClassName); 216 217 // Dependencies from injecting or component-declaring annotations: 218 // @InjectPage, @InjectComponent 219 for (Field field : component.getDeclaredFields()) 220 { 221 222 // Component injection annotation 223 if (field.isAnnotationPresent(InjectComponent.class)) 224 { 225 final Class<?> dependency = field.getType(); 226 add(component, dependency, DependencyType.USAGE); 227 processClass.accept(dependency); 228 } 229 230 // Page injection annotation 231 if (field.isAnnotationPresent(InjectPage.class)) 232 { 233 final Class<?> dependency = field.getType(); 234 add(component, dependency, DependencyType.INJECT_PAGE); 235 processClass.accept(dependency); 236 } 237 238 // @Component 239 registerComponentInstance(field, processClassName); 240 241 // Mixins, class level: @Mixin 242 registerMixin(field, processClassName); 243 244 // Mixins applied to embedded component instances through @MixinClasses or @Mixins 245 registerComponentInstanceMixins(field, processClass, processClassName); 246 } 247 248 // Superclass 249 Class<?> superclass = component.getSuperclass(); 250 if (isTransformed(superclass)) 251 { 252 processClass.accept(superclass); 253 add(component, superclass, DependencyType.SUPERCLASS); 254 } 255 256 alreadyProcessed.add(className); 257 258 for (Class<?> dependency : furtherDependencies) 259 { 260 // Avoid infinite recursion 261 final String dependencyClassName = dependency.getName(); 262 if (!alreadyProcessed.contains(dependencyClassName) 263 && plasticManager.shouldInterceptClassLoading(dependency.getName())) 264 { 265 register(dependency, classLoader); 266 } 267 } 268 269 } 270 271 /** 272 * Notice only the main template (i.e. not the locale- or axis-specific ones) 273 * are checked here. They hopefully will be covered when the ComponentModel-based 274 * component dependency processing is done. 275 * @param component 276 * @param processClassName 277 */ 278 private void registerTemplate(Class<?> component, Consumer<String> processClassName) 279 { 280 // TODO: implement caching of template dependency information, probably 281 // by listening separately to ComponentTemplateSource to invalidate caches 282 // just when template changes. 283 284 final String className = component.getName(); 285 ComponentModel mock = new ComponentModelMock(component, isPage(className)); 286 final Resource templateResource = componentTemplateLocator.locateTemplate(mock, Locale.getDefault()); 287 String dependency; 288 if (templateResource != null && templateResource.exists()) 289 { 290 final ComponentTemplate template = templateParser.parseTemplate(templateResource); 291 final List<TemplateToken> tokens = new LinkedList<>(); 292 293 tokens.addAll(template.getTokens()); 294 for (String id : template.getExtensionPointIds()) 295 { 296 tokens.addAll(template.getExtensionPointTokens(id)); 297 } 298 299 for (TemplateToken token : tokens) 300 { 301 if (token instanceof StartComponentToken) 302 { 303 StartComponentToken componentToken = (StartComponentToken) token; 304 String logicalName = componentToken.getComponentType(); 305 if (logicalName != null) 306 { 307 try 308 { 309 dependency = resolver.resolveComponentTypeToClassName(logicalName); 310 add(className, dependency, DependencyType.USAGE); 311 processClassName.accept(dependency); 312 } 313 catch (UnknownValueException e) 314 { 315 // Logical name doesn't match an existing component. Ignore 316 } 317 } 318 for (String mixin : TapestryInternalUtils.splitAtCommas(componentToken.getMixins())) 319 { 320 try 321 { 322 if (mixin.contains("::")) 323 { 324 mixin = mixin.substring(0, mixin.indexOf("::")); 325 } 326 dependency = resolver.resolveMixinTypeToClassName(mixin); 327 add(className, dependency, DependencyType.USAGE); 328 processClassName.accept(dependency); 329 } 330 catch (UnknownValueException e) 331 { 332 // Mixin name doesn't match an existing mixin. Ignore 333 } 334 335 } 336 } 337 } 338 } 339 } 340 341 private boolean isPage(final String className) 342 { 343 Boolean result = isPageCache.get(className); 344 if (result == null) 345 { 346 result = resolver.isPage(className); 347 isPageCache.put(className, result); 348 } 349 return result; 350 } 351 352 private void registerComponentInstance(Field field, Consumer<String> processClassName) 353 { 354 if (field.isAnnotationPresent(org.apache.tapestry5.annotations.Component.class)) 355 { 356 org.apache.tapestry5.annotations.Component component = 357 field.getAnnotation(org.apache.tapestry5.annotations.Component.class); 358 359 final String typeFromAnnotation = component.type().trim(); 360 String dependency; 361 if (typeFromAnnotation.isEmpty()) 362 { 363 dependency = field.getType().getName(); 364 } 365 else 366 { 367 dependency = resolver.resolveComponentTypeToClassName(typeFromAnnotation); 368 } 369 add(field.getDeclaringClass().getName(), dependency, DependencyType.USAGE); 370 processClassName.accept(dependency); 371 } 372 } 373 374 private void registerMixin(Field field, Consumer<String> processClassName) { 375 if (field.isAnnotationPresent(Mixin.class)) 376 { 377 // Logic adapted from MixinWorker 378 String mixinType = field.getAnnotation(Mixin.class).value(); 379 String mixinClassName = InternalUtils.isBlank(mixinType) ? 380 getFieldTypeClassName(field) : 381 resolver.resolveMixinTypeToClassName(mixinType); 382 383 add(getDeclaringClassName(field), mixinClassName, DependencyType.USAGE); 384 processClassName.accept(mixinClassName); 385 } 386 } 387 388 private String getDeclaringClassName(Field field) { 389 return field.getDeclaringClass().getName(); 390 } 391 392 private String getFieldTypeClassName(Field field) { 393 return field.getType().getName(); 394 } 395 396 private void registerComponentInstanceMixins(Field field, Consumer<Class<?>> processClass, Consumer<String> processClassName) 397 { 398 399 if (field.isAnnotationPresent(org.apache.tapestry5.annotations.Component.class)) 400 { 401 402 MixinClasses mixinClasses = field.getAnnotation(MixinClasses.class); 403 if (mixinClasses != null) 404 { 405 for (Class<?> dependency : mixinClasses.value()) 406 { 407 add(field.getDeclaringClass(), dependency, DependencyType.USAGE); 408 processClass.accept(dependency); 409 } 410 } 411 412 Mixins mixins = field.getAnnotation(Mixins.class); 413 if (mixins != null) 414 { 415 for (String mixin : mixins.value()) 416 { 417 // Logic adapted from MixinsWorker 418 Orderable<String> typeAndOrder = TapestryInternalUtils.mixinTypeAndOrder(mixin); 419 final String dependency = resolver.resolveMixinTypeToClassName(typeAndOrder.getTarget()); 420 add(getDeclaringClassName(field), dependency, DependencyType.USAGE); 421 processClassName.accept(dependency); 422 } 423 } 424 425 } 426 427 } 428 429 @Override 430 public void register(ComponentPageElement componentPageElement) 431 { 432 final String componentClassName = getClassName(componentPageElement); 433 434 if (!alreadyProcessed.contains(componentClassName)) 435 { 436 synchronized (map) 437 { 438 439 // Components in the tree (i.e. declared in the template 440 for (String id : componentPageElement.getEmbeddedElementIds()) 441 { 442 final ComponentPageElement child = componentPageElement.getEmbeddedElement(id); 443 add(componentPageElement, child, DependencyType.USAGE); 444 register(child); 445 } 446 447 // Mixins, class level 448 final ComponentResources componentResources = componentPageElement.getComponentResources(); 449 final ComponentModel componentModel = componentResources.getComponentModel(); 450 for (String mixinClassName : componentModel.getMixinClassNames()) 451 { 452 add(componentClassName, mixinClassName, DependencyType.USAGE); 453 } 454 455 // Mixins applied to embedded component instances 456 final List<String> embeddedComponentIds = componentModel.getEmbeddedComponentIds(); 457 for (String id : embeddedComponentIds) 458 { 459 final EmbeddedComponentModel embeddedComponentModel = componentResources 460 .getComponentModel() 461 .getEmbeddedComponentModel(id); 462 final List<String> mixinClassNames = embeddedComponentModel 463 .getMixinClassNames(); 464 for (String mixinClassName : mixinClassNames) { 465 add(componentClassName, mixinClassName, DependencyType.USAGE); 466 } 467 } 468 469 // Superclass 470 final Component component = componentPageElement.getComponent(); 471 Class<?> parent = component.getClass().getSuperclass(); 472 if (parent != null && !Object.class.equals(parent)) 473 { 474 add(componentClassName, parent.getName(), DependencyType.SUPERCLASS); 475 } 476 477 // Dependencies from injecting annotations: 478 // @InjectPage, @InjectComponent, @InjectComponent 479 final String metaDependencies = component.getComponentResources().getComponentModel().getMeta(META_ATTRIBUTE); 480 if (metaDependencies != null) 481 { 482 for (String dependency : metaDependencies.split(META_ATTRIBUTE_SEPARATOR)) 483 { 484 add(componentClassName, dependency, 485 isPage(dependency) ? DependencyType.INJECT_PAGE : DependencyType.USAGE); 486 } 487 } 488 489 alreadyProcessed.add(componentClassName); 490 491 } 492 493 } 494 495 } 496 497 @Override 498 public void register(PlasticField plasticField, MutableComponentModel componentModel) 499 { 500 if (plasticField.hasAnnotation(InjectPage.class) || 501 plasticField.hasAnnotation(InjectComponent.class) || 502 plasticField.hasAnnotation(org.apache.tapestry5.annotations.Component.class)) 503 { 504 String dependencies = componentModel.getMeta(META_ATTRIBUTE); 505 final String dependency = plasticField.getTypeName(); 506 if (dependencies == null) 507 { 508 dependencies = dependency; 509 } 510 else 511 { 512 if (!dependencies.contains(dependency)) 513 { 514 dependencies = dependencies + META_ATTRIBUTE_SEPARATOR + dependency; 515 } 516 } 517 componentModel.setMeta(META_ATTRIBUTE, dependencies); 518 } 519 } 520 521 private String getClassName(ComponentPageElement component) 522 { 523 return component.getComponentResources().getComponentModel().getComponentClassName(); 524 } 525 526 @Override 527 public void clear(String className) 528 { 529 synchronized (map) 530 { 531 alreadyProcessed.remove(className); 532 map.remove(className); 533 final Collection<Set<Dependency>> allDependentSets = map.values(); 534 for (Set<Dependency> dependents : allDependentSets) 535 { 536 if (dependents != null) 537 { 538 final Iterator<Dependency> iterator = dependents.iterator(); 539 while (iterator.hasNext()) 540 { 541 if (className.equals(iterator.next().className)) 542 { 543 iterator.remove(); 544 } 545 } 546 } 547 } 548 } 549 } 550 551 @Override 552 public void clear(ComponentPageElement component) 553 { 554 clear(getClassName(component)); 555 } 556 557 @Override 558 public void clear() { 559 map.clear(); 560 alreadyProcessed.clear(); 561 } 562 563 @Override 564 public Set<String> getDependents(String className) 565 { 566 567 ensureClassIsAlreadyProcessed(className); 568 569 final Set<Dependency> dependents = map.get(className); 570 return dependents != null 571 ? dependents.stream().map(d -> d.className).collect(Collectors.toSet()) 572 : Collections.emptySet(); 573 } 574 575 @Override 576 public Set<String> getDependencies(String className, DependencyType type) 577 { 578 579 ensureClassIsAlreadyProcessed(className); 580 581 Set<String> dependencies = Collections.emptySet(); 582 if (alreadyProcessed.contains(className)) 583 { 584 dependencies = map.entrySet().stream() 585 .filter(e -> contains(e.getValue(), className, type)) 586 .map(e -> e.getKey()) 587 .collect(Collectors.toSet()); 588 } 589 590 return dependencies; 591 } 592 593 @Override 594 public Set<String> getAllNonPageDependencies(String className) 595 { 596 final Set<String> dependencies = new HashSet<>(); 597 getAllNonPageDependencies(className, dependencies); 598 // Just in case, since it's possible to have circular dependencies. 599 dependencies.remove(className); 600 return Collections.unmodifiableSet(dependencies); 601 } 602 603 private void getAllNonPageDependencies(String className, Set<String> dependencies) 604 { 605 Set<String> theseDependencies = new HashSet<>(); 606 theseDependencies.addAll(getDependencies(className, DependencyType.USAGE)); 607 theseDependencies.addAll(getDependencies(className, DependencyType.SUPERCLASS)); 608 theseDependencies.removeAll(dependencies); 609 dependencies.addAll(theseDependencies); 610 for (String dependency : theseDependencies) 611 { 612 getAllNonPageDependencies(dependency, dependencies); 613 } 614 } 615 616 617 private boolean contains(Set<Dependency> dependencies, String className, DependencyType type) 618 { 619 boolean contains = false; 620 for (Dependency dependency : dependencies) 621 { 622 if (dependency.type.equals(type) && dependency.className.equals(className)) 623 { 624 contains = true; 625 break; 626 } 627 } 628 return contains; 629 } 630 631 private void add(ComponentPageElement component, ComponentPageElement dependency, DependencyType type) 632 { 633 add(getClassName(component), getClassName(dependency), type); 634 } 635 636 // Just for unit tests 637 void add(String component, String dependency, DependencyType type, boolean markAsAlreadyProcessed) 638 { 639 if (markAsAlreadyProcessed) 640 { 641 alreadyProcessed.add(component); 642 } 643 if (dependency != null) 644 { 645 add(component, dependency, type); 646 } 647 } 648 649 private void add(Class<?> component, Class<?> dependency, DependencyType type) 650 { 651 if (plasticManager.shouldInterceptClassLoading(dependency.getName())) 652 { 653 add(component.getName(), dependency.getName(), type); 654 } 655 } 656 657 private void add(String component, String dependency, DependencyType type) 658 { 659 Objects.requireNonNull(component, "Parameter component cannot be null"); 660 Objects.requireNonNull(dependency, "Parameter dependency cannot be null"); 661 Objects.requireNonNull(dependency, "Parameter type cannot be null"); 662 synchronized (map) 663 { 664 if (!component.equals(dependency)) 665 { 666 Set<Dependency> dependents = map.get(dependency); 667 if (dependents == null) 668 { 669 dependents = new HashSet<>(); 670 map.put(dependency, dependents); 671 } 672 dependents.add(new Dependency(component, type)); 673 } 674 } 675 } 676 677 @Override 678 public void listen(InvalidationEventHub invalidationEventHub) 679 { 680 invalidationEventHub.addInvalidationCallback(this::listen); 681 } 682 683 // Protected just for testing 684 List<String> listen(List<String> resources) 685 { 686 List<String> furtherDependents = EMPTY_LIST; 687 if (resources.isEmpty()) 688 { 689 clear(); 690 furtherDependents = EMPTY_LIST; 691 } 692 else if (INVALIDATIONS_DISABLED.get() > 0) 693 { 694 furtherDependents = Collections.emptyList(); 695 } 696 // Don't invalidate component dependency information when 697 // PageClassloaderContextManager is merging contexts 698 // TODO: is this still needed since the inception of INVALIDATIONS_ENABLED? 699 else if (!pageClassLoaderContextManager.isMerging()) 700 { 701 furtherDependents = new ArrayList<>(); 702 for (String resource : resources) 703 { 704 705 final Set<String> dependents = getDependents(resource); 706 for (String furtherDependent : dependents) 707 { 708 if (!resources.contains(furtherDependent) && !furtherDependents.contains(furtherDependent)) 709 { 710 furtherDependents.add(furtherDependent); 711 } 712 } 713 714 clear(resource); 715 716 } 717 } 718 return furtherDependents; 719 } 720 721 @Override 722 public void writeFile() 723 { 724 synchronized (this) 725 { 726 try (FileWriter fileWriter = new FileWriter(storedDependencies); 727 BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) 728 { 729 Set<String> classNames = new HashSet<>(alreadyProcessed.size()); 730 classNames.addAll(map.keySet()); 731 classNames.addAll(alreadyProcessed); 732 JSONArray jsonArray = new JSONArray(); 733 for (String className : classNames) 734 { 735 boolean hasDependencies = false; 736 for (DependencyType dependencyType : DependencyType.values()) 737 { 738 final Set<String> dependencies = getDependencies(className, dependencyType); 739 for (String dependency : dependencies) 740 { 741 JSONObject object = new JSONObject(); 742 object.put("class", className); 743 object.put("type", dependencyType.name()); 744 object.put("dependency", dependency); 745 jsonArray.add(object); 746 hasDependencies = true; 747 } 748 } 749 // Add a fake dependency so classes without dependencies 750 // nor classes depending on it are properly stored and 751 // retrieved, thus avoiding these classes getting into the 752 // unknown page classloader context. 753 if (!hasDependencies) 754 { 755 if (getDependents(className).isEmpty()) { 756 JSONObject object = new JSONObject(); 757 object.put("class", className); 758 object.put("type", NO_DEPENDENCY); 759 jsonArray.add(object); 760 } 761 } 762 } 763 bufferedWriter.write(jsonArray.toString()); 764 } 765 catch (IOException e) 766 { 767 throw new TapestryException("Exception trying to write " + storedDependencies.getAbsolutePath(), e); 768 } 769 770 Logger logger = LoggerFactory.getLogger(ComponentDependencyRegistry.class); 771 772 logger.info("Component dependencies written to {}", 773 storedDependencies.getAbsolutePath()); 774 } 775 } 776 777 @Override 778 public boolean contains(String className) 779 { 780 return alreadyProcessed.contains(className); 781 } 782 783 @Override 784 public Set<String> getClassNames() 785 { 786 return Collections.unmodifiableSet(new HashSet<>(alreadyProcessed)); 787 } 788 789 @Override 790 public Set<String> getRootClasses() { 791 return alreadyProcessed.stream() 792 .filter(c -> getDependencies(c, DependencyType.USAGE).isEmpty() && 793 getDependencies(c, DependencyType.INJECT_PAGE).isEmpty() && 794 getDependencies(c, DependencyType.SUPERCLASS).isEmpty()) 795 .collect(Collectors.toSet()); 796 } 797 798 private boolean isTransformed(Class<?> clasz) 799 { 800 return plasticManager.shouldInterceptClassLoading(clasz.getName()); 801 } 802 803 @Override 804 public boolean isStoredDependencyInformationPresent() 805 { 806 return storedDependencyInformationPresent; 807 } 808 809 @Override 810 public void disableInvalidations() 811 { 812 INVALIDATIONS_DISABLED.set(INVALIDATIONS_DISABLED.get() + 1); 813 } 814 815 @Override 816 public void enableInvalidations() 817 { 818 INVALIDATIONS_DISABLED.set(INVALIDATIONS_DISABLED.get() - 1); 819 if (INVALIDATIONS_DISABLED.get() < 0) 820 { 821 INVALIDATIONS_DISABLED.set(0); 822 } 823 } 824 825 // Only for unit tests 826 void setEnableEnsureClassIsAlreadyProcessed(boolean enableEnsureClassIsAlreadyProcessed) { 827 this.enableEnsureClassIsAlreadyProcessed = enableEnsureClassIsAlreadyProcessed; 828 } 829 830 private void ensureClassIsAlreadyProcessed(String className) { 831 if (enableEnsureClassIsAlreadyProcessed && !contains(className)) 832 { 833 ThrowawayClassLoader classLoader = new ThrowawayClassLoader(getClass().getClassLoader()); 834 try 835 { 836 register(classLoader.loadClass(className)); 837 } catch (ClassNotFoundException e) 838 { 839 throw new RuntimeException(e); 840 } 841 } 842 } 843 844 /** 845 * Only really implemented method is {@link ComponentModel#getBaseResource()} 846 */ 847 private class ComponentModelMock implements ComponentModel 848 { 849 850 final private Resource baseResource; 851 final private boolean isPage; 852 final private String componentClassName; 853 854 public ComponentModelMock(Class<?> component, boolean isPage) 855 { 856 componentClassName = component.getName(); 857 String templateLocation = componentClassName.replace('.', '/'); 858 baseResource = new ClasspathResource(templateLocation); 859 860 this.isPage = isPage; 861 } 862 863 @Override 864 public Resource getBaseResource() 865 { 866 return baseResource; 867 } 868 869 @Override 870 public String getLibraryName() 871 { 872 return null; 873 } 874 875 @Override 876 public boolean isPage() 877 { 878 return isPage; 879 } 880 881 @Override 882 public String getComponentClassName() 883 { 884 return componentClassName; 885 } 886 887 @Override 888 public List<String> getEmbeddedComponentIds() 889 { 890 return null; 891 } 892 893 @Override 894 public EmbeddedComponentModel getEmbeddedComponentModel(String componentId) 895 { 896 return null; 897 } 898 899 @Override 900 public String getFieldPersistenceStrategy(String fieldName) 901 { 902 return null; 903 } 904 905 @Override 906 public Logger getLogger() 907 { 908 return null; 909 } 910 911 @Override 912 public List<String> getMixinClassNames() 913 { 914 return null; 915 } 916 917 @Override 918 public ParameterModel getParameterModel(String parameterName) 919 { 920 return null; 921 } 922 923 @Override 924 public boolean isFormalParameter(String parameterName) 925 { 926 return false; 927 } 928 929 @Override 930 public List<String> getParameterNames() 931 { 932 return null; 933 } 934 935 @Override 936 public List<String> getDeclaredParameterNames() 937 { 938 return null; 939 } 940 941 @Override 942 public List<String> getPersistentFieldNames() 943 { 944 return null; 945 } 946 947 @Override 948 public boolean isRootClass() 949 { 950 return false; 951 } 952 953 @Override 954 public boolean getSupportsInformalParameters() 955 { 956 return false; 957 } 958 959 @Override 960 public ComponentModel getParentModel() 961 { 962 return null; 963 } 964 965 @Override 966 public boolean isMixinAfter() 967 { 968 return false; 969 } 970 971 @Override 972 public String getMeta(String key) 973 { 974 return null; 975 } 976 977 @SuppressWarnings("rawtypes") 978 @Override 979 public Set<Class> getHandledRenderPhases() 980 { 981 return null; 982 } 983 984 @Override 985 public boolean handlesEvent(String eventType) 986 { 987 return false; 988 } 989 990 @Override 991 public String[] getOrderForMixin(String mixinClassName) 992 { 993 return null; 994 } 995 996 @Override 997 public boolean handleActivationEventContext() 998 { 999 return false; 1000 } 1001 1002 } 1003 1004 private static final class Dependency 1005 { 1006 private final String className; 1007 private final DependencyType type; 1008 1009 public Dependency(String className, DependencyType dependencyType) 1010 { 1011 super(); 1012 this.className = className; 1013 this.type = dependencyType; 1014 } 1015 1016 @Override 1017 public int hashCode() { 1018 return Objects.hash(className, type); 1019 } 1020 1021 @Override 1022 public boolean equals(Object obj) 1023 { 1024 if (this == obj) 1025 { 1026 return true; 1027 } 1028 if (!(obj instanceof Dependency)) 1029 { 1030 return false; 1031 } 1032 Dependency other = (Dependency) obj; 1033 return Objects.equals(className, other.className) && type == other.type; 1034 } 1035 1036 @Override 1037 public String toString() 1038 { 1039 return "Dependency [className=" + className + ", dependencyType=" + type + "]"; 1040 } 1041 1042 } 1043 1044}