ome.services.sharing.ShareBean.java Source code

Java tutorial

Introduction

Here is the source code for ome.services.sharing.ShareBean.java

Source

/*
 *   $Id$
 *
 *   Copyright 2008 Glencoe Software, Inc. All rights reserved.
 *   Use is subject to license terms supplied in LICENSE.txt
 */

package ome.services.sharing;

import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;

import ome.annotations.NotNull;
import ome.annotations.RolesAllowed;
import ome.api.IShare;
import ome.api.ServiceInterface;
import ome.api.local.LocalAdmin;
import ome.api.local.LocalShare;
import ome.conditions.ApiUsageException;
import ome.conditions.ValidationException;
import ome.logic.AbstractLevel2Service;
import ome.model.IObject;
import ome.model.annotations.Annotation;
import ome.model.annotations.CommentAnnotation;
import ome.model.annotations.SessionAnnotationLink;
import ome.model.internal.Details;
import ome.model.meta.Event;
import ome.model.meta.Experimenter;
import ome.model.meta.Session;
import ome.model.meta.Share;
import ome.parameters.Parameters;
import ome.security.AdminAction;
import ome.security.SecureAction;
import ome.security.basic.BasicSecuritySystem;
import ome.services.sessions.SessionContext;
import ome.services.sessions.SessionManager;
import ome.services.sharing.data.Obj;
import ome.services.sharing.data.ShareData;
import ome.services.util.Executor;
import ome.system.EventContext;
import ome.system.Principal;
import ome.tools.hibernate.QueryBuilder;
import ome.util.ContextFilter;
import ome.util.Filterable;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.transaction.annotation.Transactional;

/**
 * 
 * Note: {@link SessionManager} should not be used to obtain the {@link Share}
 * data since it may not be completely in sync. i.e. Don't use SM.find()
 * 
 * @author Josh Moore, josh at glencoesoftware.com
 * @since 3.0-Beta4
 * @see IShare
 */
@Transactional(readOnly = true)
public class ShareBean extends AbstractLevel2Service implements LocalShare {

    public final static Log log = LogFactory.getLog(ShareBean.class);

    public final static String NS_ENABLED = "ome.share.enabled";

    public final static String NS_COMMENT = "ome.share.comment/";

    final protected LocalAdmin admin;

    final protected SessionManager sessionManager;

    final protected ShareStore store;

    final protected Executor executor;

    public final Class<? extends ServiceInterface> getServiceInterface() {
        return IShare.class;
    }

    public ShareBean(LocalAdmin admin, SessionManager mgr, ShareStore store, Executor executor) {
        this.admin = admin;
        this.sessionManager = mgr;
        this.store = store;
        this.executor = executor;
    }

    // ~ Service Methods
    // ===================================================

    @RolesAllowed("user")
    public void activate(long shareId) {

        // Check status of the store
        ShareData data = getShareIfAccessible(shareId);
        if (data == null) {
            throw new ValidationException("No accessible share:" + shareId);
        }
        if (!data.enabled) {
            throw new ValidationException("Share disabled.");
        }

        // Ok, set share
        setShareId(shareId);
    }

    @RolesAllowed("user")
    public void deactivate() {
        setShareId(null);
    }

    /**
     * Not in the public interface (since it allows setting to -1 which
     * makes everything readable, but used internally similar to a
     * LocalShare interface.
     *
     * @see ticket:2219
     */
    public Long setShareId(Long shareId) {
        String sessId = getSecuritySystem().getEventContext().getCurrentSessionUuid();
        SessionContext sc = (SessionContext) sessionManager.getEventContext(new Principal(sessId));
        Long old = sc.getCurrentShareId();
        sc.setShareId(shareId);
        return old;
    }

    /**
     * @see ticket:2219
     */
    public void resetReadFilter(org.hibernate.Session s) {
        // ticket:2397 and ticket:2219
        // Necessary to update the current filter in order to
        // have the new shareId be updated
        BasicSecuritySystem bss = (BasicSecuritySystem) sec;
        bss.loadEventContext(true);
        bss.updateReadFilter(s);
    }

    // ~ Getting shares and objects (READ)
    // =========================================================================

