/*
* $Id$
*
* Copyright 2006-2015 University of Dundee. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package ome.services;
import java.awt.Dimension;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
import java.util.Set;
import ome.annotations.RolesAllowed;
import ome.api.IPixels;
import ome.api.IRepositoryInfo;
import ome.api.RawPixelsStore;
import ome.api.ServiceInterface;
import ome.conditions.ApiUsageException;
import ome.conditions.ResourceError;
import ome.conditions.RootException;
import ome.conditions.ValidationException;
import ome.io.nio.DimensionsOutOfBoundsException;
import ome.io.nio.PixelBuffer;
import ome.io.nio.PixelsService;
import ome.io.nio.RomioPixelBuffer;
import ome.model.core.Pixels;
import ome.parameters.Parameters;
import ome.util.PixelData;
import ome.util.ShallowCopy;
import ome.util.SqlAction;
import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.collect.MapMaker;
/**
* Implementation of the RawPixelsStore stateful service.
*
* @author <br>
* Josh Moore <a
* href="mailto:josh.moore@gmx.de"> josh.moore@gmx.de</a>
* @version 3.0 <small> (<b>Internal version:</b> $Revision$ $Date: 2005/07/05
* 16:13:52 $) </small>
* @since OMERO3
*/
@Transactional(readOnly = true)
public class RawPixelsBean extends AbstractStatefulBean implements
RawPixelsStore {
/** The logger for this particular class */
private static Logger log = LoggerFactory.getLogger(RawPixelsBean.class);
private static final long serialVersionUID = -6640632220587930165L;
private Long id;
private transient Long reset = null;
private transient Pixels pixelsInstance;
private transient PixelBuffer buffer;
private transient PixelsService dataService;
private transient IPixels metadataService;
/** the disk space checking service */
private transient IRepositoryInfo iRepositoryInfo;
/** is file service checking for disk overflow */
private transient boolean diskSpaceChecking;
/** A copy buffer for the pixel retrieval. */
private transient byte[] readBuffer;
/** Pixels set cache. */
private transient Map<Long, Pixels> pixelsCache;
/** SQL action instance for this class. */
private transient SqlAction sql;
/** The server's OMERO data directory. */
private transient String omeroDataDir;
/**
* default constructor
*/
public RawPixelsBean() {
}
/**
* overridden to allow Spring to set boolean
*
* @param checking
*/
public RawPixelsBean(boolean checking, String omeroDataDir) {
this.diskSpaceChecking = checking;
this.omeroDataDir = omeroDataDir;
}
public synchronized Class<? extends ServiceInterface> getServiceInterface() {
return RawPixelsStore.class;
}
public synchronized final void setPixelsMetadata(IPixels metaService) {
getBeanHelper().throwIfAlreadySet(this.metadataService, metaService);
metadataService = metaService;
}
public synchronized final void setPixelsData(PixelsService dataService) {
getBeanHelper().throwIfAlreadySet(this.dataService, dataService);
this.dataService = dataService;
}
/**
* Disk Space Usage service Bean injector
*
* @param iRepositoryInfo
* an <code>IRepositoryInfo</code>
*/
public synchronized final void setIRepositoryInfo(IRepositoryInfo iRepositoryInfo) {
getBeanHelper()
.throwIfAlreadySet(this.iRepositoryInfo, iRepositoryInfo);
this.iRepositoryInfo = iRepositoryInfo;
}
/**
* SQL action Bean injector
* @param sql a <code>SqlAction</code>
*/
public synchronized final void setSqlAction(SqlAction sql) {
getBeanHelper().throwIfAlreadySet(this.sql, sql);
this.sql = sql;
}
// ~ Lifecycle methods
// =========================================================================
// See documentation on JobBean#passivate
@RolesAllowed("user")
@Transactional(readOnly = true)
public synchronized void passivate() {
// Nothing necessary
}
// See documentation on JobBean#activate
@RolesAllowed("user")
@Transactional(readOnly = true)
public synchronized void activate() {
if (id != null) {
reset = id;
id = null;
}
}
@RolesAllowed("user")
@Transactional(readOnly = false)
public synchronized Pixels save() {
if (isModified()) {
Long id = (pixelsInstance == null) ? null : pixelsInstance.getId();
if (id == null) {
return null;
}
try {
byte[] hash = buffer.calculateMessageDigest();
pixelsInstance.setSha1(Hex.encodeHexString(hash));
} catch (RuntimeException re) {
// ticket:3140
if (re.getCause() instanceof FileNotFoundException) {
String msg = "Cannot find path. Deleted? " + buffer;
log.warn(msg);
clean(); // Prevent a second exception on close.
throw new ResourceError(msg);
}
throw re;
} catch (IOException e) {
log.warn("calculateMessageDigest failed on " + buffer, e);
throw new ResourceError(e.getMessage());
}
iUpdate.flush();
modified = false;
return new ShallowCopy().copy(pixelsInstance);
}
return null;
}
@RolesAllowed("user")
@Transactional(readOnly = false)
public synchronized void close() {
try {
save();
} catch (RootException root) {
// ticket:3140
// if one of our exceptions, then just rethrow
throw root;
} catch (RuntimeException re) {
Long id = (pixelsInstance == null ? null : pixelsInstance.getId());
log.error("Failed to update pixels: " + id, re);
} finally {
clean();
}
}
public synchronized void clean() {
dataService = null;
pixelsInstance = null;
try {
closePixelBuffer();
} finally {
buffer = null;
readBuffer = null;
pixelsCache = null;
}
}
/**
* Close the active pixel buffer, cleaning up any potential messes left by
* the pixel buffer itself.
*/
private synchronized void closePixelBuffer() {
try {
if (buffer != null) {
buffer.close();
}
} catch (IOException e) {
if (log.isDebugEnabled()) {
log.debug("Buffer could not be closed successfully.", e);
}
throw new ResourceError(e.getMessage()
+ " Please check server log.");
}
}
@RolesAllowed("user")
public synchronized void setPixelsId(long pixelsId, boolean bypassOriginalFile) {
if (id == null || id.longValue() != pixelsId) {
id = new Long(pixelsId);
pixelsInstance = null;
closePixelBuffer();
buffer = null;
reset = null;
if (pixelsCache != null && pixelsCache.containsKey(pixelsId))
{
pixelsInstance = pixelsCache.get(pixelsId);
}
else
{
pixelsInstance = iQuery.findByQuery(
"select p from Pixels as p " +
"join fetch p.pixelsType where p.id = :id",
new Parameters().addId(id));
}
if (pixelsInstance == null)
{
throw new ValidationException("Cannot read pixels id=" + id);
}
try {
buffer = dataService.getPixelBuffer(pixelsInstance, true);
} catch (RuntimeException re) {
// Rolling back to let the next setPixelsId try again
// since this is most likely our MissingPyramidException.
// If it's anything more serious, then the instance
// should most likely be closed.
id = null;
throw re;
}
}
}
@RolesAllowed("user")
public synchronized long getPixelsId() {
errorIfNotLoaded();
return id.longValue();
}
@RolesAllowed("user")
public String getPixelsPath() {
errorIfNotLoaded();
return buffer.getPath();
}
@RolesAllowed("user")
public synchronized void prepare(Set<Long> pixelsIds)
{
pixelsCache = new MapMaker().makeMap();
List<Pixels> pixelsList = iQuery.findAllByQuery(
"select p from Pixels as p join fetch p.pixelsType " +
"where p.id in (:ids)", new Parameters().addIds(pixelsIds));
for (Pixels pixels : pixelsList)
{
pixelsCache.put(pixels.getId(), pixels);
}
}
private synchronized void errorIfNotLoaded() {
// If we're not loaded because of passivation, then load.
if (reset != null) {
id = null;
setPixelsId(reset.longValue(), false);
reset = null;
}
if (buffer == null) {
throw new ApiUsageException(
"This RawPixelsStore has not been properly initialized.\n"
+ "Please set the pixels id before executing any other methods.\n");
}
}
// ~ Delegation
// =========================================================================
@RolesAllowed("user")
public synchronized byte[] calculateMessageDigest() {
errorIfNotLoaded();
try {
return buffer.calculateMessageDigest();
} catch (Exception e) {
handleException(e);
}
return null;
}
@RolesAllowed("user")
public synchronized byte[] getHypercube(List<Integer> offset, List<Integer> size, List<Integer> step) {
errorIfNotLoaded();
int cubeSize = RomioPixelBuffer.safeLongToInteger(
buffer.getHypercubeSize(offset, size, step));
if (readBuffer == null || readBuffer.length != cubeSize) {
readBuffer = new byte[cubeSize];
}
try {
readBuffer = buffer.getHypercubeDirect(offset, size, step,
readBuffer);
} catch (Exception e) {
handleException(e);
}
return readBuffer;
}
@RolesAllowed("user")
public synchronized byte[] getPlaneRegion(int z, int c, int t, int count, int offset) {
errorIfNotLoaded();
int size = RomioPixelBuffer.safeLongToInteger(
buffer.getByteWidth() * (long) count);
if (readBuffer == null || readBuffer.length != size) {
readBuffer = new byte[size];
}
try {
readBuffer = buffer.getPlaneRegionDirect(z, c, t, count, offset,
readBuffer);
} catch (Exception e) {
handleException(e);
}
return readBuffer;
}
@RolesAllowed("user")
public synchronized byte[] getPlane(int arg0, int arg1, int arg2) {
errorIfNotLoaded();
int size = RomioPixelBuffer.safeLongToInteger(buffer.getPlaneSize());
if (readBuffer == null || readBuffer.length != size) {
readBuffer = new byte[size];
}
try {
readBuffer = buffer.getPlaneDirect(arg0, arg1, arg2, readBuffer);
} catch (Exception e) {
handleException(e);
}
return readBuffer;
}
@RolesAllowed("user")
public synchronized long getPlaneOffset(int arg0, int arg1, int arg2) {
errorIfNotLoaded();
try {
return buffer.getPlaneOffset(arg0, arg1, arg2);
} catch (Exception e) {
handleException(e);
}
return -1;
}
/*
* @inheritDoc
* @see ome.io.nio.PixelBuffer#getPlaneSize()
*/
@RolesAllowed("user")
public synchronized long getPlaneSize() {
errorIfNotLoaded();
return buffer.getPlaneSize();
}
@RolesAllowed("user")
public synchronized byte[] getRegion(int arg0, long arg1) {
errorIfNotLoaded();
PixelData pd = null;
byte[] bytes = null;
try {
pd = buffer.getRegion(arg0, arg1);
bytes = bufferAsByteArrayWithExceptionIfNull(pd.getData());
} catch (Exception e) {
handleException(e);
} finally {
if (pd != null) {
pd.dispose();
}
}
return bytes;
}
@RolesAllowed("user")
public synchronized byte[] getRow(int arg0, int arg1, int arg2, int arg3) {
errorIfNotLoaded();
int size = buffer.getRowSize();
if (readBuffer == null || readBuffer.length != size) {
readBuffer = new byte[size];
}
try {
readBuffer = buffer
.getRowDirect(arg0, arg1, arg2, arg3, readBuffer);
} catch (Exception e) {
handleException(e);
}
return readBuffer;
}
@RolesAllowed("user")
public synchronized byte[] getCol(int arg0, int arg1, int arg2, int arg3) {
errorIfNotLoaded();
int size = buffer.getColSize();
if (readBuffer == null || readBuffer.length != size) {
readBuffer = new byte[size];
}
try {
readBuffer = buffer
.getColDirect(arg0, arg1, arg2, arg3, readBuffer);
} catch (Exception e) {
handleException(e);
}
return readBuffer;
}
@RolesAllowed("user")
public synchronized long getRowOffset(int arg0, int arg1, int arg2, int arg3) {
errorIfNotLoaded();
try {
return buffer.getRowOffset(arg0, arg1, arg2, arg3);
} catch (Exception e) {
handleException(e);
}
return -1;
}
@RolesAllowed("user")
public synchronized int getRowSize() {
errorIfNotLoaded();
return buffer.getRowSize();
}
@RolesAllowed("user")
public synchronized byte[] getStack(int arg0, int arg1) {
errorIfNotLoaded();
int size = RomioPixelBuffer.safeLongToInteger(buffer.getStackSize());
if (readBuffer == null || readBuffer.length != size) {
readBuffer = new byte[size];
}
try {
readBuffer = buffer.getStackDirect(arg0, arg1, readBuffer);
} catch (Exception e) {
handleException(e);
}
return readBuffer;
}
@RolesAllowed("user")
public synchronized long getStackOffset(int arg0, int arg1) {
errorIfNotLoaded();
try {
return buffer.getStackOffset(arg0, arg1);
} catch (Exception e) {
handleException(e);
}
return -1;
}
@RolesAllowed("user")
public synchronized long getStackSize() {
errorIfNotLoaded();
return buffer.getStackSize();
}
@RolesAllowed("user")
public synchronized byte[] getTimepoint(int arg0) {
errorIfNotLoaded();
int size = RomioPixelBuffer.safeLongToInteger(
buffer.getTimepointSize());
if (readBuffer == null || readBuffer.length != size) {
readBuffer = new byte[size];
}
try {
readBuffer = buffer.getTimepointDirect(arg0, readBuffer);
} catch (Exception e) {
handleException(e);
}
return readBuffer;
}
@RolesAllowed("user")
public synchronized long getTimepointOffset(int arg0) {
errorIfNotLoaded();
try {
return buffer.getTimepointOffset(arg0);
} catch (Exception e) {
handleException(e);
}
return -1;
}
@RolesAllowed("user")
public synchronized long getTimepointSize() {
errorIfNotLoaded();
return buffer.getTimepointSize();
}
@RolesAllowed("user")
public synchronized long getTotalSize() {
errorIfNotLoaded();
return buffer.getTotalSize();
}
@RolesAllowed("user")
public synchronized int getByteWidth() {
errorIfNotLoaded();
return buffer.getByteWidth();
}
@RolesAllowed("user")
public synchronized boolean isSigned() {
errorIfNotLoaded();
return buffer.isSigned();
}
@RolesAllowed("user")
public synchronized boolean isFloat() {
errorIfNotLoaded();
return buffer.isFloat();
}
@RolesAllowed("user")
public synchronized void setPlane(byte[] arg0, int arg1, int arg2, int arg3) {
errorIfNotLoaded();
if (diskSpaceChecking) {
iRepositoryInfo.sanityCheckRepository();
}
try {
buffer.setPlane(arg0, arg1, arg2, arg3);
modified();
} catch (Exception e) {
handleException(e);
}
}
@RolesAllowed("user")
public synchronized void setRegion(int arg0, long arg1, byte[] arg2) {
errorIfNotLoaded();
if (diskSpaceChecking) {
iRepositoryInfo.sanityCheckRepository();
}
try {
buffer.setRegion(arg0, arg1, arg2);
modified();
} catch (Exception e) {
handleException(e);
}
}
@RolesAllowed("user")
public synchronized void setRow(byte[] arg0, int arg1, int arg2, int arg3, int arg4) {
errorIfNotLoaded();
if (diskSpaceChecking) {
iRepositoryInfo.sanityCheckRepository();
}
try {
ByteBuffer buf = ByteBuffer.wrap(arg0);
buffer.setRow(buf, arg1, arg2, arg3, arg4);
modified();
} catch (Exception e) {
handleException(e);
}
}
@RolesAllowed("user")
public synchronized void setStack(byte[] arg0, int arg1, int arg2, int arg3) {
errorIfNotLoaded();
if (diskSpaceChecking) {
iRepositoryInfo.sanityCheckRepository();
}
try {
buffer.setStack(arg0, arg1, arg2, arg3);
modified();
} catch (Exception e) {
handleException(e);
}
}
@RolesAllowed("user")
public synchronized void setTimepoint(byte[] arg0, int arg1) {
errorIfNotLoaded();
if (diskSpaceChecking) {
iRepositoryInfo.sanityCheckRepository();
}
try {
buffer.setTimepoint(arg0, arg1);
modified();
} catch (Exception e) {
handleException(e);
}
}
// ~ Helpers
// =========================================================================
private synchronized byte[] bufferAsByteArrayWithExceptionIfNull(ByteBuffer buffer) {
byte[] b = new byte[buffer.capacity()];
buffer.get(b, 0, buffer.capacity());
return b;
}
private synchronized void handleException(Exception e) {
if (e instanceof RootException) {
throw (RootException) e; // Allow our own exceptions.
}
if (log.isDebugEnabled()) {
log.debug("Error handling pixels.", e);
}
if (e instanceof IOException) {
throw new ResourceError(e.getMessage());
}
if (e instanceof DimensionsOutOfBoundsException) {
throw new ApiUsageException(e.getMessage());
}
if (e instanceof BufferUnderflowException) {
throw new ResourceError("BufferUnderflowException: " + e.getMessage());
}
if (e instanceof BufferOverflowException) {
throw new ResourceError("BufferOverflowException: " + e.getMessage());
}
// Fallthrough
if (e instanceof RuntimeException) {
throw (RuntimeException) e; // No reason to wrap if Runtime
}
throw new RuntimeException(e);
}
public synchronized boolean isDiskSpaceChecking() {
return diskSpaceChecking;
}
public synchronized void setDiskSpaceChecking(boolean diskSpaceChecking) {
this.diskSpaceChecking = diskSpaceChecking;
}
/* (non-Javadoc)
* @see ome.api.RawPixelsStore#getResolutionLevels()
*/
@RolesAllowed("user")
public synchronized int getResolutionLevels()
{
errorIfNotLoaded();
return buffer.getResolutionLevels();
}
@RolesAllowed("user")
public synchronized List<List<Integer>> getResolutionDescriptions()
{
errorIfNotLoaded();
return buffer.getResolutionDescriptions();
}
/* (non-Javadoc)
* @see ome.api.RawPixelsStore#getTileSize()
*/
@RolesAllowed("user")
public synchronized int[] getTileSize()
{
errorIfNotLoaded();
Dimension tileSize = buffer.getTileSize();
return new int[] { (int) tileSize.getWidth(),
(int) tileSize.getHeight() };
}
/* (non-Javadoc)
* @see ome.api.RawPixelsStore#requiresPixelsPyramid()
*/
@RolesAllowed("user")
public synchronized boolean requiresPixelsPyramid()
{
errorIfNotLoaded();
return dataService.requiresPixelsPyramid(pixelsInstance);
}
/* (non-Javadoc)
* @see ome.api.RawPixelsStore#getResolutionLevel()
*/
@RolesAllowed("user")
public synchronized int getResolutionLevel()
{
errorIfNotLoaded();
return buffer.getResolutionLevel();
}
/* (non-Javadoc)
* @see ome.api.RawPixelsStore#setResolutionLevel(int)
*/
@RolesAllowed("user")
public synchronized void setResolutionLevel(int resolutionLevel)
{
errorIfNotLoaded();
buffer.setResolutionLevel(resolutionLevel);
}
/* (non-Javadoc)
* @see ome.api.RawPixelsStore#getTile(int, int, int, int, int, int, int)
*/
@RolesAllowed("user")
public synchronized byte[] getTile(int z, int c, int t, int x, int y, int w, int h)
{
errorIfNotLoaded();
int size = RomioPixelBuffer.safeLongToInteger(
(long) w * (long) h * buffer.getByteWidth());
if (readBuffer == null || readBuffer.length != size) {
readBuffer = new byte[size];
}
try {
readBuffer = buffer.getTileDirect(z, c, t, x, y, w, h, readBuffer);
} catch (Exception e) {
handleException(e);
}
return readBuffer;
}
/* (non-Javadoc)
* @see ome.api.RawPixelsStore#setTile(byte[], int, int, int, int, int, int, int)
*/
@RolesAllowed("user")
public synchronized void setTile(byte[] data, int z, int c, int t, int x, int y,
int w, int h)
{
errorIfNotLoaded();
if (diskSpaceChecking) {
iRepositoryInfo.sanityCheckRepository();
}
try {
buffer.setTile(data, z, c, t, x, y, w, h);
modified();
} catch (Exception e) {
handleException(e);
}
}
}