org.esupportail.publisher.service.bean.UserContextTree.java Source code

Java tutorial

Introduction

Here is the source code for org.esupportail.publisher.service.bean.UserContextTree.java

Source

/**
 * Copyright (C) 2014 Esup Portail http://www.esup-portail.org
 * @Author (C) 2012 Julien Gribonvald <julien.gribonvald@recia.fr>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *                 http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 *
 */
package org.esupportail.publisher.service.bean;

import java.util.Map;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Named;
import javax.validation.constraints.NotNull;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.mysema.commons.lang.Pair;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.esupportail.publisher.domain.ContextKey;
import org.esupportail.publisher.domain.SubjectKey;
import org.esupportail.publisher.domain.enums.ContextType;
import org.esupportail.publisher.domain.enums.PermissionType;
import org.esupportail.publisher.domain.enums.SubjectType;
import org.esupportail.publisher.service.factories.CompositeKeyDTOFactory;
import org.esupportail.publisher.web.rest.dto.PermOnCtxDTO;
import org.esupportail.publisher.web.rest.dto.PermissionDTO;
import org.esupportail.publisher.web.rest.dto.SubjectKeyDTO;
import org.joda.time.Duration;
import org.joda.time.Instant;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

/**
 * @author GIP RECIA - Julien Gribonvald
 *
 *         Should be used as session bean
 */
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Slf4j
public class UserContextTree {

    private Map<ContextKey, UserContextInfos> contexts = Maps.newConcurrentMap();

    @Inject
    @Named("subjectKeyDTOFactoryImpl")
    private CompositeKeyDTOFactory<SubjectKeyDTO, SubjectKey, String, SubjectType> subjectKeyConverter;

    @Setter
    private Boolean superAdmin;

    @Getter
    private PermissionType upperPerm;

    private boolean userTreeLoaded;

    /**
     * Estimated duration before a reload can be done, here 10 seconds.
     * It's important for performance to avoid too much useless reload.
     * More several reload are made when the user connect to to several async call
     * requesting user permissions and contexts
     * */
    private Duration duration = new Duration(10000);
    private volatile Instant expiringInstant;
    private volatile boolean loadingInProgress = false;

    public UserContextTree() {
        super();
    }

    /**
     * To construct the tree use this add on all context.
     *
     * @param ctx
     *            the context to add discovered from the parent.
     * @param parent
     *            The parent from where the context was loaded
     * @param childs
     *            All childs of the context,
     * @param perms
     *            The upper Role that the user has.
     */
    public synchronized void addCtx(@NotNull final ContextKey ctx, final boolean isLastNode,
            final ContextKey parent, final Set<ContextKey> childs, final PermissionDTO perms,
            final PermissionType permType) {
        if (!isCtxLoaded(ctx)) {
            UserContextInfos current = new UserContextInfos();
            if (ctx instanceof OwnerContextKey && ((OwnerContextKey) ctx).getOwner() != null) {
                current.setOwner(subjectKeyConverter.convertToDTOKey(((OwnerContextKey) ctx).getOwner()));
            }
            if (childs != null) {
                current.setChilds(Sets.newConcurrentHashSet(childs));
            }
            current.setPerms(perms, permType);
            current.setLastLeaf(isLastNode);
            if (!ctx.getKeyType().equals(ContextType.ORGANIZATION)) {
                Assert.isTrue(parent != null && isCtxLoaded(parent), "Context " + ctx.toString()
                        + " doesn't have a parent = '" + parent.toString() + "' or parent isn't loaded !");
                contexts.put(ctx, current);
                this.linkToParent(ctx, parent);
            } else {
                contexts.put(ctx, current);
            }
        } else if (!ctx.getKeyType().equals(ContextType.ORGANIZATION)) {
            Assert.isTrue(parent != null && isCtxLoaded(parent), "Context " + ctx.toString()
                    + " doesn't have a parent = '" + parent.toString() + "' or parent isn't loaded !");
            this.linkToParent(ctx, parent);
        }
    }

    public void addCtx(@NotNull final ContextKey ctx, final boolean isLastNode, final ContextKey parent,
            final Set<ContextKey> childs, final PermOnCtxDTO perms) {
        if (perms != null) {
            this.addCtx(ctx, isLastNode, parent, childs, perms, perms.getRole());
        } else {
            this.addCtx(ctx, isLastNode, parent, childs, null, null);
        }
    }

    //    /**
    //     * Must be used when a user create a new context, and to be able to see it
    //     * @param ctx
    //     * @param isLastNode
    //     * @param parent
    //     */
    //    public void addCreatedCtx(@NotNull final ContextKey ctx,final boolean isLastNode,
    //                       final ContextKey parent) {
    //        final Pair<PermissionType, PermissionDTO> perms = this.getPermsFromContextTree(parent);
    //        this.addCtx(ctx, isLastNode, parent, null, null, null);
    //    }

