001// Copyright 2014 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// You may obtain a copy of the License at 006// 007// http://www.apache.org/licenses/LICENSE-2.0 008// 009// Unless required by applicable law or agreed to in writing, software 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014 015package org.apache.tapestry5.corelib.pages; 016 017import java.util.ArrayList; 018import java.util.Collections; 019import java.util.Comparator; 020import java.util.List; 021 022import org.apache.tapestry5.Block; 023import org.apache.tapestry5.annotations.Cached; 024import org.apache.tapestry5.annotations.InjectComponent; 025import org.apache.tapestry5.annotations.OnEvent; 026import org.apache.tapestry5.annotations.Persist; 027import org.apache.tapestry5.annotations.Property; 028import org.apache.tapestry5.annotations.UnknownActivationContextCheck; 029import org.apache.tapestry5.annotations.WhitelistAccessOnly; 030import org.apache.tapestry5.corelib.components.Zone; 031import org.apache.tapestry5.http.TapestryHttpSymbolConstants; 032import org.apache.tapestry5.internal.ThrowawayClassLoader; 033import org.apache.tapestry5.internal.plastic.ClassLoaderDelegate; 034import org.apache.tapestry5.internal.plastic.PlasticClassLoader; 035import org.apache.tapestry5.internal.services.ComponentDependencyGraphvizGenerator; 036import org.apache.tapestry5.internal.services.ComponentDependencyRegistry; 037import org.apache.tapestry5.internal.services.ComponentDependencyRegistry.DependencyType; 038import org.apache.tapestry5.ioc.annotations.Description; 039import org.apache.tapestry5.ioc.annotations.Inject; 040import org.apache.tapestry5.ioc.annotations.Symbol; 041import org.apache.tapestry5.json.JSONArray; 042import org.apache.tapestry5.json.JSONObject; 043import org.apache.tapestry5.services.ComponentClassResolver; 044import org.apache.tapestry5.services.ComponentLibraryInfo; 045import org.apache.tapestry5.services.ComponentLibraryInfoSource; 046import org.apache.tapestry5.services.LibraryMapping; 047import org.apache.tapestry5.util.TextStreamResponse; 048 049/** 050 * Page used to describe the component libraries being used in the application. 051 * Notice: the implementation of this page was done to avoid creating components, so the 052 * Tapestry 5 Core Library didn't get polluted with internal-only components. 053 */ 054@UnknownActivationContextCheck(false) 055@WhitelistAccessOnly 056public class ComponentLibraries 057{ 058 059 final private static String[] EMTPY_STRING_ARRAY = {}; 060 061 private static final Comparator<LibraryMapping> LIBRARY_MAPPING_COMPARATOR = new Comparator<LibraryMapping>() 062 { 063 @Override 064 public int compare(LibraryMapping mapping1, LibraryMapping mapping2) 065 { 066 return mapping1.libraryName.compareTo(mapping2.libraryName); 067 } 068 }; 069 070 private static enum Type { PAGE, COMPONENT, MIXIN } 071 072 @InjectComponent 073 private Zone zone; 074 075 @Inject 076 private ComponentClassResolver componentClassResolver; 077 078 @Property 079 private LibraryMapping libraryMapping; 080 081 @Property 082 private String logicalName; 083 084 @Property 085 private List<String> logicalNames; 086 087 @Property 088 private String headerName; 089 090 @Property 091 private List<String> pages; 092 093 @Property 094 private List<String> components; 095 096 @Property 097 private List<String> mixins; 098 099 private Type type; 100 101 @Inject 102 private Block classesTable; 103 104 @Inject 105 @Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE) 106 @Property 107 private boolean productionMode; 108 109 @Inject 110 private ComponentLibraryInfoSource componentLibraryInfoSource; 111 112 @Inject 113 private ComponentDependencyRegistry componentDependencyRegistry; 114 115 @Inject 116 private ComponentDependencyGraphvizGenerator componentDependencyGraphvizGenerator; 117 118 @Property 119 private String selectedComponent; 120 121 @Property 122 private String dependency; 123 124 @Persist 125 @Property 126 private boolean showEverything; 127 128// void onActivate(List<String> context) 129// { 130// if (context.size() > 0) 131// { 132// selectedComponent = String.join("/", context); 133// } 134// else 135// { 136// selectedComponent = null; 137// } 138// } 139// 140// Object[] onPassivate() 141// { 142// return selectedComponent.split("/"); 143// } 144 145 @Cached 146 public List<LibraryMapping> getLibraryMappings() 147 { 148 List<LibraryMapping> mappings = new ArrayList<LibraryMapping>(); 149 150 // add all the library mappings, except the "" (empty string) one. 151 for (LibraryMapping libraryMapping : componentClassResolver.getLibraryMappings()) 152 { 153 if (showEverything || !"".equals(libraryMapping.libraryName)) { 154 mappings.add(libraryMapping); 155 } 156 } 157 158 Collections.sort(mappings, LIBRARY_MAPPING_COMPARATOR); 159 return mappings; 160 } 161 162 @Cached(watch="libraryMapping") 163 public ComponentLibraryInfo getInfo() 164 { 165 return componentLibraryInfoSource.find(libraryMapping); 166 } 167 168 public List<String> getLibraryNames() { 169 return componentClassResolver.getLibraryNames(); 170 } 171 172 public String getLibraryClientId() 173 { 174 return libraryMapping.libraryName.replace("/", "-"); 175 } 176 177 private List<String> filter(final List<String> allNames) 178 { 179 List<String> logicalNames = new ArrayList<String>(); 180 final List<LibraryMapping> libraryMappings = getLibraryMappings(); 181 for (String name : allNames) 182 { 183 184 if (name.startsWith(libraryMapping.libraryName + "/") && 185 !(libraryMapping.libraryName.equals("core") && name.endsWith("Test"))) 186 { 187 logicalNames.add(name); 188 } 189 else 190 { 191 if (libraryMapping.libraryName.equals("")) 192 { 193 boolean isWebappLibrary = true; 194 for (LibraryMapping otherLibraryMapping : libraryMappings) { 195 if (!libraryMapping.equals(otherLibraryMapping) && 196 name.startsWith(otherLibraryMapping.libraryName + "/")) 197 { 198 isWebappLibrary = false; 199 break; 200 } 201 } 202 if (isWebappLibrary) 203 { 204 logicalNames.add(name); 205 } 206 } 207 } 208 } 209 210 return logicalNames; 211 } 212 213 public Block getComponentsTable() 214 { 215 logicalNames = filter(componentClassResolver.getComponentNames()); 216 type = Type.COMPONENT; 217 headerName = "Components"; 218 return classesTable; 219 } 220 221 public Block getPagesTable() 222 { 223 logicalNames = filter(componentClassResolver.getPageNames()); 224 type = Type.PAGE; 225 headerName = "Pages"; 226 return classesTable; 227 } 228 229 public Block getMixinsTable() 230 { 231 logicalNames = filter(componentClassResolver.getMixinNames()); 232 type = Type.MIXIN; 233 headerName = "Mixins"; 234 return classesTable; 235 } 236 237 public String getSourceUrl() 238 { 239 return getInfo() != null ? getInfo().getSourceUrl(getClassName()) : null; 240 } 241 242 public String getJavaDocUrl() 243 { 244 return getInfo() != null ? getInfo().getJavadocUrl(getClassName()) : null; 245 } 246 247 private String getClassName() 248 { 249 return getClassName(logicalName, type, componentClassResolver); 250 } 251 252 private static String getClassName(String logicalName, Type type, ComponentClassResolver componentClassResolver) 253 { 254 String className; 255 switch (type) 256 { 257 case PAGE: className = componentClassResolver.resolvePageNameToClassName(logicalName); break; 258 case COMPONENT: className = componentClassResolver.resolveComponentTypeToClassName(logicalName); break; 259 case MIXIN: className = componentClassResolver.resolveMixinTypeToClassName(logicalName); break; 260 default: className = null; // should never happen 261 } 262 return className; 263 } 264 265 public String getSimpleLogicalName() 266 { 267 return logicalName.replace("core/", ""); 268 } 269 270 @Cached(watch = "logicalName") 271 public String[] getTags() throws ClassNotFoundException { 272 Description description = getDescription(); 273 return description != null ? description.tags() : EMTPY_STRING_ARRAY; 274 } 275 276 @Cached(watch = "logicalName") 277 public Description getDescription() throws ClassNotFoundException 278 { 279 try { 280 return Class.forName(getClassName()).getAnnotation(Description.class); 281 } catch (Exception e) { 282 e.printStackTrace(); 283 return null; 284 } 285 } 286 287 public boolean isClassHasTags() throws ClassNotFoundException 288 { 289 return getTags().length > 0; 290 } 291 292 @OnEvent("json") 293 Object generateJSONDescription(String libraryName) 294 { 295 for (LibraryMapping mapping : componentClassResolver.getLibraryMappings()) 296 { 297 if (libraryName.equalsIgnoreCase(mapping.libraryName)) 298 { 299 this.libraryMapping = mapping; 300 break; 301 } 302 } 303 JSONObject object = new JSONObject(); 304 object.put("libraryName", libraryName); 305 object.put("rootPackage", libraryMapping.getRootPackage()); 306 307 final ComponentLibraryInfo info = getInfo(); 308 if (info != null) 309 { 310 JSONObject infoJsonObject = new JSONObject(); 311 putIfNotNull("description", info.getDescription(), infoJsonObject); 312 putIfNotNull("homepage", info.getHomepageUrl(), infoJsonObject); 313 putIfNotNull("documentationUrl", info.getDocumentationUrl(), infoJsonObject); 314 putIfNotNull("javadocUrl", info.getJavadocUrl(), infoJsonObject); 315 putIfNotNull("groupId", info.getGroupId(), infoJsonObject); 316 putIfNotNull("artifactId", info.getArtifactId(), infoJsonObject); 317 putIfNotNull("version", info.getVersion(), infoJsonObject); 318 putIfNotNull("sourceBrowseUrl", info.getSourceBrowseUrl(), infoJsonObject); 319 putIfNotNull("sourceRootUrl", info.getSourceRootUrl(), infoJsonObject); 320 putIfNotNull("issueTrackerUrl", info.getIssueTrackerUrl(), infoJsonObject); 321 putIfNotNull("dependencyInfoUrl", info.getDependencyManagementInfoUrl(), infoJsonObject); 322 323 if (info.getTags() != null) 324 { 325 for (String tag : info.getTags()) 326 { 327 infoJsonObject.accumulate("tags", tag); 328 } 329 } 330 331 object.put("info", infoJsonObject); 332 333 } 334 335 addClasses("components", filter(componentClassResolver.getComponentNames()), Type.COMPONENT, info, object); 336 addClasses("pages", filter(componentClassResolver.getPageNames()), Type.PAGE, info, object); 337 addClasses("mixins", filter(componentClassResolver.getMixinNames()), Type.MIXIN, info, object); 338 339 return new TextStreamResponse("text/javascript", object.toString()); 340 341 } 342 343 private void addClasses(final String property, List<String> classes, Type type, 344 final ComponentLibraryInfo info, JSONObject object) 345 { 346 if (classes.size() > 0) 347 { 348 JSONArray classesJsonArray = new JSONArray(); 349 for (String logicalName : classes) 350 { 351 logicalName = logicalName.replace("core/", ""); 352 final String className = getClassName(logicalName, type, componentClassResolver); 353 JSONObject classJsonObject = new JSONObject(); 354 classJsonObject.put("logicalName", logicalName); 355 classJsonObject.put("class", className); 356 if (info != null) 357 { 358 putIfNotNull("sourceUrl", info.getSourceUrl(className), classJsonObject); 359 putIfNotNull("javadocUrl", info.getJavadocUrl(className), classJsonObject); 360 } 361 try 362 { 363 final Description description = getClass(className); 364 if (description != null) 365 { 366 putIfNotNull("description", description.text(), classJsonObject); 367 if (description.tags().length > 0) 368 { 369 for (String tag : description.tags()) 370 { 371 classJsonObject.accumulate("tag", tag); 372 } 373 } 374 } 375 } 376 catch (ClassNotFoundException e) 377 { 378 throw new RuntimeException(e); 379 } 380 classesJsonArray.put(classJsonObject); 381 } 382 object.put(property, classesJsonArray); 383 } 384 } 385 386 private Description getClass(final String className) throws ClassNotFoundException 387 { 388 return Class.forName(className).getAnnotation(Description.class); 389 } 390 391 private void putIfNotNull(String propertyName, String value, JSONObject object) 392 { 393 if (value != null) 394 { 395 object.put(propertyName, value); 396 } 397 } 398 399 public String getGraphvizValue() 400 { 401 return componentDependencyGraphvizGenerator.generate( 402 getClassName(selectedComponent)); 403 } 404 405 public String getClassName(String logicalName) 406 { 407 return componentClassResolver.getClassName(logicalName); 408 } 409 410 public String getComponentClassName() 411 { 412 return getClassName(selectedComponent); 413 } 414 415 public List<String> getDependencies() 416 { 417 final String className = componentClassResolver.getClassName(selectedComponent); 418 final List<String> dependencies = new ArrayList<>(); 419 dependencies.addAll(componentDependencyRegistry.getDependencies(className, DependencyType.INJECT_PAGE)); 420 dependencies.addAll(componentDependencyRegistry.getDependencies(className, DependencyType.SUPERCLASS)); 421 dependencies.addAll(componentDependencyRegistry.getDependencies(className, DependencyType.USAGE)); 422 Collections.sort(dependencies); 423 return dependencies; 424 } 425 426 public List<String> getDependents() 427 { 428 final String className = componentClassResolver.getClassName(selectedComponent); 429 List<String> dependents = new ArrayList<>( 430 componentDependencyRegistry.getDependents(className)); 431 Collections.sort(dependents); 432 return dependents; 433 } 434 435 public String getDisplayLogicalName() 436 { 437 return componentClassResolver.getLogicalName(dependency); 438 } 439 440 public Object onSelectComponent(String selectedComponent) 441 { 442 this.selectedComponent = selectedComponent; 443 final String className = componentClassResolver.getClassName(selectedComponent); 444 if (!componentDependencyRegistry.contains(className)) 445 { 446 447 final ClassLoader classLoader = new ThrowawayClassLoader(getClass().getClassLoader()); 448 449 try 450 { 451 componentDependencyRegistry.register(classLoader.loadClass(className)); 452 } catch (ClassNotFoundException e) 453 { 454 throw new RuntimeException(e); 455 } 456 } 457 return zone.getBody(); 458 } 459 460 public Object getContext() 461 { 462 return logicalName; 463 } 464 465 public Object onReset() 466 { 467 selectedComponent = null; 468 return zone.getBody(); 469 } 470 471 public Object onShowEverything() 472 { 473 showEverything = true; 474 return zone.getBody(); 475 } 476 477 public Object onShowRestricted() 478 { 479 showEverything = false; 480 return zone.getBody(); 481 } 482 483 public String getLibraryName() 484 { 485 return !libraryMapping.libraryName.isEmpty() ? libraryMapping.libraryName : "Webapp's own component library"; 486 } 487 488}