Java tutorial
/* * $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); } */ }