You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by th...@apache.org on 2014/07/04 21:49:03 UTC

git commit: Partial implementation of TAP5-2192 : a new Component Libraries tab was added to the T5Dashboard, presenting information and the list of components, pages and mixins from each one.

Repository: tapestry-5
Updated Branches:
  refs/heads/master ee0220c64 -> 6843db06f


Partial implementation of TAP5-2192 : a new Component Libraries tab was
added to the T5Dashboard, presenting information and the list of
components, pages and mixins from each one.

Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/6843db06
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/6843db06
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/6843db06

Branch: refs/heads/master
Commit: 6843db06f07427cdc00e2b556dcb1077269a134b
Parents: ee0220c
Author: Thiago H. de Paula Figueiredo <th...@apache.org>
Authored: Fri Jul 4 16:48:43 2014 -0300
Committer: Thiago H. de Paula Figueiredo <th...@apache.org>
Committed: Fri Jul 4 16:48:43 2014 -0300

----------------------------------------------------------------------
 .../corelib/pages/ComponentLibraries.java       | 152 +++++++++
 .../tapestry5/corelib/pages/ServiceStatus.java  |   2 +-
 .../services/ComponentClassResolverImpl.java    |  34 ++
 .../tapestry5/modules/DashboardModule.java      |   3 +-
 .../tapestry5/modules/TapestryModule.java       |  18 +-
 .../services/ComponentClassResolver.java        |  24 +-
 .../services/ComponentLibraryInfo.java          | 324 +++++++++++++++++++
 .../tapestry5/services/LibraryMapping.java      |  37 ++-
 .../org/apache/tapestry5/core.properties        |   7 +-
 .../corelib/pages/ComponentLibraries.tml        | 126 ++++++++
 10 files changed, 717 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6843db06/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ComponentLibraries.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ComponentLibraries.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ComponentLibraries.java
