Java tutorial
/* * $Id$ * * Copyright 2008 University of Dundee. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ package ome.services.blitz.impl; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import ome.api.IUpdate; import ome.api.RawFileStore; import ome.model.core.OriginalFile; import ome.services.blitz.util.BlitzExecutor; import ome.services.blitz.util.BlitzOnly; import ome.services.blitz.util.ServiceFactoryAware; import ome.services.scripts.RepoFile; import ome.services.scripts.ScriptRepoHelper; import ome.services.util.Executor; import ome.system.EventContext; import ome.system.ServiceFactory; import ome.tools.hibernate.QueryBuilder; import ome.util.Utils; import omero.ApiUsageException; import omero.RInt; import omero.RType; import omero.ResourceError; import omero.ServerError; import omero.ValidationException; import omero.api.AMD_IScript_canRunScript; import omero.api.AMD_IScript_deleteScript; import omero.api.AMD_IScript_editScript; import omero.api.AMD_IScript_getParams; import omero.api.AMD_IScript_getScriptID; import omero.api.AMD_IScript_getScriptText; import omero.api.AMD_IScript_getScriptWithDetails; import omero.api.AMD_IScript_getScripts; import omero.api.AMD_IScript_getUserScripts; import omero.api.AMD_IScript_runScript; import omero.api.AMD_IScript_uploadOfficialScript; import omero.api.AMD_IScript_uploadScript; import omero.api.AMD_IScript_validateScript; import omero.api._IScriptOperations; import omero.grid.InteractiveProcessorI; import omero.grid.InteractiveProcessorPrx; import omero.grid.JobParams; import omero.grid.ParamsHelper; import omero.grid.ProcessPrx; import omero.grid.ProcessorPrx; import omero.grid.ScriptProcessPrx; import omero.grid.SharedResourcesPrx; import omero.model.Experimenter; import omero.model.ExperimenterGroup; import omero.model.IObject; import omero.model.Job; import omero.model.OriginalFileI; import omero.model.ScriptJob; import omero.model.ScriptJobI; import omero.util.IceMapper; import org.apache.commons.io.FilenameUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.Session; import org.springframework.transaction.annotation.Transactional; import Ice.Current; /** * implementation of the IScript service interface. * * @author Donald MacDonald, donald@lifesci.dundee.ac.uk * @author Josh Moore, josh at glencoesoftware.com * @since 3.0-Beta3 * @see ome.api.IScript */ public class ScriptI extends AbstractAmdServant implements _IScriptOperations, ServiceFactoryAware, BlitzOnly { private final static Log log = LogFactory.getLog(ScriptI.class); protected ServiceFactoryI factory; protected ParamsHelper helper; protected final ScriptRepoHelper scripts; public ScriptI(BlitzExecutor be, ScriptRepoHelper scripts) { super(null, be); this.scripts = scripts; } public void setServiceFactory(ServiceFactoryI sf) throws ServerError { this.factory = sf; SharedResourcesI resources = (SharedResourcesI) sf.getServant(sf.sharedResources().ice_getIdentity()); helper = new ParamsHelper(resources, sf.getExecutor(), sf.getPrincipal()); } // ~ Process Service methods // ========================================================================= public void runScript_async(AMD_IScript_runScript __cb, final long scriptID, final Map<String, RType> inputs, final RInt waitSecs, final Current __current) throws ServerError { safeRunnableCall(__current, __cb, false, new Callable<ScriptProcessPrx>() { public ScriptProcessPrx call() throws ServerError { ScriptJob job = new ScriptJobI(); job.linkOriginalFile(new OriginalFileI(scriptID, false)); int timeout = 5; if (waitSecs != null) { timeout = waitSecs.getValue(); } SharedResourcesPrx srPrx = factory.sharedResources(__current); SharedResourcesI sr = (SharedResourcesI) factory.getServant(srPrx.ice_getIdentity()); InteractiveProcessorPrx ipPrx = sr.acquireProcessor(job, timeout, __current); InteractiveProcessorI ip = (InteractiveProcessorI) factory.getServant(ipPrx.ice_getIdentity()); ProcessPrx proc = ip.execute(omero.rtypes.rmap(inputs), __current); ScriptProcessI process = new ScriptProcessI(factory, __current, ipPrx, ip, proc); return process.getProxy(); } }); } public void canRunScript_async(AMD_IScript_canRunScript __cb, final long scriptID, final Current __current) throws ServerError { safeRunnableCall(__current, __cb, false, new Callable<Boolean>() { public Boolean call() throws ServerError { if (scripts.isInRepo(scriptID)) { return true; } // currently assuming that if we have the scriptID then // it will have to be visible to the user/group and // therefore those are the values we should ask the // processor about. ProcessorCallbackI callback = new ProcessorCallbackI(factory); ProcessorPrx server = callback.activateAndWait(__current); return server != null; } }); } // ~ Script Service methods // ========================================================================= /** * Get the id of the official script with given path. * * @param scriptPath * {@link OriginalFile#getPath()} of the script to find id for. * @param __current * ice context. * @return The id of the script, -1 if no script found, or more than one * script with that name. */ public void getScriptID_async(final AMD_IScript_getScriptID __cb, final String scriptPath, final Current __current) throws ServerError { safeRunnableCall(__current, __cb, false, new Callable<Long>() { public Long call() { Long id = scripts.findInDb(scriptPath, true); if (id == null) { return -1L; } else { return id; } } }); } /** * Upload script to the server. * * @param scriptText * @param __current * ice context. * @return id of the script. */ public void uploadScript_async(final AMD_IScript_uploadScript __cb, final String path, final String scriptText, final Current __current) throws ServerError { safeRunnableCall(__current, __cb, false, new Callable<Long>() { public Long call() throws Exception { OriginalFile file = makeFile(path, scriptText); file = writeContent(file, scriptText); validateParams(__current, file); return file.getId(); } }); } public void uploadOfficialScript_async(AMD_IScript_uploadOfficialScript __cb, final String path, final String scriptText, final Current __current) throws ServerError { safeRunnableCall(__current, __cb, false, new Callable<Long>() { public Long call() throws Exception { EventContext ec = factory.getEventContext(); if (!ec.isCurrentUserAdmin()) { throw new omero.SecurityViolation(null, null, "User is not an administrator"); } try { // ticket:2356 - should only overwrite non-scripts Long scriptID = scripts.findInDb(path, true); Long fileID = scripts.findInDb(path, false); if (scriptID != null) { throw new ApiUsageException(null, null, "Path already exists: " + path + "\n" + "Use editScript to modify existing official scripts."); } else if (fileID != null) { log.info("Overwriting existing non-script: " + fileID); } RepoFile f = scripts.write(path, scriptText); OriginalFile file = scripts.addOrReplace(f, fileID); validateParams(__current, file); return file.getId(); } catch (IOException e) { omero.ServerError se = new omero.InternalException(null, null, "Cannot write " + path); IceMapper.fillServerError(se, e); throw se; } } }); } public void editScript_async(final AMD_IScript_editScript __cb, final omero.model.OriginalFile fileObject, final String scriptText, final Current __current) throws ServerError { safeRunnableCall(__current, __cb, true, new Callable<Object>() { public Object call() throws Exception { if (fileObject == null) { throw new ApiUsageException(null, null, "file object cannot be null"); } OriginalFile file = null; if (fileObject.isLoaded()) { IceMapper mapper = new IceMapper(); file = (OriginalFile) mapper.reverse(fileObject); } else { file = getOriginalFileOrNull(fileObject.getId().getValue()); } if (file == null) { throw new ApiUsageException(null, null, "could not find file: " + fileObject.getId().getValue()); } // Removing update event // to prevent optimistic locking file.setMimetype("text/x-python"); file = updateFile(file); OriginalFile official = scripts.load(file.getId(), true); if (official != null) { String fullname = official.getPath() + official.getName(); RepoFile f = scripts.write(fullname, scriptText); file = scripts.update(f, file.getId()); } else { file = writeContent(file, scriptText); } validateParams(__current, file); return null; // void } }); } /** * Return the script with the name to the user. * * @param name * see above. * @param __current * ice context. * @return see above. * @throws ServerError * validation, api usage. */ public void getScriptWithDetails_async(final AMD_IScript_getScriptWithDetails __cb, final long id, Current __current) throws ServerError { safeRunnableCall(__current, __cb, false, new Callable<Object>() { public Object call() throws Exception { final OriginalFile file = getOriginalFileOrNull(id); if (file == null) { return null; } Map<String, RType> scr = new HashMap<String, RType>(); scr.put(loadText(file), new omero.util.IceMapper().toRType(file)); return scr; } }); } private String loadText(final OriginalFile file) throws ServerError { if (scripts.isInRepo(file.getId())) { try { return scripts.read(file.getPath() + file.getName()); } catch (IOException e) { omero.ResourceError re = new omero.ResourceError(null, null, "Failed to load " + file); IceMapper.fillServerError(re, e); throw re; } } final Long size = file.getSize(); if (size == null || size.longValue() > Integer.MAX_VALUE || size.longValue() < 0) { throw new ValidationException(null, null, "Script size : " + size + " invalid on Blitz.OMERO server."); } return (String) factory.executor.execute(factory.principal, new Executor.SimpleWork(this, "getScriptWithDetails") { @Transactional(readOnly = true) public Object doWork(Session session, ServiceFactory sf) { RawFileStore rawFileStore = sf.createRawFileStore(); try { rawFileStore.setFileId(file.getId()); String script = new String(rawFileStore.read(0L, (int) file.getSize().longValue())); return script; } finally { rawFileStore.close(); } } }); } /** * Return the script with the name to the user. * * @param name * see above. * @param __current * ice context. * @return see above. * @throws ServerError * validation, api usage. */ public void getScriptText_async(final AMD_IScript_getScriptText __cb, final long id, Current __current) throws ServerError { safeRunnableCall(__current, __cb, false, new Callable<Object>() { public Object call() throws Exception { final OriginalFile file = getOriginalFileOrNull(id); if (file == null) { return null; } return loadText(file); } }); } /** * Get the Parameters of the script. * * @param id * see above. * @param __current * Ice context * @return see above. * @throws ServerError * validation, api usage. */ public void getParams_async(final AMD_IScript_getParams __cb, final long id, final Current __current) throws ServerError { safeRunnableCall(__current, __cb, false, new Callable<Object>() { public Object call() throws Exception { return helper.getOrCreateParams(id, __current); } }); } /** * Get Scripts will return all the scripts by id and name available on the * server. * * @param __current * ice context, * @return see above. * @throws ServerError * validation, api usage. */ public void getScripts_async(final AMD_IScript_getScripts __cb, Current __current) throws ServerError { safeRunnableCall(__current, __cb, false, new Callable<Object>() { public Object call() throws Exception { List<OriginalFile> files = scripts.loadAll(true); // FIXME IceMapper mapper = new IceMapper(); return mapper.map(files); } }); } @SuppressWarnings("unchecked") public void getUserScripts_async(AMD_IScript_getUserScripts __cb, final List<IObject> acceptsList, Current __current) throws ServerError { safeRunnableCall(__current, __cb, false, new Callable<Object>() { public Object call() throws Exception { final QueryBuilder qb = new QueryBuilder(); qb.select("o").from("OriginalFile", "o"); if (!parseAcceptsList(qb, acceptsList)) { long gid = factory.sessionManager.getEventContext(factory.principal).getCurrentGroupId(); qb.and("o.details.group.id = " + gid); } List<Long> officialIds = scripts.idsInDb(); if (officialIds != null && officialIds.size() > 0) { qb.and("o.id not in (:ids) "); qb.paramList("ids", officialIds); } List<OriginalFile> files = (List<OriginalFile>) factory.executor.execute(factory.principal, new Executor.SimpleWork(this, "getUserScripts") { @Transactional(readOnly = true) public Object doWork(Session session, ServiceFactory sf) { return qb.query(session).list(); } }); IceMapper mapper = new IceMapper(); return mapper.map(files); } }); } @SuppressWarnings("unchecked") public void validateScript_async(AMD_IScript_validateScript __cb, final Job j, final List<IObject> acceptsList, Current __current) throws ServerError { safeRunnableCall(__current, __cb, false, new Callable<Object>() { public Object call() throws Exception { final boolean official = acceptsList != null && acceptsList.size() == 0; final QueryBuilder qb = new QueryBuilder(); qb.select("o").from("Job", "j"); qb.join("j.originalFileLinks", "links", false, false); qb.join("links.child", "o", false, false); parseAcceptsList(qb, acceptsList); qb.and("j.id = :id"); qb.param("id", j.getId().getValue()); OriginalFile file = (OriginalFile) factory.executor.execute(factory.principal, new Executor.SimpleWork(this, "validateScript", j.getId().getValue(), acceptsList) { @Transactional(readOnly = true) public Object doWork(Session session, ServiceFactory sf) { List<OriginalFile> files = (List<OriginalFile>) qb.query(session).list(); if (files.size() != 1) { throw new ome.conditions.ValidationException( "Found wrong number of files: " + files); } Long id = files.get(0).getId(); if (official) { return scripts.load(id, session, getSqlAction(), true); } else { return sf.getQueryService().get(OriginalFile.class, id); } } }); return new IceMapper().map(file); } }); } /** * Delete the script with id from the server. * * @param id * the id of the script to delete. * */ public void deleteScript_async(final AMD_IScript_deleteScript cb, final long id, Current __current) throws ServerError { safeRunnableCall(__current, cb, true, new Callable<Object>() { public Object call() throws Exception { OriginalFile file = getOriginalFileOrNull(id); if (file == null) { throw new ApiUsageException(null, null, "No script with id " + id + " on server."); } deleteOriginalFile(file); return null; // void } }); } // Non-public-methods // ========================================================================= private boolean parseAcceptsList(final QueryBuilder qb, final List<IObject> acceptsList) { qb.where(); qb.and("o.mimetype = '" + ParamsHelper.PYTHONSCRIPT + "'"); if (acceptsList != null && acceptsList.size() > 0) { for (IObject object : acceptsList) { if (object instanceof Experimenter) { qb.and("o.details.owner.id = :oid"); qb.param("oid", object.getId().getValue()); } else if (object instanceof ExperimenterGroup) { qb.and("o.details.group.id = :gid"); qb.param("gid", object.getId().getValue()); } else { throw new ome.conditions.ValidationException("Unsupported accept-type: " + object); } } return true; } return false; } /** * Make the file, this is a temporary file which will be changed when the * script is validated. * * @param script * script. * @return OriginalFile tempfile.. * @throws ServerError */ private OriginalFile makeFile(final String path, final String script) throws ServerError { OriginalFile file = new OriginalFile(); file.setName(FilenameUtils.getName(path)); file.setPath(FilenameUtils.getFullPath(path)); file.setMimetype(ParamsHelper.PYTHONSCRIPT); file.setSize((long) script.getBytes().length); file.setSha1(Utils.bufferToSha1(script.getBytes())); return updateFile(file); } /** * Update the file with new data. * * @param file * new file data to be updated. * @return updated file. * @throws ServerError */ private OriginalFile updateFile(final OriginalFile file) throws ServerError { OriginalFile updatedFile = (OriginalFile) factory.executor.execute(factory.principal, new Executor.SimpleWork(this, "updateFile") { @Transactional(readOnly = false) public Object doWork(Session session, ServiceFactory sf) { IUpdate update = sf.getUpdateService(); file.getDetails().setUpdateEvent(null); return update.saveAndReturnObject(file); } }); return updatedFile; } /** * Write the content of the script to the script to the originalfile. * * @param file * file * @param script * script * @throws ServerError */ private OriginalFile writeContent(final OriginalFile file, final String script) throws ServerError { return (OriginalFile) factory.executor.execute(factory.principal, new Executor.SimpleWork(this, "writeContent") { @Transactional(readOnly = false) public Object doWork(Session session, ServiceFactory sf) { final byte[] buf = script.getBytes(); final RawFileStore rawFileStore = sf.createRawFileStore(); try { rawFileStore.setFileId(file.getId()); rawFileStore.truncate(buf.length); // ticket:2337 rawFileStore.write(buf, 0, buf.length); return rawFileStore.save(); } finally { rawFileStore.close(); } } }); } /** * Method to delete the original file * * @param file * the original file. * */ private void deleteOriginalFile(final OriginalFile file) throws ServerError { if (file == null) { return; } if (scripts.delete(file.getId())) { return; } Boolean success = (Boolean) factory.executor.execute(factory.principal, new Executor.SimpleWork(this, "deleteOriginalFile") { @Transactional(readOnly = false) public Object doWork(Session session, ServiceFactory sf) { IUpdate update = sf.getUpdateService(); try { update.deleteObject(file); } catch (ome.conditions.ValidationException ve) { return false; } return true; } }); if (success == null || !success) { throw new omero.ApiUsageException(null, null, "Cannot delete " + file + "\nIs in use by other objects"); } } /** * Method to get the original file of the script with id. This method will * not throw an exception, but instead will return null. * * @param name * See above. * @return original file or null if script does not exist or more than one * script with name exists. */ @SuppressWarnings("unchecked") private OriginalFile getOriginalFileOrNull(long id) { try { final String queryString = "from OriginalFile as o where o.mimetype = '" + ParamsHelper.PYTHONSCRIPT + "' and o.id = " + id; OriginalFile file = (OriginalFile) factory.executor.execute(factory.principal, new Executor.SimpleWork(this, "getOriginalFileOrNull", id) { @Transactional(readOnly = true) public Object doWork(Session session, ServiceFactory sf) { return sf.getQueryService().findByQuery(queryString, null); } }); return file; } catch (RuntimeException re) { return null; } } private void validateParams(final Current __current, OriginalFile file) throws ServerError, ApiUsageException { try { JobParams params = helper.getOrCreateParams(file.getId(), __current); if (params == null) { throw new ApiUsageException(null, null, "Script error: no params found."); } } catch (ResourceError re) { // Probably a user script for which there's currently // no processor running. This doesn't signal a bad script // like null params do, but rather just that we'll have to // generate the params later. } catch (ValidationException ve) { // Disable file - ticket:2282 file.setMimetype("text/plain"); file = updateFile(file); // ticket:2184 - No longer catching ValidationException // so that if a processor is available that users get // feedback as quickly as possible. If there is something // else throwing a ValidationException in this call path // then we will have to add a new exception subclass. throw ve; } catch (Exception e) { // ticket:2044. Ignoring other exceptions as well since these // may be caused by processor misbehavior. Note: the same // exception may be thrown again during execution log.warn("Unexpected exception on validateParams", e); } } }