    @RolesAllowed("user")
    public Map<Long, Long> getMemberCount(final Set<Long> shareIds) {

        if (shareIds == null || shareIds.size() == 0) {
            throw new ApiUsageException("Nothing to do");
        }

        final QueryBuilder qb = new QueryBuilder();
        qb.select("share2.id", "count(distinct links2.id)");
        qb.from("ShareMember", "links2");
        qb.join("links2.parent", "share2", false, false);
        qb.where();
        qb.paramList("ids", shareIds);
        qb.and("share2.id in (:ids) and share2.id in ");
        // -- subselect for all accessible shares
        {
            QueryBuilder sub = new QueryBuilder();
            sub.select("share");
            sub.from("ShareMember", "memberLinks");
            sub.join("memberLinks.parent", "share", false, false);
            sub.join("memberLinks.child", "user", false, false);
            sub.where();
            sub.and("1=1"); // WORKAROUND for ticket:1239 for root
            applyIfShareAccessible(sub);
            qb.subselect(sub);
        }
        // -- end subselect
        qb.append("group by share2.id");

        final Map<Long, Long> rv = new HashMap<Long, Long>(shareIds.size());
        sec.runAsAdmin(new AdminAction() {
            public void runAsAdmin() {
                iQuery.execute(new HibernateCallback() {
                    public Object doInHibernate(org.hibernate.Session s) throws HibernateException, SQLException {
                        Query q = qb.query(s);
                        List<Object[]> results = q.list();
                        if (results.size() != shareIds.size()) {
                            throw new ValidationException("Missing or protected shares specified");
                        }
                        for (Object[] values : results) {
                            Long shareId = (Long) values[0];
                            Long count = (Long) values[1];
                            rv.put(shareId, count);
                        }
                        return null;
                    }
                });
            }
        });
        return rv;
    }

    long getCurrentUserId() {
        return getSecuritySystem().getEventContext().getCurrentUserId();
    }

    @RolesAllowed("user")
    public Set<Session> getOwnShares(boolean active) {
        long id = getCurrentUserId();
        List<ShareData> shares = store.getShares(id, true /* own */, active);
        return sharesToSessions(shares);
    }

    @RolesAllowed("user")
    public Set<Session> getMemberShares(boolean active) {
        long id = getCurrentUserId();
        List<ShareData> shares = store.getShares(id, false /* own */, active);
        return sharesToSessions(shares);
    }

    @RolesAllowed("user")
    public Set<Session> getSharesOwnedBy(@NotNull Experimenter user, boolean active) {
        List<ShareData> shares = store.getShares(user.getId(), true /* own */, active);
        return sharesToSessions(shares);
    }

    @RolesAllowed("user")
    public Set<Session> getMemberSharesFor(@NotNull Experimenter user, boolean active) {
        List<ShareData> shares = store.getShares(user.getId(), false /* own */, active);
        return sharesToSessions(shares);
    }

    @RolesAllowed("user")
    public Share getShare(long sessionId) {
        Share session = null;
        ShareData data = getShareIfAccessible(sessionId);
        if (data != null) {
            session = shareToSession(data);
        }
        return session;
    }

    @RolesAllowed("user")
    public <T extends IObject> List<T> getContents(long shareId) {
        ShareData data = getShareIfAccessible(shareId);
        throwOnNullData(shareId, data);
        return list(data.objectList);
    }

    @RolesAllowed("user")
    public <T extends IObject> List<T> getContentSubList(long shareId, int start, int finish) {
        ShareData data = getShareIfAccessible(shareId);
        throwOnNullData(shareId, data);
        try {
            return list(data.objectList.subList(start, finish));
        } catch (IndexOutOfBoundsException ioobe) {
            throw new ApiUsageException("Invalid range: " + start + " to " + finish);
        }
    }

    @RolesAllowed("user")
    public <T extends IObject> Map<Class<T>, List<Long>> getContentMap(long shareId) {
        ShareData data = getShareIfAccessible(shareId);
        throwOnNullData(shareId, data);
        return map(data.objectMap);
    }

    @RolesAllowed("user")
    public int getContentSize(long shareId) {
        ShareData data = getShareIfAccessible(shareId);
        throwOnNullData(shareId, data);
        return data.objectList.size();
    }