new file mode 100644
index 0000000..6e27f6c
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ComponentLibraries.java
@@ -0,0 +1,152 @@
+// Copyright 2014 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.corelib.pages;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.tapestry5.Block;
+import org.apache.tapestry5.annotations.Cached;
+import org.apache.tapestry5.annotations.Property;
+import org.apache.tapestry5.annotations.UnknownActivationContextCheck;
+import org.apache.tapestry5.annotations.WhitelistAccessOnly;
+import org.apache.tapestry5.ioc.annotations.Inject;
+import org.apache.tapestry5.services.ComponentClassResolver;
+import org.apache.tapestry5.services.ComponentLibraryInfo;
+
+/**
+ * Page used to describe the component libraries being used in the application.
+ * Notice: the implementation of this page was done to avoid creating components, so the
+ * Tapestry 5 Core Library didn't get polluted with internal-only components.
+ */
+@UnknownActivationContextCheck(false)
+@WhitelistAccessOnly
+public class ComponentLibraries
+{
+    
+    private static enum Type { PAGE, COMPONENT, MIXIN }
+
+    @Inject
+    private ComponentClassResolver componentClassResolver;
+
+    @Property
+    private String libraryName;
+
+    @Property
+    private String logicalName;
+    
+    @Property
+    private List<String> logicalNames;
+
+    @Property
+    private String headerName;
+
+    @Property
+    private List<String> pages;
+    
+    @Property
+    private List<String> components;
+    
+    @Property
+    private List<String> mixins;
+    
+    private Type type; 
+    
+    @Inject
+    private Block classesTable;
+    
+    @Cached(watch="libraryName")
+    public ComponentLibraryInfo getInfo()
+    {
+        return componentClassResolver.getComponentLibraryInfo(libraryName);
+    }
+    
+    public List<String> getLibraryNames() {
+        return componentClassResolver.getLibraryNames();
+    }
+    
+    public String getLibraryClientId() 
+    {
+        return libraryName.replace("/", "-");
+    }
+
+    private List<String> filter(final List<String> allNames)
+    {
+        List<String> logicalNames = new ArrayList<String>();
+        for (String name : allNames)
+        {
+            
+            if (name.startsWith(libraryName + "/") && !(libraryName.equals("core") && name.endsWith("Test")))
+            {
+                logicalNames.add(name);
+            }
+        }
+        
+        return logicalNames;
+    }
+    
+    public Block getComponentsTable()
+    {
+        logicalNames = filter(componentClassResolver.getComponentNames());
+        type = Type.COMPONENT;
+        headerName = "Components";
+        return classesTable;
+    }
+    
+    public Block getPagesTable()
+    {
+        logicalNames = filter(componentClassResolver.getPageNames());
+        type = Type.PAGE;
+        headerName = "Pages";
+        return classesTable;
+    }
+
+    public Block getMixinsTable()
+    {
+        logicalNames = filter(componentClassResolver.getMixinNames());
+        type = Type.MIXIN;
+        headerName = "Mixins";
+        return classesTable;
+    }
+    
+    public String getSourceUrl()
+    {
+        return getInfo() != null ? getInfo().getSourceUrl(getClassName()) : null;
+    }
+    
+    public String getJavaDocUrl() 
+    {
+        return getInfo() != null ? getInfo().getJavadocUrl(getClassName()) : null;
+    }
+
+    private String getClassName()
+    {
+        final String className;
+        switch (type)
+        {
+            case PAGE: className = componentClassResolver.resolvePageNameToClassName(logicalName); break;
+            case COMPONENT: className = componentClassResolver.resolveComponentTypeToClassName(logicalName); break;
+            case MIXIN: className = componentClassResolver.resolveMixinTypeToClassName(logicalName); break;
+            default: className = null; // should never happen
+        }
+        return className;
+    }
+    
+    public String getSimpleLogicalName()
+    {
+        return logicalName.replace("core/", "");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6843db06/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ServiceStatus.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ServiceStatus.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ServiceStatus.java
index 4c99023..55a3b62 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ServiceStatus.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ServiceStatus.java
@@ -62,7 +62,7 @@ public class ServiceStatus
         model.addEmpty("serviceInterface");
 
         // There's no line number information for interfaces, so we'll reorder the
-        // proprieties manually.
+        // properties manually.
 
         model.reorder("serviceId", "serviceInterface", "scope", "status");
     }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6843db06/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.java
index cad9404..ba027e4 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.java
@@ -24,6 +24,7 @@ import org.apache.tapestry5.ioc.services.ClassNameLocator;
 import org.apache.tapestry5.ioc.util.AvailableValues;
 import org.apache.tapestry5.ioc.util.UnknownValueException;
 import org.apache.tapestry5.services.ComponentClassResolver;
+import org.apache.tapestry5.services.ComponentLibraryInfo;
 import org.apache.tapestry5.services.InvalidationListener;
 import org.apache.tapestry5.services.LibraryMapping;
 import org.apache.tapestry5.services.transform.ControlledPackageType;
@@ -51,6 +52,9 @@ public class ComponentClassResolverImpl implements ComponentClassResolver, Inval
     // Map from library name to a list of root package names (usuallly just one).
     private final Map<String, List<String>> libraryNameToPackageNames = CollectionFactory.newCaseInsensitiveMap();
 
+    // Map from library name to a ComponentLibraryInfo
+    private final Map<String, ComponentLibraryInfo> libraryNameToInfo = CollectionFactory.newCaseInsensitiveMap();
+
     private final Map<String, ControlledPackageType> packageNameToType = CollectionFactory.newMap();
 
     /**
@@ -255,6 +259,8 @@ public class ComponentClassResolverImpl implements ComponentClassResolver, Inval
         for (LibraryMapping mapping : mappings)
         {
             String libraryName = mapping.libraryName;
+            
+            libraryNameToInfo.put(libraryName, mapping.getComponentLibraryInfo());
 
             List<String> packages = this.libraryNameToPackageNames.get(libraryName);
 
@@ -453,6 +459,28 @@ public class ComponentClassResolverImpl implements ComponentClassResolver, Inval
         return result;
     }
 
+    public List<String> getComponentNames()
+    {
+        Data data = getData();
+
+        List<String> result = CollectionFactory.newList(data.componentToClassName.keySet());
+
+        Collections.sort(result);
+
+        return result;
+    }
+
+    public List<String> getMixinNames()
+    {
+        Data data = getData();
+
+        List<String> result = CollectionFactory.newList(data.mixinToClassName.keySet());
+
+        Collections.sort(result);
+
+        return result;
+    }
+
     public String resolveComponentTypeToClassName(final String componentType)
     {
         Data data = getData();
@@ -669,4 +697,10 @@ public class ComponentClassResolverImpl implements ComponentClassResolver, Inval
             }
         }
     }
+
+    public ComponentLibraryInfo getComponentLibraryInfo(String libraryName)
+    {
+        return libraryNameToInfo.get(libraryName);
+    }
+    
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6843db06/tapestry-core/src/main/java/org/apache/tapestry5/modules/DashboardModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/DashboardModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/DashboardModule.java
index 22abd21..9eccdc1 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/DashboardModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/DashboardModule.java
@@ -1,4 +1,4 @@
-// Copyright 2013 The Apache Software Foundation
+// Copyright 2013, 2014 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -33,5 +33,6 @@ public class DashboardModule
     {
         configuration.add("Pages", new DashboardTab("Pages", "core/PageCatalog"));
         configuration.add("Services", new DashboardTab("Services", "core/ServiceStatus"));
+        configuration.add("Libraries", new DashboardTab("ComponentLibraries", "core/ComponentLibraries"));
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6843db06/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
index 50d03b4..8e3e070 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
@@ -443,8 +443,24 @@ public final class TapestryModule
                                                   @Symbol(InternalConstants.TAPESTRY_APP_PACKAGE_PARAM)
                                                   String appRootPackage)
     {
-        configuration.add(new LibraryMapping(InternalConstants.CORE_LIBRARY, "org.apache.tapestry5.corelib"));
+        
+        ComponentLibraryInfo info = new ComponentLibraryInfo();
+        info.setName("Tapestry 5 Core Library");
+        info.setDescription("The set of components, pages and mixins provided by Tapestry out-of-the-box.");
+        info.setHomepageUrl("http://tapestry.apache.org");
+        info.setDocumentationUrl("http://tapestry.apache.org/documentation.html");
+        info.setSourceBrowseUrl("https://git-wip-us.apache.org/repos/asf?p=tapestry-5.git;a=summary");
+        info.setSourceRootUrl("https://git-wip-us.apache.org/repos/asf?p=tapestry-5.git;a=blob;f=tapestry-core/src/main/java");
+        info.setJavadocUrl("http://tapestry.apache.org/current/apidocs/");
+        info.setIssueTrackerUrl("https://issues.apache.org/jira/browse/TAP5");
+        info.setGroupId("org.apache.tapestry");
+        info.setArtifactId("tapestry-core");
+        info.setVersion("5.4.0");
+        info.setSourceUrlResolver(new ComponentLibraryInfo.GitWebMavenSourceUrlResolver());
+        
+        configuration.add(new LibraryMapping(InternalConstants.CORE_LIBRARY, "org.apache.tapestry5.corelib", info));
         configuration.add(new LibraryMapping("", appRootPackage));
+        
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6843db06/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentClassResolver.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentClassResolver.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentClassResolver.java
index f6e8a1f..d6e3049 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentClassResolver.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentClassResolver.java
@@ -15,6 +15,7 @@
 package org.apache.tapestry5.services;
 
 import org.apache.tapestry5.Asset;
+import org.apache.tapestry5.ioc.annotations.IncompatibleChange;
 import org.apache.tapestry5.ioc.annotations.UsesConfiguration;
 import org.apache.tapestry5.ioc.services.ClassNameLocator;
 import org.apache.tapestry5.services.transform.ControlledPackageType;
@@ -67,6 +68,20 @@ public interface ComponentClassResolver
     List<String> getPageNames();
 
     /**
+     * Returns a list of all component names, in sorted order. These are the "canonical" component names.
+     * @since 5.4
+     */
+    @IncompatibleChange(release = "5.4", details = "added method")
+    List<String> getComponentNames();
+
+    /**
+     * Returns a list of all mixin names, in sorted order. These are the "canonical" mixin names.
+     * @since 5.4
+     */
+    @IncompatibleChange(release = "5.4", details = "added method")
+    List<String> getMixinNames();
+    
+    /**
      * Converts a fully qualified page class name into a page name (often, for inclusion as part of the URI). This value
      * may later be passed to {@link #resolvePageNameToClassName(String)}.
      *
@@ -132,7 +147,14 @@ public interface ComponentClassResolver
      * @since 5.4
      */
     List<String> getLibraryNames();
-
+    
+    /**
+     * Returns an object encapsulating information about a component library, if provided.
+     * @param libraryName the library name (prefix).
+     * @return a {@link ComponentLibraryInfo} or <code>null</code>
+     */
+    ComponentLibraryInfo getComponentLibraryInfo(String libraryName);
+    
     /**
      * Used to identify which packages are controlled packages (from which components are loaded). Future expansion
      * may allow for additional packages which are live reloaded but not components (or perhaps are transformed, but not

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6843db06/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentLibraryInfo.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentLibraryInfo.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentLibraryInfo.java
new file mode 100644
index 0000000..78d2cae
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentLibraryInfo.java
@@ -0,0 +1,324 @@
+// Copyright 2014 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package org.apache.tapestry5.services;
+
+import java.io.Serializable;
+
+/**
+ * Class that encapsulates information about a component library, going beyond what a library mapping
+ * provides.
+ * 
+ * @see LibraryMapping
+ * @see SourceUrlResolver
+ * @since 5.4
+ */
+public final class ComponentLibraryInfo implements Serializable 
+{
+    private static final long serialVersionUID = 1L;
+    
+    private LibraryMapping libraryMapping;
+    
+    private SourceUrlResolver sourceUrlResolver;
+    
+    private String name, description, homepageUrl, documentationUrl, sourceBrowseUrl, issueTrackerUrl, sourceRootUrl, 
+                   javadocUrl, groupId, artifactId, version;
+    
+    /**
+     * Returns the actual name of the component library (not the identifier). 
+     * For example, "Tapestry 5 Core Library".
+     */
+    public String getName()
+    {
+        return name;
+    }
+
+    /**
+     * Returns a description of the component library. 
+     * For example, "The set of components, pages and mixins provided by Tapestry out-of-the-box.".
+     */
+    public String getDescription()
+    {
+        return description;
+    }
+    
+    /**
+     * Returns the URL of the homepage of the component library.
+     * For example, "http://tapestry.apache.org".
+     */
+    public String getHomepageUrl()
+    {
+        return homepageUrl;
+    }
+
+    /**
+     * Returns the URL of the component library's documentation.
+     * For example, "http://tapestry.apache.org/documentation.html".
+     */
+    public String getDocumentationUrl()
+    {
+        return documentationUrl;
+    }
+
+    /**
+     * Returns the URL where the component library's source can be browsed.
+     * For example, "https://git-wip-us.apache.org/repos/asf?p=tapestry-5.git;a=summary".
+     */
+    public String getSourceBrowseUrl()
+    {
+        return sourceBrowseUrl;
+    }
+
+    /**
+     * Returns the URL where the root folder of component library's source can be found.
+     * For example, "https://git-wip-us.apache.org/repos/asf?p=tapestry-5.git;a=tree".
+     */
+    public String getSourceRootUrl()
+    {
+        return sourceRootUrl;
+    }
+
+    /**
+     * Returns the URL of the component's library issue tracker.
+     * For example, "https://issues.apache.org/jira/browse/TAP5".
+     */
+    public String getIssueTrackerUrl()
+    {
+        return issueTrackerUrl;
+    }
+
+    /**
+     * Returns the URL of the component library's JavaDoc URL.
+     * For example, "http://tapestry.apache.org/current/apidocs/"
+     */
+    public String getJavadocUrl()
+    {
+        return javadocUrl;
+    }
+
+    /**
+     * Returns the component library's group id for dependency management tools like Maven and Gradle.
+     * For example, "org.apache.tapestry".
+     * @see #artifactId
+     * @see #version
+     */
+    public String getGroupId()
+    {
+        return groupId;
+    }
+
+    /**
+     * Returns the component library's group id for dependency management tools like Maven and Gradle.
+     * For example, "tapestry-core".
+     * @see #groupId
+     * @see #version
+     */
+    public String getArtifactId()
+    {
+        return artifactId;
+    }
+
+    /**
+     * Return the component library version. For example, "5.4.0".
+     * @see #artifactId
+     * @see #groupId
+     */
+    public String getVersion()
+    {
+        return version;
+    }
+
+    public void setName(String name)
+    {
+        if (this.name != null) throwExceptionIfAlreadySet("name", name);
+        this.name = name;
+    }
+    
+    public void setDescription(String description)
+    {
+        if (this.description != null) throwExceptionIfAlreadySet("description", description);
+        this.description = description;
+    }
+
+    public void setHomepageUrl(String homepageUrl)
+    {
+        if (this.homepageUrl != null) throwExceptionIfAlreadySet("homepageUrl", homepageUrl);
+        this.homepageUrl = homepageUrl;
+    }
+
+    public void setDocumentationUrl(String documentationUrl)
+    {
+        if (this.documentationUrl != null) throwExceptionIfAlreadySet("documentationUrl", documentationUrl);
+        this.documentationUrl = documentationUrl;
+    }
+
+    public void setSourceBrowseUrl(String sourceBrowseUrl)
+    {
+        if (this.sourceBrowseUrl != null) throwExceptionIfAlreadySet("sourceBrowseUrl", sourceBrowseUrl);
+        this.sourceBrowseUrl = sourceBrowseUrl;
+    }
+
+    public void setSourceRootUrl(String sourceRootUrl)
+    {
+        if (this.sourceRootUrl != null) throwExceptionIfAlreadySet("sourceRootUrl", sourceRootUrl);
+        this.sourceRootUrl = sourceRootUrl;
+    }
+
+    public void setJavadocUrl(String javadocUrl)
+    {
+        if (this.javadocUrl != null) throwExceptionIfAlreadySet("javadocUrl", javadocUrl);
+        this.javadocUrl = javadocUrl;
+    }
+
+    public void setVersion(String version)
+    {
+        if (this.version != null) throwExceptionIfAlreadySet("version", version);
+        this.version = version;
+    }
+    
+    public void setGroupId(String groupId)
+    {
+        if (this.groupId != null) throwExceptionIfAlreadySet("groupId", artifactId);
+        this.groupId = groupId;
+    }
+    
+    public void setArtifactId(String artifactId)
+    {
+        if (this.artifactId != null) throwExceptionIfAlreadySet("artifactId", artifactId);
+        this.artifactId = artifactId;
+    }
+    
+    public void setIssueTrackerUrl(String issueTrackingUrl)
+    {
+        if (this.issueTrackerUrl != null) throwExceptionIfAlreadySet("issueTrackingUrl", issueTrackingUrl);
+        this.issueTrackerUrl = issueTrackingUrl;
+    }
+
+    public void setLibraryMapping(LibraryMapping libraryMapping)
+    {
+        if (this.libraryMapping != null) throwExceptionIfAlreadySet("libraryMapping", libraryMapping);
+        this.libraryMapping = libraryMapping;
+    }
+    
+    public void setSourceUrlResolver(SourceUrlResolver sourceUrlResolver)
+    {
+        if (this.sourceUrlResolver != null) throwExceptionIfAlreadySet("sourceUrlResolver", sourceUrlResolver);
+        this.sourceUrlResolver = sourceUrlResolver;
+        if (sourceUrlResolver != null)
+        {
+            sourceUrlResolver.setRootUrl(getSourceRootUrl());
+        }
+    }
+
+    /**
+     * Tells whether full dependency management info (group id, artifact id and version) are present.
+     */
+    public boolean isDependencyManagementInfoPresent()
+    {
+        return groupId != null && artifactId != null && version != null;
+    }
+    
+    /**
+     * Given a logical name, tells whether a given component, page or mixin is part of this
+     * component library.
+     */
+    public boolean isPart(String logicalName)
+    {
+        return logicalName.startsWith(libraryMapping.libraryName + "/") || 
+                (libraryMapping.libraryName.equals("") && logicalName.indexOf("/") < 0);
+    }
+    
+    /**
+     * Returns the JavaDoc URL for a given class or <code>null</code> if the root JavaDoc URL was 
+     * not provided. 
+     * @param className the fully qualified class name.
+     */
+    public String getJavadocUrl(String className)
+    {
+        String url = null;
+        String baseUrl = getJavadocUrl();
+        if (baseUrl != null)
+        {
+            if (!baseUrl.endsWith("/"))
+            {
+                baseUrl = baseUrl + "/";
+            }
+            url = baseUrl + className.replace('.', '/') + ".html";
+        }
+        return url;
+    }
+
+    /**
+     * Returns the URL where the source of this class can be found or <code>null</code> if 
+     * not available. This implementation delegates to {@link SourceUrlResolver} if set.
+     * @param className the fully qualified class name.
+     */
+    public String getSourceUrl(String className)
+    {
+        String url = null;
+        if (sourceUrlResolver != null)
+        {
+            url = sourceUrlResolver.resolve(className);
+        }
+        return url;
+    }
+
+    private void throwExceptionIfAlreadySet(String propertyName, Object propertyValue)
+    {
+        if (propertyValue != null)
+        {
+            throw new RuntimeException(String.format("%s already has a value of \"%s\"", propertyName, propertyValue));
+        }
+    }
+    
+    /**
+     * Interface that provides the source URL for a given {@link ComponentLibraryInfo}.
+     */
+    public static interface SourceUrlResolver
+    {
+        /**
+         * Returns the source URL for a given class.
+         * @param className the fully qualified class name.
+         */
+        String resolve(String className);
+        
+        /**
+         * Sets the source root URL. This method will be invoked by {@link ComponentLibraryInfo#setSourceBrowseUrl(String)}.
+         */
+        void setRootUrl(String url);
+    }
+    
+    /**
+     * {@link SourceUrlResolver} implementation based on Maven Java project conventions and 
+     * GitWeb as online Git repository viewer, which Tapestry itself uses.
+     */
+    public static class GitWebMavenSourceUrlResolver implements SourceUrlResolver
+    {
+
+        private String sourceRootUrl;
+
+        @Override
+        public String resolve(String className)
+        {
+            return sourceRootUrl + "/" + className.replace('.', '/') + ".java";
+        }
+
+        @Override
+        public void setRootUrl(String url)
+        {
+            this.sourceRootUrl = url;
+        }
+        
+    }
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6843db06/tapestry-core/src/main/java/org/apache/tapestry5/services/LibraryMapping.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/LibraryMapping.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/LibraryMapping.java
index 476c187..69e4f12 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/LibraryMapping.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/LibraryMapping.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2010, 2011, 2012 The Apache Software Foundation
+// Copyright 2006, 2010, 2011, 2012, 2014 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -11,7 +11,6 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-
 package org.apache.tapestry5.services;
 
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
@@ -31,11 +30,18 @@ import org.apache.tapestry5.ioc.internal.util.InternalUtils;
  * <dt>base</dt>
  * <dd>contains base classes</dd>
  * </dl>
+ * <p>
+ * Since 5.4 on, a library mapping can also have a {@link ComponentLibraryInfo} to provide more
+ * information about itself, such as URLs (project, documentation, JavaDoc, sources) and
+ * coordinates for dependency management tools (group id, artifact id, version).
+ * </p>
  */
 public final class LibraryMapping
 {
     public final String libraryName, rootPackage;
-
+    
+    private ComponentLibraryInfo componentLibraryInfo;
+    
     /**
      * Identifies the root package of a library. The application has uses the library name "" (the empty string).
      * The special library "core" is all the built-in components.
@@ -53,7 +59,7 @@ public final class LibraryMapping
      * @param libraryName
      *         the unique identifier for the library.
      * @param rootPackage
-     *         The root package to search for classes; sub-packages will include ".pages", ".components", etc.
+     *         the root package to search for classes; sub-packages will include ".pages", ".components", etc.
      */
     public LibraryMapping(String libraryName, String rootPackage)
     {
@@ -68,6 +74,18 @@ public final class LibraryMapping
 
         this.libraryName = libraryName;
         this.rootPackage = rootPackage;
+        this.componentLibraryInfo = null;
+    }
+
+    /**
+     * Same as {@link #LibraryMapping(String, String)}, with with an additional {@link ComponentLibraryInfo} parameter.
+     * @since 5.4
+     */
+    public LibraryMapping(String libraryName, String rootPackage, ComponentLibraryInfo componentLibraryInfo)
+    {
+        this(libraryName, rootPackage);
+        this.componentLibraryInfo = componentLibraryInfo;
+        componentLibraryInfo.setLibraryMapping(this);
     }
 
     /**
@@ -85,10 +103,21 @@ public final class LibraryMapping
     {
         return rootPackage;
     }
+    
+    /**
+     * Returns the component library information for this library mapping.
+     * @return a {@link ComponentLibraryInfo}.
+     * @since 5.4
+     */
+    public ComponentLibraryInfo getComponentLibraryInfo()
+    {
+        return componentLibraryInfo;
+    }
 
     @Override
     public String toString()
     {
         return String.format("LibraryMapping[%s, %s]", libraryName, rootPackage);
     }
+    
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6843db06/tapestry-core/src/main/resources/org/apache/tapestry5/core.properties
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/org/apache/tapestry5/core.properties b/tapestry-core/src/main/resources/org/apache/tapestry5/core.properties
index bda03c8..2601000 100644
--- a/tapestry-core/src/main/resources/org/apache/tapestry5/core.properties
+++ b/tapestry-core/src/main/resources/org/apache/tapestry5/core.properties
@@ -1,4 +1,4 @@
-# Copyright 2013 The Apache Software Foundation
+# Copyright 2013-2014 The Apache Software Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -127,4 +127,7 @@ private-default-confirm-message=Are you sure you want to continue?
 private-default-confirm-title=Confirm
 
 # See the LocalDate component
-private-default-localdate-format=lll
\ No newline at end of file
+private-default-localdate-format=lll
+
+# see ComponentLibraries page
+not-informed=Not informed
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6843db06/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ComponentLibraries.tml
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ComponentLibraries.tml b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ComponentLibraries.tml
new file mode 100644
index 0000000..147912c
--- /dev/null
+++ b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ComponentLibraries.tml
@@ -0,0 +1,126 @@
+<t:block id="content"
+         xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd" xmlns:p="tapestry:parameter">
+
+    <h1><strong>${libraryNames.size()}</strong> component libraries found</h1>
+    
+    <ul id="libraryList" class="list-group">
+    	<li t:type="Loop" t:source="libraryNames" t:value="libraryName" class="list-group-item">
+    		<a href="#${libraryClientId}">
+    			<code>${libraryName}</code> <t:if test="info">: ${info.name}</t:if>
+    		</a>
+    		<p t:type="If" t:test="info?.description">
+    			${info.description}
+    		</p>
+    	</li>
+    </ul>
+
+	<div id="libraries">
+	
+		<div class="libraryInfo" t:type="Loop" t:source="libraryNames" t:value="libraryName" id="${libraryClientId}">
+	
+			<h2><code>${libraryName}</code> <t:if test="info">: ${info.name}</t:if></h2>
+			
+			<t:if test="info">
+		
+			    <dl class="dl-horizontal">
+			    
+			        <dt>Homepage</dt>
+			        <dd>
+			        	<t:if test="info.homepageUrl" else="message:not-informed">
+			        		<a href="${info.homepageUrl}">${info.homepageUrl}</a>
+			        	</t:if>
+			        </dd>
+			
+			        <dt>Documentation URL</dt>
+			        <dd>
+			        	<t:if test="info.documentationUrl" else="message:not-informed">
+			        		<a href="${info.documentationUrl}">${info.documentationUrl}</a>
+			        	</t:if>
+			        </dd>
+			        
+			        <dt>Source JavaDoc URL</dt>
+			        <dd>
+			        	<t:if test="info.javadocUrl" else="message:not-informed">
+			        		<a href="${info.javadocUrl}">${info.javadocUrl}</a>
+			        	</t:if>
+			        </dd>
+			        
+			        <t:if test="info.dependencyManagementInfoPresent">
+			        	<dt>Dependency information</dt>
+			        	<dd>
+		        			Group id <code class="groupId">${info.groupId}</code>,
+		        			artifact id <code class="groupId">${info.artifactId}</code>,
+		        			version. <code class="groupId">${info.version}</code>
+		        			<br/>
+			        		<a href="http://search.maven.org/#artifactdetails|${info.groupId}|${info.artifactId}|version=${info.version}|jar" 
+			        			target="_blank">
+			        			<em>More information at Maven Central Respository</em>
+			        		</a>
+			        	</dd>
+			        </t:if>
+			        
+			        <dt>Source browse URL</dt>
+			        <dd>
+			        	<t:if test="info.sourceBrowseUrl" else="message:not-informed">
+			        		<a href="${info.sourceBrowseUrl}">${info.sourceBrowseUrl}</a>
+			        	</t:if>
+			        </dd>
+			
+			        <dt>Source root URL</dt>
+			        <dd>
+			        	<t:if test="info.sourceRootUrl" else="message:not-informed">
+			        		<a href="${info.sourceRootUrl}">${info.sourceRootUrl}</a>
+			        	</t:if>
+			        </dd>
+
+			        <dt>Issue tracker URL</dt>
+			        <dd>
+			        	<t:if test="info.issueTrackerUrl" else="message:not-informed">
+			        		<a href="${info.issueTrackerUrl}">${info.issueTrackerUrl}</a>
+			        	</t:if>
+			        </dd>
+			
+			    </dl>
+		    
+			</t:if>
+			
+			<p t:type="If" t:test="!info">No additional information provided for <code>${libraryName}</code>.</p>
+			
+<!-- 			<div t:type="Zone" t:id="pages" id="prop:libraryClientZoneClientId"> -->
+<!-- 			</div> -->
+
+			<div t:type="Delegate" to="componentsTable"></div>
+			<div t:type="Delegate" to="pagesTable"></div>
+			<div t:type="Delegate" to="mixinsTable"></div>
+			
+		</div>
+		
+	</div>
+	
+	<t:block id="classesTable">
+		<div t:type="If" t:test="!logicalNames.empty">
+			<h3>${headerName}</h3>
+			<table class="table table-striped table-hover table-condensed">
+				<thead>
+					<tr>
+						<td>Name</td>
+						<td>JavaDoc URL</td>
+						<td>Source URL</td>
+					</tr>
+				</thead>
+				<tbody>
+					<tr t:type="Loop" t:source="logicalNames" t:value="logicalName">
+						<td><code>${simpleLogicalName}</code></td>
+						<td><t:if test="javadocUrl" else="message:not-informed" target="_blank">
+							<a href="${javadocUrl}">JavaDoc</a></t:if>
+						</td>
+						<td><t:if test="sourceUrl" else="message:not-informed" target="_blank">
+							<a href="${sourceUrl}">Source</a></t:if>
+						</td>
+					</tr>
+				</tbody>
+			</table>
+		</div>
+	</t:block>
+
+</t:block>