    /**
     * There mustn't have childs in the context to remove.
     *
     * @param ctx
     *              the context to remove.
     */
    public synchronized void removeCtx(final ContextKey ctx) {
        if (contexts.containsKey(ctx)) {
            UserContextInfos current = contexts.get(ctx);
            Assert.isTrue(current.getChilds() == null || current.getChilds().isEmpty(),
                    "Can't remove context as it contains childs !");
            contexts.remove(ctx);
            for (ContextKey parent : current.getParents()) {
                contexts.get(parent).getChilds().remove(ctx);
            }
        }
    }

    public synchronized void removeLinkToParent(final ContextKey ctx, final ContextKey parent) {
        Assert.isTrue(isCtxLoaded(ctx), "Context wasn't loaded !");
        Assert.isTrue(isCtxLoaded(parent), "Context parent wasn't loaded !");
        UserContextInfos currentInfos = contexts.get(ctx);
        UserContextInfos parentInfos = contexts.get(parent);
        if (currentInfos.getParents().contains(parent)) {
            Assert.isTrue(currentInfos.getParents().size() > 1, "Context will become orphaned !");
            currentInfos.getParents().remove(parent);
            parentInfos.getChilds().remove(ctx);
            // process parent role to update after removing parent
            currentInfos.initParentsPerms();
            for (ContextKey parentKey : currentInfos.getParents()) {
                UserContextInfos uci = contexts.get(parentKey);
                currentInfos.setParentsPerms(uci.getPermsObject(), uci.getPermsType());
            }
        }
    }

    /**
     * Return the upper role after checking recursively the parent tree. All the
     * tree must be loaded.
     *
     * @param ctx
     * @return
     */
    public PermissionType getRoleFromContextTree(@NotNull final ContextKey ctx) {
        if (userTreeLoaded) {
            if (this.superAdmin)
                return PermissionType.ADMIN;
            if (contexts.containsKey(ctx))
                return contexts.get(ctx).getPermsType();
            log.warn("Call getRoleFromContextTree - ContextKey {} not found in User Tree !", ctx);
        } else {
            log.warn("Call getRoleFromContextTree - User Tree is not loaded !");
        }
        return null;
    }

    public Pair<PermissionType, PermissionDTO> getPermsFromContextTree(@NotNull final ContextKey ctx) {
        if (userTreeLoaded) {
            if (this.superAdmin)
                return new Pair<>(PermissionType.ADMIN, null);
            if (contexts.containsKey(ctx)) {
                UserContextInfos infos = contexts.get(ctx);
                return new Pair<>(infos.getPermsType(), infos.getPermsObject());
            }
            log.warn("Call getPermsFromContextTree - ContextKey {} not found in User Tree !", ctx);
        } else {
            log.warn("Call getPermsFromContextTree - User Tree is not loaded !");
        }
        return null;
    }

    public boolean isItemOwner(final long itemId, final SubjectKeyDTO userId) {
        final ContextKey ctx = new ContextKey(itemId, ContextType.ITEM);
        if (userTreeLoaded && userId != null) {
            UserContextInfos infos = contexts.get(ctx);
            if (infos != null && userId.equals(infos.getOwner()))
                return true;
        } else {
            log.warn("Call isItemOwner - User Tree is not loaded or userId null !");
        }
        return false;
    }

    /*public boolean hasRoleAnyWhere (final PermissionType permissionType) {
    if (userTreeLoaded && permissionType != null) {
        for (Map.Entry<ContextKey, UserContextInfos> ctx : contexts.entrySet()) {
            if (!ctx.getKey().getKeyType().equals(ContextType.ITEM) && ctx.getValue() != null && ctx.getValue().getPermsType().getMask() >= permissionType.getMask()) {
                return true;
            }
        }
    } else {
        log.warn("Call hasRole - User Tree is not loaded or parameter permissionType is null !");
    }
    return false;
    }*/

    public Set<ContextKey> getChildsOfContext(@NotNull final ContextKey ctx) {
        if (userTreeLoaded && contexts.containsKey(ctx)) {
            return Sets.newHashSet(contexts.get(ctx).getChilds());
        } else {
            log.warn("Call getChildsOfContext - User Tree is not loaded or context is unknown !");
        }
        return null;
    }