    // ~ Creating share (WRITE)
    // =========================================================================

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public <T extends IObject> long createShare(@NotNull final String description, Timestamp expiration,
            List<T> items, List<Experimenter> exps, List<String> guests, final boolean enabled) {

        //
        // Input validation
        //
        final long time = expirationAsLong(System.currentTimeMillis(), expiration);

        if (exps == null) {
            exps = Collections.emptyList();
        }
        if (guests == null) {
            guests = Collections.emptyList();
        }
        if (items == null) {
            items = Collections.emptyList();
        }

        //
        // Setting defaults on new session
        //
        final EventContext ec = getSecuritySystem().getEventContext();
        final String omename = ec.getCurrentUserName();
        final Long user = ec.getCurrentUserId();
        final Long group = ec.getCurrentGroupId();
        final Future<Share> future = executor.submit(new Callable<Share>() {
            public Share call() throws Exception {
                return sessionManager.createShare(new Principal(omename), enabled, time, "SHARE", description,
                        group);
            }
        });

        final List<T> _items = items;
        final List<String> _guests = guests;
        final List<Long> _users = new ArrayList<Long>(exps.size());
        for (Experimenter e : exps) {
            _users.add(e.getId());
        }

        final long shareId = executor.get(future).getId();
        final Share share = iQuery.find(Share.class, shareId); // Reload!
        this.sec.doAction(new SecureShare() {
            @Override
            void doUpdate(Share share) {
                store.set(share, user, _items, _users, _guests, enabled);
            }
        }, share);
        adminFlush();
        return share.getId();
    }

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public void setDescription(long shareId, String description) {
        Share share = (Share) iQuery.find(Share.class, shareId);
        ShareData data = store.get(shareId);
        share.setMessage(description);
        storeShareData(shareId, data);
    }

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public void setExpiration(long shareId, Timestamp expiration) {
        Share share = (Share) iQuery.find(Share.class, shareId);
        ShareData data = store.get(shareId);
        share.setTimeToLive(expirationAsLong(share.getStarted().getTime(), expiration));
        storeShareData(shareId, data);
    }

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public void setActive(long shareId, boolean active) {
        ShareData data = getShareIfAccessible(shareId);
        throwOnNullData(shareId, data);
        data.enabled = active;
        storeShareData(shareId, data);
    }

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public void closeShare(long shareId) {
        final String uuid = idToUuid(shareId);
        Future<Object> future = executor.submit(new Callable<Object>() {
            public Object call() throws Exception {
                sessionManager.close(uuid);
                return null;
            }
        });
        executor.get(future);
    }

    // ~ Getting items
    // =========================================================================

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public <T extends IObject> void addObjects(long shareId, @NotNull T... objects) {
        ShareData data = getShareIfAccessible(shareId);
        throwOnNullData(shareId, data);
        Graph graph = new Graph();
        for (T object : objects) {
            graph.filter("top", object);
        }
        _addGraph(data, graph);
        storeShareData(shareId, data);
    }

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public <T extends IObject> void addObject(long shareId, @NotNull T object) {
        ShareData data = getShareIfAccessible(shareId);
        throwOnNullData(shareId, data);
        Graph graph = new Graph();
        graph.filter("top", object);
        _addGraph(data, graph);
        storeShareData(shareId, data);
    }

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public <T extends IObject> void removeObjects(long shareId, @NotNull T... objects) {
        ShareData data = getShareIfAccessible(shareId);
        throwOnNullData(shareId, data);
        for (T object : objects) {
            List<Long> ids = data.objectMap.get(object.getClass().getName());
            if (ids != null) {
                ids.remove(object.getId());
            }
        }
        List<Obj> toRemove = new ArrayList<Obj>();
        for (T object : objects) {
            for (Obj obj : data.objectList) {
                if (obj.type.equals(object.getClass().getName())) {
                    if (obj.id == object.getId().longValue()) {
                        toRemove.add(obj);
                    }
                }
            }
        }
        data.objectList.removeAll(toRemove);
        storeShareData(shareId, data);
    }

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public <T extends IObject> void removeObject(long shareId, @NotNull T object) {
        ShareData data = getShareIfAccessible(shareId);
        List<Long> ids = data.objectMap.get(object.getClass().getName());
        if (ids != null) {
            ids.remove(object.getId());
        }
        List<Obj> toRemove = new ArrayList<Obj>();
        for (Obj obj : data.objectList) {
            if (obj.type.equals(object.getClass().getName())) {
                if (obj.id == object.getId().longValue()) {
                    toRemove.add(obj);
                }
            }
        }
        data.objectList.removeAll(toRemove);
        storeShareData(shareId, data);
    }

