SLING-5810 [HApi] Add support for a collection of types
SLING-5807 [HApi] Annotate the html rendering a type with HApi


git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1750096 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/sling/hapi/HApiType.java b/src/main/java/org/apache/sling/hapi/HApiType.java
index e562187..a8f647a 100644
--- a/src/main/java/org/apache/sling/hapi/HApiType.java
+++ b/src/main/java/org/apache/sling/hapi/HApiType.java
@@ -41,13 +41,13 @@
     String getDescription();
 
     /**
-     * The JCR path of the node representing this type
+     * The path of the Resource representing this type
      * @return
      */
     String getPath();
 
     /**
-     * The URL of the node representing this type
+     * The external URL of the node representing this type
      * @return
      */
     String getUrl();
diff --git a/src/main/java/org/apache/sling/hapi/HApiTypesCollection.java b/src/main/java/org/apache/sling/hapi/HApiTypesCollection.java
new file mode 100644
index 0000000..c995d97
--- /dev/null
+++ b/src/main/java/org/apache/sling/hapi/HApiTypesCollection.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.sling.hapi;
+
+import java.util.Collection;
+
+public interface HApiTypesCollection extends Collection<HApiType> {
+    /**
+     * The name of this type collection
+     * @return
+     */
+    String getName();
+
+
+    /**
+     * The description of this type collection
+     * @return
+     */
+    String getDescription();
+
+    /**
+     * The Resource path of this type collection
+     * @return
+     */
+    String getPath();
+
+    /**
+     * The external URL of the type collection
+     * @return
+     */
+    String getUrl();
+
+    /**
+     * The fully qualified domain name of this type collection
+     * @return
+     */
+    String getFqdn();
+
+}
diff --git a/src/main/java/org/apache/sling/hapi/HApiUtil.java b/src/main/java/org/apache/sling/hapi/HApiUtil.java
index 822c52d..c2b336a 100644
--- a/src/main/java/org/apache/sling/hapi/HApiUtil.java
+++ b/src/main/java/org/apache/sling/hapi/HApiUtil.java
@@ -28,6 +28,15 @@
 public interface HApiUtil {
 
     String DEFAULT_RESOURCE_TYPE = "sling/hapi/components/type";
+    String RESOURCE_TYPE = "org.apache.sling.hapi.tools.resourcetype";
+
+    String DEFAULT_COLLECTION_RESOURCE_TYPE = "sling/hapi/components/typescollection";
+    String COLLECTION_RESOURCE_TYPE = "org.apache.sling.hapi.tools.collectionresourcetype";
+
+    String SEARCH_PATHS = "org.apache.sling.hapi.tools.searchpaths";
+
+    String DEFAULT_SERVER_URL = "http://localhost:8080";
+    String EXTERNAL_URL = "org.apache.sling.hapi.tools.externalurl";
 
     /**
      * <p>Get a HApi type jcr node from a type identifier.</p>
@@ -48,7 +57,8 @@
     /**
      * <p>Get a HApi type Resource from a type identifier.</p>
      * <p>The Resource must be [nt:unstructured], a descendant of any of the HAPi search path defined by the
-     * {@see HAPI_PATHS} config and the sling:resourceType should be set to the value defined by the {@see HAPI_RESOURCE_TYPE} config</p>
+     * {@see HAPI_PATHS} config and the sling:resourceType should be set to the value defined by the {@see HapiUtil#RESOURCE_TYPE}
+     * config</p>
      * <p>The first result is returned</p>
      * @param resolver The sling resource resolver object
      * @param type The type identifier, which is either in the form of a jcr path,
@@ -60,6 +70,22 @@
     Resource getTypeResource(ResourceResolver resolver, String type) throws RepositoryException;
 
     /**
+     * <p>Get a HApi type collection Resource from a collection identifier.</p>
+     * <p>The Resource must be [nt:unstructured], a descendant of any of the HAPi search path defined by the
+     * {@see HAPI_PATHS} config and the sling:resourceType should be set to the value defined by the
+     * {@see HapiUtil#COLLECTION_RESOURCE_TYPE} config</p>
+     * <p>The first result is returned</p>
+     * @param resolver The sling resource resolver object
+     * @param collection The collection identifier, which is either in the form of a jcr path,
+     *             same as the path for {@link: ResourceResolver#getResource(String)}. If the path cannot be resolved, collection is
+     *             treated like a fully qualified domain name, which has to match the "fqdn" property on the Resource which
+     *                   represents the type.
+     * @return The first Resource that matches that collection or null if none is found.
+     * @throws RepositoryException
+     */
+    Resource getTypeCollectionResource(ResourceResolver resolver, String collection) throws RepositoryException;
+
+    /**
      * <p>Get a HApi type object from a type identifier.</p>
      * <p>The type identifier is resolved to a {@link javax.jcr.Node} and then
      * {@link #fromNode(org.apache.sling.api.resource.ResourceResolver, javax.jcr.Node)} is called.</p>
@@ -136,6 +162,36 @@
     HApiType fromResource(ResourceResolver resolver, Resource typeResource) throws RepositoryException;
 
     /**
+     * <p>Get a {@link HApiTypesCollection} object from a {@link org.apache.sling.api.resource.Resource}.</p>
+     * The Resource has the following properties:
+     * <ul>
+     *     <li>name: A 'Name' of the type collection (mandatory)</li>
+     *     <li>description: A 'String' with the description text for this type collection (mandatory)</li>
+     *     <li>fqdn: A 'String' with the fully qualified domain name; A namespace like a java package (mandatory)</li>
+     * </ul>
+     *
+     * <p> The types collection will be populated with direct child Resources of the {{collectionResource}},
+     * which have the resourceType equal to the value of the {@link HApiUtil#RESOURCE_TYPE} property
+     *
+     * @param resolver The resource resolver
+     * @param collectionResource The sling Resource of the HApi type collection
+     * @return The HApiTypesCollection
+     * @throws RepositoryException
+     */
+    HApiTypesCollection collectionFromResource(ResourceResolver resolver, Resource collectionResource) throws RepositoryException;
+
+    /**
+     * <p>Get a {@link HApiTypesCollection} object from a path String.</p>
+     * <p>{@see HApiUtil#collectionFromResource}</p>
+     *
+     * @param resolver The resource resolver
+     * @param collectionPath The sling resource path of the HApi type collection
+     * @return The HApiTypesCollection
+     * @throws RepositoryException
+     */
+    HApiTypesCollection collectionFromPath(ResourceResolver resolver, String collectionPath) throws RepositoryException;
+
+    /**
      * Get a new instance of AttributeHelper for the type identified by 'type'
      * @param resolver
      * @param type See {@link #getTypeNode(org.apache.sling.api.resource.ResourceResolver, String)}
diff --git a/src/main/java/org/apache/sling/hapi/impl/HApiPropertyImpl.java b/src/main/java/org/apache/sling/hapi/impl/HApiPropertyImpl.java
index cb3a280..8ea4267 100644
--- a/src/main/java/org/apache/sling/hapi/impl/HApiPropertyImpl.java
+++ b/src/main/java/org/apache/sling/hapi/impl/HApiPropertyImpl.java
@@ -105,7 +105,7 @@
         return "HApiProperty{" +
                 "name='" + name + '\'' +
                 ", description='" + description + '\'' +
-                ", type=" + type +
+                ", type=" + type.getPath() +
                 ", multiple=" + multiple +
                 '}';
     }
diff --git a/src/main/java/org/apache/sling/hapi/impl/HApiTypeImpl.java b/src/main/java/org/apache/sling/hapi/impl/HApiTypeImpl.java
index f0ec536..516229b 100644
--- a/src/main/java/org/apache/sling/hapi/impl/HApiTypeImpl.java
+++ b/src/main/java/org/apache/sling/hapi/impl/HApiTypeImpl.java
@@ -169,6 +169,6 @@
 
     @Override
     public String toString() {
-        return this.getName() + ": Properties: " + this.getProperties();
+        return this.getName() + "(" + this.getPath() + "): Properties: " + this.getProperties();
     }
 }
diff --git a/src/main/java/org/apache/sling/hapi/impl/HApiTypesCollectionImpl.java b/src/main/java/org/apache/sling/hapi/impl/HApiTypesCollectionImpl.java
new file mode 100644
index 0000000..a9cb23e
--- /dev/null
+++ b/src/main/java/org/apache/sling/hapi/impl/HApiTypesCollectionImpl.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.sling.hapi.impl;
+
+import org.apache.sling.hapi.HApiType;
+import org.apache.sling.hapi.HApiTypesCollection;
+
+import java.util.ArrayList;
+
+public class HApiTypesCollectionImpl extends ArrayList<HApiType> implements HApiTypesCollection {
+
+    private final String name;
+    private final String description;
+    private final String path;
+    private final String serverUrl;
+    private final String fqdn;
+
+    /**
+     *
+     * @param name The name of the collection.
+     * @param description The description of the collection
+     * @param path The path of the resource describing the collection
+     */
+    public HApiTypesCollectionImpl(String name, String description, String serverUrl, String path, String fqdn) {
+        this.name = name;
+        this.description = description;
+        this.serverUrl = serverUrl.substring(0, serverUrl.length() - (serverUrl.endsWith("/") ? 1 : 0));
+        this.path = path;
+        this.fqdn = fqdn;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getUrl() {
+        return this.serverUrl + getPath() + ".html";
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getFqdn() {
+        return fqdn;
+    }
+}
diff --git a/src/main/java/org/apache/sling/hapi/impl/HApiUtilImpl.java b/src/main/java/org/apache/sling/hapi/impl/HApiUtilImpl.java
index d657710..f3d5e33 100644
--- a/src/main/java/org/apache/sling/hapi/impl/HApiUtilImpl.java
+++ b/src/main/java/org/apache/sling/hapi/impl/HApiUtilImpl.java
@@ -42,10 +42,7 @@
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.commons.osgi.PropertiesUtil;
-import org.apache.sling.hapi.HApiProperty;
-import org.apache.sling.hapi.HApiType;
-import org.apache.sling.hapi.HApiUtil;
-import org.apache.sling.hapi.MicrodataAttributeHelper;
+import org.apache.sling.hapi.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -57,17 +54,19 @@
     private final Logger LOG = LoggerFactory.getLogger(HApiUtil.class);
 
     @Property(label = "HApi Resource Type", cardinality = 0, value = DEFAULT_RESOURCE_TYPE)
-    public static final String HAPI_RESOURCE_TYPE = "org.apache.sling.hapi.tools.resourcetype";
+    public static final String HAPI_RESOURCE_TYPE = RESOURCE_TYPE;
+
+    @Property(label = "HApi Collection Resource Type", cardinality = 0, value = DEFAULT_COLLECTION_RESOURCE_TYPE)
+    private static final String HAPI_COLLECTION_RESOURCE_TYPE = COLLECTION_RESOURCE_TYPE;
 
     @Property(label = "HApi Types Search Paths", cardinality=50, value = {"/libs/sling/hapi/types"})
-    public static final String HAPI_PATHS = "org.apache.sling.hapi.tools.searchpaths";
+    public static final String HAPI_PATHS = SEARCH_PATHS;
 
-
-    private static final String DEFAULT_SERVER_URL = "http://localhost:8080";
     @Property(label = "External server URL", cardinality = 0, value = DEFAULT_SERVER_URL)
-    public static final String HAPI_EXTERNAL_URL = "org.apache.sling.hapi.tools.externalurl";
+    public static final String HAPI_EXTERNAL_URL = EXTERNAL_URL;
 
     public static String resourceType;
+    private String collectionResourceType;
     public static String[] hApiPaths;
     public static String serverContextPath;
 
@@ -75,6 +74,8 @@
     @Activate
     private void activate(Map<String, Object> configuration) {
         resourceType = PropertiesUtil.toString(configuration.get(HAPI_RESOURCE_TYPE), DEFAULT_RESOURCE_TYPE);
+        collectionResourceType = PropertiesUtil.toString(configuration.get(HAPI_COLLECTION_RESOURCE_TYPE),
+                DEFAULT_COLLECTION_RESOURCE_TYPE);
         hApiPaths = PropertiesUtil.toStringArray(configuration.get(HAPI_PATHS));
         serverContextPath = PropertiesUtil.toString(configuration.get(HAPI_EXTERNAL_URL), DEFAULT_SERVER_URL);
     }
@@ -93,10 +94,24 @@
      */
     @Override
     public Resource getTypeResource(ResourceResolver resolver, String type) throws RepositoryException {
+        return getFqdnResource(resolver, type, resourceType);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Resource getTypeCollectionResource(ResourceResolver resolver, String collection) throws RepositoryException {
+        return getFqdnResource(resolver, collection, collectionResourceType);
+    }
+
+
+
+    private Resource getFqdnResource(ResourceResolver resolver, String fqdn, String resType) throws RepositoryException {
         Session session = resolver.adaptTo(Session.class);
 
         // Try to resolve the resource as a path
-        Resource res = resolver.getResource(type);
+        Resource res = resolver.getResource(fqdn);
         if (null != res) {
             LOG.debug("res = " + res.getName() + " " + res.getPath());
             return res;
@@ -111,11 +126,11 @@
                 // Build query for the search paths
                 StringBuilder queryString = new StringBuilder("SELECT * FROM [nt:unstructured] WHERE ");
                 queryString.append(String.format("ISDESCENDANTNODE([%s]) ", path));
-                queryString.append(String.format("AND [sling:resourceType]='%s' AND fqdn = '%s'", resourceType, type));
+                queryString.append(String.format("AND [sling:resourceType]='%s' AND fqdn = '%s'", resType, fqdn));
 
                 // Execute query
                 Query query = queryManager.createQuery(queryString.toString(), Query.JCR_SQL2);
-                LOG.debug("Querying HAPi: {}", queryString.toString());
+                LOG.debug("Querying HApi: {}", queryString.toString());
                 QueryResult result = query.execute();
 
                 NodeIterator nodeIter = result.getNodes();
@@ -177,6 +192,7 @@
         }
         HApiTypeImpl newType = new HApiTypeImpl(name, description, serverContextPath, path, fqdn, parameters, null, null, false);
         TypesCache.getInstance(this).addType(newType);
+        LOG.debug("Inserted type {} to cache: {}", newType, TypesCache.getInstance(this));
 
 
         try {
@@ -184,7 +200,7 @@
             HApiType parent = null;
             String parentPath = resProps.get("extends", (String) null);
             if (null != parentPath) {
-                parent = TypesCache.getInstance(this).getType(resolver, parentPath);
+                parent = TypesCache.getInstance(this).getType(resolver, getTypeResource(resolver, parentPath));
             }
 
             // Get properties
@@ -194,8 +210,12 @@
 
                 String propName = res.getName();
                 String propDescription = resValueMap.get("description", "");
-                String typePath = resValueMap.get("type", (String) null);
-                HApiType propType = TypesCache.getInstance(this).getType(resolver, typePath);
+                String typeFqdnOrPath = resValueMap.get("type", (String) null);
+                Resource propTypeResource = getTypeResource(resolver, typeFqdnOrPath);
+                HApiType propType = (null != propTypeResource)
+                        ? TypesCache.getInstance(this).getType(resolver, propTypeResource)
+                        : new AbstractHapiTypeImpl(typeFqdnOrPath);
+                LOG.debug("Fetched type {} from cache", propType);
                 Boolean propMultiple = resValueMap.get("multiple", false);
 
                 HApiProperty prop = new HApiPropertyImpl(propName, propDescription, propType, propMultiple);
@@ -223,8 +243,43 @@
      * {@inheritDoc}
      */
     @Override
+    public HApiTypesCollection collectionFromResource(ResourceResolver resolver, Resource collectionResource) throws RepositoryException {
+        if (null == collectionResource) return null;
+        ValueMap resProps = collectionResource.adaptTo(ValueMap.class);
+        String name = resProps.get("name", (String) null);
+        String description = resProps.get("description", (String) null);
+        String path = collectionResource.getPath();
+        String fqdn = resProps.get("fqdn", (String) null);
+        HApiTypesCollection collection = new HApiTypesCollectionImpl(name, description, serverContextPath, path, fqdn); // no types yet
+
+        // iterate the children of the resource and add to the collection
+        for (Resource typeRes : collectionResource.getChildren()) {
+            if (typeRes.isResourceType(resourceType)) {
+                // child resource if a hapi type, add it to the collection
+                LOG.debug("Trying to add type from resource: {} to the collection", typeRes);
+                HApiType type = fromResource(resolver, typeRes);
+                collection.add(type);
+                LOG.debug("Added type {} to the collection.", type);
+            }
+        }
+        LOG.debug("Collection: {}", collection);
+        return collection;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public HApiTypesCollection collectionFromPath(ResourceResolver resolver, String collectionPath) throws RepositoryException {
+        return collectionFromResource(resolver, this.getTypeCollectionResource(resolver, collectionPath));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public MicrodataAttributeHelper getHelper(ResourceResolver resolver, String type) throws RepositoryException {
-        return new MicrodataAttributeHelperImpl(resolver, TypesCache.getInstance(this).getType(resolver, type));
+        return new MicrodataAttributeHelperImpl(resolver, TypesCache.getInstance(this).getType(resolver, getTypeResource(resolver, type)));
     }
 }
 
@@ -234,15 +289,20 @@
  */
 class TypesCache {
     private static final Logger LOG = LoggerFactory.getLogger(TypesCache.class);
-    Map<String, HApiType> types;
     private static TypesCache singleton = null;
+
     private HApiUtil hApiUtil;
+    Map<String, HApiType> types;
 
     public static TypesCache getInstance(HApiUtil hApiUtil) {
         if (null == singleton) {
             singleton = new TypesCache(hApiUtil);
+            LOG.debug("Created new cache instance");
+        } else {
+            singleton.hApiUtil = hApiUtil;
         }
-        LOG.debug("singleton: {}", singleton);
+
+        LOG.debug("type cache singleton: {}", singleton);
         return singleton;
     }
 
@@ -251,13 +311,18 @@
         this.hApiUtil = hApiUtil;
     }
 
-    public HApiType getType(ResourceResolver resolver, String typePath) throws RepositoryException {
-        if (types.containsKey(typePath)) {
+    public HApiType getType(ResourceResolver resolver, Resource typeResource) throws RepositoryException {
+        if (null == typeResource) return new AbstractHapiTypeImpl("Abstract");
+
+        String typePath = typeResource.getPath();
+        LOG.debug("Trying to get type {}; cache: {}", typePath, types);
+        if (this.types.containsKey(typePath)) {
+            LOG.debug("Returning type {} from cache", typePath);
             return this.types.get(typePath);
         } else {
-
+            LOG.debug("Creating type {} in cache", typePath);
             HApiType type = hApiUtil.fromPath(resolver, typePath);
-            types.put(type.getPath(), type);
+            this.types.put(type.getPath(), type);
             return type;
         }
     }
@@ -269,5 +334,11 @@
     public void removeType(String path) {
         this.types.remove(path);
     }
+
+    @Override
+    public String toString() {
+        return "TypesCache{" +
+                "types=" + types + "}";
+    }
 }
 
diff --git a/src/main/java/org/apache/sling/hapi/impl/MicrodataAttributeHelperImpl.java b/src/main/java/org/apache/sling/hapi/impl/MicrodataAttributeHelperImpl.java
index 85ae13d..b78ef5d 100644
--- a/src/main/java/org/apache/sling/hapi/impl/MicrodataAttributeHelperImpl.java
+++ b/src/main/java/org/apache/sling/hapi/impl/MicrodataAttributeHelperImpl.java
@@ -23,6 +23,8 @@
 import org.apache.sling.hapi.MicrodataAttributeHelper;
 import org.apache.sling.hapi.HApiException;
 import org.apache.sling.api.resource.ResourceResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -32,6 +34,7 @@
  * Helper class for HTML microdata attributes
  */
 public class MicrodataAttributeHelperImpl implements MicrodataAttributeHelper {
+    private static final Logger LOG = LoggerFactory.getLogger(MicrodataAttributeHelperImpl.class);
     private final ResourceResolver resolver;
     HApiType type;
 
@@ -134,6 +137,7 @@
         public Map<String, String> get(Object key) {
             Map<String, String> val = super.get(key);
             if (null == val) {
+                LOG.debug("type = {}", type);
                 throw new HApiException("Property " + key + " does not exist for type " + type.getPath());
             }
             return val;
diff --git a/src/main/java/org/apache/sling/hapi/sightly/TypesCollectionView.java b/src/main/java/org/apache/sling/hapi/sightly/TypesCollectionView.java
new file mode 100644
index 0000000..3916cfc
--- /dev/null
+++ b/src/main/java/org/apache/sling/hapi/sightly/TypesCollectionView.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.sling.hapi.sightly;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.api.scripting.SlingScriptHelper;
+import org.apache.sling.hapi.HApiType;
+import org.apache.sling.hapi.HApiTypesCollection;
+import org.apache.sling.hapi.HApiUtil;
+import org.apache.sling.scripting.sightly.pojo.Use;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.script.Bindings;
+import java.util.Collection;
+
+public class TypesCollectionView implements Use {
+    private static final Logger LOG = LoggerFactory.getLogger(TypesCollectionView.class);
+
+    private HApiUtil hapi;
+    private HApiTypesCollection me;
+
+    private String description;
+    private SlingHttpServletRequest request;
+    private SlingScriptHelper sling;
+    private Resource resource;
+    private ResourceResolver resourceResolver;
+
+    public void init(Bindings bindings) {
+        request = (SlingHttpServletRequest) bindings.get(SlingBindings.REQUEST);
+        sling =   (SlingScriptHelper) bindings.get(SlingBindings.SLING);
+        resource = (Resource)bindings.get(SlingBindings.RESOURCE);
+        resourceResolver = request.getResourceResolver();
+
+        try {
+            activate();
+        } catch (Exception e) {
+            LOG.error("Failed to activate Use class", e);
+        }
+    }
+
+    public void activate() throws Exception {
+        hapi = sling.getService(HApiUtil.class);
+        me = hapi.collectionFromPath(resourceResolver, resource.getPath());
+        LOG.debug("me: {}  resource: {}", me, resource.getPath());
+        description = me.getDescription();
+    }
+
+    public String getTitle() {
+        return me.getFqdn();
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public Collection<HApiType> getTypes() {
+        return me;
+    }
+
+    public boolean getHasTypes() {
+        return getTypes().size() > 0;
+    }
+}
diff --git a/src/main/java/org/apache/sling/hapi/sightly/package-info.java b/src/main/java/org/apache/sling/hapi/sightly/package-info.java
index 45c370c..0e07165 100644
--- a/src/main/java/org/apache/sling/hapi/sightly/package-info.java
+++ b/src/main/java/org/apache/sling/hapi/sightly/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  ******************************************************************************/
 
-@Version("1.0.0")
+@Version("1.1.0")
 package org.apache.sling.hapi.sightly;
 
 import aQute.bnd.annotation.Version;
diff --git a/src/main/resources/SLING-INF/libs/sling/hapi/components/type/type.html b/src/main/resources/SLING-INF/libs/sling/hapi/components/type/type.html
index 39d8f18..dd95a5d 100644
--- a/src/main/resources/SLING-INF/libs/sling/hapi/components/type/type.html
+++ b/src/main/resources/SLING-INF/libs/sling/hapi/components/type/type.html
@@ -17,7 +17,8 @@
   ~ specific language governing permissions and limitations
   ~ under the License.
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
-<html data-sly-use.type="org.apache.sling.hapi.sightly.TypeView" lang="${type.lang}">
+<html data-sly-use.type="org.apache.sling.hapi.sightly.TypeView"
+      data-sly-use.hapitype="${'org.apache.sling.hapi.sightly.HApiUse' @type='org.apache.sling.hapi.common.hapi_type'}" lang="${type.lang}">
 
     <head>
         <meta charset="utf-8">
@@ -38,18 +39,18 @@
 
     </head>
     <body>
-        <div class="container">
+        <div class="container" data-sly-attribute="${hapitype.itemtype}">
 
-            <h1>${type.title}</h1>
+            <h1 data-sly-attribute="${hapitype.itemprop.hapi_name}">${type.title}</h1>
             <div>
-                <p>${type.description}</p>
+                <p data-sly-attribute="${hapitype.itemprop.hapi_description}">${type.description}</p>
             </div>
             <h2 data-sly-test.parentUrl="${type.parentUrl}"> extends
-                <a href="${parentUrl}" >${type.parentFqdn}</a>
+                <a data-sly-attribute="${hapitype.itemprop.hapi_parent}" href="${parentUrl}" >${type.parentFqdn}</a>
             </h2>
             <h3 data-sly-test="${type.parameters}">Parameters: </h3>
                 <ul data-sly-list.param="${type.parameters}" title="Parameters" class="list-inline">
-                <li>${param}</li>
+                <li data-sly-attribute="${hapitype.itemprop.hapi_parameter}">${param}</li>
             </ul>
             <h3>Properties: </h3>
             <table class="table" data-sly-test.props="${type.props}">
@@ -62,13 +63,17 @@
                     </tr>
                 </thead>
                 <tbody data-sly-list.prop="${type.props}">
-                    <tr>
-                        <th>${prop.name}</th>
+                    <tr data-sly-attribute="${hapitype.itemprop.hapi_property}"
+                        data-sly-use.hapi_type_prop="${'org.apache.sling.hapi.sightly.HApiUse' @type=hapitype.proptype.hapi_property}">
+
+                        <th data-sly-attribute="${hapi_type_prop.itemprop.hapi_name}">${prop.name}</th>
                         <td>
-                            <a href="${prop.type.url}">${prop.type.name}</a>
+                            <a data-sly-attribute="${hapi_type_prop.itemprop.hapi_property_type}" href="${prop.type.url}">
+                                ${prop.type.name}
+                            </a>
                         </td>
-                        <td>${prop.multiple}</td>
-                        <td>${prop.description}</td>
+                        <td data-sly-attribute="${hapi_type_prop.itemprop.hapi_multiple}">${prop.multiple}</td>
+                        <td data-sly-attribute="${hapi_type_prop.itemprop.hapi_description}">${prop.description}</td>
                     </tr>
                 </tbody>
             </table>
diff --git a/src/main/resources/SLING-INF/libs/sling/hapi/components/typescollection/typescollection.html b/src/main/resources/SLING-INF/libs/sling/hapi/components/typescollection/typescollection.html
new file mode 100644
index 0000000..ea8427f
--- /dev/null
+++ b/src/main/resources/SLING-INF/libs/sling/hapi/components/typescollection/typescollection.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you 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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+<html data-sly-use.collection="org.apache.sling.hapi.sightly.TypesCollectionView"
+      data-sly-use.hapicollection="${'org.apache.sling.hapi.sightly.HApiUse' @type='org.apache.sling.hapi.common.hapi_typescollection'}"
+      lang="${type.lang}">
+
+    <head>
+        <meta charset="utf-8">
+        <meta http-equiv="X-UA-Compatible" content="IE=edge">
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+
+        <!-- Bootstrap -->
+        <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet">
+
+        <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
+        <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
+        <!--[if lt IE 9]>
+          <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
+          <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+        <![endif]-->
+
+        <title>${collection.title}</title>
+
+    </head>
+    <body>
+        <div class="container">
+
+            <h1 data-sly-attribute="${hapicollection.itemprop.hapi_name}">${collection.title}</h1>
+            <div>
+                <p data-sly-attribute="${hapicollection.itemprop.hapi_description}">${collection.description}</p>
+            </div>
+
+            <h3>Types: </h3>
+            <table class="table" data-sly-test.types="${collection.types}">
+                <thead>
+                    <tr>
+                        <th>Name</th>
+                        <th>Description</th>
+                    </tr>
+                </thead>
+                <tbody data-sly-list.type="${collection.types}">
+                    <tr data-sly-attribute="${hapicollection.itemprop.hapi_type}"
+                        data-sly-use.hapi_type="${'org.apache.sling.hapi.sightly.HApiUse' @type=hapicollection.proptype.hapi_type}">
+                        <td>
+                            <a rel="hapi_type" data-sly-attribute="${hapi_type.itemprop.hapi_url}" href="${type.url}">
+                                <span data-sly-attribute="${hapi_type.itemprop.hapi_name}">${type.fqdn}</span>
+                            </a>
+                        </td>
+                        <td data-sly-attribute="${hapi_type.itemprop.hapi_description}">${type.description}</td>
+                    </tr>
+                </tbody>
+            </table>
+            <div data-sly-test="${!types}">None</div>
+        </div>
+    </body>
+</html>
diff --git a/src/main/resources/SLING-INF/libs/sling/hapi/types.json b/src/main/resources/SLING-INF/libs/sling/hapi/types.json
index 7984006..012c6c9 100644
--- a/src/main/resources/SLING-INF/libs/sling/hapi/types.json
+++ b/src/main/resources/SLING-INF/libs/sling/hapi/types.json
@@ -1,110 +1,215 @@
 {
-  "jcr:primaryType": "sling:Folder",
-  "image": {
     "jcr:primaryType": "nt:unstructured",
-    "description": "An image src",
-    "fqdn": "org.apache.sling.hapi.common.Image",
-    "name": "Image",
-    "sling:resourceType": "sling/hapi/components/type"
-  },
-  "date": {
-    "jcr:primaryType": "nt:unstructured",
-    "description": "a date value; format has to be described in each property of this type",
-    "fqdn": "org.apache.sling.hapi.common.Date",
-    "name": "Date",
-    "sling:resourceType": "sling/hapi/components/type"
-  },
-  "text": {
-    "jcr:primaryType": "nt:unstructured",
-    "description": "A text value",
-    "fqdn": "org.apache.sling.hapi.common.Text",
-    "name": "Text",
-    "sling:resourceType": "sling/hapi/components/type"
-  },
-  "collection": {
-    "jcr:primaryType": "nt:unstructured",
-    "description": "Generic collection type. Can be parameterized with a generic type.",
-    "fqdn": "org.apache.sling.hapi.common.collection",
-    "name": "collection",
-    "parameters": [
-      "T"
-    ],
-    "sling:resourceType": "sling/hapi/components/type",
-    "item": {
-      "jcr:primaryType": "nt:unstructured",
-      "description": "",
-      "type": "T",
-      "multiple": true
-    }
-  },
-  "boolean": {
-    "jcr:primaryType": "nt:unstructured",
-    "description": "true or false",
-    "fqdn": "org.apache.sling.hapi.common.Boolean",
-    "name": "Boolean",
-    "sling:resourceType": "sling/hapi/components/type"
-  },
-  "pair": {
-    "jcr:primaryType": "nt:unstructured",
-    "description": "The key-value pair. Usually used to indicate a Map entry.",
-    "fqdn": "org.apache.sling.hapi.common.pair",
-    "name": "pair",
-    "parameters": [
-      "K",
-      "V"
-    ],
-    "sling:resourceType": "sling/hapi/components/type",
-    "key": {
-      "jcr:primaryType": "nt:unstructured",
-      "description": "The key of the pair",
-      "type": "K",
-      "multiple": false
+    "fqdn": "org.apache.sling.hapi.common",
+    "description": "Common HApi types collection",
+    "name": "common",
+    "sling:resourceType": "sling/hapi/components/typescollection",
+    "image": {
+        "jcr:primaryType": "nt:unstructured",
+        "description": "An image src",
+        "fqdn": "org.apache.sling.hapi.common.Image",
+        "name": "Image",
+        "sling:resourceType": "sling/hapi/components/type"
     },
-    "value": {
-      "jcr:primaryType": "nt:unstructured",
-      "description": "The value of the pair",
-      "type": "V",
-      "multiple": false
-    }
-  },
-  "number": {
-    "jcr:primaryType": "nt:unstructured",
-    "description": "A number value",
-    "fqdn": "org.apache.sling.hapi.common.Number",
-    "name": "Number",
-    "sling:resourceType": "sling/hapi/components/type"
-  },
-  "demo_type": {
-    "jcr:primaryType": "nt:unstructured",
-    "description": "A demo HApi type to show a more advanced structure",
-    "demo_description": {
-      "jcr:primaryType": "nt:unstructured",
-      "description": "The description of the demo object",
-      "type": "org.apache.sling.hapi.common.Text",
-      "multiple": false
+    "date": {
+        "jcr:primaryType": "nt:unstructured",
+        "description": "a date value; format has to be described in each property of this type",
+        "fqdn": "org.apache.sling.hapi.common.Date",
+        "name": "Date",
+        "sling:resourceType": "sling/hapi/components/type"
     },
-    "fqdn": "org.apache.sling.hapi.common.collection",
-    "name": "collection",
-    "parameters": [],
-    "sling:resourceType": "sling/hapi/components/type",
-    "title": {
-      "jcr:primaryType": "nt:unstructured",
-      "description": "The title of the demo object",
-      "type": "org.apache.sling.hapi.common.Text",
-      "multiple": false
+    "text": {
+        "jcr:primaryType": "nt:unstructured",
+        "description": "A text value",
+        "fqdn": "org.apache.sling.hapi.common.Text",
+        "name": "Text",
+        "sling:resourceType": "sling/hapi/components/type"
     },
-    "entries": {
-      "jcr:primaryType": "nt:unstructured",
-      "description": "An entry of type pair in the demo object",
-      "type": "org.apache.sling.hapi.common.pair",
-      "multiple": true
-    }
-  },
-  "url": {
-    "jcr:primaryType": "nt:unstructured",
-    "description": "A URL value type",
-    "fqdn": "org.apache.sling.hapi.common.URL",
-    "name": "URL",
-    "sling:resourceType": "sling/hapi/components/type"
-  }
+    "collection": {
+        "jcr:primaryType": "nt:unstructured",
+        "description": "Generic collection type. Can be parameterized with a generic type.",
+        "fqdn": "org.apache.sling.hapi.common.collection",
+        "name": "collection",
+        "parameters": [
+            "T"
+        ],
+        "sling:resourceType": "sling/hapi/components/type",
+        "item": {
+            "jcr:primaryType": "nt:unstructured",
+            "description": "",
+            "type": "T",
+            "multiple": true
+        }
+    },
+    "boolean": {
+        "jcr:primaryType": "nt:unstructured",
+        "description": "true or false",
+        "fqdn": "org.apache.sling.hapi.common.Boolean",
+        "name": "Boolean",
+        "sling:resourceType": "sling/hapi/components/type"
+    },
+    "pair": {
+        "jcr:primaryType": "nt:unstructured",
+        "description": "The key-value pair. Usually used to indicate a Map entry.",
+        "fqdn": "org.apache.sling.hapi.common.pair",
+        "name": "pair",
+        "parameters": [
+            "K",
+            "V"
+        ],
+        "sling:resourceType": "sling/hapi/components/type",
+        "key": {
+            "jcr:primaryType": "nt:unstructured",
+            "description": "The key of the pair",
+            "type": "K",
+            "multiple": false
+        },
+        "value": {
+            "jcr:primaryType": "nt:unstructured",
+            "description": "The value of the pair",
+            "type": "V",
+            "multiple": false
+        }
+    },
+    "number": {
+        "jcr:primaryType": "nt:unstructured",
+        "description": "A number value",
+        "fqdn": "org.apache.sling.hapi.common.Number",
+        "name": "Number",
+        "sling:resourceType": "sling/hapi/components/type"
+    },
+    "demo_type": {
+        "jcr:primaryType": "nt:unstructured",
+        "description": "A demo HApi type to show a more advanced structure",
+        "demo_description": {
+            "jcr:primaryType": "nt:unstructured",
+            "description": "The description of the demo object",
+            "type": "org.apache.sling.hapi.common.Text",
+            "multiple": false
+        },
+        "fqdn": "org.apache.sling.hapi.common.collection",
+        "name": "collection",
+        "parameters": [],
+        "sling:resourceType": "sling/hapi/components/type",
+        "title": {
+            "jcr:primaryType": "nt:unstructured",
+            "description": "The title of the demo object",
+            "type": "org.apache.sling.hapi.common.Text",
+            "multiple": false
+        },
+        "entries": {
+            "jcr:primaryType": "nt:unstructured",
+            "description": "An entry of type pair in the demo object",
+            "type": "org.apache.sling.hapi.common.pair",
+            "multiple": true
+        }
+    },
+    "url": {
+        "jcr:primaryType": "nt:unstructured",
+        "description": "A URL value type",
+        "fqdn": "org.apache.sling.hapi.common.URL",
+        "name": "URL",
+        "sling:resourceType": "sling/hapi/components/type"
+    },
+    "hapi_type": {
+        "jcr:primaryType": "nt:unstructured",
+        "description": "The representation of the structure of the HApi type. This is used to annotate the html page describing any type",
+        "hapi_name": {
+            "jcr:primaryType": "nt:unstructured",
+            "description": "The FQDN of the HApi Type",
+            "type": "org.apache.sling.hapi.common.Text",
+            "multiple": false
+        },
+        "hapi_description": {
+            "jcr:primaryType": "nt:unstructured",
+            "description": "The description of the HApi Type",
+            "type": "org.apache.sling.hapi.common.Text",
+            "multiple": false
+        },
+        "hapi_parent": {
+            "jcr:primaryType": "nt:unstructured",
+            "description": "The parent type of the HApi Type",
+            "type": "org.apache.sling.hapi.common.hapi_type",
+            "multiple": false
+        },
+        "hapi_url": {
+            "jcr:primaryType": "nt:unstructured",
+            "description": "The external URL of the type",
+            "type": "org.apache.sling.hapi.common.URL",
+            "multiple": false
+        },
+        "hapi_parameter": {
+            "jcr:primaryType": "nt:unstructured",
+            "description": "A parameter of this type, inc ase it's a parameterized type",
+            "type": "org.apache.sling.hapi.common.Text",
+            "multiple": true
+        },
+        "hapi_property": {
+            "jcr:primaryType": "nt:unstructured",
+            "description": "A property of the HApi type",
+            "type": "org.apache.sling.hapi.common.hapi_type_property",
+            "multiple": true
+        },
+        "fqdn": "org.apache.sling.hapi.common.hapi_type",
+        "name": "hapi_type",
+        "parameters": [],
+        "sling:resourceType": "sling/hapi/components/type"
+    },
+    "hapi_type_property": {
+        "jcr:primaryType": "nt:unstructured",
+        "description": "The representation of the structure of a property for a HApi type. This is used to annotate the html page describing any type",
+        "hapi_name": {
+            "jcr:primaryType": "nt:unstructured",
+            "description": "The name of the HApi Type Property",
+            "type": "org.apache.sling.hapi.common.Text",
+            "multiple": false
+        },
+        "hapi_description": {
+            "jcr:primaryType": "nt:unstructured",
+            "description": "The description of the HApi Type property",
+            "type": "org.apache.sling.hapi.common.Text",
+            "multiple": false
+        },
+        "hapi_property_type": {
+            "jcr:primaryType": "nt:unstructured",
+            "description": "A link to the type of the property",
+            "type": "org.apache.sling.hapi.common.hapi_type",
+            "multiple": false
+        },
+        "hapi_multiple": {
+            "jcr:primaryType": "nt:unstructured",
+            "description": "Whether the property can occur multiple times in an object of the type that this property belongs to",
+            "type": "org.apache.sling.hapi.common.Boolean",
+            "multiple": false
+        },
+        "fqdn": "org.apache.sling.hapi.common.hapi_type_property",
+        "name": "hapi_type_property",
+        "parameters": [],
+        "sling:resourceType": "sling/hapi/components/type"
+    },
+    "hapi_typescollection": {
+        "jcr:primaryType": "nt:unstructured",
+        "description": "The representation of the structure of a HApi typescollection. This is used to annotate the html page describing any types collection",
+        "hapi_name": {
+            "jcr:primaryType": "nt:unstructured",
+            "description": "The name of the HApi Types Collection",
+            "type": "org.apache.sling.hapi.common.Text",
+            "multiple": false
+        },
+        "hapi_description": {
+            "jcr:primaryType": "nt:unstructured",
+            "description": "The description of the HApi Types Collection",
+            "type": "org.apache.sling.hapi.common.Text",
+            "multiple": false
+        },
+        "hapi_type": {
+            "jcr:primaryType": "nt:unstructured",
+            "description": "A type in the typescollection",
+            "type": "org.apache.sling.hapi.common.hapi_type",
+            "multiple": true
+        },
+        "fqdn": "org.apache.sling.hapi.common.hapi_typescollection",
+        "name": "hapi_typescollection",
+        "sling:resourceType": "sling/hapi/components/type"
+    },
 }