    public Set<ContextKey> getParentsOfContext(@NotNull final ContextKey ctx) {
        if (userTreeLoaded && contexts.containsKey(ctx)) {
            return Sets.newHashSet(contexts.get(ctx).getParents());
        } else {
            log.warn("Call getParentsOfContext - User Tree is not loaded or context is unknown !");
        }
        return null;
    }

    public Set<Long> getAllAuthorizedContextIdsOfType(@NotNull final ContextType contextType,
            @NotNull final PermissionType permissionType) {
        log.debug("Call getAllAuthorizedContextIdsOfType with params ctxType {}, permType {}", contextType,
                permissionType);
        if (userTreeLoaded) {
            Set<Long> ctxs = Sets.newHashSet();
            for (Map.Entry<ContextKey, UserContextInfos> entry : this.contexts.entrySet()) {
                log.debug("watch for ctx {} with perms {}", entry.getKey(), entry.getValue());
                if (contextType.equals(entry.getKey().getKeyType()) && entry.getValue().getPermsType() != null
                        && permissionType.getMask() <= entry.getValue().getPermsType().getMask()) {
                    log.debug("Authorized context!");
                    ctxs.add(entry.getKey().getKeyId());
                }
            }
            return ctxs;
        } else {
            log.warn("Call getAllAuthorizedContextIdsOfType - User Tree is not loaded !");
        }
        return null;
    }

    public Pair<ContextType, Set<Long>> getChildsOfContext(@NotNull final ContextKey ctxKey,
            @NotNull final PermissionType permissionType) {
        if (userTreeLoaded && contexts.containsKey(ctxKey)) {
            Set<Long> ctxs = Sets.newHashSet();
            ContextType type = null;
            log.debug("getChildsOfContext with params ctxKey {}, permissionType {}", ctxKey, permissionType);
            for (ContextKey ctx : contexts.get(ctxKey).getChilds()) {
                Assert.notNull(ctx.getKeyId(), "A context must not have a null ID");
                Assert.notNull(ctx.getKeyType(), "A context must not have a null Type");
                // case of unclassified items associated to Organization we go to next iteration
                if (ContextType.ITEM.equals(ctx.getKeyType())
                        && ContextType.ORGANIZATION.equals(ctxKey.getKeyType())) {
                    continue;
                }
                UserContextInfos infos = contexts.get(ctx);
                log.debug("Checking on ctx {} with contextInfos {}", ctx, infos);
                if (type == null) {
                    type = ctx.getKeyType();
                } else if (!ctx.getKeyType().equals(type)) {
                    log.warn("Context {} has childs of different ContextType !", ctxKey);
                    return null;
                }
                if (infos.getPermsType() != null && permissionType.getMask() <= infos.getPermsType().getMask()) {
                    ctxs.add(ctx.getKeyId());
                    log.debug("Adding child !");
                }
            }
            return new Pair<ContextType, Set<Long>>(type, ctxs);
        } else {
            log.warn("Call getChildsOfContext - User Tree is not loaded or context is unknown !");
        }
        return null;
    }

    public boolean hasChildsOnContext(@NotNull final ContextKey ctxKey, final PermissionType permissionType) {
        PermissionType perm = permissionType;
        if (permissionType == null)
            perm = PermissionType.LOOKOVER;
        if (userTreeLoaded && contexts.containsKey(ctxKey)) {
            Set<Long> ctxs = Sets.newHashSet();
            for (ContextKey ctx : contexts.get(ctxKey).getChilds()) {
                Assert.notNull(ctx.getKeyId(), "A context must not have a null ID");
                Assert.notNull(ctx.getKeyType(), "A context must not have a null Type");
                UserContextInfos infos = contexts.get(ctx);
                if (infos.getPermsType() != null && perm.getMask() <= infos.getPermsType().getMask()) {
                    ctxs.add(ctx.getKeyId());
                }
            }
            if (!ctxs.isEmpty())
                return true;
        } else {
            log.warn("Call hasChildsOnContext - User Tree is not loaded or context is unknown !");
        }
        return false;
    }

    public Boolean contextContainsItems(@NotNull final ContextKey ctxKey) {
        if (userTreeLoaded && contexts.containsKey(ctxKey)) {
            UserContextInfos infos = contexts.get(ctxKey);
            return infos.isLastLeaf();
        }
        return null;
    }

    private void linkToParent(final ContextKey ctx, final ContextKey parent) {
        Assert.isTrue(isCtxLoaded(ctx), "Context wasn't loaded !");
        Assert.isTrue(isCtxLoaded(parent), "Context parent wasn't loaded !");
        UserContextInfos currentInfos = contexts.get(ctx);
        UserContextInfos parentInfos = contexts.get(parent);
        currentInfos.getParents().add(parent);
        parentInfos.getChilds().add(ctx);
        currentInfos.setParentsPerms(parentInfos.getPermsObject(), parentInfos.getPermsType());
    }