    // ~ Getting comments
    // =========================================================================

    @RolesAllowed("user")
    public Map<Long, Long> getCommentCount(final Set<Long> ids) {

        if (ids == null || ids.size() == 0) {
            throw new ApiUsageException("Nothing to do");
        }

        final QueryBuilder qb = new QueryBuilder();
        qb.select("share.id", "count(distinct sal)");
        qb.from("ShareMember", "sm");
        qb.join("sm.parent", "share", false, false);
        qb.join("share.annotationLinks", "sal", false, false);
        qb.join("sal.child", "comment", false, false);
        qb.join("sm.child", "user", false, false);
        qb.where();
        qb.and("share.id in (:ids)");
        qb.paramList("ids", ids);
        qb.and("comment.ns like :ns");
        qb.param("ns", NS_COMMENT + "%");
        applyIfShareAccessible(qb);
        qb.append("group by share.id");

        final Map<Long, Long> rv = new HashMap<Long, Long>(ids.size());
        final Long old = setShareId(-1L); // Allow everything. ticket:2219
        try {

            iQuery.execute(new HibernateCallback() {
                public Object doInHibernate(org.hibernate.Session s) throws HibernateException, SQLException {

                    resetReadFilter(s);

                    Query q = qb.query(s);
                    List<Object[]> counts = q.list();
                    // ticket:1227 - Returning 0 if missing
                    // if (counts.size() != ids.size()) {
                    //    throw new ValidationException(
                    //    "Missing or protected shares specified");
                    //}
                    for (Object[] values : counts) {
                        Long shareId = (Long) values[0];
                        Long count = (Long) values[1];
                        rv.put(shareId, count);
                    }
                    return null;
                }
            });
        } finally {
            setShareId(old);
        }
        for (Long id : ids) {
            Long value = rv.get(id);
            if (value == null) {
                rv.put(id, 0L);
            }
        }
        return rv;
    }

    @RolesAllowed("user")
    @SuppressWarnings("unchecked")
    public List<Annotation> getComments(final long shareId) {

        final List<Annotation> rv = new ArrayList<Annotation>();
        if (getShareIfAccessible(shareId) == null) {
            return rv; // EARLY EXIT.
        }

        // Now load the comments with the read filter,
        // otherwise it is necessary to add every link
        // to the share
        Long oldShareId = setShareId(-1L);
        try {
            List<SessionAnnotationLink> links = (List<SessionAnnotationLink>) iQuery
                    .execute(new HibernateCallback() {
                        public Object doInHibernate(org.hibernate.Session arg0)
                                throws HibernateException, SQLException {

                            resetReadFilter(arg0);
                            return arg0.createQuery("select l from SessionAnnotationLink l "
                                    + "join fetch l.details.owner " + "join fetch l.parent as share "
                                    + "join fetch l.child as comment " + "join fetch comment.details.updateEvent "
                                    + "where share.id = :id and comment.ns like :ns ")
                                    .setParameter("ns", NS_COMMENT + "%").setParameter("id", shareId).list();

                        }
                    });

            for (SessionAnnotationLink link : links) {
                rv.add(link.child());
            }

        } finally {
            setShareId(oldShareId);
        }

        return rv;
    }

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public CommentAnnotation addComment(final long shareId, @NotNull final String commentText) {

        getShareIfAccessible(shareId);

        final CommentAnnotation[] rv = new CommentAnnotation[1];
        sec.runAsAdmin(new AdminAction() {
            public void runAsAdmin() {

                Share share = iQuery.get(Share.class, shareId);
                Long gid = share.getGroup().getId();
                Map<String, String> ctx = new HashMap<String, String>();
                ctx.put("omero.group", "" + gid);
                try {
                    executor.setCallGroup(gid);
                    CommentAnnotation comment = new CommentAnnotation();
                    comment.setTextValue(commentText);
                    comment.setNs(NS_COMMENT);
                    //comment.getDetails().setOwner(commentOwner);
                    SessionAnnotationLink link = share.linkAnnotation(comment);
                    //link.getDetails().setOwner(commentOwner);
                    //
                    // ticket:1434 - no longer setting permissions, since they
                    // will be set to the value of the group automatically.
                    //
                    // comment.getDetails().setPermissions(Permissions.DEFAULT);
                    // link.getDetails().setPermissions(Permissions.DEFAULT);

                    iUpdate.flush();
                    rv[0] = iQuery.get(CommentAnnotation.class, comment.getId());
                } finally {
                    executor.resetCallGroup();
                }

            }
        });
        return rv[0];
    }

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public CommentAnnotation addReply(long shareId, @NotNull String comment, @NotNull CommentAnnotation replyTo) {
        throw new UnsupportedOperationException();
    }

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public void deleteComment(@NotNull Annotation comment) {
        List<SessionAnnotationLink> links = iQuery.findAllByQuery(
                "select l from SessionAnnotationLink l " + "where l.child.id = :id",
                new Parameters().addId(comment.getId()));
        for (SessionAnnotationLink sessionAnnotationLink : links) {
            iUpdate.deleteObject(sessionAnnotationLink);
        }
        iUpdate.deleteObject(comment);
    }

