Remove old tag handling, implement comment handling

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1630666 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/slingshot/pom.xml b/slingshot/pom.xml
index 0bf37ad..5327867 100644
--- a/slingshot/pom.xml
+++ b/slingshot/pom.xml
@@ -128,5 +128,14 @@
             <version>2.0.0</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+        	<groupId>javax.jcr</groupId>
+        	<artifactId>jcr</artifactId>
+        	<version>2.0</version>
+        </dependency>
+        <dependency>
+        	<groupId>org.slf4j</groupId>
+        	<artifactId>slf4j-api</artifactId>
+        </dependency>
     </dependencies>
 </project>
diff --git a/slingshot/src/main/appended-resources/META-INF/NOTICE b/slingshot/src/main/appended-resources/META-INF/NOTICE
index 6e51c42..8ba66b8 100644
--- a/slingshot/src/main/appended-resources/META-INF/NOTICE
+++ b/slingshot/src/main/appended-resources/META-INF/NOTICE
@@ -1,4 +1,9 @@
-Uses jquery-breadcrumb from http://phonicuk.com
+Includes and uses Metro UI CSS
+MIT License
+Copyright (c) 2012-2014 Sergey Pimenov
+http://metroui.org.ua
+----------------------------------------------------------------------
+Includes and uses jquery-breadcrumb from http://phonicuk.com
 jquery-breadcrumb 1.0
 http://code.google.com/p/jquery-breadcrumb/
 
