ubic.gemma.persistence.util.EntityUtils.java Source code

Java tutorial

Introduction

Here is the source code for ubic.gemma.persistence.util.EntityUtils.java

Source

/*
 * The Gemma project
 *
 * Copyright (c) 2007 University of British Columbia
 *
 * 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 ubic.gemma.persistence.util;

import gemma.gsec.AuthorityConstants;
import gemma.gsec.acl.domain.AclEntry;
import gemma.gsec.acl.domain.AclGrantedAuthoritySid;
import gemma.gsec.acl.domain.AclObjectIdentity;
import gemma.gsec.acl.domain.AclPrincipalSid;
import gemma.gsec.model.Securable;
import gemma.gsec.util.SecurityUtil;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.hibernate.*;
import org.hibernate.proxy.HibernateProxy;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.model.Sid;
import ubic.gemma.model.common.Identifiable;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.util.*;

/**
 * @author paul
 */
public class EntityUtils {

    /**
     * Checks if the given map already contains a Set for the given key, and if it does, adds the given has code to it.
     * If a key does not exist in the map, it creates a new set, and adds the has into it.
     *
     * @param map  the map to be checked
     * @param key  the key
     * @param hash the hash to be added to the set at the given key of the map
     * @param <T>  the key type parameter of the map
     * @param <S>  the type parameter of the set
     */
    public static <T, S> void populateMapSet(Map<T, Set<S>> map, T key, S hash) {
        if (map.containsKey(key)) {
            map.get(key).add(hash);
        } else {
            Set<S> set = new HashSet<>();
            if (hash != null) {
                set.add(hash);
            }
            map.put(key, set);
        }
    }

    /**
     * Expert only. Put the given entity into the Session, with LockMode.NONE
     * Based on idea from https://forum.hibernate.org/viewtopic.php?p=2284826#p2284826
     *
     * @param session Hibernate Session (use factory.getCurrentSession())
     * @param obj     the entity
     * @param clazz   the class type of the persisted entity. Don't use obj.getClass() as this might return a proxy type.
     * @param id      identifier of the obj
     */
    public static void attach(Session session, Object obj, Class<?> clazz, Long id) {
        if (obj == null || id == null)
            return;
        if (!session.isOpen())
            throw new IllegalArgumentException("Illegal attempt to use a closed session");
        if (!session.contains(obj)) {

            Object oldObj = session.get(clazz, id);

            if (oldObj != null) {
                session.evict(oldObj);
            }
        }
        session.buildLockRequest(LockOptions.NONE).lock(obj);
    }

    public static Long getId(Object entity) {
        return EntityUtils.getId(entity, "getId");
    }