    // ~ Member administration
    // =========================================================================

    @RolesAllowed("user")
    public Set<Experimenter> getAllMembers(long shareId) {
        ShareData data = getShareIfAccessible(shareId);
        throwOnNullData(shareId, data);
        List<Experimenter> e = loadMembers(data);
        return new HashSet<Experimenter>(e);
    }

    @RolesAllowed("user")
    public Set<String> getAllGuests(long shareId) {
        ShareData data = getShareIfAccessible(shareId);
        throwOnNullData(shareId, data);
        return new HashSet<String>(data.guests);
    }

    @RolesAllowed("user")
    public Set<String> getAllUsers(long shareId) throws ValidationException {
        ShareData data = getShareIfAccessible(shareId);
        List<Experimenter> members = loadMembers(data);
        Set<String> names = new HashSet<String>();
        for (Experimenter e : members) {
            names.add(e.getOmeName());
        }
        for (String string : data.guests) {
            if (names.contains(string)) {
                throw new ValidationException(string + " is both a guest name and a member name");
            } else {
                names.add(string);
            }
        }
        return names;
    }

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public void addUsers(long shareId, Experimenter... exps) {
        List<Experimenter> es = Arrays.asList(exps);
        ShareData data = getShareIfAccessible(shareId);
        throwOnNullData(shareId, data);
        for (Experimenter experimenter : es) {
            data.members.add(experimenter.getId());
        }
        storeShareData(shareId, data);
    }

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public void addGuests(long shareId, String... emailAddresses) {
        List<String> addresses = Arrays.asList(emailAddresses);
        ShareData data = getShareIfAccessible(shareId);
        throwOnNullData(shareId, data);
        data.guests.addAll(addresses);
        storeShareData(shareId, data);
    }

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public void removeUsers(long shareId, List<Experimenter> exps) {
        ShareData data = getShareIfAccessible(shareId);
        throwOnNullData(shareId, data);
        for (Experimenter experimenter : exps) {
            data.members.remove(experimenter.getId());
        }
        storeShareData(shareId, data);
    }

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public void removeGuests(long shareId, String... emailAddresses) {
        List<String> addresses = Arrays.asList(emailAddresses);
        ShareData data = getShareIfAccessible(shareId);
        data.guests.removeAll(addresses);
        storeShareData(shareId, data);
    }

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public void addUser(long shareId, Experimenter exp) {
        ShareData data = getShareIfAccessible(shareId);
        throwOnNullData(shareId, data);
        data.members.add(exp.getId());
        storeShareData(shareId, data);
    }

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public void addGuest(long shareId, String emailAddress) {
        ShareData data = getShareIfAccessible(shareId);
        throwOnNullData(shareId, data);
        data.guests.add(emailAddress);
        storeShareData(shareId, data);
    }

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public void removeUser(long shareId, Experimenter exp) {
        ShareData data = getShareIfAccessible(shareId);
        throwOnNullData(shareId, data);
        data.members.remove(exp.getId());
        storeShareData(shareId, data);
    }

