/*
* Copyright 2008 Glencoe Software, Inc. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package ome.services.blitz.impl;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import ome.api.IQuery;
import ome.conditions.InternalException;
import ome.conditions.ResourceError;
import ome.formats.OMEROMetadataStore;
import ome.io.nio.OriginalFilesService;
import ome.model.IObject;
import ome.model.core.OriginalFile;
import ome.model.core.Pixels;
import ome.model.enums.Format;
import ome.model.screen.Plate;
import ome.parameters.Parameters;
import ome.services.blitz.util.BlitzExecutor;
import ome.services.blitz.util.BlitzOnly;
import ome.services.blitz.util.ServiceFactoryAware;
import ome.services.roi.PopulateRoiJob;
import ome.services.throttling.Adapter;
import ome.services.util.Executor;
import ome.system.OmeroContext;
import ome.system.ServiceFactory;
import ome.tools.spring.InternalServiceFactory;
import ome.util.SqlAction;
import omero.RBool;
import omero.RDouble;
import omero.RFloat;
import omero.RInt;
import omero.RLong;
import omero.RMap;
import omero.RString;
import omero.ServerError;
import omero.api.AMD_MetadataStore_createRoot;
import omero.api.AMD_MetadataStore_populateMinMax;
import omero.api.AMD_MetadataStore_saveToDB;
import omero.api.AMD_MetadataStore_setPixelsFile;
import omero.api.AMD_MetadataStore_updateObjects;
import omero.api.AMD_MetadataStore_updateReferences;
import omero.api.AMD_StatefulServiceInterface_close;
import omero.api._MetadataStoreOperations;
import omero.grid.InteractiveProcessorPrx;
import omero.grid.SharedResourcesPrx;
import omero.metadatastore.IObjectContainer;
import omero.model.FilesetJobLink;
import omero.model.ScriptJob;
import omero.util.IceMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.hibernate.Session;
import org.springframework.transaction.annotation.Transactional;
import Ice.Current;
public class MetadataStoreI extends AbstractCloseableAmdServant implements
_MetadataStoreOperations, ServiceFactoryAware, BlitzOnly {
private final static Logger log = LoggerFactory.getLogger(MetadataStoreI.class);
protected final Set<Long> savedPlates = new HashSet<Long>();
protected final Set<Long> savedImagesNotInPlates = new HashSet<Long>();
protected OMEROMetadataStore store;
protected ServiceFactoryI sf;
protected PopulateRoiJob popRoi;
protected final SqlAction sql;
protected final OriginalFilesService filesService;
protected final String omeroDataDir;
public MetadataStoreI(final BlitzExecutor be, PopulateRoiJob popRoi,
SqlAction sql, OriginalFilesService filesService,
String omeroDataDir) throws Exception {
super(null, be);
this.popRoi = popRoi;
this.sql = sql;
this.filesService = filesService;
this.omeroDataDir =
new File(omeroDataDir).getAbsolutePath() + File.separator;
}
public void setServiceFactory(ServiceFactoryI sf) throws ServerError {
this.sf = sf;
}
@Override
public void onSetOmeroContext(final OmeroContext ctx) throws Exception {
ServiceFactory sf = new InternalServiceFactory(ctx);
this.store = new OMEROMetadataStore(sf, sql);
}
@SuppressWarnings("unchecked")
private <T extends IObject> T safeReverse(Object o, IceMapper mapper) {
try {
return (T) mapper.reverse(o);
} catch (Exception e) {
throw new RuntimeException("Failed to safely reverse: " + o);
}
}
/**
* Called during
* {@link #saveToDB_async(AMD_MetadataStore_saveToDB, Current)} to prepare
* the list of pixels for post-processing.
*
* @see #processing()
*/
private void parsePixels(List<Pixels> pixels, Map<String, List<? extends IObject>> rv, IQuery query) {
synchronized (savedPlates) {
for (Pixels p : pixels) {
ome.model.core.Image i = p.getImage();
if (i != null) {
if (i.sizeOfWellSamples() < 1) {
savedImagesNotInPlates.add(i.getId());
} else {
for (ome.model.screen.WellSample ws :
i.unmodifiableWellSamples()) {
ome.model.screen.Well w = ws.getWell();
if (w != null) {
Plate plate = w.getPlate();
if (plate != null) {
savedPlates.add(plate.getId());
}
}
}
}
}
}
}
List<IObject> plates = loadObjects("Plate", query, savedPlates);
List<IObject> images = loadObjects("Image", query, savedImagesNotInPlates);
rv.put("Plate", plates);
rv.put("Image", images);
rv.put("Pixels", pixels);
}
private List<IObject> loadObjects(String type, IQuery query, Collection<Long> ids) {
if (ids != null && ids.size() > 0) {
return query.findAllByQuery(
"select p from " + type + " p where p.id in (:ids)",
new Parameters().addIds(ids));
}
return null;
}
// ~ Service methods
// =========================================================================
public void createRoot_async(final AMD_MetadataStore_createRoot __cb,
final Current __current) throws ServerError {
final IceMapper mapper = new IceMapper(IceMapper.VOID);
runnableCall(__current, new Adapter(__cb, __current, mapper,
this.sf.executor, this.sf.principal, new Executor.SimpleWork(
this, "createRoot") {
@Transactional(readOnly = true)
public Object doWork(Session session, ServiceFactory sf) {
store.createRoot();
return null;
}
}));
}
public void populateMinMax_async(
final AMD_MetadataStore_populateMinMax __cb,
final double[][][] imageChannelGlobalMinMax, final Current __current)
throws ServerError {
final IceMapper mapper = new IceMapper(IceMapper.VOID);
runnableCall(__current, new Adapter(__cb, __current, mapper,
this.sf.executor, this.sf.principal, new Executor.SimpleWork(
this, "populateMinMax") {
@Transactional(readOnly = false)
public Object doWork(Session session, ServiceFactory sf) {
store.populateMinMax(imageChannelGlobalMinMax);
return null;
}
}));
}
public void saveToDB_async(final AMD_MetadataStore_saveToDB __cb,
final FilesetJobLink link, final Current __current) throws ServerError {
final IceMapper mapper = new IceMapper(IceMapper.PRIMITIVE_FILTERABLE_COLLECTION_MAP);
final ome.model.fs.FilesetJobLink link_ =
(ome.model.fs.FilesetJobLink) mapper.reverse(link);
runnableCall(__current, new Adapter(__cb, __current, mapper,
this.sf.executor, this.sf.principal, new Executor.SimpleWork(
this, "saveToDb") {
@Transactional(readOnly = false)
public Object doWork(Session session, ServiceFactory sf) {
Map<String, List<? extends IObject>> rv = new HashMap<String, List<? extends IObject>>();
List<Pixels> pix = store.saveToDB(link_);
rv.put("Pixels", pix);
parsePixels(pix, rv, sf.getQueryService());
return rv;
}
}));
}
public void updateObjects_async(AMD_MetadataStore_updateObjects __cb,
final IObjectContainer[] objects, Current __current)
throws ServerError {
final IceMapper mapper = new IceMapper(IceMapper.VOID);
runnableCall(__current, new Adapter(__cb, __current, mapper,
this.sf.executor, this.sf.principal, new Executor.SimpleWork(
this, "updateObjects") {
@Transactional(readOnly = true)
public Object doWork(Session session, ServiceFactory sf) {
for (IObjectContainer o : objects) {
IObject sourceObject;
try {
sourceObject = (IObject) mapper
.reverse(o.sourceObject);
} catch (Exception e) {
// TODO: This is **WRONG**; exception handling
// here is messed up.
throw new RuntimeException(e);
}
store.updateObject(o.LSID, sourceObject, o.indexes);
}
return null;
}
}));
}
public void updateReferences_async(AMD_MetadataStore_updateReferences __cb,
final Map<String, String[]> references, Current __current)
throws ServerError {
final IceMapper mapper = new IceMapper(IceMapper.VOID);
runnableCall(__current, new Adapter(__cb, __current, mapper,
this.sf.executor, this.sf.principal, new Executor.SimpleWork(
this, "updateReferences") {
@Transactional(readOnly = true)
public Object doWork(Session session, ServiceFactory sf) {
store.updateReferences(references);
return null;
}
}));
}
/**
* Called after some number of Passes the {@link #savedPlates} to a
* background processor for further work. This happens on
* {@link #close_async(AMD_StatefulServiceInterface_close, Current)} since
* no further pixels can be created, but also on
* {@link #createRoot_async(AMD_MetadataStore_createRoot, Current)} which is
* used by the client to reset the status of this instance. To prevent any
* possible
*/
public void postProcess_async(omero.api.AMD_MetadataStore_postProcess __cb, Current __current)
throws ServerError {
final IceMapper mapper = new IceMapper(IceMapper.UNMAPPED);
final List<Long> copy = new ArrayList<Long>();
final List<InteractiveProcessorPrx> procs = new ArrayList<InteractiveProcessorPrx>();
runnableCall(__current, new Adapter(__cb, __current, mapper,
this.sf.executor, this.sf.principal, new Executor.SimpleWork(
this, "postProcess") {
@Transactional(readOnly = true)
public Object doWork(Session session, ServiceFactory _sf) {
synchronized (savedPlates) {
copy.addAll(savedPlates);
if (copy.size() == 0) {
return null;
}
for (Long id : copy) {
RMap inputs = omero.rtypes.rmap("Plate_ID",
omero.rtypes.rlong(id));
ScriptJob job = popRoi.createJob(_sf);
InteractiveProcessorPrx prx;
try {
SharedResourcesPrx sr = sf.sharedResources(null);
prx = sr.acquireProcessor(job, 15);
prx.execute(inputs);
prx.setDetach(true);
procs.add(prx);
log.info("Launched populateroi.py on plate " + id);
} catch (ServerError e) {
String msg = "Error acquiring post processor";
log.error(msg, e);
throw new InternalException(msg);
}
}
savedPlates.clear();
return procs;
}
}
}));
}
/* (non-Javadoc)
* @see omero.api._MetadataStoreOperations#setPixelsFile_async(omero.api.AMD_MetadataStore_setPixelsFile, long, String, String, Ice.Current)
*/
public void setPixelsFile_async(
AMD_MetadataStore_setPixelsFile __cb,
final long pixelsId, final String file, final String repo,
Current __current)
throws ServerError
{
final IceMapper mapper = new IceMapper(IceMapper.VOID);
runnableCall(__current, new Adapter(__cb, __current, mapper,
this.sf.executor, this.sf.principal, new Executor.SimpleWork(
this, "setPixelsParams")
{
@Transactional(readOnly = false)
public Object doWork(Session session, ServiceFactory sf)
{
final File targetFile;
if (file != null) {
targetFile = new File(file);
} else {
Pixels pixels = sf.getQueryService().get(
Pixels.class, pixelsId);
Format format = pixels.getImage().getFormat();
List<OriginalFile> files = pixels.linkedOriginalFileList();
if (files == null || files.size() == 0)
{
throw new ResourceError(String.format(
"Pixels:%d has no linked original files!",
pixelsId));
}
OriginalFile source = null;
for (OriginalFile file : files)
{
if (file.getMimetype().equals(format.getValue()))
{
if (source != null)
{
throw new ResourceError(String.format(
"Pixels:%d has at least two source " +
"original files %d and %d", pixelsId,
source.getId(), file.getId()));
}
source = file;
}
}
targetFile = new File(filesService.getFilesPath(source.getId()));
}
// We need to perform a case insensitive replacement due to the
// posibilitity that we're running on a case insensitive
// filesystem like NTFS. (See #5654)
Pattern p = Pattern.compile(
Pattern.quote(omeroDataDir), Pattern.CASE_INSENSITIVE);
String parent = targetFile.getParent();
if (log.isDebugEnabled())
{
log.debug(String.format(
"omero.data.dir: '%s' file.absolutePath: '%s' " +
"parent: '%s'", omeroDataDir,
targetFile.getAbsolutePath(), parent));
}
String path = p.matcher(parent).replaceFirst("");
sql.setPixelsNamePathRepo(pixelsId, targetFile.getName(),
path, repo);
return null;
}
}));
}
/**
* Transforms an OMERO RType into the corresponding Java type.
*
* @param x
* OMERO RType value.
* @return Java type or <code>null</code> if <code>x</code> is
* <code>null</code>.
*/
public Integer toJavaType(RInt x) {
return x == null ? null : x.getValue();
}
/**
* Transforms an OMERO RType into the corresponding Java type.
*
* @param x
* OMERO RType value.
* @return Java type or <code>null</code> if <code>x</code> is
* <code>null</code>.
*/
public Long toJavaType(RLong x) {
return x == null ? null : x.getValue();
}
/**
* Transforms an OMERO RType into the corresponding Java type.
*
* @param x
* OMERO RType value.
* @return Java type or <code>null</code> if <code>x</code> is
* <code>null</code>.
*/
public Boolean toJavaType(RBool x) {
return x == null ? null : x.getValue();
}
/**
* Transforms an OMERO RType into the corresponding Java type.
*
* @param x
* OMERO RType value.
* @return Java type or <code>null</code> if <code>x</code> is
* <code>null</code>.
*/
public Float toJavaType(RFloat x) {
return x == null ? null : x.getValue();
}
/**
* Transforms an OMERO RType into the corresponding Java type.
*
* @param x
* OMERO RType value.
* @return Java type or <code>null</code> if <code>x</code> is
* <code>null</code>.
*/
public Double toJavaType(RDouble x) {
return x == null ? null : x.getValue();
}
/**
* Transforms an OMERO RType into the corresponding Java type.
*
* @param x
* OMERO RType value.
* @return Java type or <code>null</code> if <code>x</code> is
* <code>null</code>.
*/
public String toJavaType(RString x) {
return x == null ? null : x.getValue();
}
// Stateful interface methods
// =========================================================================
@Override
protected void preClose(Ice.Current current) {
// Nulling should be sufficient.
store = null;
}
@Override
protected void postClose(Current current) {
// no-op
}
}