001// Copyright 2023 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.services.pageload; 015 016import java.util.ArrayList; 017import java.util.Collections; 018import java.util.HashSet; 019import java.util.Objects; 020import java.util.Set; 021import java.util.function.Function; 022 023import org.apache.tapestry5.commons.services.PlasticProxyFactory; 024import org.apache.tapestry5.internal.plastic.PlasticClassLoader; 025import org.apache.tapestry5.internal.plastic.PlasticClassPool; 026import org.apache.tapestry5.plastic.PlasticManager; 027import org.apache.tapestry5.plastic.PlasticUtils; 028import org.slf4j.Logger; 029import org.slf4j.LoggerFactory; 030 031/** 032 * Class that encapsulates a classloader context for Tapestry's live class reloading. 033 * Each instance contains basically a classloader, a set of classnames, a parent 034 * context (possibly null) and child contexts (possibly empty). 035 */ 036public class PageClassLoaderContext 037{ 038 039 private static final Logger LOGGER = LoggerFactory.getLogger(PageClassLoaderContext.class); 040 041 private final String name; 042 043 private final PageClassLoaderContext parent; 044 045 private final Set<String> classNames = new HashSet<>(); 046 047 private final Set<PageClassLoaderContext> children; 048 049 private final PlasticManager plasticManager; 050 051 private final PlasticProxyFactory proxyFactory; 052 053 private PageClassLoaderContext root; 054 055 private final Function<String, PageClassLoaderContext> provider; 056 057 /** 058 * Name of the <code>unknown</code> context (i.e. the one for controlled classes 059 * without dependency information at the moment). 060 */ 061 public static final String UNKOWN_CONTEXT_NAME = "unknown"; 062 063 public PageClassLoaderContext(String name, 064 PageClassLoaderContext parent, 065 Set<String> classNames, 066 PlasticProxyFactory plasticProxyFactory, 067 Function<String, PageClassLoaderContext> provider) 068 { 069 super(); 070 this.name = name; 071 this.parent = parent; 072 this.classNames.addAll(classNames); 073 this.plasticManager = plasticProxyFactory.getPlasticManager(); 074 this.proxyFactory = plasticProxyFactory; 075 this.provider = provider; 076 children = new HashSet<>(); 077 if (plasticProxyFactory.getClassLoader() instanceof PlasticClassLoader) 078 { 079 final PlasticClassLoader plasticClassLoader = (PlasticClassLoader) plasticManager.getClassLoader(); 080 plasticClassLoader.setTag(name); 081 plasticClassLoader.setFilter(this::filter); 082 plasticClassLoader.setAlternativeClassloading(this::alternativeClassLoading); 083 // getPlasticManager().getPool().setName(name); 084 if (parent != null) 085 { 086 getPlasticManager().getPool().setParent(parent.getPlasticManager().getPool()); 087 } 088 } 089 } 090 091 private Class<?> alternativeClassLoading(String className) 092 { 093 Class<?> clasz = null; 094 setRootFieldIfNeeded(); 095 PageClassLoaderContext context = root.findByClassName( 096 PlasticUtils.getEnclosingClassName(className)); 097 if (isRoot() && context == null) 098 { 099 context = this; 100 } 101 if (context != null) 102 { 103 try 104 { 105 final PlasticClassLoader classLoader = (PlasticClassLoader) context.getClassLoader(); 106 // Avoiding infinite recursion 107 synchronized (classLoader) 108 { 109 classLoader.setAlternativeClassloading(null); 110 clasz = classLoader.loadClass(className); 111 classLoader.setAlternativeClassloading(this::alternativeClassLoading); 112 } 113 } catch (ClassNotFoundException e) 114 { 115 throw new RuntimeException(e); 116 } 117 } 118 else if (root.getPlasticManager().shouldInterceptClassLoading(className)) 119 { 120 context = provider.apply(className); 121 } 122 return clasz; 123 } 124 125 private void setRootFieldIfNeeded() 126 { 127 if (root == null) 128 { 129 if (isRoot()) 130 { 131 root = this; 132 } 133 else 134 { 135 root = this; 136 while (!root.isRoot()) 137 { 138 root = root.getParent(); 139 } 140 } 141 } 142 } 143 144 /** 145 * Returns the name of this context. 146 */ 147 public String getName() 148 { 149 return name; 150 } 151 152 /** 153 * Returns the parent of this context. 154 */ 155 public PageClassLoaderContext getParent() 156 { 157 return parent; 158 } 159 160 /** 161 * Returns the set of classes that belong in this context. 162 */ 163 public Set<String> getClassNames() 164 { 165 return classNames; 166 } 167 168 /** 169 * Returns the children of this context. 170 */ 171 public Set<PageClassLoaderContext> getChildren() 172 { 173 return children; 174 } 175 176 /** 177 * Returns this context's {@linkplain PlasticManager} instance. 178 */ 179 public PlasticManager getPlasticManager() 180 { 181 return plasticManager; 182 } 183 184 /** 185 * Returns this context's {@linkplain PlasticProxyFactory} instance. 186 */ 187 public PlasticProxyFactory getProxyFactory() 188 { 189 return proxyFactory; 190 } 191 192 /** 193 * Adds a class to this context. 194 */ 195 public void addClass(String className) 196 { 197 classNames.add(className); 198 } 199 200 /** 201 * Adds a child context. 202 */ 203 public void addChild(PageClassLoaderContext context) 204 { 205 children.add(context); 206 } 207 208 /** 209 * Removes a child context. 210 */ 211 public void removeChild(PageClassLoaderContext context) 212 { 213 children.remove(context); 214 } 215 216 /** 217 * Searches for the context that contains the given class in itself and recursivel in its children. 218 */ 219 public PageClassLoaderContext findByClassName(String className) 220 { 221 PageClassLoaderContext context = null; 222 if (classNames.contains(className)) 223 { 224 context = this; 225 } 226 else 227 { 228 for (PageClassLoaderContext child : children) { 229 context = child.findByClassName(className); 230 if (context != null) 231 { 232 break; 233 } 234 } 235 } 236 return context; 237 } 238 239 /** 240 * Returns the {@linkplain ClassLoader} associated with this context. 241 */ 242 public ClassLoader getClassLoader() 243 { 244 return proxyFactory.getClassLoader(); 245 } 246 247 /** 248 * Invalidates this context and its children recursively. This shouldn't 249 * be called directly, just through {@link PageClassLoaderContextManager#invalidate(PageClassLoaderContext...)}. 250 */ 251 public void invalidate() 252 { 253 for (PageClassLoaderContext child : new ArrayList<>(children)) 254 { 255 child.invalidate(); 256 } 257 LOGGER.debug("Invalidating page classloader context '{}' (class loader {}, classes : {})", 258 name, proxyFactory.getClassLoader(), classNames); 259// classNames.clear(); 260 parent.getChildren().remove(this); 261 proxyFactory.clearCache(); 262 } 263 264 /** 265 * Returns whether this is the root context. 266 */ 267 public boolean isRoot() 268 { 269 return parent == null; 270 } 271 272 /** 273 * Returns whether this is the <code>unknwon</code> context. 274 * @see #UNKOWN_CONTEXT_NAME 275 */ 276 public boolean isUnknown() 277 { 278 return name.equals(UNKOWN_CONTEXT_NAME); 279 } 280 281 /** 282 * Returns the set of descendents (children and their children recursively 283 * of this context. 284 */ 285 public Set<PageClassLoaderContext> getDescendents() 286 { 287 Set<PageClassLoaderContext> descendents; 288 if (children.isEmpty()) 289 { 290 descendents = Collections.emptySet(); 291 } 292 else 293 { 294 descendents = new HashSet<>(children); 295 for (PageClassLoaderContext child : children) 296 { 297 descendents.addAll(child.getDescendents()); 298 } 299 } 300 return descendents; 301 } 302 303 @Override 304 public int hashCode() { 305 return Objects.hash(name); 306 } 307 308 @Override 309 public boolean equals(Object obj) { 310 if (this == obj) { 311 return true; 312 } 313 if (!(obj instanceof PageClassLoaderContext)) { 314 return false; 315 } 316 PageClassLoaderContext other = (PageClassLoaderContext) obj; 317 return Objects.equals(name, other.name); 318 } 319 320 @Override 321 public String toString() 322 { 323 return "PageClassloaderContext [name=" + name + 324 ", parent=" + (parent != null ? parent.getName() : "null" ) + 325 ", classLoader=" + afterAt(proxyFactory.getClassLoader().toString()) + 326 (isRoot() ? "" : ", classNames=" + classNames) + 327 "]"; 328 } 329 330 public String toRecursiveString() 331 { 332 StringBuilder builder = new StringBuilder(); 333 toRecursiveString(builder, ""); 334 return builder.toString(); 335 } 336 337 private void toRecursiveString(StringBuilder builder, String tabs) 338 { 339 builder.append(tabs); 340 builder.append(name); 341 builder.append(" : "); 342 builder.append(afterAt(proxyFactory.getClassLoader().toString())); 343 builder.append(" : "); 344 builder.append(classNames); 345 builder.append("\n"); 346 for (PageClassLoaderContext child : children) { 347 child.toRecursiveString(builder, tabs + "\t"); 348 } 349 } 350 351 private static String afterAt(String string) 352 { 353 int index = string.indexOf('@'); 354 if (index > 0) 355 { 356 string = string.substring(index + 1); 357 } 358 return string; 359 } 360 361 private boolean filter(String className) 362 { 363 final int index = className.indexOf("$"); 364 if (index > 0) 365 { 366 className = className.substring(0, index); 367 } 368 // TODO: do we really need the className.contains(".base.") part? 369 return classNames.contains(className) || className.contains(".base.") || isUnknown(); 370 } 371 372}