    @RolesAllowed("user")
    @Transactional(readOnly = false)
    public void removeGuest(long shareId, String emailAddress) {
        ShareData data = getShareIfAccessible(shareId);
        throwOnNullData(shareId, data);
        data.guests.remove(emailAddress);
        storeShareData(shareId, data);
    }

    // Events
    // =========================================================================

    @RolesAllowed("user")
    public Map<String, Experimenter> getActiveConnections(long shareId) {
        throw new UnsupportedOperationException();
    }

    @RolesAllowed("user")
    public List<Event> getEvents(long shareId, Experimenter experimenter, Timestamp from, Timestamp to) {
        throw new UnsupportedOperationException();
    }

    @RolesAllowed("user")
    public Map<String, Experimenter> getPastConnections(long shareId) {
        throw new UnsupportedOperationException();
    }

    @RolesAllowed("user")
    public void invalidateConnection(long shareId, Experimenter exp) {
        throw new UnsupportedOperationException();
    }

    // Helpers
    // =========================================================================

    protected String idToUuid(long shareId) {
        Session s = iQuery.get(Session.class, shareId);
        return s.getUuid();
    }

    protected List<Experimenter> loadMembers(ShareData data) {
        List<Experimenter> members = new ArrayList<Experimenter>();
        if (data.members.size() > 0)
            members = iQuery.findAllByQuery("select e from Experimenter e " + "where e.id in (:ids)",
                    new Parameters().addIds(data.members));
        return members;
    }

    /**
     * Convert a {@link Timestamp expiration} into a long which can be set on
     * {@link Session#setTimeToLive(Long)}.
     * 
     * @return the time in milliseconds that this session can exist.
     */
    public static long expirationAsLong(long started, Timestamp expiration) {
        long time;
        if (expiration != null) {
            time = expiration.getTime();
            if (time < System.currentTimeMillis()) {
                throw new ApiUsageException("Expiration time must be in the future.");
            }
        } else {
            time = Long.MAX_VALUE;
        }

        return time - started;
    }

    protected Set<Session> sharesToSessions(List<ShareData> datas) {
        /*
         * TODO: When Share will have details method can be updated: +
         * "join fetch sh.details.owner where sh.id in (:ids) ",
         */
        /*
         * Set<Session> sessions = new HashSet<Session>(); for (ShareData data :
         * datas) { sessions.add(shareToSession(data)); } return sessions;
         */
        Set<Long> ids = new HashSet<Long>();
        for (ShareData data : datas) {
            ids.add(data.id);
        }
        if (ids.size() == 0) {
            return Collections.emptySet();
        }

        List<Session> list = iQuery.findAllByQuery(
                "select sh from Session sh " + "join fetch sh.owner where sh.id in (:ids) ",
                new Parameters().addIds(ids));
        for (Session session : list) {
            if (session != null) {
                session.putAt("#2733", "ALLOW");
            }
        }
        return new HashSet<Session>(list);
    }

    protected Share shareToSession(ShareData data) {
        Share share = iQuery.findByQuery("select sh from Share sh " + "join fetch sh.owner where sh.id = :id ",
                new Parameters().addId(data.id));
        if (share != null) {
            share.putAt("#2733", "ALLOW");
        }
        return share;
    }

    @SuppressWarnings("unchecked")
    protected <T extends IObject> Map<Class<T>, List<Long>> map(Map<String, List<Long>> map) {
        Map<Class<T>, List<Long>> rv = new HashMap<Class<T>, List<Long>>();
        for (String key : map.keySet()) {
            try {
                Class<T> kls = (Class<T>) Class.forName(key);
                rv.put(kls, map.get(key));
            } catch (Exception e) {
                throw new ValidationException("Share contains invalid type: " + key);
            }
        }
        return rv;
    }