diff --git a/slingshot/src/main/java/org/apache/sling/sample/slingshot/Constants.java b/slingshot/src/main/java/org/apache/sling/sample/slingshot/Constants.java
deleted file mode 100644
index c32f011..0000000
--- a/slingshot/src/main/java/org/apache/sling/sample/slingshot/Constants.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.sample.slingshot;
-
-import org.apache.sling.api.resource.Resource;
-
-public abstract class Constants {
-
-    /** This is the resource path for the root of our application. */
-    public static final String APP_ROOT = "/slingshot";
-
-    /** This is the resource path for the root album. */
-    public static final String ALBUMS_ROOT = APP_ROOT + "/albums";
-
-    /** The resource type of a folder (= album) */
-    public static final String RESOURCETYPE_FOLDER = "nt:folder";
-
-    /** The resource type of an extended folder (= album) */
-    public static final String RESOURCETYPE_EXT_FOLDER = "sling:Folder";
-
-    /** The resource type for an album. */
-    public static final String RESOURCETYPE_ALBUM = "slingshot/Album";
-
-    /** The resource type of a file (= photo) */
-    public static final String RESOURCETYPE_FILE = "nt:file";
-
-    /** The resource type for a photo. */
-    public static final String RESOURCETYPE_PHOTO = "slingshot/Photo";
-
-    /** The property containing the tags. */
-    public static final String PROPERTY_SLINGSHOT_TAGS = "slingshot:tags";
-
-    /** Name of the preview folder */
-    public static final String FOLDER_NAME_PREVIEW = "preview";
-
-    /**
-     * We only include resource which names do not start with a dot
-     * and we also exclude the preview folder.
-     */
-    public static boolean includeAsAlbum(final Resource resource) {
-        final String name = resource.getName();
-        if ( name.equals(FOLDER_NAME_PREVIEW) || name.startsWith(".") ) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Exclude all files starting with a dot
-     */
-    public static boolean includeAsMedia(final Resource resource) {
-        final String name = resource.getName();
-        if ( name.startsWith(".") ) {
-            return false;
-        }
-        return true;
-    }
-}
diff --git a/slingshot/src/main/java/org/apache/sling/sample/slingshot/SlingshotConstants.java b/slingshot/src/main/java/org/apache/sling/sample/slingshot/SlingshotConstants.java
index d82e83d..da4d26c 100644
--- a/slingshot/src/main/java/org/apache/sling/sample/slingshot/SlingshotConstants.java
+++ b/slingshot/src/main/java/org/apache/sling/sample/slingshot/SlingshotConstants.java
@@ -34,6 +34,15 @@
     /** The resource type for a comment. */
     public static final String RESOURCETYPE_COMMENT = "slingshot/Comment";
 
+    /** The resource type for a rating. */
+    public static final String RESOURCETYPE_RATING = "slingshot/Rating";
+
+    /** The resource type for the resource holder of the comments. */
+    public static final String RESOURCETYPE_COMMENTS = "slingshot/Comments";
+
+    /** The resource type for the resource holder of the ratings */
+    public static final String RESOURCETYPE_RATINGS = "slingshot/Ratings";
+
     /** The resource type for a user. */
     public static final String RESOURCETYPE_HOME = "slingshot/Home";
 
diff --git a/slingshot/src/main/java/org/apache/sling/sample/slingshot/impl/AutomaticTaggingService.java b/slingshot/src/main/java/org/apache/sling/sample/slingshot/impl/AutomaticTaggingService.java
deleted file mode 100644
index 5da2cdf..0000000
--- a/slingshot/src/main/java/org/apache/sling/sample/slingshot/impl/AutomaticTaggingService.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * 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.sample.slingshot.impl;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Properties;
-import org.apache.felix.scr.annotations.Property;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.sling.api.resource.LoginException;
-import org.apache.sling.api.resource.ModifiableValueMap;
-import org.apache.sling.api.resource.PersistenceException;
-import org.apache.sling.api.resource.Resource;
-import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.api.resource.ResourceResolverFactory;
-import org.apache.sling.sample.slingshot.Constants;
-import org.apache.sling.sample.slingshot.SlingshotConstants;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventConstants;
-import org.osgi.service.event.EventHandler;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * This is a sample for observation which adds tags to new resources.
- */
-@Component(immediate=true)
-@Service(value=EventHandler.class)
-@Properties({
-   @Property(name="service.description",
-             value="Apache Sling - Slingshot Tagging Service"),
-   @Property(name=EventConstants.EVENT_TOPIC, value=org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_ADDED)
-})
-public class AutomaticTaggingService
-    implements EventHandler {
-
-    private final Logger logger = LoggerFactory.getLogger(this.getClass());
-
-    @Reference
-    private ResourceResolverFactory resourceResolverFactory;
-
-    private final Random random = new Random(System.currentTimeMillis());
-
-    private final String MIXIN_TYPE_PROPERTY = "jcr:mixinTypes";
-    private final String MIXIN_TYPE_PHOTO = "slingshot:Photo";
-
-    /**
-     * @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
-     */
-    public void handleEvent(final Event event) {
-        final String path = (String)event.getProperty(org.apache.sling.api.SlingConstants.PROPERTY_PATH);
-        if ( path != null && path.startsWith(Constants.ALBUMS_ROOT) ) {
-            ResourceResolver resolver = null;
-            try {
-                resolver = this.resourceResolverFactory.getAdministrativeResourceResolver(null);
-                final Resource r = resolver.getResource(path);
-                if ( r != null && r.isResourceType(Constants.RESOURCETYPE_PHOTO) ) {
-                    final ModifiableValueMap mvm = r.adaptTo(ModifiableValueMap.class);
-                    if ( mvm != null ) {
-                        String[] types = mvm.get(MIXIN_TYPE_PROPERTY, String[].class);
-                        if ( types == null ) {
-                            mvm.put(MIXIN_TYPE_PROPERTY, MIXIN_TYPE_PHOTO);
-                        } else {
-                            String[] newTypes = new String[types.length + 1];
-                            System.arraycopy(types, 0, newTypes, 0, types.length);
-                            newTypes[types.length] = MIXIN_TYPE_PHOTO;
-                            mvm.put(MIXIN_TYPE_PROPERTY, newTypes);
-                        }
-
-                        final int tagsValue = this.random.nextInt(8);
-                        final List<String> tags = new ArrayList<String>();
-                        if ( (tagsValue & 1) == 1 ) {
-                            tags.add("ApacheCon");
-                        }
-                        if ( (tagsValue & 2) == 2 ) {
-                            tags.add("Vacation");
-                        }
-                        if ( (tagsValue & 4) == 4 ) {
-                            tags.add("Cool");
-                        }
-                        mvm.put(SlingshotConstants.PROPERTY_TAGS, tags.toArray(new String[tags.size()]));
-                        try {
-                            resolver.commit();
-                        } catch (final PersistenceException e) {
-                            // we just ignore this for now
-                            logger.info("Unable to add tags to photo: " + r.getPath(), e);
-                        }
-                    }
-                }
-            } catch (final LoginException e) {
-                // this should never happen, therefore we ignore
-            } finally {
-                if ( resolver != null ) {
-                    resolver.close();
-                }
-            }
-        }
-    }
-}
diff --git a/slingshot/src/main/java/org/apache/sling/sample/slingshot/impl/CommentPostServlet.java b/slingshot/src/main/java/org/apache/sling/sample/slingshot/impl/CommentPostServlet.java
new file mode 100644
index 0000000..8528805
--- /dev/null
+++ b/slingshot/src/main/java/org/apache/sling/sample/slingshot/impl/CommentPostServlet.java
@@ -0,0 +1,123 @@
+/*
+ * 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.sample.slingshot.impl;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.servlet.ServletException;
+
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.sling.SlingServlet;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.servlets.SlingAllMethodsServlet;
+import org.apache.sling.sample.slingshot.SlingshotConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SlingServlet(methods="POST", resourceTypes=SlingshotConstants.RESOURCETYPE_COMMENTS)
+public class CommentPostServlet extends SlingAllMethodsServlet {
+
+    private static final long serialVersionUID = 1L;
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    @Reference
+    private ResourceResolverFactory factory;
+
+    @Override
+    protected void doPost(final SlingHttpServletRequest request,
+            final SlingHttpServletResponse response)
+    throws ServletException, IOException {
+        final String title = request.getParameter(SlingshotConstants.PROPERTY_TITLE);
+        final String description = request.getParameter(SlingshotConstants.PROPERTY_DESCRIPTION);
+
+        final String userId = request.getRemoteUser();
+
+        // TODO - check values
+
+        // save comment
+        ResourceResolver resolver = null;
+        try {
+            final Map<String, Object> loginmap = new HashMap<String, Object>();
+            loginmap.put(ResourceResolverFactory.USER_IMPERSONATION, userId);
+            resolver = factory.getAdministrativeResourceResolver(loginmap);
+
+            final Map<String, Object> properties = new HashMap<String, Object>();
+            properties.put(ResourceResolver.PROPERTY_RESOURCE_TYPE, SlingshotConstants.RESOURCETYPE_COMMENT);
+            properties.put(SlingshotConstants.PROPERTY_TITLE, title);
+            properties.put(SlingshotConstants.PROPERTY_DESCRIPTION, description);
+
+            // we try it five times
+            PersistenceException exception = null;
+            Resource newResource = null;
+            for(int i=0; i<5; i++) {
+                try {
+                    exception = null;
+                    final String name = ResourceUtil.createUniqueChildName(request.getResource(), Util.filter(title));
+                    newResource = resolver.create(request.getResource(), name, properties);
+
+                    resolver.commit();
+                    break;
+                } catch ( final PersistenceException pe) {
+                    resolver.revert();
+                    resolver.refresh();
+                    exception = pe;
+                }
+            }
+            if ( exception != null ) {
+                throw exception;
+            }
+            // order node at the top (if jcr based)
+            final Node newNode = newResource.adaptTo(Node.class);
+            if ( newNode != null ) {
+                try {
+                    final Node parent = newNode.getParent();
+                    final Node firstNode = parent.getNodes().nextNode();
+                    if ( !firstNode.getName().equals(newNode.getName()) ) {
+                        parent.orderBefore(newNode.getName(), firstNode.getName());
+                        newNode.getSession().save();
+                    }
+                } catch ( final RepositoryException re) {
+                    logger.error("Unable to order comment to the top", re);
+                }
+            }
+        } catch ( final LoginException le ) {
+            throw new ServletException("Unable to login", le);
+        } finally {
+            if ( resolver != null ) {
+                resolver.close();
+            }
+        }
+
+        // send redirect at the end
+        final String path = request.getResource().getParent().getPath();
+
+        response.sendRedirect(request.getContextPath() + path + ".html");
+    }
+
+}
diff --git a/slingshot/src/main/java/org/apache/sling/sample/slingshot/impl/RatingPostServlet.java b/slingshot/src/main/java/org/apache/sling/sample/slingshot/impl/RatingPostServlet.java
new file mode 100644
index 0000000..960638f
--- /dev/null
+++ b/slingshot/src/main/java/org/apache/sling/sample/slingshot/impl/RatingPostServlet.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
+ *
+ *      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.sample.slingshot.impl;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.sling.SlingServlet;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.servlets.SlingAllMethodsServlet;
+import org.apache.sling.sample.slingshot.SlingshotConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SlingServlet(methods="POST", resourceTypes=SlingshotConstants.RESOURCETYPE_RATINGS)
+public class RatingPostServlet extends SlingAllMethodsServlet {
+
+    private static final long serialVersionUID = 1L;
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    @Reference
+    private ResourceResolverFactory factory;
+
+    @Override
+    protected void doPost(final SlingHttpServletRequest request,
+            final SlingHttpServletResponse response)
+    throws ServletException, IOException {
+        final String title = request.getParameter(SlingshotConstants.PROPERTY_TITLE);
+        final String description = request.getParameter(SlingshotConstants.PROPERTY_DESCRIPTION);
+
+        final String userId = request.getRemoteUser();
+
+        // TODO - check values
+
+        // save comment
+        ResourceResolver resolver = null;
+        try {
+            final Map<String, Object> loginmap = new HashMap<String, Object>();
+            loginmap.put(ResourceResolverFactory.USER_IMPERSONATION, userId);
+            resolver = factory.getAdministrativeResourceResolver(loginmap);
+
+            final Map<String, Object> properties = new HashMap<String, Object>();
+            properties.put(ResourceResolver.PROPERTY_RESOURCE_TYPE, SlingshotConstants.RESOURCETYPE_COMMENT);
+            properties.put(SlingshotConstants.PROPERTY_TITLE, title);
+            properties.put(SlingshotConstants.PROPERTY_DESCRIPTION, description);
+
+            resolver.create(request.getResource(), "bla", properties);
+
+            resolver.commit();
+        } catch ( final LoginException le ) {
+            throw new ServletException("Unable to login", le);
+        } finally {
+            if ( resolver != null ) {
+                resolver.close();
+            }
+        }
+
+        // send redirect at the end
+        final String path = request.getResource().getParent().getPath();
+
+        response.sendRedirect(request.getContextPath() + path + ".html");
+    }
+
+}
diff --git a/slingshot/src/main/java/org/apache/sling/sample/slingshot/impl/SetupService.java b/slingshot/src/main/java/org/apache/sling/sample/slingshot/impl/SetupService.java
index d55f416..7484794 100644
--- a/slingshot/src/main/java/org/apache/sling/sample/slingshot/impl/SetupService.java
+++ b/slingshot/src/main/java/org/apache/sling/sample/slingshot/impl/SetupService.java
@@ -1,14 +1,33 @@
+/*
+ * 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.sample.slingshot.impl;
 
+import java.security.Principal;
 import java.util.HashMap;
 import java.util.Map;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
+import javax.jcr.security.Privilege;
 
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Reference;
+import org.apache.jackrabbit.api.security.principal.PrincipalManager;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.UserManager;
 import org.apache.sling.api.resource.LoginException;
@@ -40,6 +59,7 @@
             resolver = this.factory.getAdministrativeResourceResolver(null);
             setupUsers(resolver);
             setupContent(resolver);
+            setupACL(resolver);
         } finally {
             if ( resolver != null ) {
                 resolver.close();
@@ -48,6 +68,50 @@
         logger.info("Finished setting up SlingShot");
     }
 
+    private void setupACL(final ResourceResolver resolver) throws RepositoryException {
+        final Session session = resolver.adaptTo(Session.class);
+
+        for(final String principalId : USERS) {
+            final String resourcePath = SlingshotConstants.APP_ROOT_PATH + "/public/" + principalId;
+
+            final Map<String, String> privileges = new HashMap<String, String>();
+            privileges.put(Privilege.JCR_ALL, "granted");
+
+            modifyAce(session, resourcePath, principalId, Privilege.JCR_ALL, true);
+
+            privileges.clear();
+        }
+    }
+
+    private void modifyAce(final Session jcrSession,
+            final String resourcePath,
+            final String principalId,
+            final String privilege,
+            final boolean granted)
+    throws RepositoryException {
+        final PrincipalManager principalManager = AccessControlUtil.getPrincipalManager(jcrSession);
+        final Principal principal = principalManager.getPrincipal(principalId);
+
+        final String[] grantedPrivilegeNames;
+        final String[] deniedPrivilegeNames;
+        if ( granted ) {
+            grantedPrivilegeNames = new String[] {privilege};
+            deniedPrivilegeNames = null;
+        } else {
+            grantedPrivilegeNames = null;
+            deniedPrivilegeNames = new String[] {privilege};
+        }
+
+        AccessControlUtil.replaceAccessControlEntry(jcrSession, resourcePath, principal,
+                grantedPrivilegeNames,
+                deniedPrivilegeNames,
+                null,
+                null);
+        if (jcrSession.hasPendingChanges()) {
+            jcrSession.save();
+        }
+    }
+
     private void setupUsers(final ResourceResolver resolver) throws RepositoryException {
         final UserManager um = AccessControlUtil.getUserManager(resolver.adaptTo(Session.class));
         for(final String userName : USERS) {
diff --git a/slingshot/src/main/java/org/apache/sling/sample/slingshot/impl/Util.java b/slingshot/src/main/java/org/apache/sling/sample/slingshot/impl/Util.java
new file mode 100644
index 0000000..c0d069d
--- /dev/null
+++ b/slingshot/src/main/java/org/apache/sling/sample/slingshot/impl/Util.java
@@ -0,0 +1,54 @@
+/*
+ * 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.sample.slingshot.impl;
+
+public abstract class Util {
+
+    private static final String ALLOWED_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789_";
+    private static final char REPLACEMENT_CHAR = '_';
+
+    public static String filter(final String rsrcname) {
+        final StringBuilder sb  = new StringBuilder();
+        char lastAdded = 0;
+
+        final String name = rsrcname.toLowerCase();
+        for(int i=0; i < name.length(); i++) {
+            final char c = name.charAt(i);
+            char toAdd = c;
+
+            if (ALLOWED_CHARS.indexOf(c) < 0) {
+                if (lastAdded == REPLACEMENT_CHAR) {
+                    // do not add several _ in a row
+                    continue;
+                }
+                toAdd = REPLACEMENT_CHAR;
+
+            } else if(i == 0 && Character.isDigit(c)) {
+                sb.append(REPLACEMENT_CHAR);
+            }
+
+            sb.append(toAdd);
+            lastAdded = toAdd;
+        }
+
+        if (sb.length()==0) {
+            sb.append(REPLACEMENT_CHAR);
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/slingshot/src/main/resources/SLING-INF/content/libs/slingshot/Category/user.html.jsp b/slingshot/src/main/resources/SLING-INF/content/libs/slingshot/Category/user.html.jsp
index 63bf76a..a5d646f 100644
--- a/slingshot/src/main/resources/SLING-INF/content/libs/slingshot/Category/user.html.jsp
+++ b/slingshot/src/main/resources/SLING-INF/content/libs/slingshot/Category/user.html.jsp
@@ -27,28 +27,16 @@
     final ValueMap attr = resource.getValueMap();
     final String itemTitle = attr.get(SlingshotConstants.PROPERTY_TITLE, resource.getName());
     
-    String imagePath = null;
     int count = 0;
     for(final Resource current : resource.getChildren()) {
-        if ( current.isResourceType(SlingshotConstants.RESOURCETYPE_ITEM)) {
-            if ( imagePath == null ) {
-                final Resource imagesResource = resource.getResourceResolver().getResource(current, "images");
-                if ( imagesResource != null ) {
-                    for(final Resource imgResource : imagesResource.getChildren()) {
-                        imagePath = imgResource.getPath();
-                        break;
-                    }
-                }
-            }
-        }
         count++;
     }
 %><div class="tile double ui-slingshot-clickable" data-link="<%= request.getContextPath() %><%=resource.getPath()%>.html">
-    <div class="tile-content image">
-        <img src="<%= request.getContextPath() %><%=imagePath%>"/>
+    <div class="tile-content icon">
+        <i class="icon-pictures fg-blue"></i>
     </div>
     <div class="brand">
-        <span class="label fg-white"><%= ResponseUtil.escapeXml(itemTitle) %></span>
+        <span class="label fg-black"><%= ResponseUtil.escapeXml(itemTitle) %></span>
         <span class="badge bg-orange"><%= count %></span>
     </div>
 </div>
diff --git a/slingshot/src/main/resources/SLING-INF/content/libs/slingshot/Component/head.html.jsp b/slingshot/src/main/resources/SLING-INF/content/libs/slingshot/Component/head.html.jsp
index 561cace..34f4e6e 100644
--- a/slingshot/src/main/resources/SLING-INF/content/libs/slingshot/Component/head.html.jsp
+++ b/slingshot/src/main/resources/SLING-INF/content/libs/slingshot/Component/head.html.jsp
@@ -22,6 +22,7 @@
   <link href="<%= request.getContextPath() %><%= SlingshotConstants.APP_ROOT_PATH %>/resources/css/jquery-ui.theme.min.css" rel="stylesheet"/>
   <link href="<%= request.getContextPath() %><%= SlingshotConstants.APP_ROOT_PATH %>/resources/css/metro-bootstrap.min.css" rel="stylesheet"/>
   <link href="<%= request.getContextPath() %><%= SlingshotConstants.APP_ROOT_PATH %>/resources/css/jquery-breadcrumbs.css" rel="stylesheet"/>
+  <link href="<%= request.getContextPath() %><%= SlingshotConstants.APP_ROOT_PATH %>/resources/css/iconFont.css" rel="stylesheet"/>
   <link href="<%= request.getContextPath() %><%= SlingshotConstants.APP_ROOT_PATH %>/resources/css/slingshot.css" rel="stylesheet"/>
   <script src="<%= request.getContextPath() %><%= SlingshotConstants.APP_ROOT_PATH %>/resources/js/jquery.js" type="text/javascript" ></script>
   <script src="<%= request.getContextPath() %><%= SlingshotConstants.APP_ROOT_PATH %>/resources/js/jquery-ui.min.js" type="text/javascript" ></script>
diff --git a/slingshot/src/main/resources/SLING-INF/content/libs/slingshot/Item/comments.html.jsp b/slingshot/src/main/resources/SLING-INF/content/libs/slingshot/Item/comments.html.jsp
index a2c84c8..6f81e72 100644
--- a/slingshot/src/main/resources/SLING-INF/content/libs/slingshot/Item/comments.html.jsp
+++ b/slingshot/src/main/resources/SLING-INF/content/libs/slingshot/Item/comments.html.jsp
@@ -42,10 +42,7 @@
     if ( slingRequest.getAuthType() != null ) {
         %>
         <hr/><p>Leave a comment...</p>
-        <form method="POST" action="<%= request.getContextPath() %><%=resource.getName() %>/comments/*">
-        <input type="hidden" name="sling:resourceType" value="<%= SlingshotConstants.RESOURCETYPE_COMMENT %>"/>
-        <input type="hidden" name=":order" value="first"/>
-        <input type="hidden" name=":redirect" value="<%= request.getContextPath() %><%=resource.getPath() %>.html"/>
+        <form method="POST" action="<%= request.getContextPath() %><%=resource.getName() %>/comments">
         <p>Title: <input name="<%= SlingshotConstants.PROPERTY_TITLE %>"/></p>
         <p>Description: <input name="<%= SlingshotConstants.PROPERTY_DESCRIPTION %>"/></p>
         <button class="ui-button ui-form-button" type="submit">Add</button>
diff --git a/slingshot/src/main/resources/SLING-INF/content/libs/slingshot/Tag/html.jsp b/slingshot/src/main/resources/SLING-INF/content/libs/slingshot/Tag/html.jsp
deleted file mode 100644
index 0076eb7..0000000
--- a/slingshot/src/main/resources/SLING-INF/content/libs/slingshot/Tag/html.jsp
+++ /dev/null
@@ -1,53 +0,0 @@
-<%--
-    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.
---%><%@page session="false" %><%
-%><%@page import="org.apache.sling.api.resource.Resource,
-                org.apache.sling.api.resource.ResourceResolver,
-                org.apache.sling.api.resource.ResourceUtil,
-                org.apache.sling.api.resource.ValueMap,
-                org.apache.sling.api.request.ResponseUtil,
-                org.apache.sling.sample.slingshot.Constants,
-                java.util.Iterator"%><%
-%><%@taglib prefix="sling" uri="http://sling.apache.org/taglibs/sling/1.0" %><%
-%><sling:defineObjects/><%
-    final ValueMap attributes = ResourceUtil.getValueMap(resource);
-    final String name = resource.getName();
-    final ResourceResolver resolver = resource.getResourceResolver();
-%><html>
-  <head>
-    <title>Tag <%= ResponseUtil.escapeXml(name) %></title>
-  </head>
-  <body>
-<h1>Tag <%=ResponseUtil.escapeXml(name) %></h1>
-<div class="photolist">
-<%
-    int i = 0;
-    final Iterator<Resource> fi = resolver.findResources("SELECT * FROM nt:file WHERE jcr:path LIKE '/slingshot/albums/%' AND slingshot:tags='" + name + "'", "sql");
-    while ( fi.hasNext()) {
-        final Resource current = fi.next();
-        if ( current.isResourceType(Constants.RESOURCETYPE_PHOTO) 
-                && Constants.includeAsMedia(current)) {
-          %>
-        <sling:include resource="<%= current %>" resourceType="slingshot/Photo" replaceSelectors="main"/>
-          <%
-        }
-    }
-%>
-</div>
-</body>
-</html>
\ No newline at end of file
diff --git a/slingshot/src/main/resources/SLING-INF/content/slingshot/public/slingshot1/travel/home.json b/slingshot/src/main/resources/SLING-INF/content/slingshot/public/slingshot1/travel/home.json
index 100c49f..2e9fabc 100644
--- a/slingshot/src/main/resources/SLING-INF/content/slingshot/public/slingshot1/travel/home.json
+++ b/slingshot/src/main/resources/SLING-INF/content/slingshot/public/slingshot1/travel/home.json
@@ -2,5 +2,14 @@
     "jcr:primaryType":"sling:OrderedFolder",
     "sling:resourceType":"slingshot/Item",
     "title":"Home Sweet Home",
-    "description":"This is the place where I live..."
+    "description":"This is the place where I live...",
+    
+    comments : {
+      "jcr:primaryType":"sling:OrderedFolder",
+      "sling:resourceType":"slingshot/Comments",
+    },
+    ratings : {
+      "jcr:primaryType":"sling:OrderedFolder",
+      "sling:resourceType":"slingshot/Ratings",
+    }
 }
\ No newline at end of file
diff --git a/slingshot/src/main/resources/SLING-INF/content/slingshot/public/slingshot1/travel/pet.json b/slingshot/src/main/resources/SLING-INF/content/slingshot/public/slingshot1/travel/pet.json
index 34e8f4b..0ced92b 100644
--- a/slingshot/src/main/resources/SLING-INF/content/slingshot/public/slingshot1/travel/pet.json
+++ b/slingshot/src/main/resources/SLING-INF/content/slingshot/public/slingshot1/travel/pet.json
@@ -2,5 +2,14 @@
     "jcr:primaryType":"sling:OrderedFolder",
     "sling:resourceType":"slingshot/Item",
     "title":"My Pet",
-    "description":"Always hungry...but never gets fat"
+    "description":"Always hungry...but never gets fat",
+
+    comments : {
+      "jcr:primaryType":"sling:OrderedFolder",
+      "sling:resourceType":"slingshot/Comments",
+    },
+    ratings : {
+      "jcr:primaryType":"sling:OrderedFolder",
+      "sling:resourceType":"slingshot/Ratings",
+    }
 }
diff --git a/slingshot/src/main/resources/SLING-INF/content/slingshot/public/slingshot1/travel/sea.json b/slingshot/src/main/resources/SLING-INF/content/slingshot/public/slingshot1/travel/sea.json
index 02785d8..04e034a 100644
--- a/slingshot/src/main/resources/SLING-INF/content/slingshot/public/slingshot1/travel/sea.json
+++ b/slingshot/src/main/resources/SLING-INF/content/slingshot/public/slingshot1/travel/sea.json
@@ -2,5 +2,14 @@
     "jcr:primaryType":"sling:OrderedFolder",
     "sling:resourceType":"slingshot/Item",
     "title":"The Sea",
-    "description":"You can see my boat in the distance"
+    "description":"You can see my boat in the distance",
+
+    comments : {
+      "jcr:primaryType":"sling:OrderedFolder",
+      "sling:resourceType":"slingshot/Comments",
+    },
+    ratings : {
+      "jcr:primaryType":"sling:OrderedFolder",
+      "sling:resourceType":"slingshot/Ratings",
+    }
 }
\ No newline at end of file
diff --git a/slingshot/src/main/resources/SLING-INF/content/slingshot/public/slingshot1/travel/view.json b/slingshot/src/main/resources/SLING-INF/content/slingshot/public/slingshot1/travel/view.json
index 9f89233..f34085b 100644
--- a/slingshot/src/main/resources/SLING-INF/content/slingshot/public/slingshot1/travel/view.json
+++ b/slingshot/src/main/resources/SLING-INF/content/slingshot/public/slingshot1/travel/view.json
@@ -2,5 +2,14 @@
     "jcr:primaryType":"sling:OrderedFolder",
     "sling:resourceType":"slingshot/Item",
     "title":"Nice View",
-    "description":"A view from the top of my home"
+    "description":"A view from the top of my home",
+
+    comments : {
+      "jcr:primaryType":"sling:OrderedFolder",
+      "sling:resourceType":"slingshot/Comments",
+    },
+    ratings : {
+      "jcr:primaryType":"sling:OrderedFolder",
+      "sling:resourceType":"slingshot/Ratings",
+    }    
 }
\ No newline at end of file
diff --git a/slingshot/src/main/resources/SLING-INF/content/slingshot/tags.json b/slingshot/src/main/resources/SLING-INF/content/slingshot/tags.json
deleted file mode 100644
index 577dd3f..0000000
--- a/slingshot/src/main/resources/SLING-INF/content/slingshot/tags.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-    "jcr:primaryType":"sling:Folder",
-    "ApacheCon" : {
-        "jcr:primaryType":"sling:Folder",
-        "description":"Photos from the ApacheCons."
-    },
-    "Vacation" : {
-        "jcr:primaryType":"sling:Folder",
-        "description":"Photos shot during vacations."
-    },
-    "Cool" : {
-        "jcr:primaryType":"sling:Folder",
-        "description":"Very cool photos."
-    }
-}
\ No newline at end of file