    @SuppressWarnings({ "unused", "WeakerAccess" }) // Possible external use
    public static Long getId(Object entity, String methodName) {
        try {
            Method m = entity.getClass().getMethod(methodName);
            return (Long) m.invoke(entity);
        } catch (SecurityException | NoSuchMethodException | InvocationTargetException | IllegalAccessException
                | IllegalArgumentException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Given a set of entities, create a map of their ids to the entities.
     *
     * @param entities where id is called "id"
     * @param <T>      the type
     * @return the created map
     */
    public static <T> Map<Long, T> getIdMap(Collection<? extends T> entities) {
        Map<Long, T> result = new HashMap<>();

        for (T object : entities) {
            result.put(EntityUtils.getId(object, "getId"), object);
        }

        return result;

    }

    /**
     * @param methodName     accessor e.g. "getId"
     * @param <T>            the type
     * @param entities       entities
     * @param nestedProperty nested property
     * @return the created map
     */
    public static <T> Map<Long, T> getNestedIdMap(Collection<? extends T> entities, String nestedProperty,
            String methodName) {
        Map<Long, T> result = new HashMap<>();

        for (T object : entities) {
            try {
                result.put(EntityUtils.getId(FieldUtils.readField(object, nestedProperty, true), methodName),
                        object);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }

        return result;
    }

    /**
     * @param methodName accessor e.g. "getId"
     * @param entities   entities
     * @param <T>        the type
     * @return the created map
     */
    public static <T> Map<Long, T> getIdMap(Collection<? extends T> entities, String methodName) {
        Map<Long, T> result = new HashMap<>();

        for (T object : entities) {
            result.put(EntityUtils.getId(object, methodName), object);
        }

        return result;
    }

    /**
     * @param entities entities
     * @return returns a collection of IDs. Avoids using reflection by requiring that the given entities all
     * implement the Identifiable interface.
     */
    public static Collection<Long> getIds(Collection<? extends Identifiable> entities) {
        Collection<Long> r = new ArrayList<>(entities.size());
        for (Identifiable i : entities) {
            r.add(i.getId());
        }
        return r;
    }

    /**
     * Convenience method for pushing an ID into a collection (encapsulates a common idiom)
     *
     * @param entity entity
     * @return a collection with one item in it.
     */
    public static Collection<Long> getIds(Object entity) {
        Collection<Long> r;
        r = new HashSet<>();
        r.add(EntityUtils.getId(entity));
        return r;
    }

    public static Class<?> getImplClass(Class<?> type) {
        String canonicalName = type.getName();
        try {
            return Class.forName(canonicalName.endsWith("Impl") ? type.getName() : type.getName() + "Impl");
        } catch (ClassNotFoundException e) {
            // OLDCODE throw new RuntimeException( "No 'impl' class for " + type.getCanonicalName() + " found" );
            // not all our mapped classes end with Impl any more.
            return type;
        }
    }

    /**
     * Expert only. Must be called within a session? Not sure why this is necessary. Obtain the implementation for a
     * proxy. If target is not an instanceof HibernateProxy, target is returned.
     *
     * @param target The object to be un-proxied.
     * @return the underlying implementation.
     */
    public static Object getImplementationForProxy(Object target) {
        if (EntityUtils.isProxy(target)) {
            HibernateProxy proxy = (HibernateProxy) target;
            return proxy.getHibernateLazyInitializer().getImplementation();
        }
        return target;
    }

    /**
     * @param target target
     * @return true if the target is a hibernate proxy.
     */
    public static boolean isProxy(Object target) {
        return target instanceof HibernateProxy;
    }

    /**
     * Expert use only. Used to expose some ACL information to the DAO layer (normally this happens in an interceptor).
     *
     * @param sess             session
     * @param securedClass     Securable type
     * @param ids              to be filtered
     * @param showPublic       also show public items (won't work if showOnlyEditable is true)
     * @param showOnlyEditable show only editable
     * @return filtered IDs, at the very least limited to those that are readable by the current user
     */
    public static Collection<Long> securityFilterIds(Class<? extends Securable> securedClass, Collection<Long> ids,
            boolean showOnlyEditable, boolean showPublic, Session sess) {

        if (ids.isEmpty())
            return ids;
        if (SecurityUtil.isUserAdmin()) {
            return ids;
        }

        /*
         * Find groups user is a member of
         */

        String userName = SecurityUtil.getCurrentUsername();

        boolean isAnonymous = SecurityUtil.isUserAnonymous();

        if (isAnonymous && (showOnlyEditable || !showPublic)) {
            return new HashSet<>();
        }

        String queryString = "select aoi.OBJECT_ID";
        queryString += " from ACLOBJECTIDENTITY aoi";
        queryString += " join ACLENTRY ace on ace.OBJECTIDENTITY_FK = aoi.ID ";
        queryString += " join ACLSID sid on sid.ID = aoi.OWNER_SID_FK ";
        queryString += " where aoi.OBJECT_ID in (:ids)";
        queryString += " and aoi.OBJECT_CLASS = :clazz and ";
        queryString += EntityUtils.addGroupAndUserNameRestriction(showOnlyEditable, showPublic);

        // will be empty if anonymous
        //noinspection unchecked
        Collection<String> groups = sess.createQuery(
                "select ug.name from UserGroup ug inner join ug.groupMembers memb where memb.userName = :user")
                .setParameter("user", userName).list();

        Query query = sess.createSQLQuery(queryString).setParameter("clazz", securedClass.getName())
                .setParameterList("ids", ids);

        if (queryString.contains(":groups")) {
            query.setParameterList("groups", groups);
        }

        if (queryString.contains(":userName")) {
            query.setParameter("userName", userName);
        }

        //noinspection unchecked
        List<BigInteger> r = query.list();
        Set<Long> rl = new HashSet<>();
        for (BigInteger bi : r) {
            rl.add(bi.longValue());
        }

        if (!ids.containsAll(rl)) {
            // really an assertion, but being extra-careful
            throw new SecurityException("Security filter failure");
        }

        return rl;
    }

    /**
     * Have to add 'and' to start of this if it's a later clause
     * Author: nicolas with fixes to generalize by paul, same code appears in the PhenotypeAssociationDaoImpl
     *
     * @param showOnlyEditable only show those the user has access to edit
     * @param showPublic       also show public items (wont work if showOnlyEditable is true)
     * @return clause
     */
    public static String addGroupAndUserNameRestriction(boolean showOnlyEditable, boolean showPublic) {

        String sqlQuery = "";

        if (!SecurityUtil.isUserAnonymous()) {

            if (showPublic && !showOnlyEditable) {
                sqlQuery += "  ((sid.PRINCIPAL = :userName";
            } else {
                sqlQuery += "  (sid.PRINCIPAL = :userName";
            }

            // if ( !groups.isEmpty() ) { // is it possible to be empty? If they are non-anonymous it will not be, there
            // // will at least be GROUP_USER? Or that doesn't count?
            sqlQuery += " or (ace.SID_FK in (";
            // SUBSELECT
            sqlQuery += " select sid.ID from USER_GROUP ug ";
            sqlQuery += " join GROUP_AUTHORITY ga on ug.ID = ga.GROUP_FK ";
            sqlQuery += " join ACLSID sid on sid.GRANTED_AUTHORITY=CONCAT('GROUP_', ga.AUTHORITY) ";
            sqlQuery += " where ug.name in (:groups) ";
            if (showOnlyEditable) {
                sqlQuery += ") and ace.MASK = 2) "; // 2 = read-writable
            } else {
                sqlQuery += ") and (ace.MASK = 1 or ace.MASK = 2)) "; // 1 = read only
            }
            // }
            sqlQuery += ") ";

            if (showPublic && !showOnlyEditable) {
                // publicly readable data.
                sqlQuery += "or (ace.SID_FK = 4 and ace.MASK = 1)) "; // 4 =IS_AUTHENTICATED_ANONYMOUSLY
            }

        } else if (showPublic && !showOnlyEditable) {
            // publicly readable data
            sqlQuery += " (ace.SID_FK = 4 and ace.MASK = 1) "; // 4 = IS_AUTHENTICATED_ANONYMOUSLY
        }

        return sqlQuery;
    }

    /**
     * Populates parameters in query created using addGroupAndUserNameRestriction(boolean, boolean).
     *
     * @param queryObject    the query object created using the sql query with group and username restrictions.
     * @param sessionFactory session factory from the DAO that is using this method.
     */
    public static void addUserAndGroupParameters(SQLQuery queryObject, SessionFactory sessionFactory) {
        if (SecurityUtil.isUserAnonymous()) {
            return;
        }
        String sqlQuery = queryObject.getQueryString();
        String userName = SecurityUtil.getCurrentUsername();

        // if user is member of any groups.
        if (sqlQuery.contains(":groups")) {
            //noinspection unchecked
            Collection<String> groups = sessionFactory.getCurrentSession().createQuery(
                    "select ug.name from UserGroup ug inner join ug.groupMembers memb where memb.userName = :user")
                    .setParameter("user", userName).list();
            queryObject.setParameterList("groups", groups);
        }

        if (sqlQuery.contains(":userName")) {
            queryObject.setParameter("userName", userName);
        }

    }

    /**
     * Checks ACL related properties from the AclObjectIdentity.
     * Some of the code is adapted from {@link gemma.gsec.util.SecurityUtil}, but allows usage without an Acl object.
     *
     * @param aoi the acl object identity of an object whose permissions are to be checked.
     * @return an array of booleans that represent permissions of currently logged in user as follows:
     * <ol>
     * <li>is object public</li>
     * <li>can user write to object</li>
     * <li>is object shared</li>
     * </ol>
     * (note that actual indexing in the array starts at 0).
     */
    public static boolean[] getPermissions(AclObjectIdentity aoi) {
        boolean isPublic = false;
        boolean canWrite = false;
        boolean isShared = false;

        for (AclEntry ace : aoi.getEntries()) {
            if (SecurityUtil.isUserAdmin()) {
                canWrite = true;
            } else if (SecurityUtil.isUserAnonymous()) {
                canWrite = false;
            } else {
                if (ace.getMask() == BasePermission.WRITE.getMask()
                        || ace.getMask() == BasePermission.ADMINISTRATION.getMask()) {
                    Sid sid = ace.getSid();
                    if (sid instanceof AclGrantedAuthoritySid) {
                        //noinspection unused //FIXME if user is in granted group then he can write probably
                        String grantedAuthority = ((AclGrantedAuthoritySid) sid).getGrantedAuthority();
                    } else if (sid instanceof AclPrincipalSid) {
                        if (((AclPrincipalSid) sid).getPrincipal().equals(SecurityUtil.getCurrentUsername())) {
                            canWrite = true;
                        }
                    }
                }
            }

            // Check public and shared - code adapted from SecurityUtils, only we do not hold an ACL object.
            if (ace.getPermission().equals(BasePermission.READ)) {
                Sid sid = ace.getSid();
                if (sid instanceof AclGrantedAuthoritySid) {
                    String grantedAuthority = ((AclGrantedAuthoritySid) sid).getGrantedAuthority();

                    if (grantedAuthority.equals(AuthorityConstants.IS_AUTHENTICATED_ANONYMOUSLY)
                            && ace.isGranting()) {
                        isPublic = true;
                    }
                    if (grantedAuthority.startsWith("GROUP_") && ace.isGranting()) {
                        if (!grantedAuthority.equals(AuthorityConstants.AGENT_GROUP_AUTHORITY)
                                && !grantedAuthority.equals(AuthorityConstants.ADMIN_GROUP_AUTHORITY)) {
                            isShared = true;
                        }
                    }
                }
            }
        }

        return new boolean[] { isPublic, canWrite, isShared };
    }

    public static void mkdirs(File parentDir) {
        if (!parentDir.exists()) {
            if (!parentDir.mkdirs()) {
                Exception e = new RuntimeException("Could not mkdirs in " + parentDir.getPath());
                e.printStackTrace();
            }
        }
    }

    public static void createFile(File file) throws IOException {
        if (!file.createNewFile()) {
            Exception e = new RuntimeException("Could not create file " + file.getPath());
            e.printStackTrace();
        }
    }

    public static void deleteFile(File file) {
        if (!file.delete()) {
            Exception e = new RuntimeException("Could not delete file " + file.getPath());
            e.printStackTrace();
        }
    }

    public static void renameFile(File file, File newFile) {
        if (!file.renameTo(newFile)) {
            Exception e = new RuntimeException("Could not rename file " + file.getPath());
            e.printStackTrace();
        }
    }
}