    @SuppressWarnings("unchecked")
    protected <T extends IObject> List<T> list(List<Obj> objectList) {
        List<T> rv = new ArrayList<T>();
        for (Obj o : objectList) {
            T t;
            try {
                t = (T) Class.forName(o.type).newInstance();
            } catch (Exception e) {
                throw new ValidationException("Share contains invalid type: " + o.type);
            }
            t.setId(o.id);
            t.unload();
            rv.add(t);
        }
        return rv;
    }

    protected void adminFlush() {
        getSecuritySystem().runAsAdmin(new AdminAction() {
            public void runAsAdmin() {
                iUpdate.flush();
            }
        });
    }

    protected void throwOnNullData(long shareId, ShareData data) {
        if (data == null) {
            throw new ValidationException("Share not found: " + shareId);
        }
    }

    /**
     * If the current user is not an admin, then this methods adds a subclause
     * to the HQL:
     * 
     *   AND ( share.owner.id = :userId or user.id = :userId )
     * 
     * {@link QueryBuilder#where()} should already have been called.
     */
    protected void applyIfShareAccessible(QueryBuilder qb) {
        EventContext ec = getSecuritySystem().getEventContext();
        if (!ec.isCurrentUserAdmin()) {
            qb.param("userId", ec.getCurrentUserId());
            qb.and("(");
            qb.append("share.owner.id = :userId");
            qb.append(" OR ");
            qb.append("user.id = :userId");
            qb.append(" ) ");
        }
    }

    /**
     * Loads share and checks it's owner and member data against the current
     * context (owner/member/admin). This method must be kept in sync with
     * {@link #applyIfShareAccessible(QueryBuilder)} which does the same check
     * at the database rather than binary data level.
     */
    protected ShareData getShareIfAccessible(long shareId) {

        ShareData data = store.get(shareId);
        if (data == null) {
            return null;
        }

        EventContext ec = getSecuritySystem().getEventContext();
        boolean isAdmin = ec.isCurrentUserAdmin();
        long userId = ec.getCurrentUserId();
        if (data.owner == userId || data.members.contains(userId) || isAdmin) {
            return data;
        }
        return null;
    }

    protected void _addGraph(ShareData data, Graph g) {
        for (IObject object : g.objects()) {
            List<Long> ids = data.objectMap.get(object.getClass().getName());
            if (ids == null) {
                ids = new ArrayList<Long>();
                data.objectMap.put(object.getClass().getName(), ids);
            }
            if (!ids.contains(object.getId())) {
                ids.add(object.getId());
                Obj obj = new Obj();
                obj.type = object.getClass().getName();
                obj.id = object.getId();
                data.objectList.add(obj);
            }
        }
    }

    // Graph : used for
    // =========================================================================

    private static class Graph extends ContextFilter {

        public List<IObject> objects() {
            List<IObject> rv = new ArrayList<IObject>();
            for (Object o : _cache.keySet()) {
                if (o instanceof IObject) {
                    IObject obj = (IObject) o;
                    rv.add(obj);
                }
            }
            return rv;
        }

        @Override
        protected void doFilter(String fieldId, Filterable f) {
            if (!(f instanceof Details)) {
                super.doFilter(fieldId, f);
            }
        }

    }

    // All update access should be performed with these methods to keep
    // everything in sync. The other methods which mutate are create & close
    // =========================================================================

    abstract class SecureShare implements SecureAction {

        public final <T extends IObject> T updateObject(T... objs) {
            doUpdate((Share) objs[0]);
            return null;
        }

        abstract void doUpdate(Share share);

    }

    class SecureStore extends SecureShare {

        ShareData data;

        SecureStore(ShareData data) {
            this.data = data;
        }

        @Override
        void doUpdate(Share share) {
            store.update(share, data);
        }

    }

    protected void storeShareData(long shareId, ShareData data) {
        // This should reload the object already in the first-cache
        Share share = iQuery.get(Share.class, shareId);

        this.sec.doAction(new SecureStore(data), share);
        adminFlush();
    }

    /*
     * private void updateShare(final Share share) { Future<Object> future =
     * executor.submit(new Callable<Object>() { public Object call() throws
     * Exception { sessionManager.update(share, true); return null; } });
     * executor.get(future); }
     */

}