/*
 * 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.jackrabbit.usermanager.impl.resource;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.servlet.http.HttpServletRequest;
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.Service;
import org.apache.jackrabbit.api.security.principal.PrincipalIterator;
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.SlingException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceProvider;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.SyntheticResource;
import org.apache.sling.jcr.base.util.AccessControlUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * Resource Provider implementation for jackrabbit UserManager resources.
 */
@Component (immediate=true, metatype=true,
    label="%authorizable.resourceprovider.name",
    description="%authorizable.resourceprovider.description")
@Service (value=ResourceProvider.class)
@Properties ({
  @Property (name="service.description", 
      value="Resource provider implementation for UserManager resources"),
  @Property (name="service.vendor",
      value="The Apache Software Foundation"),
  @Property (name="provider.roots",
      value="/system/userManager/")      
})
public class AuthorizableResourceProvider implements ResourceProvider {
    /**
     * default log
     */
    private final Logger log = LoggerFactory.getLogger(getClass());
    public static final String SYSTEM_USER_MANAGER_PATH = "/system/userManager";
    public static final String SYSTEM_USER_MANAGER_USER_PATH = SYSTEM_USER_MANAGER_PATH
        + "/user";
    public static final String SYSTEM_USER_MANAGER_GROUP_PATH = SYSTEM_USER_MANAGER_PATH
        + "/group";
    public static final String SYSTEM_USER_MANAGER_USER_PREFIX = SYSTEM_USER_MANAGER_USER_PATH
        + "/";
    public static final String SYSTEM_USER_MANAGER_GROUP_PREFIX = SYSTEM_USER_MANAGER_GROUP_PATH
        + "/";
    /*
     * (non-Javadoc)
     * @see
     * org.apache.sling.api.resource.ResourceProvider#getResource(org.apache
     * .sling.api.resource.ResourceResolver,
     * javax.servlet.http.HttpServletRequest, java.lang.String)
     */
    public Resource getResource(ResourceResolver resourceResolver,
            HttpServletRequest request, String path) {
        return getResource(resourceResolver, path);
    }
    /*
     * (non-Javadoc)
     * @see
     * org.apache.sling.api.resource.ResourceProvider#getResource(org.apache
     * .sling.api.resource.ResourceResolver, java.lang.String)
     */
    public Resource getResource(ResourceResolver resourceResolver, String path) {
        // handle resources for the virtual container resources
        if (path.equals(SYSTEM_USER_MANAGER_PATH)) {
            return new SyntheticResource(resourceResolver, path,
                "sling/userManager");
        } else if (path.equals(SYSTEM_USER_MANAGER_USER_PATH)) {
            return new SyntheticResource(resourceResolver, path, "sling/users");
        } else if (path.equals(SYSTEM_USER_MANAGER_GROUP_PATH)) {
            return new SyntheticResource(resourceResolver, path, "sling/groups");
        }
        // the principalId should be the first segment after the prefix
        String pid = null;
        if (path.startsWith(SYSTEM_USER_MANAGER_USER_PREFIX)) {
            pid = path.substring(SYSTEM_USER_MANAGER_USER_PREFIX.length());
        } else if (path.startsWith(SYSTEM_USER_MANAGER_GROUP_PREFIX)) {
            pid = path.substring(SYSTEM_USER_MANAGER_GROUP_PREFIX.length());
        }
        if (pid != null) {
            if (pid.indexOf('/') != -1) {
                return null; // something bogus on the end of the path so bail
                             // out now.
            }
            try {
                Session session = resourceResolver.adaptTo(Session.class);
                if (session != null) {
                    UserManager userManager = AccessControlUtil.getUserManager(session);
                    if (userManager != null) {
                        Authorizable authorizable = userManager.getAuthorizable(pid);
                        if (authorizable != null) {
                            // found the Authorizable, so return the resource
                            // that wraps it.
                            return new AuthorizableResource(authorizable,
                                resourceResolver, path);
                        }
                    }
                }
            } catch (RepositoryException re) {
                throw new SlingException(
                    "Error looking up Authorizable for principal: " + pid, re);
            }
        }
        return null;
    }
    /*
     * (non-Javadoc)
     * @see
     * org.apache.sling.api.resource.ResourceProvider#listChildren(org.apache
     * .sling.api.resource.Resource)
     */
    public Iterator<Resource> listChildren(Resource parent) {
        if (parent == null) {
            throw new NullPointerException("parent is null");
        }
        try {
            String path = parent.getPath();
            ResourceResolver resourceResolver = parent.getResourceResolver();
            // handle children of /system/userManager
            if (SYSTEM_USER_MANAGER_PATH.equals(path)) {
                List<Resource> resources = new ArrayList<Resource>();
                if (resourceResolver != null) {
                    resources.add(getResource(resourceResolver,
                        SYSTEM_USER_MANAGER_USER_PATH));
                    resources.add(getResource(resourceResolver,
                        SYSTEM_USER_MANAGER_GROUP_PATH));
                }
                return resources.iterator();
            }
            int searchType = -1;
            if (SYSTEM_USER_MANAGER_USER_PATH.equals(path)) {
                searchType = PrincipalManager.SEARCH_TYPE_NOT_GROUP;
            } else if (SYSTEM_USER_MANAGER_GROUP_PATH.equals(path)) {
                searchType = PrincipalManager.SEARCH_TYPE_GROUP;
            }
            if (searchType != -1) {
                PrincipalIterator principals = null;
                Session session = resourceResolver.adaptTo(Session.class);
                if (session != null) {
                    PrincipalManager principalManager = AccessControlUtil.getPrincipalManager(session);
                    principals = principalManager.getPrincipals(searchType);
                }
                if (principals != null) {
                    return new ChildrenIterator(parent, principals);
                }
            }
        } catch (RepositoryException re) {
            throw new SlingException("Error listing children of resource: "
                + parent.getPath(), re);
        }
        return null;
    }
    private final class ChildrenIterator implements Iterator<Resource> {
        private PrincipalIterator principals;
        private Resource parent;
        public ChildrenIterator(Resource parent, PrincipalIterator principals) {
            this.parent = parent;
            this.principals = principals;
        }
        public boolean hasNext() {
            return principals.hasNext();
        }
        public Resource next() {
            Principal nextPrincipal = principals.nextPrincipal();
            try {
                ResourceResolver resourceResolver = parent.getResourceResolver();
                if (resourceResolver != null) {
                    Session session = resourceResolver.adaptTo(Session.class);
                    if (session != null) {
                        UserManager userManager = AccessControlUtil.getUserManager(session);
                        if (userManager != null) {
                            Authorizable authorizable = userManager.getAuthorizable(nextPrincipal.getName());
                            if (authorizable != null) {
                                String path;
                                if (authorizable.isGroup()) {
                                    path = SYSTEM_USER_MANAGER_GROUP_PREFIX
                                        + nextPrincipal.getName();
                                } else {
                                    path = SYSTEM_USER_MANAGER_USER_PREFIX
                                        + nextPrincipal.getName();
                                }
                                return new AuthorizableResource(authorizable,
                                    resourceResolver, path);
                            }
                        }
                    }
                }
            } catch (RepositoryException re) {
                log.error("Exception while looking up authorizable resource.",
                    re);
            }
            return null;
        }
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
}