/*
* $Id$
*
* Copyright 2009 Glencoe Software, Inc. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package ome.services.blitz.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import ome.api.JobHandle;
import ome.model.IObject;
import ome.services.blitz.fire.Registry;
import ome.services.blitz.fire.TopicManager;
import ome.services.blitz.util.BlitzExecutor;
import ome.services.blitz.util.BlitzOnly;
import ome.services.blitz.util.ParamsCache;
import ome.services.blitz.util.ResultHolder;
import ome.services.blitz.util.ServiceFactoryAware;
import ome.services.scripts.ScriptRepoHelper;
import ome.services.util.Executor;
import ome.system.ServiceFactory;
import ome.util.Filterable;
import omero.ApiUsageException;
import omero.InternalException;
import omero.RTime;
import omero.ServerError;
import omero.ValidationException;
import omero.constants.categories.PROCESSCALLBACK;
import omero.constants.categories.PROCESSORCALLBACK;
import omero.constants.topics.PROCESSORACCEPTS;
import omero.grid.InteractiveProcessorI;
import omero.grid.InteractiveProcessorPrx;
import omero.grid.InteractiveProcessorPrxHelper;
import omero.grid.InternalRepositoryPrx;
import omero.grid.ParamsHelper;
import omero.grid.ProcessorPrx;
import omero.grid.RepositoryMap;
import omero.grid.RepositoryPrx;
import omero.grid.TablePrx;
import omero.grid.TablePrxHelper;
import omero.grid.TablesPrx;
import omero.grid.TablesPrxHelper;
import omero.grid._InteractiveProcessorTie;
import omero.grid._SharedResourcesOperations;
import omero.model.Job;
import omero.model.JobStatus;
import omero.model.JobStatusI;
import omero.model.OriginalFile;
import omero.model.OriginalFileI;
import omero.util.IceMapper;
import org.hibernate.ObjectNotFoundException;
import org.hibernate.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import Ice.Current;
import Ice.LocalException;
import Ice.UserException;
/**
* Implementation of the SharedResources interface.
*
* @author Josh Moore, josh at glencoesoftware.com
* @since Beta4.1
* @see omero.grid.SharedResources
*/
public class SharedResourcesI extends AbstractCloseableAmdServant implements
_SharedResourcesOperations, BlitzOnly, ServiceFactoryAware,
ParamsHelper.Acquirer { // FIXME
/**
* If no value is passed to this instance, this is the time (1 hour)
* which will be used for a script session.
*/
public final static long DEFAULT_TIMEOUT = 60 * 60 * 1000L;
private final static Logger log = LoggerFactory.getLogger(SharedResourcesI.class);
private final Set<String> tableIds = new HashSet<String>();
private final Set<String> processorIds = new HashSet<String>();
private final TopicManager topicManager;
private final Registry registry;
private final ScriptRepoHelper helper;
private final ParamsCache paramsCache;
/**
* How long to wait for shared resources (e.g. TablesPrx) to respond to
* requests.
*/
private final long waitMillis;
/**
* Length of time (ms) to give a script session to live. Once the session
* times out, the process will be killed.
*
* @since 5.0.3
*/
private final long timeout;
private ServiceFactoryI sf;
public SharedResourcesI(BlitzExecutor be, TopicManager topicManager,
Registry registry, ScriptRepoHelper helper, ParamsCache cache) {
this(be, topicManager, registry, helper, cache, 5000);
}
public SharedResourcesI(BlitzExecutor be, TopicManager topicManager,
Registry registry, ScriptRepoHelper helper, ParamsCache cache,
long waitMillis) {
this( be, topicManager, registry, helper, cache, waitMillis, DEFAULT_TIMEOUT);
}
public SharedResourcesI(BlitzExecutor be, TopicManager topicManager,
Registry registry, ScriptRepoHelper helper, ParamsCache cache,
long waitMillis, long timeout) {
super(null, be);
this.waitMillis = waitMillis;
this.topicManager = topicManager;
this.registry = registry;
this.helper = helper;
this.paramsCache = cache;
this.timeout = timeout;
// In order to prevent overflow of currentTimeMillis+timeout and more
// generally to prevent nonsensical timeouts, we limit to 1 year.
if (timeout > DEFAULT_TIMEOUT * 24 * 365) {
throw new ome.conditions.InternalException(
"Timeout too large: " + timeout);
}
}
public void setServiceFactory(ServiceFactoryI sf) throws ServerError {
this.sf = sf;
}
@Override
protected void preClose(Ice.Current current) {
synchronized (tableIds) {
for (String id : tableIds) {
TablePrx table =
TablePrxHelper.uncheckedCast(
sf.adapter.getCommunicator().stringToProxy(id));
try {
table.close();
} catch (Ice.NotRegisteredException e) {
log.debug("Table already gone: " + id);
} catch (Exception e) {
log.error("Exception while closing table " + id, e);
}
}
tableIds.clear();
}
}
@Override
protected void postClose(Current current) {
// no-op
}
// Acquisition framework
// =========================================================================
private void register(TablePrx prx) {
if (prx != null) {
synchronized(tableIds) {
tableIds.add(
Ice.Util.identityToString(prx.ice_getIdentity()));
}
}
}
private void checkAcquisitionWait(int seconds) throws ApiUsageException {
if (seconds > (3 * 60)) {
ApiUsageException aue = new ApiUsageException();
aue.message = "Delay is too long. Maximum = 3 minutes.";
throw aue;
}
}
/**
* A task that gets applied to various proxies to test their validity.
* Usually defined inline as anonymous classes.
*
* @see {@link ProcessorCheck}
*/
private interface RepeatTask<U extends Ice.ObjectPrx> {
void requestService(Ice.ObjectPrx server, ResultHolder<U> holder)
throws ServerError;
}
private <U extends Ice.ObjectPrx> U lookup(long millis, List<Ice.ObjectPrx> objectPrxs,
RepeatTask<U> task) throws ServerError {
ResultHolder<U> holder = new ResultHolder<U>(millis);
for (Ice.ObjectPrx prx : objectPrxs) {
if (prx != null) {
task.requestService(prx, holder);
}
}
return holder.get();
}
// Public interface
// =========================================================================
static String QUERY = "select o from OriginalFile o where o.mimetype = 'Repository'";
public RepositoryPrx getScriptRepository(Current __current)
throws ServerError {
InternalRepositoryPrx[] repos = registry.lookupRepositories();
InternalRepositoryPrx prx = null;
if (repos != null) {
for (int i = 0; i < repos.length; i++) {
if (repos[i] != null) {
if (repos[i].toString().contains(helper.getUuid())) {
prx = repos[i];
}
}
}
}
return prx == null ? null : prx.getProxy();
}
@SuppressWarnings("unchecked")
public RepositoryMap repositories(Current current) throws ServerError {
// TODO
// Possibly need to throttle the numbers of acquisitions per time.
// Need to keep up with closing
// might need to cache the found repositories.
IceMapper mapper = new IceMapper();
List<OriginalFile> objs = (List<OriginalFile>) mapper
.map((List<Filterable>) sf.executor.execute(current.ctx, sf.principal,
new Executor.SimpleWork(this, "acquireRepositories") {
@Transactional(readOnly = true)
public Object doWork(Session session,
ServiceFactory sf) {
return sf.getQueryService().findAllByQuery(
QUERY, null);
}
}));
InternalRepositoryPrx[] repos = registry.lookupRepositories();
RepositoryMap map = new RepositoryMap();
map.descriptions = new ArrayList<OriginalFile>();
map.proxies = new ArrayList<RepositoryPrx>();
List<Long> found = new ArrayList<Long>();
for (InternalRepositoryPrx i : repos) {
if (i == null) {
continue;
}
try {
OriginalFile desc = i.getDescription();
if (desc == null || desc.getId() == null) {
log.warn("Description is null for " + i);
continue;
}
RepositoryPrx proxy = i.getProxy();
map.descriptions.add(desc);
map.proxies.add(proxy);
found.add(desc.getId().getValue());
sf.allow(proxy);
} catch (Ice.LocalException e) {
// Ok.
}
}
for (OriginalFile r : objs) {
if (!found.contains(r.getId().getValue())) {
map.descriptions.add(r);
map.proxies.add(null);
}
}
return map;
}
public boolean areTablesEnabled(Current __current) throws ServerError {
TablesPrx[] tables = registry.lookupTables();
return null != lookup(waitMillis, Arrays.<Ice.ObjectPrx> asList(tables),
new RepeatTask<TablesPrx>() {
public void requestService(Ice.ObjectPrx prx,
final ResultHolder<TablesPrx> holder) {
final TablesPrx server = TablesPrxHelper
.checkedCast(prx);
try {
if (server != null && server.getRepository() != null) {
holder.set(server);
}
} catch (Exception e) {
log.debug("Exception on getRepository: " + e);
holder.set(null);
}
}
});
}
public TablePrx newTable(final long repo, String path, Current __current)
throws ServerError {
// Overriding repository logic for creation. As long as the
// security system is still in charge, we need to have the files
// being created for the proper user.
final OriginalFile file = new OriginalFileI();
RTime time = omero.rtypes.rtime(System.currentTimeMillis());
file.setAtime(time);
file.setMtime(time);
file.setCtime(time);
file.setMimetype(omero.rtypes.rstring("OMERO.tables"));
file.setPath(omero.rtypes.rstring(path));
file.setName(omero.rtypes.rstring(path));
IObject obj = (IObject) sf.executor.execute(__current.ctx,
sf.principal, new Executor.SimpleWork(this, "newTable", repo, path) {
@Transactional(readOnly = false)
public Object doWork(Session session, ServiceFactory sf) {
try {
IObject obj = (IObject) new IceMapper().reverse(file);
return sf.getUpdateService().saveAndReturnObject(obj);
} catch (Exception e) {
log.error(e.toString()); // slf4j migration: toString()
return null;
}
}
});
OriginalFile saved = (OriginalFile) new IceMapper().map(obj);
if (saved == null) {
throw new InternalException(null, null, "Failed to save file");
}
return openTable(saved, __current);
}
@SuppressWarnings("unchecked")
public TablePrx openTable(final OriginalFile file, final Current __current)
throws ServerError {
// Now make sure the current user has permissions to do this
if (file == null || file.getId() == null) {
throw new ValidationException(null, null,
"file must be a managed instance.");
}
try {
sf.executor.execute(__current.ctx, sf.principal,
new Executor.SimpleWork(this,
"checkOriginalFilePermissions", file.getId().getValue()) {
@Transactional(readOnly = true)
public Object doWork(Session session, ServiceFactory sf) {
return sf.getQueryService().get(
ome.model.core.OriginalFile.class,
file.getId().getValue());
}
});
} catch (Exception e) {
IceMapper mapper = new IceMapper();
ServerError ue = mapper.handleServerError(e, this.ctx);
throw ue;
}
// Okay. All's valid.
TablesPrx[] tables = registry.lookupTables();
TablePrx tablePrx = (TablePrx) lookup(waitMillis,
Arrays.<Ice.ObjectPrx> asList(tables),
new RepeatTask<TablePrx>() {
public void requestService(Ice.ObjectPrx prx,
final ResultHolder holder) throws ServerError {
final TablesPrx server = TablesPrxHelper
.uncheckedCast(prx);
Map<String, String> ctx = new HashMap<String, String>();
ctx.put("omero.group", "-1");
server.begin_getTable(file, sf.proxy(), ctx,
new Ice.Callback() {
public void completed(Ice.AsyncResult r) {
try {
holder.set(server.end_getTable(r));
} catch (Exception e) {
holder.set(null);
}
}});
}
});
sf.allow(tablePrx);
register(tablePrx);
return tablePrx;
}
// Check job
// Lookup processor
// Create wrapper (InteractiveProcessor)
// Create session (with session)
// Setup environment
// Send off to processor
public InteractiveProcessorPrx acquireProcessor(final Job submittedJob,
int seconds, final Current current) throws ServerError {
checkAcquisitionWait(seconds);
// Check job
final IceMapper mapper = new IceMapper();
final ome.model.jobs.Job savedJob = saveJob(submittedJob, mapper, current);
if (savedJob == null) {
throw new ApiUsageException(null, null, "Could not submit job. ");
}
// Okay. All's valid.
final Job job = (Job) mapper.map(savedJob);
ResultHolder<ProcessorPrx> holder = new ResultHolder<ProcessorPrx>(seconds*1000);
ProcessorCallbackI callback = new ProcessorCallbackI(sf, holder, job);
ProcessorPrx server = callback.activateAndWait(current);
// Nothing left to try
if (server == null) {
final int count = callback.getResponses();
final String msg = String.format(
"No processor available! [%d response(s)]", count);
updateJob(job.getId().getValue(), "Error", msg, current);
throw new omero.NoProcessorAvailable(null, null, msg, count);
}
InteractiveProcessorI ip = new InteractiveProcessorI(sf.principal,
sf.sessionManager, sf.executor, server, job, timeout,
sf.control, paramsCache,
new ParamsHelper(this, sf.getExecutor(), sf.getPrincipal()),
helper, current);
Ice.Identity procId = sessionedID("InteractiveProcessor");
Ice.ObjectPrx rv = sf.registerServant(procId, new _InteractiveProcessorTie(ip));
sf.allow(rv);
return InteractiveProcessorPrxHelper.uncheckedCast(rv);
}
public void addProcessor(ProcessorPrx proc, Current __current)
throws ServerError {
topicManager.register(PROCESSORACCEPTS.value, proc, false);
processorIds.add(Ice.Util.identityToString(proc.ice_getIdentity()));
if (sf.control != null) {
sf.control.categories().add(
new String[]{PROCESSORCALLBACK.value, PROCESSCALLBACK.value});
}
}
public void removeProcessor(ProcessorPrx proc, Current __current)
throws ServerError {
topicManager.unregister(PROCESSORACCEPTS.value, proc);
processorIds.remove(Ice.Util.identityToString(proc.ice_getIdentity()));
}
//
// HELPERS
//
// =========================================================================
private Ice.Identity sessionedID(String type) {
String key = type + "-" + UUID.randomUUID();
return sf.getIdentity(key);
}
private ome.model.jobs.Job saveJob(final Job submittedJob,
final IceMapper mapper, final Ice.Current current) {
// First create the job with a status of WAITING.
// The InteractiveProcessor will be responsible for its
// further lifetime.
final ome.model.jobs.Job savedJob = (ome.model.jobs.Job) sf.executor
.execute(current.ctx, sf.principal, new Executor.SimpleWork(this,
"submitJob") {
@Transactional(readOnly = false)
public ome.model.jobs.Job doWork(Session session,
ServiceFactory sf) {
final JobHandle handle = sf.createJobHandle();
try {
JobStatus status = new JobStatusI();
status.setValue(omero.rtypes
.rstring(JobHandle.WAITING));
submittedJob.setStatus(status);
handle.submit((ome.model.jobs.Job) mapper
.reverse(submittedJob));
return handle.getJob();
} catch (ApiUsageException e) {
return null;
} catch (ObjectNotFoundException onfe) {
return null;
} finally {
if (handle != null) {
handle.close();
}
}
}
});
return savedJob;
}
private void updateJob(final long id, final String status, final String message, final Ice.Current current) {
sf.executor.execute(current.ctx, sf.principal, new Executor.SimpleWork(this, "updateJob") {
@Transactional(readOnly = false)
public Object doWork(Session session,
ServiceFactory sf) {
final JobHandle handle = sf.createJobHandle();
try {
handle.attach(id);
handle.setStatusAndMessage(status, message);
return null;
} finally {
handle.close();
}
}
});
}
}