    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}