/*
* Copyright 2008 - 2014 Glencoe Software, Inc. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package ome.services.sharing;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import ome.api.IShare;
import ome.conditions.OptimisticLockException;
import ome.model.IObject;
import ome.model.acquisition.Detector;
import ome.model.acquisition.DetectorSettings;
import ome.model.acquisition.Dichroic;
import ome.model.acquisition.Filter;
import ome.model.acquisition.FilterSet;
import ome.model.acquisition.Instrument;
import ome.model.acquisition.Laser;
import ome.model.acquisition.LightPath;
import ome.model.acquisition.LightSettings;
import ome.model.acquisition.LightSource;
import ome.model.acquisition.Microscope;
import ome.model.acquisition.Objective;
import ome.model.acquisition.ObjectiveSettings;
import ome.model.acquisition.TransmittanceRange;
import ome.model.core.Channel;
import ome.model.core.Image;
import ome.model.core.LogicalChannel;
import ome.model.core.Pixels;
import ome.model.core.PlaneInfo;
import ome.model.display.ChannelBinding;
import ome.model.display.QuantumDef;
import ome.model.display.RenderingDef;
import ome.model.display.Thumbnail;
import ome.model.meta.Experimenter;
import ome.model.meta.Share;
import ome.model.meta.ShareMember;
import ome.model.stats.StatsInfo;
import ome.services.sharing.data.Obj;
import ome.services.sharing.data.ShareData;
import ome.services.sharing.data.ShareItem;
import ome.system.OmeroContext;
import ome.tools.hibernate.QueryBuilder;
import ome.tools.hibernate.SessionFactory;
import ome.util.SqlAction;
import org.apache.commons.collections.CollectionUtils;
import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.dao.EmptyResultDataAccessException;
/**
* Implements {@link ShareStore} and provides functionality to work with binary
* Ice data from the share. Also provides methods for verification if metadata
* graph elements are safe to load (part of the security system's ACL vote).
*
* @author Josh Moore, josh at glencoesoftware.com
* @since 3.0-Beta4
* @see IShare
*/
public class BlobShareStore extends ShareStore implements
ApplicationContextAware {
/**
* Used <em>indirectly</em> to obtain sessions for querying and updating the
* store during normal operation. Due to this classes late initialization,
* all sessions should be obtained from {@link #session()}.
*/
protected SessionFactory __dont_use_me_factory;
protected OmeroContext ctx;
protected SqlAction sqlAction;
protected Map<Long, Long> pixToImageCache = new HashMap<Long, Long>();
protected Map<Long, List<Long>> obToImageCache = new HashMap<Long, List<Long>>();
/**
* Because there is a cyclic dependency (SF->ACLVoter->BlobStore->SF), we
* have to lazy-load the session factory via the context.
*/
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.ctx = (OmeroContext) applicationContext;
}
public void setSqlAction(SqlAction sqlAction) {
this.sqlAction = sqlAction;
}
// Initialization/Destruction
@Override
public void doInit() {
// Currently nothing.
}
// Overrides
// =========================================================================
@Override
public Long totalShares() {
return (Long) session().createQuery("select count(id) from Share")
.uniqueResult();
}
@Override
public Long totalSharedItems() {
return (Long) session().createQuery("select sum(itemCount) from Share")
.uniqueResult();
}
@Override
@SuppressWarnings("unchecked")
public void doSet(Share share, ShareData data, List<ShareItem> items) {
long oldOptLock = data.optlock;
long newOptLock = oldOptLock + 1;
Session session = session();
List<Share> list = (List<Share>) session.createQuery(
"select s from Share s where s.id = " + data.id
+ " and s.version =" + data.optlock).list();
if (list.size() == 0) {
throw new OptimisticLockException("Share " + data.id
+ " has been updated by someone else.");
}
data.optlock = newOptLock;
share.setData(parse(data));
share.setActive(data.enabled);
share.setItemCount((long) items.size());
share.setVersion((int) newOptLock);
session.merge(share);
synchronizeMembers(session, data);
}
@Override
public ShareData get(final long id) {
Session session = session();
Share s = (Share) session.get(Share.class, id);
if (s == null) {
return null;
}
byte[] data = s.getData();
return parse(id, data);
}
@Override
@SuppressWarnings("unchecked")
public List<ShareData> getShares(long userId, boolean own,
boolean activeOnly) {
Session session = session();
QueryBuilder qb = new QueryBuilder();
qb.select("share.id");
qb.from("ShareMember", "sm");
qb.join("sm.parent", "share", false, false);
qb.where();
qb.and("sm.child.id = :userId");
qb.param("userId", userId);
if (own) {
qb.and("share.owner.id = sm.child.id");
} else {
qb.and("share.owner.id != sm.child.id");
}
if (activeOnly) {
qb.and("share.active is true");
}
Query query = qb.query(session);
List<Long> shareIds = (List<Long>) query.list();
if (shareIds.size() == 0) {
return new ArrayList<ShareData>(); // EARLY EXIT!
}
List<ShareData> rv = new ArrayList<ShareData>();
try {
Map<Long, byte[]> data = data(shareIds);
for (Long id : data.keySet()) {
byte[] bs = data.get(id);
ShareData d = parse(id, bs);
rv.add(d);
}
return rv;
} catch (EmptyResultDataAccessException empty) {
return null;
}
}
boolean imagesContainsPixels(Session s, List<Long> images, Pixels pix,
Map<Long, Long> cache) {
Long pixID = pix.getId();
return imagesContainsPixels(s, images, pixID, cache);
}
boolean imagesContainsPixels(Session s, List<Long> images, long pixID,
Map<Long, Long> cache) {
Long imgID;
if (cache.containsKey(pixID)) {
imgID = cache.get(pixID);
} else {
imgID = (Long) s
.createQuery("select image.id from Pixels where id = ?")
.setParameter(0, pixID).uniqueResult();
cache.put(pixID, imgID);
}
return images.contains(imgID);
}
@SuppressWarnings("unchecked")
boolean imagesContainsInstrument(Session s, List<Long> images,
Instrument instr, Map<Long, List<Long>> cache) {
if (instr == null) {
return false;
}
Long instrID = instr.getId();
List<Long> imgIDs;
if (cache.containsKey(instrID)) {
imgIDs = cache.get(instrID);
} else {
imgIDs = (List<Long>) s
.createQuery("select id from Image where instrument.id = ?")
.setParameter(0, instrID).list();
cache.put(instrID, imgIDs);
}
return CollectionUtils.containsAny(images, imgIDs);
}
boolean imagesContainsObjectiveSettings(Session s, List<Long> images,
ObjectiveSettings os, Map<Long, List<Long>> cache) {
Long osID = os.getId();
return imagesContainsObjectiveSettings(s, images, osID, cache);
}
@SuppressWarnings("unchecked")
boolean imagesContainsObjectiveSettings(Session s, List<Long> images,
long osID, Map<Long, List<Long>> cache) {
List<Long> imgIDs;
if (cache.containsKey(osID)) {
imgIDs = cache.get(osID);
} else {
imgIDs = (List<Long>) s
.createQuery(
"select id from Image where objectiveSettings.id = ?")
.setParameter(0, osID).list();
cache.put(osID, imgIDs);
}
return CollectionUtils.containsAny(images, imgIDs);
}
@Override
public <T extends IObject> boolean doContains(long sessionId, Class<T> kls,
long objId) {
ShareData data = get(sessionId);
if (data == null) {
return false;
}
return doContains(data, kls, objId);
}
@SuppressWarnings("unchecked")
protected <T extends IObject> boolean doContains(ShareData data,
Class<T> kls, long objId) {
List<Long> ids = data.objectMap.get(kls.getName());
if (ids != null && ids.contains(objId)) {
return true;
}
// ticket:2249 - Implementing logic similar to the query
// in DeleteBean in order to allow all objects which link
// back to an Image to also be loaded.
/*
* + "left outer join fetch i.pixels as p " +
* "left outer join fetch p.channels as c " +
* "left outer join fetch c.logicalChannel as lc " +
* "left outer join fetch lc.channels as c2 " +
* "left outer join fetch c.statsInfo as sinfo " +
* "left outer join fetch p.planeInfo as pinfo " +
* "left outer join fetch p.thumbnails as thumb " +
* "left outer join fetch p.pixelsFileMaps as map " +
* "left outer join fetch map.parent as ofile " +
* "left outer join fetch p.settings as setting " // rdef +
* "left outer join fetch r.waveRendering " +
* "left outer join fetch r.quantization "
*/
List<Long> images = data.objectMap.get(Image.class.getName());
Session s = session();
if (Pixels.class.isAssignableFrom(kls)) {
return imagesContainsPixels(s, images, objId, pixToImageCache);
} else if (RenderingDef.class.isAssignableFrom(kls)) {
RenderingDef obj = (RenderingDef) s.get(RenderingDef.class, objId);
return imagesContainsPixels(s, images, obj.getPixels(),
pixToImageCache);
} else if (ChannelBinding.class.isAssignableFrom(kls)) {
ChannelBinding obj = (ChannelBinding) s.get(ChannelBinding.class,
objId);
return imagesContainsPixels(s, images, obj.getRenderingDef()
.getPixels(), pixToImageCache);
} else if (Thumbnail.class.isAssignableFrom(kls)) {
Thumbnail obj = (Thumbnail) s.get(Thumbnail.class, objId);
return imagesContainsPixels(s, images, obj.getPixels(),
pixToImageCache);
} else if (Channel.class.isAssignableFrom(kls)) {
Channel obj = (Channel) s.get(Channel.class, objId);
return imagesContainsPixels(s, images, obj.getPixels(),
pixToImageCache);
} else if (LogicalChannel.class.isAssignableFrom(kls)) {
LogicalChannel obj = (LogicalChannel) s.get(LogicalChannel.class,
objId);
Iterator<Channel> it = obj.iterateChannels();
while (it.hasNext()) {
Channel ch = it.next();
if (images.contains(ch.getPixels().getImage().getId())) {
return true;
}
}
} else if (PlaneInfo.class.isAssignableFrom(kls)) {
PlaneInfo obj = (PlaneInfo) s.get(PlaneInfo.class, objId);
return imagesContainsPixels(s, images, obj.getPixels(),
pixToImageCache);
} else if (StatsInfo.class.isAssignableFrom(kls)
|| QuantumDef.class.isAssignableFrom(kls)
|| LightPath.class.isAssignableFrom(kls)
|| Microscope.class.isAssignableFrom(kls)
|| TransmittanceRange.class.isAssignableFrom(kls)) {
// Objects we just don't care about so let the
// user load them if they really want to.
return true;
}
if (ObjectiveSettings.class.isAssignableFrom(kls)) {
ObjectiveSettings obj = (ObjectiveSettings) s.get(
ObjectiveSettings.class, objId);
return imagesContainsObjectiveSettings(s, images, obj,
obToImageCache);
} else if (Objective.class.isAssignableFrom(kls)) {
Objective obj = (Objective) s.get(Objective.class, objId);
return imagesContainsInstrument(s, images, obj.getInstrument(),
obToImageCache);
} else if (Detector.class.isAssignableFrom(kls)) {
Detector obj = (Detector) s.get(Detector.class, objId);
return imagesContainsInstrument(s, images, obj.getInstrument(),
obToImageCache);
} else if (Dichroic.class.isAssignableFrom(kls)) {
Dichroic obj = (Dichroic) s.get(Dichroic.class, objId);
return imagesContainsInstrument(s, images, obj.getInstrument(),
obToImageCache);
} else if (FilterSet.class.isAssignableFrom(kls)) {
FilterSet obj = (FilterSet) s.get(FilterSet.class, objId);
return imagesContainsInstrument(s, images, obj.getInstrument(),
obToImageCache);
} else if (Filter.class.isAssignableFrom(kls)) {
Filter obj = (Filter) s.get(Filter.class, objId);
return imagesContainsInstrument(s, images, obj.getInstrument(),
obToImageCache);
} else if (LightSource.class.isAssignableFrom(kls)) {
LightSource obj = (LightSource) s.get(LightSource.class, objId);
return imagesContainsInstrument(s, images, obj.getInstrument(),
obToImageCache);
} else if (Laser.class.isAssignableFrom(kls)) {
Laser obj = (Laser) s.get(Laser.class, objId);
return imagesContainsInstrument(s, images, obj.getInstrument(),
obToImageCache);
} else if (LightSettings.class.isAssignableFrom(kls)) {
LightSettings obj = (LightSettings) s.get(LightSettings.class,
objId);
return imagesContainsInstrument(s, images, obj.getLightSource()
.getInstrument(), obToImageCache);
} else if (DetectorSettings.class.isAssignableFrom(kls)) {
DetectorSettings obj = (DetectorSettings) s.get(
DetectorSettings.class, objId);
if (imagesContainsInstrument(s, images, obj.getDetector()
.getInstrument(), obToImageCache)) {
return true;
} else {
List<LogicalChannel> lcs = (List<LogicalChannel>) s
.createQuery(
"select l from LogicalChannel l "
+ "where l.detectorSettings.id = "
+ obj.getId()).list();
for (LogicalChannel lc : lcs) {
if (doContains(data, LogicalChannel.class, lc.getId())) {
return true;
}
}
}
}
return false;
}
@Override
public void doClose() {
// no-op
}
@Override
@SuppressWarnings("unchecked")
public Set<Long> keys() {
Session session = session();
List<Long> list = (List<Long>) session.createQuery(
"select id from Share").list();
return new HashSet<Long>(list);
}
// Helpers
// =========================================================================
/**
* Returns a list of data from all shares.
*
* @return map of share ID to byte data.
*/
@SuppressWarnings("unchecked")
private Map<Long, byte[]> data(List<Long> ids) {
return sqlAction.getShareData(ids);
}
private Session session() {
return initialize().getSession();
}
/**
* Loads the {@link SessionFactory}. This is the only method which should
* access the {@link #__dont_use_me_factory} instance variable, since it
* guarantees loading. Any direct access may well throw a
* {@link NullPointerException}
*/
private synchronized SessionFactory initialize() {
if (__dont_use_me_factory != null) {
return __dont_use_me_factory; // GOOD!
}
if (ctx == null) {
throw new IllegalStateException("Have no context to load factory");
}
__dont_use_me_factory = (SessionFactory) ctx
.getBean("omeroSessionFactory");
if (__dont_use_me_factory == null) {
throw new IllegalStateException("Cannot find factory");
}
// Finally calling init here, since before it's not possible
init();
return __dont_use_me_factory;
}
@SuppressWarnings("unchecked")
private void synchronizeMembers(Session session, ShareData data) {
Query q = session.createQuery("select sm from ShareMember sm "
+ "where sm.parent = ?");
q.setLong(0, data.id);
List<ShareMember> members = (List<ShareMember>) q.list();
Map<Long, ShareMember> lookup = new HashMap<Long, ShareMember>();
for (ShareMember sm : members) {
lookup.put(sm.getChild().getId(), sm);
}
Set<Long> intendedUserIds = new HashSet<Long>(data.members);
intendedUserIds.add(data.owner);
Set<Long> currentUserIds = lookup.keySet();
Set<Long> added = new HashSet<Long>(intendedUserIds);
added.removeAll(currentUserIds);
for (Long toAdd : added) {
ShareMember sm = new ShareMember();
sm.link(new Share(data.id, false), new Experimenter(toAdd, false));
session.merge(sm);
}
Set<Long> removed = new HashSet<Long>(currentUserIds);
removed.removeAll(intendedUserIds);
for (Long toRemove : removed) {
session.delete(lookup.get(toRemove));
}
}
public static void main(String[] args) throws Exception {
final BlobShareStore store = new BlobShareStore();
final ShareData template = new ShareData();
template.enabled = true;
template.guests = Arrays.asList("a", "b", "c");
template.id = 100;
template.members = Arrays.asList(1L, 2L, 3L);
template.objectList = Arrays.asList(new Obj("type", 200L));
template.objectMap = new HashMap<String, List<Long>>();
template.objectMap.put("type", Arrays.asList(100L));
template.optlock = 12345;
template.owner = 67890;
File file = new File(args[0]);
if (file.exists()) {
int size = (int) file.length();
byte[] buf = new byte[size];
FileInputStream fis = new FileInputStream(file);
fis.read(buf);
ShareData data = store.parse(1, buf);
if (data == null) {
System.out.println("No share found");
System.exit(100);
}
System.out.println("enabled:" + data.enabled);
System.out.println("guests:" + data.guests);
System.out.println("id:" + data.id);
System.out.println("members:" + data.members);
System.out.println("objectList:" + data.objectList);
System.out.println("objectMap:" + data.objectMap);
System.out.println("optlock:" + data.optlock);
System.out.println("owner:" + data.owner);
} else {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
byte[] buf = store.parse(template);
fos.write(buf);
} finally {
if (fos != null) {
try {
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
System.exit(0);
}
}