    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.SymbolConstants;
    024import org.apache.tapestry5.annotations.Cached;
    025import org.apache.tapestry5.annotations.OnEvent;
    026import org.apache.tapestry5.annotations.Property;
    027import org.apache.tapestry5.annotations.UnknownActivationContextCheck;
    028import org.apache.tapestry5.annotations.WhitelistAccessOnly;
    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(SymbolConstants.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}