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