/* * Copyright (C) 2012-2014 Glencoe Software, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package ome.services.blitz.repo; import java.util.Map; import java.util.List; import java.util.HashMap; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.aop.framework.Advised; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import Ice.Current; import ome.services.blitz.impl.AbstractCloseableAmdServant; import ome.services.blitz.impl.ServiceFactoryI; import ome.services.blitz.repo.PublicRepositoryI.AMD_submit; import ome.services.blitz.repo.path.FsFile; import ome.services.blitz.util.ServiceFactoryAware; import omero.ServerError; import omero.api.RawFileStorePrx; import omero.cmd.HandlePrx; import omero.grid.ImportLocation; import omero.grid.ImportProcessPrx; import omero.grid.ImportProcessPrxHelper; import omero.grid.ImportRequest; import omero.grid.ImportSettings; import omero.grid._ImportProcessOperations; import omero.grid._ImportProcessTie; import omero.model.Fileset; import omero.model.FilesetJobLink; /** * Represents a single import within a defined-session * all running server-side. * * @author Josh Moore, josh at glencoesoftware.com * @since 4.5 */ public class ManagedImportProcessI extends AbstractCloseableAmdServant implements _ImportProcessOperations, ServiceFactoryAware, ProcessContainer.Process { private final static Logger log = LoggerFactory.getLogger(ManagedImportProcessI.class); static class UploadState { final RawFileStorePrx prx; /** Next byte which should be written */ long offset = 0; UploadState(RawFileStorePrx prx) { if (prx == null) { throw new RuntimeException("Null not allowed!"); } this.prx = prx; } void setOffset(long offset) { this.offset = offset; } } /** * Current which created this instance. */ private final Ice.Current current; /** * The managed repo instance which created (and ultimately is reponsible * for) this import process. */ private final ManagedRepositoryI repo; /** * A proxy to this servant which can be given to clients to monitor the * import process. */ private final ImportProcessPrx proxy; /** * The model object as originally passed in by the client and then * modified and saved by the managed repository. */ private final Fileset fs; /** * The settings as passed in by the user. Never null. */ private final ImportSettings settings; /** * The import location as defined by the managed repository during * importFileset. Never null. */ private final ImportLocation location; /** * SessionI/ServiceFactoryI that this process is running in. */ private/* final */ServiceFactoryI sf; /** * A sparse and often empty map of the {@link UploadState} instances which * this import process is aware of. In a single-threaded model, this map * will likely only have at most one element, but depending on threads, * pauses, and restarts, this may contain more elements. After close * is called on each of the proxies, {@link #closeCalled(int)} will be * invoked with the integer lookup to this map, in which case the instance * will be purged. */ private final Cache<Integer, UploadState> uploaders = CacheBuilder.newBuilder().build(); /** * Handle which is the initial first step of import. */ private HandlePrx handle; /** * Create and register a servant for servicing the import process * within a managed repository. */ public ManagedImportProcessI(ManagedRepositoryI repo, Fileset fs, ImportLocation location, ImportSettings settings, Current __current) throws ServerError { super(null, null); this.repo = repo; this.fs = fs; this.settings = settings; this.location = location; this.current = __current; this.proxy = registerProxy(__current); setApplicationContext(repo.context); // TODO: The above could be moved to SessionI.internalServantConfig as // long as we're careful to remove all other, redundant calls to setAC. } public void setServiceFactory(ServiceFactoryI sf) throws ServerError { this.sf = sf; } /** * Adds this instance to the current session so that clients can communicate * with it. Once we move to opening a new session for this import, care * must be taken to guarantee that these instances don't leak: * i.e. who's responsible for closing them and removing them from the * adapter. */ protected ImportProcessPrx registerProxy(Ice.Current ignore) throws ServerError { _ImportProcessTie tie = new _ImportProcessTie(this); Ice.Current adjustedCurr = repo.makeAdjustedCurrent(current); Ice.ObjectPrx prx = repo.registerServant(tie, this, adjustedCurr); return ImportProcessPrxHelper.uncheckedCast(prx); } public ImportProcessPrx getProxy() { return this.proxy; } public Fileset getFileset() { return this.fs; } public ImportSettings getImportSettings(Current __current) { return this.settings; } // // ProcessContainer INTERFACE METHODS // public long getGroup() { return fs.getDetails().getGroup().getId().getValue(); } public void ping() { throw new RuntimeException("NYI"); } public void shutdown() { throw new RuntimeException("NYI"); } // // ICE INTERFACE METHODS // public RawFileStorePrx getUploader(final int i, Current current) throws ServerError { String mode = null; if (current != null && current.ctx != null) { mode = current.ctx.get("omero.fs.mode"); if (mode == null) { mode = "rw"; } } final String applicableMode = mode; final Callable<UploadState> rfsOpener = new Callable<UploadState>() { @Override public UploadState call() throws ServerError { final String path = location.sharedPath + FsFile.separatorChar + location.usedFiles.get(i); final RawFileStorePrx prx = repo.file(path, applicableMode, ManagedImportProcessI.this.current); try { registerCallback(prx, i); } catch (RuntimeException re) { try { prx.close(); // close if anything happens } catch (Exception e) { log.error("Failed to close RawFileStorePrx", e); } throw re; } return new UploadState(prx); } }; try { return uploaders.get(i, rfsOpener).prx; } catch (ExecutionException e) { if (e.getCause() instanceof RuntimeException) { throw (RuntimeException) e.getCause(); } else { /* there are no checked exceptions to worry about, so this cannot happen */ return null; } } } protected void registerCallback(RawFileStorePrx prx, final int idx) { Object servant = this.sf.getServant(prx.ice_getIdentity()); if (servant instanceof Advised) { try { servant = ((Advised) servant).getTargetSource().getTarget(); } catch (Exception e) { throw new RuntimeException(e); } } RepoRawFileStoreI store = (RepoRawFileStoreI) servant; final ManagedImportProcessI proc = this; store.setCallback(new RepoRawFileStoreI.NoOpCallback() { @Override public void onWrite(byte[] buf, long position, long length) { proc.setOffset(idx, position+length); } /** * During the close process, remove this instance from the * "uploaders" hash map in order to prevent concurrent access * issues. */ @Override public void onPreClose() { proc.closeCalled(idx); } }); } public HandlePrx verifyUpload(List<String> hashes, Current __current) throws ServerError { final int size = fs.sizeOfUsedFiles(); if (hashes == null) { throw new omero.ApiUsageException(null, null, "hashes list cannot be null"); } else if (hashes.size() != size) { throw new omero.ApiUsageException(null, null, String.format("hashes size should be %s not %s", size, hashes.size())); } Map<Integer, String> failingChecksums = new HashMap<Integer, String>(); for (int i = 0; i < size; i++) { String usedFile = location.sharedPath + FsFile.separatorChar + location.usedFiles.get(i); CheckedPath cp = repo.checkPath(usedFile, settings.checksumAlgorithm, this.current); final String clientHash = hashes.get(i); final String serverHash = cp.hash(); if (!clientHash.equals(serverHash)) { failingChecksums.put(i, serverHash); } } if (!failingChecksums.isEmpty()) { throw new omero.ChecksumValidationException(null, omero.ChecksumValidationException.class.toString(), "A checksum mismatch has occurred.", failingChecksums); } // i==0 is the upload job which is implicit. FilesetJobLink link = fs.getFilesetJobLink(0); repo.repositoryDao.updateJob(link.getChild(), "Finished", "Finished", this.current); // Now move on to the metadata import. link = fs.getFilesetJobLink(1); CheckedPath checkedPath = ((ManagedImportLocationI) location).getLogFile(); final omero.model.OriginalFile logFile = repo.findInDb(checkedPath, "r", __current); final String reqId = ImportRequest.ice_staticId(); final ImportRequest req = (ImportRequest) repo.getFactory(reqId, this.current).create(reqId); // TODO: Should eventually be from a new omero.client req.clientUuid = UUID.randomUUID().toString(); req.repoUuid = repo.getRepoUuid(); req.process = this.proxy; req.activity = link; req.location = location; req.settings = settings; req.logFile = logFile; final AMD_submit submit = repo.submitRequest(sf, req, this.current); this.handle = submit.ret; // TODO: in 5.1 this should be added to the request object ((ManagedImportRequestI) req).handle = submit.ret; return submit.ret; } // // GETTERS // public long getUploadOffset(int idx, Current ignore) throws ServerError { final UploadState state = uploaders.getIfPresent(idx); if (state == null) { return 0; } return state.offset; } public HandlePrx getHandle(Ice.Current ignore) { return handle; } // // OTHER LOCAL INVOCATIONS // public void setOffset(int idx, long offset) { final UploadState state = uploaders.getIfPresent(idx); if (state == null) { log.warn(String.format("setOffset(%s, %s) - no such object", idx, offset)); } else { state.setOffset(offset); log.debug(String.format("setOffset(%s, %s) successfully", idx, offset)); } } public void closeCalled(int idx) { final UploadState state = uploaders.getIfPresent(idx); if (state == null) { log.warn(String.format("closeCalled(%s) - no such object", idx)); } else { uploaders.invalidate(idx); log.debug(String.format("closeCalled(%s) successfully", idx)); } } // // CLOSE LOGIC // @Override protected void preClose(Current current) throws Throwable { // no-op } @Override protected void postClose(Current current) { // no-op } }