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}