    public boolean isCtxLoaded(final ContextKey ctx) {
        return contexts.containsKey(ctx) && contexts.get(ctx) != null;
    }

    public boolean isFilteredTree() {
        return superAdmin != null && !superAdmin;
    }

    public boolean isTreeLoaded() {
        return superAdmin != null && userTreeLoaded;
    }

    public boolean isTreeLoadInProgress() {
        return loadingInProgress;
    }

    public boolean loadingCanBeDone() {
        return (this.expiringInstant == null || this.expiringInstant.isBeforeNow()) && !this.loadingInProgress;
    }

    public void processingLoading() {
        loadingInProgress = true;
        contexts.clear();
        superAdmin = null;
        userTreeLoaded = false;
        log.debug("============ >>>>>>> processingLoading");
    }

    public void notifyEndLoading() {
        userTreeLoaded = true;
        // we set this expiration time to avoid to make several reload at the same time
        expiringInstant = new Instant().plus(duration);
        loadingInProgress = false;
        log.debug("============ >>>>>>> notifyEndLoading");
    }

    private class UserContextInfos {
        private PermissionDTO perms;
        private PermissionDTO parentsPerms;
        //@Setter
        private PermissionType permType;
        private PermissionType parentsPermType;
        @Setter
        @Getter
        private SubjectKeyDTO owner;

        @Getter
        @Setter
        private Set<ContextKey> parents = Sets.newConcurrentHashSet();
        @Getter
        @Setter
        private Set<ContextKey> childs = Sets.newConcurrentHashSet();

        // helper to know if the node is the last, and contains directly items
        @Getter
        @Setter
        private boolean isLastLeaf = false;

        public UserContextInfos() {
            super();
        }

        public PermissionType getPermsType() {
            if (parentsPermType == null || permType != null && permType.getMask() > parentsPermType.getMask()) {
                return permType;
            }
            return parentsPermType;
        }

        /**
         * warning permObject can be null when permissions are obtained from childs
         * @return
         */
        public PermissionDTO getPermsObject() {
            if (parentsPermType == null || permType != null && permType.getMask() > parentsPermType.getMask()) {
                return perms;
            }
            return parentsPerms;
        }

        public void setPerms(final PermOnCtxDTO perms) {
            this.perms = perms;
            this.permType = perms.getRole();
        }

        public void setPerms(final PermissionDTO perms, final PermissionType permType) {
            this.perms = perms;
            this.permType = permType;
            setUpperPerm(permType);
        }

        private void setUpperPerm(final PermissionType permType) {
            if (permType != null && (upperPerm == null || upperPerm.getMask() < permType.getMask())) {
                upperPerm = permType;
            }
        }

        public void initParentsPerms() {
            this.parentsPerms = null;
            this.parentsPermType = null;
        }

        //        public void setParentsPerms(final PermOnCtxDTO parentsPerms) {
        //            if (parentsPerms != null && (this.parentsPerms == null || parentsPerms.getRole().getMask() > this.parentsPermType.getMask())) {
        //                this.parentsPerms = parentsPerms;
        //                this.parentsPermType = parentsPerms.getRole();
        //            }
        //        }

        public void setParentsPerms(final PermissionDTO parentsPerms, final PermissionType permType) {
            if (parentsPerms != null
                    && (this.parentsPerms == null || permType.getMask() > this.parentsPermType.getMask())) {
                this.parentsPerms = parentsPerms;
                this.parentsPermType = permType;
                setUpperPerm(permType);
            }
        }

        @Override
        public String toString() {
            return "UserContextInfos{" + "permsType=" + permType + ", parentsPermType=" + parentsPermType
                    + ", isLastLeaf=" + isLastLeaf + ", owner='" + owner + '\'' + ", perms="
                    + ((perms != null) ? perms.toStringLite() : null) + ", parentsPerms="
                    + ((parentsPerms != null) ? parentsPerms.toStringLite() : null) + '}';
        }

        // private boolean childsLoaded;
        //
        // private boolean permsLoaded;
    }

    @Override
    public String toString() {
        StringBuilder str = new StringBuilder("UserContextTree{" + "superAdmin=" + superAdmin + ", userTreeLoaded="
                + userTreeLoaded + ", contexts= [");

        for (Map.Entry<ContextKey, UserContextInfos> entry : contexts.entrySet()) {
            str.append("\n{").append(entry.getKey()).append("=").append(entry.getValue()).append("}");
        }
        if (!contexts.entrySet().isEmpty())
            str.append("\n");
        str.append("]}");

        return str.toString();
    }
}