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.OnEvent; 025import org.apache.tapestry5.annotations.Property; 026import org.apache.tapestry5.annotations.UnknownActivationContextCheck; 027import org.apache.tapestry5.annotations.WhitelistAccessOnly; 028import org.apache.tapestry5.http.TapestryHttpSymbolConstants; 029import org.apache.tapestry5.ioc.annotations.Description; 030import org.apache.tapestry5.ioc.annotations.Inject; 031import org.apache.tapestry5.ioc.annotations.Symbol; 032import org.apache.tapestry5.json.JSONArray; 033import org.apache.tapestry5.json.JSONObject; 034import org.apache.tapestry5.services.ComponentClassResolver; 035import org.apache.tapestry5.services.ComponentLibraryInfo; 036import org.apache.tapestry5.services.ComponentLibraryInfoSource; 037import org.apache.tapestry5.services.LibraryMapping; 038import org.apache.tapestry5.util.TextStreamResponse; 039 040/** 041 * Page used to describe the component libraries being used in the application. 042 * Notice: the implementation of this page was done to avoid creating components, so the 043 * Tapestry 5 Core Library didn't get polluted with internal-only components. 044 */ 045@UnknownActivationContextCheck(false) 046@WhitelistAccessOnly 047public class ComponentLibraries 048{ 049 050 final private static String[] EMTPY_STRING_ARRAY = {}; 051 052 private static final Comparator<LibraryMapping> LIBRARY_MAPPING_COMPARATOR = new Comparator<LibraryMapping>() 053 { 054 @Override 055 public int compare(LibraryMapping mapping1, LibraryMapping mapping2) 056 { 057 return mapping1.libraryName.compareTo(mapping2.libraryName); 058 } 059 }; 060 061 private static enum Type { PAGE, COMPONENT, MIXIN } 062 063 @Inject 064 private ComponentClassResolver componentClassResolver; 065 066 @Property 067 private LibraryMapping libraryMapping; 068 069 @Property 070 private String logicalName; 071 072 @Property 073 private List<String> logicalNames; 074 075 @Property 076 private String headerName; 077 078 @Property 079 private List<String> pages; 080 081 @Property 082 private List<String> components; 083 084 @Property 085 private List<String> mixins; 086 087 private Type type; 088 089 @Inject 090 private Block classesTable; 091 092 @Inject 093 @Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE) 094 @Property 095 private boolean productionMode; 096 097 @Inject 098 private ComponentLibraryInfoSource componentLibraryInfoSource; 099 100 @Cached 101 public List<LibraryMapping> getLibraryMappings() 102 { 103 List<LibraryMapping> mappings = new ArrayList<LibraryMapping>(); 104 105 // add all the library mappings, except the "" (empty string) one. 106 for (LibraryMapping libraryMapping : componentClassResolver.getLibraryMappings()) 107 { 108 if (!"".equals(libraryMapping.libraryName)) { 109 mappings.add(libraryMapping); 110 } 111 } 112 113 Collections.sort(mappings, LIBRARY_MAPPING_COMPARATOR); 114 return mappings; 115 } 116 117 @Cached(watch="libraryMapping") 118 public ComponentLibraryInfo getInfo() 119 { 120 return componentLibraryInfoSource.find(libraryMapping); 121 } 122 123 public List<String> getLibraryNames() { 124 return componentClassResolver.getLibraryNames(); 125 } 126 127 public String getLibraryClientId() 128 { 129 return libraryMapping.libraryName.replace("/", "-"); 130 } 131 132 private List<String> filter(final List<String> allNames) 133 { 134 List<String> logicalNames = new ArrayList<String>(); 135 for (String name : allNames) 136 { 137 138 if (name.startsWith(libraryMapping.libraryName + "/") && 139 !(libraryMapping.libraryName.equals("core") && name.endsWith("Test"))) 140 { 141 logicalNames.add(name); 142 } 143 } 144 145 return logicalNames; 146 } 147 148 public Block getComponentsTable() 149 { 150 logicalNames = filter(componentClassResolver.getComponentNames()); 151 type = Type.COMPONENT; 152 headerName = "Components"; 153 return classesTable; 154 } 155 156 public Block getPagesTable() 157 { 158 logicalNames = filter(componentClassResolver.getPageNames()); 159 type = Type.PAGE; 160 headerName = "Pages"; 161 return classesTable; 162 } 163 164 public Block getMixinsTable() 165 { 166 logicalNames = filter(componentClassResolver.getMixinNames()); 167 type = Type.MIXIN; 168 headerName = "Mixins"; 169 return classesTable; 170 } 171 172 public String getSourceUrl() 173 { 174 return getInfo() != null ? getInfo().getSourceUrl(getClassName()) : null; 175 } 176 177 public String getJavaDocUrl() 178 { 179 return getInfo() != null ? getInfo().getJavadocUrl(getClassName()) : null; 180 } 181 182 private String getClassName() 183 { 184 return getClassName(logicalName, type, componentClassResolver); 185 } 186 187 private static String getClassName(String logicalName, Type type, ComponentClassResolver componentClassResolver) 188 { 189 String className; 190 switch (type) 191 { 192 case PAGE: className = componentClassResolver.resolvePageNameToClassName(logicalName); break; 193 case COMPONENT: className = componentClassResolver.resolveComponentTypeToClassName(logicalName); break; 194 case MIXIN: className = componentClassResolver.resolveMixinTypeToClassName(logicalName); break; 195 default: className = null; // should never happen 196 } 197 return className; 198 } 199 200 public String getSimpleLogicalName() 201 { 202 return logicalName.replace("core/", ""); 203 } 204 205 @Cached(watch = "logicalName") 206 public String[] getTags() throws ClassNotFoundException { 207 Description description = getDescription(); 208 return description != null ? description.tags() : EMTPY_STRING_ARRAY; 209 } 210 211 @Cached(watch = "logicalName") 212 public Description getDescription() throws ClassNotFoundException 213 { 214 return Class.forName(getClassName()).getAnnotation(Description.class); 215 } 216 217 public boolean isClassHasTags() throws ClassNotFoundException 218 { 219 return getTags().length > 0; 220 } 221 222 @OnEvent("json") 223 Object generateJSONDescription(String libraryName) 224 { 225 for (LibraryMapping mapping : componentClassResolver.getLibraryMappings()) 226 { 227 if (libraryName.equalsIgnoreCase(mapping.libraryName)) 228 { 229 this.libraryMapping = mapping; 230 break; 231 } 232 } 233 JSONObject object = new JSONObject(); 234 object.put("libraryName", libraryName); 235 object.put("rootPackage", libraryMapping.getRootPackage()); 236 237 final ComponentLibraryInfo info = getInfo(); 238 if (info != null) 239 { 240 JSONObject infoJsonObject = new JSONObject(); 241 putIfNotNull("description", info.getDescription(), infoJsonObject); 242 putIfNotNull("homepage", info.getHomepageUrl(), infoJsonObject); 243 putIfNotNull("documentationUrl", info.getDocumentationUrl(), infoJsonObject); 244 putIfNotNull("javadocUrl", info.getJavadocUrl(), infoJsonObject); 245 putIfNotNull("groupId", info.getGroupId(), infoJsonObject); 246 putIfNotNull("artifactId", info.getArtifactId(), infoJsonObject); 247 putIfNotNull("version", info.getVersion(), infoJsonObject); 248 putIfNotNull("sourceBrowseUrl", info.getSourceBrowseUrl(), infoJsonObject); 249 putIfNotNull("sourceRootUrl", info.getSourceRootUrl(), infoJsonObject); 250 putIfNotNull("issueTrackerUrl", info.getIssueTrackerUrl(), infoJsonObject); 251 putIfNotNull("dependencyInfoUrl", info.getDependencyManagementInfoUrl(), infoJsonObject); 252 253 if (info.getTags() != null) 254 { 255 for (String tag : info.getTags()) 256 { 257 infoJsonObject.accumulate("tags", tag); 258 } 259 } 260 261 object.put("info", infoJsonObject); 262 263 } 264 265 addClasses("components", filter(componentClassResolver.getComponentNames()), Type.COMPONENT, info, object); 266 addClasses("pages", filter(componentClassResolver.getPageNames()), Type.PAGE, info, object); 267 addClasses("mixins", filter(componentClassResolver.getMixinNames()), Type.MIXIN, info, object); 268 269 return new TextStreamResponse("text/javascript", object.toString()); 270 271 } 272 273 private void addClasses(final String property, List<String> classes, Type type, 274 final ComponentLibraryInfo info, JSONObject object) 275 { 276 if (classes.size() > 0) 277 { 278 JSONArray classesJsonArray = new JSONArray(); 279 for (String logicalName : classes) 280 { 281 logicalName = logicalName.replace("core/", ""); 282 final String className = getClassName(logicalName, type, componentClassResolver); 283 JSONObject classJsonObject = new JSONObject(); 284 classJsonObject.put("logicalName", logicalName); 285 classJsonObject.put("class", className); 286 if (info != null) 287 { 288 putIfNotNull("sourceUrl", info.getSourceUrl(className), classJsonObject); 289 putIfNotNull("javadocUrl", info.getJavadocUrl(className), classJsonObject); 290 } 291 try 292 { 293 final Description description = getClass(className); 294 if (description != null) 295 { 296 putIfNotNull("description", description.text(), classJsonObject); 297 if (description.tags().length > 0) 298 { 299 for (String tag : description.tags()) 300 { 301 classJsonObject.accumulate("tag", tag); 302 } 303 } 304 } 305 } 306 catch (ClassNotFoundException e) 307 { 308 throw new RuntimeException(e); 309 } 310 classesJsonArray.put(classJsonObject); 311 } 312 object.put(property, classesJsonArray); 313 } 314 } 315 316 private Description getClass(final String className) throws ClassNotFoundException 317 { 318 return Class.forName(className).getAnnotation(Description.class); 319 } 320 321 private void putIfNotNull(String propertyName, String value, JSONObject object) 322 { 323 if (value != null) 324 { 325 object.put(propertyName, value); 326 } 327 } 328 329}