Java tutorial
/* * $Id$ * * Copyright 2006 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.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.locks.ReentrantReadWriteLock; import ome.annotations.RevisionDate; import ome.annotations.RevisionNumber; import ome.annotations.RolesAllowed; import ome.api.IPixels; import ome.api.IRenderingSettings; import ome.api.ServiceInterface; import ome.api.local.LocalCompress; import ome.conditions.ApiUsageException; import ome.conditions.InternalException; import ome.conditions.ResourceError; import ome.conditions.ValidationException; import ome.io.nio.InMemoryPlanarPixelBuffer; import ome.io.nio.PixelBuffer; import ome.io.nio.PixelsService; import ome.model.IObject; import ome.model.core.Channel; import ome.model.core.Pixels; import ome.model.display.ChannelBinding; import ome.model.display.QuantumDef; import ome.model.display.RenderingDef; import ome.model.enums.Family; import ome.model.enums.RenderingModel; import ome.model.internal.Permissions; import ome.security.SecuritySystem; import ome.services.util.Executor; import ome.system.EventContext; import ome.system.ServiceFactory; import ome.system.SimpleEventContext; import ome.util.ImageUtil; import ome.util.ShallowCopy; import omeis.providers.re.RGBBuffer; import omeis.providers.re.Renderer; import omeis.providers.re.RenderingEngine; import omeis.providers.re.codomain.CodomainMapContext; import omeis.providers.re.data.PlaneDef; import omeis.providers.re.data.RegionDef; import omeis.providers.re.quantum.QuantizationException; import omeis.providers.re.quantum.QuantumFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.Session; import org.springframework.transaction.annotation.Transactional; /** * Provides the {@link RenderingEngine} service. This class is an Adapter to * wrap the {@link Renderer} so to make it thread-safe. * <p> * The multi-threaded design of this component is based on dynamic locking and * confinement techniques. All access to the component's internal parts happens * through a <code>RenderingEngineImpl</code> object, which is fully * synchronized. Internal parts are either never leaked out or given away only * if read-only objects. (The only exception are the {@link CodomainMapContext} * objects which are not read-only but are copied upon every method invocation * so to maintain safety.) * </p> * <p> * Finally the {@link RenderingEngine} component doesn't make use of constructs * that could compromise liveness. * </p> * * @author Andrea Falconi, a.falconi at dundee.ac.uk * @author Chris Allan, callan at blackcat.ca * @author Jean-Marie Burel, j.burel at dundee.ac.uk * @author Josh Moore, josh.moore at gmx.de * @version $Revision$, $Date$ * @see RenderingEngine * @since 3.0-M3 */ @RevisionDate("$Date$") @RevisionNumber("$Revision$") @Transactional(readOnly = true) public class RenderingBean implements RenderingEngine, Serializable { /** The serial number. */ private static final long serialVersionUID = -4383698215540637039L; /** Reference to the logger. */ private static final Log log = LogFactory.getLog(RenderingBean.class); /** * Returns the service corresponding to this class. * * @return See above. */ public Class<? extends ServiceInterface> getServiceInterface() { return RenderingEngine.class; } // ~ State // ========================================================================= /* * LIFECYCLE: 1. new() || new(Services[]) 2. (lookupX || useX)+ && load 3. * call methods 4. optionally: return to 2. 5. destroy() * * TODO: when a setXXX() method is called, when do I reload? always? or * on dirty? */ /** * Transforms the raw data. Entry point to the asynchronous parts of the * component. As soon as Renderer is not null, the Engine is ready to use. */ private transient Renderer renderer; /** The pixels set to the rendering engine is for. */ private Pixels pixelsObj; /** The rendering settings associated to the pixels set. */ private RenderingDef rendDefObj; /** Reference to the service used to retrieve the pixels data. */ private transient PixelsService pixDataSrv; /** * read-write lock to prevent READ-calls during WRITE operations. * * It is safe for the lock to be serialized. On de-serialization, it will be * in the unlocked state. */ private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); /** Reference to the executor. */ private final Executor ex; /** Reference to the security system. */ private final SecuritySystem secSys; /** Reference to the compression service. */ private final LocalCompress compressionSrv; /** Notification that the bean has just returned from passivation. */ private transient boolean wasPassivated = false; /** The resolution level to be used by the pixel buffer. */ private Integer resolutionLevel; /** * Compression service Bean injector. * * @param compressionService * an <code>ICompress</code>. */ public RenderingBean(PixelsService dataService, LocalCompress compress, Executor ex, SecuritySystem secSys) { this.ex = ex; this.secSys = secSys; this.pixDataSrv = dataService; this.compressionSrv = compress; } @RolesAllowed("user") public long getRenderingDefId() { if (rendDefObj == null || rendDefObj.getId() == null) { throw new ApiUsageException("No rendering def"); } return rendDefObj.getId(); } // ~ Lifecycle methods // ========================================================================= // See documentation on JobBean#passivate @RolesAllowed("user") @Transactional(readOnly = true) public void passivate() { log.debug("***** Passivating... ******"); rwl.writeLock().lock(); try { closeRenderer(); renderer = null; } finally { rwl.writeLock().unlock(); } } // See documentation on JobBean#activate @RolesAllowed("user") @Transactional(readOnly = true) public void activate() { log.debug("***** Returning from passivation... ******"); rwl.writeLock().lock(); try { wasPassivated = true; } finally { rwl.writeLock().unlock(); } } @RolesAllowed("user") public void close() { rwl.writeLock().lock(); try { // Mark us unready. All other state is marked transient. closeRenderer(); renderer = null; } finally { rwl.writeLock().unlock(); } } /* * SETTERS for use in dependency injection. All invalidate the current * renderer. Only possible with the WriteLock so no possibility of * corrupting data. * * a.k.a. STATE-MANAGEMENT */ /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#lookupPixels(long) */ @RolesAllowed("user") public void lookupPixels(long pixelsId) { rwl.writeLock().lock(); try { pixelsObj = retrievePixels(pixelsId); closeRenderer(); renderer = null; resolutionLevel = null; if (pixelsObj == null) { throw new ValidationException("Pixels object with id " + pixelsId + " not found."); } } finally { rwl.writeLock().unlock(); } if (log.isDebugEnabled()) { log.debug("lookupPixels for id " + pixelsId + " succeeded: " + this.pixelsObj); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#lookupRenderingDef(long) */ @RolesAllowed("user") public boolean lookupRenderingDef(long pixelsId) { rwl.writeLock().lock(); try { rendDefObj = retrieveRndSettings(pixelsId); closeRenderer(); renderer = null; if (rendDefObj == null) { // We've been initialized on a pixels set that has no rendering // definition for the given user. In order to maintain the // proper state and ensure that we avoid transactional problems // we're going to notify the caller instead of performing *any* // magic that would require a database update. // *** Ticket #564 -- Chris Allan <callan@blackcat.ca> *** return false; } // Ensure that the pixels object is unloaded to avoid transactional // headaches later due to Hibernate object caching techniques. If // this is not performed, rendDefObj.pixels will be the same // instance as pixelsObj; which if passed to IUpdate will be // set unloaded by the service. We *really* don't want this. // *** Ticket #848 -- Chris Allan <callan@blackcat.ca> *** Pixels unloadedPixels = new Pixels(pixelsId, false); rendDefObj.setPixels(unloadedPixels); // Ensure that we do not have "dirty" pixels or rendering settings // left around in the Hibernate session cache. // Josh: iQuery.clear(); } finally { rwl.writeLock().unlock(); } if (log.isDebugEnabled()) { log.debug("lookupRenderingDef for Pixels=" + pixelsId + " succeeded: " + this.rendDefObj); } return true; } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#loadRenderingDef(long) */ @RolesAllowed("user") public void loadRenderingDef(long renderingDefId) { rwl.writeLock().lock(); try { rendDefObj = loadRndSettings(renderingDefId); if (rendDefObj == null) { throw new ValidationException("No rendering definition exists with ID: " + renderingDefId); } // Need b/c rendDefObj.getPixels() is not loaded // and we cannot check if the sets are compatible. // See ticket #1327 if (rendDefObj.getPixels() == null) { rendDefObj = null; throw new ValidationException( "The rendering definition " + renderingDefId + " is not linked to a pixels set."); } Pixels pixels = retrievePixels(rendDefObj.getPixels().getId()); if (!sanityCheckPixels(pixelsObj, pixels)) { rendDefObj = null; throw new ValidationException("The rendering definition " + renderingDefId + " is incompatible with pixels set " + pixelsObj.getId()); } closeRenderer(); renderer = null; // Ensure that the pixels object is unloaded to avoid transactional // headaches later due to Hibernate object caching techniques. If // this is not performed, rendDefObj.pixels may be the same // instance as pixelsObj; which if passed to IUpdate will be // set unloaded by the service. We *really* don't want this. // Furthermore, as rendDefObj.pixels may be owned by another user // as rendDefObj can be owned by anyone we really don't want to be // saddled with this object being attached. // *** Ticket #848 -- Chris Allan <callan@blackcat.ca> *** Pixels unloadedPixels = new Pixels(rendDefObj.getPixels().getId(), false); rendDefObj.setPixels(unloadedPixels); // Ensure that we do not have "dirty" pixels or rendering settings // left around in the Hibernate session cache. // Josh: iQuery.clear(); } finally { rwl.writeLock().unlock(); } if (log.isDebugEnabled()) { log.debug("loadRenderingDef for RenderingDef=" + renderingDefId + " succeeded: " + this.rendDefObj); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#load() */ @RolesAllowed("user") public void load() { rwl.writeLock().lock(); try { errorIfNullPixels(); errorIfNullRenderingDef(); /* * TODO we could also allow for setting of the buffer! perhaps * better caching, etc. */ closeRenderer(); List<Family> families = getAllEnumerations(Family.class); List<RenderingModel> renderingModels = getAllEnumerations(RenderingModel.class); QuantumFactory quantumFactory = new QuantumFactory(families); // Loading last to try to ensure that the buffer will get closed. PixelBuffer buffer = getPixelBuffer(); renderer = new Renderer(quantumFactory, renderingModels, pixelsObj, rendDefObj, buffer); } finally { rwl.writeLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#setOverlays() */ @RolesAllowed("user") public void setOverlays(Map<byte[], Integer> overlays) { renderer.setOverlays(overlays); } /** * Implemented as specified by the {@link RenderingEngine} interface. TODO * * @see RenderingEngine#getCurrentEventContext() */ @RolesAllowed("user") public EventContext getCurrentEventContext() { return new SimpleEventContext(secSys.getEventContext()); } /* * DELEGATING METHODS ================================================== * * All following methods follow the pattern: 1) get read or write lock try { * 2) error on null 3) delegate method to renderer/renderingObj/pixelsObj } * finally { 4) release lock } * * TODO for all of these methods it would be good to have an interceptor to * check for a null renderer value. would have to be JBoss specific or apply * at the Renderer level or this would have to be a delegate to REngine to * Renderer! * * @Write || @Read on each. for example see * http://www.onjava.com/pub/a/onjava/2004/08/25/aoa.html?page=3 for asynch. * annotations (also @TxSynchronized) */ /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#render(PlaneDef) */ @RolesAllowed("user") public RGBBuffer render(PlaneDef pd) { rwl.readLock().lock(); try { errorIfInvalidState(); return renderer.render(pd); } catch (IOException e) { log.error("IO error while rendering.", e); throw new ResourceError(e.getMessage()); } catch (QuantizationException e) { log.error("Quantization exception while rendering.", e); throw new InternalException(e.getMessage()); } finally { rwl.readLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#render(PlaneDef) */ @RolesAllowed("user") public int[] renderAsPackedInt(PlaneDef pd) { rwl.writeLock().lock(); try { errorIfInvalidState(); checkPlaneDef(pd); if (resolutionLevel != null) { renderer.setResolutionLevel(resolutionLevel); } return renderer.renderAsPackedInt(pd, null); } catch (IOException e) { log.error("IO error while rendering.", e); throw new ResourceError(e.getMessage()); } catch (QuantizationException e) { log.error("Quantization exception while rendering.", e); throw new InternalException(e.getMessage()); } finally { rwl.writeLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#render(PlaneDef) */ @RolesAllowed("user") public int[] renderAsPackedIntAsRGBA(PlaneDef pd) { rwl.writeLock().lock(); try { errorIfInvalidState(); checkPlaneDef(pd); if (resolutionLevel != null) { renderer.setResolutionLevel(resolutionLevel); } return renderer.renderAsPackedIntAsRGBA(pd, null); } catch (IOException e) { log.error("IO error while rendering.", e); throw new ResourceError(e.getMessage()); } catch (QuantizationException e) { log.error("Quantization exception while rendering.", e); throw new InternalException(e.getMessage()); } finally { rwl.writeLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#renderCompressed() */ @RolesAllowed("user") public byte[] renderCompressed(PlaneDef pd) { rwl.writeLock().lock(); ByteArrayOutputStream byteStream = null; try { int stride = pd.getStride(); if (stride < 0) stride = 0; stride++; int[] buf = renderAsPackedInt(pd); int sizeX = pixelsObj.getSizeX(); int sizeY = pixelsObj.getSizeY(); RegionDef region = pd.getRegion(); if (region != null) { sizeX = region.getWidth(); sizeY = region.getHeight(); } sizeX = sizeX / stride; sizeY = sizeY / stride; BufferedImage image = ImageUtil.createBufferedImage(buf, sizeX, sizeY); byteStream = new ByteArrayOutputStream(); compressionSrv.compressToStream(image, byteStream); return byteStream.toByteArray(); } catch (IOException e) { log.error("Could not compress rendered image.", e); throw new ResourceError(e.getMessage()); } finally { rwl.writeLock().unlock(); try { if (byteStream != null) { byteStream.close(); } } catch (IOException e) { log.error("Could not close byte stream.", e); throw new ResourceError(e.getMessage()); } } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#renderProjectedAsPackedInt() */ @RolesAllowed("user") public int[] renderProjectedAsPackedInt(int algorithm, int timepoint, int stepping, int start, int end) { rwl.writeLock().lock(); try { errorIfInvalidState(); if (resolutionLevel != null) { renderer.setResolutionLevel(resolutionLevel); } ChannelBinding[] channelBindings = renderer.getChannelBindings(); byte[][][][] planes = new byte[1][pixelsObj.getSizeC()][1][]; long pixelsId = pixelsObj.getId(); int projectedSizeC = 0; for (int i = 0; i < channelBindings.length; i++) { if (channelBindings[i].getActive()) { planes[0][i][0] = projectStack(algorithm, timepoint, stepping, start, end, pixelsId, i); projectedSizeC += 1; } } Pixels projectedPixels = new Pixels(); projectedPixels.setSizeX(pixelsObj.getSizeX()); projectedPixels.setSizeY(pixelsObj.getSizeY()); projectedPixels.setSizeZ(1); projectedPixels.setSizeT(1); projectedPixels.setSizeC(projectedSizeC); projectedPixels.setPixelsType(pixelsObj.getPixelsType()); PixelBuffer projectedPlanes = new InMemoryPlanarPixelBuffer(projectedPixels, planes); PlaneDef pd = new PlaneDef(PlaneDef.XY, 0); pd.setZ(0); return renderer.renderAsPackedInt(pd, projectedPlanes); } catch (IOException e) { log.error("IO error while rendering.", e); throw new ResourceError(e.getMessage()); } catch (QuantizationException e) { log.error("Quantization exception while rendering.", e); throw new InternalException(e.getMessage()); } finally { rwl.writeLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#renderProjectedCompressed() */ @RolesAllowed("user") public byte[] renderProjectedCompressed(int algorithm, int timepoint, int stepping, int start, int end) { rwl.writeLock().lock(); ByteArrayOutputStream byteStream = null; try { if (resolutionLevel != null) { renderer.setResolutionLevel(resolutionLevel); } int[] buf = renderProjectedAsPackedInt(algorithm, timepoint, stepping, start, end); int sizeX = pixelsObj.getSizeX(); int sizeY = pixelsObj.getSizeY(); BufferedImage image = ImageUtil.createBufferedImage(buf, sizeX, sizeY); byteStream = new ByteArrayOutputStream(); compressionSrv.compressToStream(image, byteStream); return byteStream.toByteArray(); } catch (IOException e) { log.error("Could not compress rendered image.", e); throw new ResourceError(e.getMessage()); } finally { rwl.writeLock().unlock(); try { if (byteStream != null) { byteStream.close(); } } catch (IOException e) { log.error("Could not close byte stream.", e); throw new ResourceError(e.getMessage()); } } } // ~ Settings // ========================================================================= /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#resetDefaults() */ @RolesAllowed("user") public void resetDefaults() { rwl.writeLock().lock(); try { errorIfNullPixels(); final long pixelsId = pixelsObj.getId(); // Ensure that we haven't just been called before // lookupRenderingDef(). if (rendDefObj == null) { rendDefObj = retrieveRndSettings(pixelsId); if (rendDefObj != null) { // We've been called before lookupRenderingDef() or // loadRenderingDef(), report an error. errorIfInvalidState(); } rendDefObj = createNewRenderingDef(pixelsObj); _resetDefaults(rendDefObj, pixelsObj); } else { errorIfInvalidState(); _resetDefaults(rendDefObj, pixelsObj); rendDefObj = retrieveRndSettings(pixelsObj.getId()); // The above save step sets the rendDefObj instance (for which // the renderer holds a reference) unloaded, which *will* cause // IllegalStateExceptions if we're not careful. To compensate // we will now reload the renderer. // *** Ticket #848 -- Chris Allan <callan@blackcat.ca> *** load(); } } finally { rwl.writeLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#resetDefaults() */ @RolesAllowed("user") public void resetDefaultsNoSave() { rwl.writeLock().lock(); try { errorIfInvalidState(); ex.execute(/*ex*/null/*principal*/, new Executor.SimpleWork(this, "resetDefaultsNoSave") { @Transactional(readOnly = true) public Object doWork(Session session, ServiceFactory sf) { IRenderingSettings settingsSrv = sf.getRenderingSettingsService(); settingsSrv.resetDefaultsNoSave(rendDefObj, pixelsObj); return null; } }); load(); //require or we are out of synch } finally { rwl.writeLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#setCompressionLevel() */ @RolesAllowed("user") public void setCompressionLevel(float percentage) { compressionSrv.setCompressionLevel(percentage); } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#getCompressionLevel() */ @RolesAllowed("user") public float getCompressionLevel() { return compressionSrv.getCompressionLevel(); } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#saveCurrentSettings() */ @RolesAllowed("user") @Transactional(readOnly = false) public void saveCurrentSettings() { rwl.writeLock().lock(); try { errorIfNullRenderingDef(); // Increment the version of the rendering settings so that we can // have some notification that either the RenderingDef object // itself or one of its children in the object graph has been // updated. FIXME: This should be implemented using IUpdate.touch() // or similar once that functionality exists. RenderingDef old = rendDefObj; rendDefObj = createNewRenderingDef(pixelsObj); rendDefObj.setId(old.getId()); rendDefObj.setDefaultZ(old.getDefaultZ()); rendDefObj.setDefaultT(old.getDefaultT()); rendDefObj.setCompression(old.getCompression()); rendDefObj.setName(old.getName()); QuantumDef qDefNew = rendDefObj.getQuantization(); QuantumDef qDefOld = old.getQuantization(); qDefNew.setId(qDefOld.getId()); qDefNew.setBitResolution(qDefOld.getBitResolution()); qDefNew.setCdStart(qDefOld.getCdStart()); qDefNew.setCdEnd(qDefOld.getCdEnd()); rendDefObj.setVersion(old.getVersion() + 1); // Unload the model object to avoid transactional headaches RenderingModel unloadedModel = new RenderingModel(old.getModel().getId(), false); rendDefObj.setModel(unloadedModel); // Unload the family of each channel binding to avoid transactional // headaches. /* for (ChannelBinding binding : old .unmodifiableWaveRendering()) { Family unloadedFamily = new Family(binding.getFamily().getId(), false); binding.setFamily(unloadedFamily); } */ Family family; int index = 0; ChannelBinding cb; for (ChannelBinding binding : old.unmodifiableWaveRendering()) { family = new Family(binding.getFamily().getId(), false); cb = rendDefObj.getChannelBinding(index); cb.setFamily(family); cb.setActive(binding.getActive()); cb.setAlpha(binding.getAlpha()); cb.setBlue(binding.getBlue()); cb.setRed(binding.getRed()); cb.setGreen(binding.getGreen()); cb.setId(binding.getId()); cb.setInputStart(binding.getInputStart()); cb.setInputEnd(binding.getInputEnd()); cb.setCoefficient(binding.getCoefficient()); cb.setNoiseReduction(binding.getNoiseReduction()); //binding.setFamily(unloadedFamily); index++; } // Actually save and reload the rendering settings rendDefObj = (RenderingDef) ex.execute(/*ex*/null/*principal*/, new Executor.SimpleWork(this, "saveCurrentSettings") { @Transactional(readOnly = false) // ticket:1434 public Object doWork(Session session, ServiceFactory sf) { IPixels pixMetaSrv = sf.getPixelsService(); pixMetaSrv.saveRndSettings(rendDefObj); return pixMetaSrv.retrieveRndSettings(pixelsObj.getId()); } }); // Unload the linked pixels set to avoid transactional headaches on // the next save. Pixels unloadedPixels = new Pixels(pixelsObj.getId(), false); rendDefObj.setPixels(unloadedPixels); // The above save and reload step sets the rendDefObj instance // (for which the renderer hold a reference) unloaded, which *will* // cause IllegalStateExceptions if we're not careful. To compensate // we will now reload the renderer. // *** Ticket #848 -- Chris Allan <callan@blackcat.ca> *** load(); } finally { rwl.writeLock().unlock(); } } // ~ Renderer Delegation (READ) // ========================================================================= /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#getChannelCurveCoefficient(int) */ @RolesAllowed("user") public double getChannelCurveCoefficient(int w) { rwl.readLock().lock(); try { errorIfInvalidState(); ChannelBinding[] cb = renderer.getChannelBindings(); return cb[w].getCoefficient().doubleValue(); } finally { rwl.readLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#getChannelFamily(int) */ @RolesAllowed("user") public Family getChannelFamily(int w) { rwl.readLock().lock(); try { errorIfInvalidState(); ChannelBinding[] cb = renderer.getChannelBindings(); Family family = cb[w].getFamily(); return copyFamily(family); } finally { rwl.readLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#getChannelNoiseReduction(int) */ @RolesAllowed("user") public boolean getChannelNoiseReduction(int w) { rwl.readLock().lock(); try { errorIfInvalidState(); ChannelBinding[] cb = renderer.getChannelBindings(); return cb[w].getNoiseReduction().booleanValue(); } finally { rwl.readLock().unlock(); } } /** Implemented as specified by the {@link RenderingEngine} interface. */ @RolesAllowed("user") public double[] getChannelStats(int w) { rwl.readLock().lock(); try { errorIfInvalidState(); // ChannelBinding[] cb = renderer.getChannelBindings(); // FIXME // double[] stats = cb[w].getStats(), copy = new // double[stats.length]; // System.arraycopy(stats, 0, copy, 0, stats.length); return null; } finally { rwl.readLock().unlock(); } // FIXME copy; // NOTE: These stats are supposed to be read-only; however we make a // copy to be on the safe side. } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#getChannelWindowEnd(int) */ @RolesAllowed("user") public double getChannelWindowEnd(int w) { rwl.readLock().lock(); try { errorIfInvalidState(); ChannelBinding[] cb = renderer.getChannelBindings(); return cb[w].getInputEnd().intValue(); } finally { rwl.readLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#getChannelWindowStart(int) */ @RolesAllowed("user") public double getChannelWindowStart(int w) { rwl.readLock().lock(); try { errorIfInvalidState(); ChannelBinding[] cb = renderer.getChannelBindings(); return cb[w].getInputStart().intValue(); } finally { rwl.readLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#getRGBA(int) */ @RolesAllowed("user") public int[] getRGBA(int w) { rwl.readLock().lock(); try { errorIfInvalidState(); int[] rgba = new int[4]; ChannelBinding[] cb = renderer.getChannelBindings(); // NOTE: The rgba is supposed to be read-only; however we make a // copy to be on the safe side. rgba[0] = cb[w].getRed().intValue(); rgba[1] = cb[w].getGreen().intValue(); rgba[2] = cb[w].getBlue().intValue(); rgba[3] = cb[w].getAlpha().intValue(); return rgba; } finally { rwl.readLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#isActive(int) */ @RolesAllowed("user") public boolean isActive(int w) { rwl.readLock().lock(); try { errorIfInvalidState(); ChannelBinding[] cb = renderer.getChannelBindings(); return cb[w].getActive().booleanValue(); } finally { rwl.readLock().unlock(); } } // ~ RendDefObj Delegation // ========================================================================= /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#getDefaultT() */ @RolesAllowed("user") public int getDefaultT() { rwl.readLock().lock(); try { errorIfNullRenderingDef(); return rendDefObj.getDefaultT().intValue(); } finally { rwl.readLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#getDefaultZ() */ @RolesAllowed("user") public int getDefaultZ() { rwl.readLock().lock(); try { errorIfNullRenderingDef(); return rendDefObj.getDefaultZ().intValue(); } finally { rwl.readLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#getModel() */ @RolesAllowed("user") public RenderingModel getModel() { rwl.readLock().lock(); try { errorIfNullRenderingDef(); return copyRenderingModel(rendDefObj.getModel()); } finally { rwl.readLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#getQuantumDef() */ @RolesAllowed("user") public QuantumDef getQuantumDef() { rwl.readLock().lock(); try { errorIfNullRenderingDef(); return new ShallowCopy().copy(rendDefObj.getQuantization()); } finally { rwl.readLock().unlock(); } } // ~ Pixels Delegation // ========================================================================= /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#getPixels() */ @RolesAllowed("user") public Pixels getPixels() { rwl.readLock().lock(); try { errorIfNullPixels(); return copyPixels(pixelsObj); } finally { rwl.readLock().unlock(); } } // ~ Service Delegation // ========================================================================= /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#getAvailableModels() */ @RolesAllowed("user") public List getAvailableModels() { rwl.readLock().lock(); try { List<RenderingModel> models = getAllEnumerations(RenderingModel.class); List<RenderingModel> result = new ArrayList<RenderingModel>(); for (RenderingModel model : models) { result.add(copyRenderingModel(model)); } return result; } finally { rwl.readLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#getAvailableFamilies() */ @RolesAllowed("user") public List getAvailableFamilies() { rwl.readLock().lock(); try { List<Family> families = getAllEnumerations(Family.class); List<Family> result = new ArrayList<Family>(); for (Family family : families) { result.add(copyFamily(family)); } return result; } finally { rwl.readLock().unlock(); } } // ~ Renderer Delegation (WRITE) // ========================================================================= /** Implemented as specified by the {@link RenderingEngine} interface. */ @RolesAllowed("user") public void addCodomainMap(CodomainMapContext mapCtx) { rwl.writeLock().lock(); try { errorIfInvalidState(); renderer.getCodomainChain().add(mapCtx.copy()); } finally { rwl.writeLock().unlock(); } } /** Implemented as specified by the {@link RenderingEngine} interface. */ @RolesAllowed("user") public void removeCodomainMap(CodomainMapContext mapCtx) { rwl.writeLock().lock(); try { errorIfInvalidState(); renderer.getCodomainChain().remove(mapCtx.copy()); } finally { rwl.writeLock().unlock(); } } /** Implemented as specified by the {@link RenderingEngine} interface. */ @RolesAllowed("user") public void updateCodomainMap(CodomainMapContext mapCtx) { rwl.writeLock().lock(); try { errorIfInvalidState(); renderer.getCodomainChain().update(mapCtx.copy()); } finally { rwl.writeLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#setActive(int, boolean) */ @RolesAllowed("user") public void setActive(int w, boolean active) { try { rwl.writeLock().lock(); errorIfInvalidState(); renderer.setActive(w, active); } finally { rwl.writeLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#setChannelWindow(int, double, double) */ @RolesAllowed("user") public void setChannelWindow(int w, double start, double end) { rwl.writeLock().lock(); try { errorIfInvalidState(); renderer.setChannelWindow(w, start, end); } finally { rwl.writeLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#setCodomainInterval(int, int) */ @RolesAllowed("user") public void setCodomainInterval(int start, int end) { rwl.writeLock().lock(); try { errorIfInvalidState(); renderer.setCodomainInterval(start, end); } finally { rwl.writeLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#setDefaultT(int) */ @RolesAllowed("user") public void setDefaultT(int t) { rwl.writeLock().lock(); try { errorIfInvalidState(); renderer.setDefaultT(t); } finally { rwl.writeLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#setDefaultZ(int) */ @RolesAllowed("user") public void setDefaultZ(int z) { rwl.writeLock().lock(); try { errorIfInvalidState(); renderer.setDefaultZ(z); } finally { rwl.writeLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#setModel(RenderingModel) */ @RolesAllowed("user") public void setModel(RenderingModel model) { rwl.writeLock().lock(); try { errorIfInvalidState(); RenderingModel m = lookup(model); renderer.setModel(m); } finally { rwl.writeLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#setQuantizationMap(int, Family, double, boolean) */ @RolesAllowed("user") public void setQuantizationMap(int w, Family family, double coefficient, boolean noiseReduction) { rwl.writeLock().lock(); try { errorIfInvalidState(); Family f = lookup(family); renderer.setQuantizationMap(w, f, coefficient, noiseReduction); } finally { rwl.writeLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#setQuantumStrategy(int) */ @RolesAllowed("user") public void setQuantumStrategy(int bitResolution) { rwl.writeLock().lock(); try { errorIfInvalidState(); renderer.setQuantumStrategy(bitResolution); } finally { rwl.writeLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#setRGBA(int, int, int, int, int) */ @RolesAllowed("user") public void setRGBA(int w, int red, int green, int blue, int alpha) { rwl.writeLock().lock(); try { errorIfInvalidState(); renderer.setRGBA(w, red, green, blue, alpha); } finally { rwl.writeLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#isPixelsTypeSigned() */ @RolesAllowed("user") public boolean isPixelsTypeSigned() { rwl.readLock().lock(); try { errorIfInvalidState(); return renderer.isPixelsTypeSigned(); } finally { rwl.readLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#getPixelsTypeLowerBound(int) */ @RolesAllowed("user") public double getPixelsTypeLowerBound(int w) { rwl.readLock().lock(); try { errorIfInvalidState(); return renderer.getPixelsTypeLowerBound(w); } finally { rwl.readLock().unlock(); } } /* (non-Javadoc) * @see omeis.providers.re.RenderingEngine#getResolutionLevel() */ @RolesAllowed("user") public int getResolutionLevel() { rwl.writeLock().lock(); try { errorIfInvalidState(); return renderer.getResolutionLevel(); } finally { rwl.writeLock().unlock(); } } /* (non-Javadoc) * @see omeis.providers.re.RenderingEngine#getResolutionLevels() */ @RolesAllowed("user") public int getResolutionLevels() { rwl.writeLock().lock(); try { errorIfInvalidState(); return renderer.getResolutionLevels(); } finally { rwl.writeLock().unlock(); } } /* (non-Javadoc) * @see omeis.providers.re.RenderingEngine#getTileSize() */ @RolesAllowed("user") public int[] getTileSize() { rwl.writeLock().lock(); try { errorIfInvalidState(); Dimension tileSize = renderer.getTileSize(); return new int[] { (int) tileSize.getWidth(), (int) tileSize.getHeight() }; } finally { rwl.writeLock().unlock(); } } /* (non-Javadoc) * @see omeis.providers.re.RenderingEngine#requiresPixelsPyramid() */ @RolesAllowed("user") public boolean requiresPixelsPyramid() { rwl.writeLock().lock(); try { errorIfInvalidState(); return pixDataSrv.requiresPixelsPyramid(pixelsObj); } finally { rwl.writeLock().unlock(); } } /* (non-Javadoc) * @see omeis.providers.re.RenderingEngine#setResolutionLevel(int) */ @RolesAllowed("user") public void setResolutionLevel(int resolutionLevel) { rwl.writeLock().lock(); try { errorIfInvalidState(); this.resolutionLevel = resolutionLevel; renderer.setResolutionLevel(resolutionLevel); } finally { rwl.writeLock().unlock(); } } /** * Implemented as specified by the {@link RenderingEngine} interface. * * @see RenderingEngine#getPixelsTypeUpperBound(int) */ @RolesAllowed("user") public double getPixelsTypeUpperBound(int w) { rwl.readLock().lock(); try { errorIfInvalidState(); return renderer.getPixelsTypeUpperBound(w); } finally { rwl.readLock().unlock(); } } /** * Validates the plane definition. * @param pd Plane definition to validate. */ private void checkPlaneDef(PlaneDef pd) { RegionDef rd = pd.getRegion(); if (rd == null) { return; } PixelBuffer pixelBuffer = renderer.getPixels(); int sizeX = pixelBuffer.getSizeX(); int sizeY = pixelBuffer.getSizeY(); if (rd.getWidth() + rd.getX() > sizeX) { int newWidth = sizeX - rd.getX(); if (log.isDebugEnabled()) { log.debug( String.format("Resetting out of bounds region XOffset %d width %d" + " vs. sizeX %d to %d", rd.getX(), rd.getWidth(), sizeX, newWidth)); } rd.setWidth(newWidth); } else { if (log.isDebugEnabled()) { log.debug(String.format("Leaving region xOffset %d width %d alone vs. sizeX %d", rd.getX(), rd.getWidth(), sizeX)); } } if (rd.getHeight() + rd.getY() > sizeY) { int newHeight = sizeY - rd.getY(); if (log.isDebugEnabled()) { log.debug( String.format("Resetting out of bounds region yOffset %d height %d" + " vs. sizeY %d to %d", rd.getY(), rd.getHeight(), sizeY, newHeight)); } rd.setHeight(newHeight); } else { if (log.isDebugEnabled()) { log.debug(String.format("Leaving region yOffset %d height %d alone vs. sizeY %d", rd.getY(), rd.getHeight(), sizeY)); } } } /** * Close the active renderer, cleaning up any potential messes left by the * included pixel buffer. */ private void closeRenderer() { if (renderer != null) { renderer.close(); } } // ~ Error checking methods // ========================================================================= /** Message if the rendering engine is not ready. */ protected final static String NULL_RENDERER = "RenderingEngine not ready: renderer is null.\n" + "This method can only be called " + "after the renderer is properly " + "initialized (not-null).\n" + "Try lookup and/or use methods."; // TODO ObjectUnreadyException protected void errorIfInvalidState() { errorIfNullPixels(); errorIfNullRenderingDef(); errorIfNullRenderer(); } /** Throws an {@link ApiUsageException} if the pixels are not set. */ protected void errorIfNullPixels() { if (pixelsObj == null) { throw new ApiUsageException("RenderingEngine not ready: Pixels object not set."); } } /** * Throws an {@link ApiUsageException} if the rendering settings are not * set. */ protected void errorIfNullRenderingDef() { if (rendDefObj == null) { throw new ApiUsageException("RenderingEngine not ready: RenderingDef object not set."); } } /** * Reloads the rendering engine if <code>null</code> and has been * made passive or throws an {@link ApiUsageException} if the rendering * engine is not set. */ protected void errorIfNullRenderer() { if (renderer == null && wasPassivated) { load(); } else if (renderer == null) { throw new ApiUsageException(NULL_RENDERER); } } // ~ Copies // ========================================================================= /** * Copies the specified pixels set. * * @param pixels The pixels to set. */ @SuppressWarnings("unchecked") private Pixels copyPixels(Pixels pixels) { if (pixels == null) { return null; } Pixels newPixels = new ShallowCopy().copy(pixels); newPixels.putAt(Pixels.CHANNELS, new ArrayList<Channel>()); copyChannels(pixels, newPixels); newPixels.setPixelsType(new ShallowCopy().copy(pixels.getPixelsType())); return newPixels; } /** * Copies the channels from the Pixels source to another set of pixels. * * @param from The pixels set to copy from. * @param to The pixels set to copy to. */ private void copyChannels(Pixels from, Pixels to) { Iterator<Channel> it = from.iterateChannels(); while (it.hasNext()) { to.addChannel(copyChannel(it.next())); } } /** * Copies the specified channel. * * @param channel The channel to copy. * @return See above. */ private Channel copyChannel(Channel channel) { if (channel == null) return null; Channel newChannel = new ShallowCopy().copy(channel); newChannel.setLogicalChannel(new ShallowCopy().copy(channel.getLogicalChannel())); newChannel.setStatsInfo(new ShallowCopy().copy(channel.getStatsInfo())); return newChannel; } /** * Copies the rendering model. * * @param model The model to copy. * @return See above. */ private RenderingModel copyRenderingModel(RenderingModel model) { if (model == null) { return null; } RenderingModel newModel = new RenderingModel(); newModel.setId(model.getId()); newModel.setValue(model.getValue()); newModel.getDetails().copy(model.getDetails() == null ? null : model.getDetails().shallowCopy()); return newModel; } /** * Copies the specified family. * * @param family The family to copy. * @return See above. */ private Family copyFamily(Family family) { if (family == null) { return null; } Family newFamily = new Family(); newFamily.setId(family.getId()); newFamily.setValue(family.getValue()); newFamily.getDetails().copy(family.getDetails() == null ? null : family.getDetails().shallowCopy()); return newFamily; } // ~ All state access happens here // ========================================================================= /** * Looks up for the passed argument * * @param argument The argument to handle. * @return See above. */ @SuppressWarnings("unchecked") private <T extends IObject> T lookup(final T argument) { if (argument == null) { return null; } if (argument.getId() == null) { return argument; } return (T) ex.execute(null, new Executor.SimpleWork(this, "lookup") { @Transactional(readOnly = true) public Object doWork(Session session, ServiceFactory sf) { return (T) sf.getQueryService().get(argument.getClass(), argument.getId().longValue()); } }); } /** * Returns <code>true</code> if it is a critical graph, * <code>false</code> otherwise. * * @return See above */ private boolean isGraphCritical() { return (Boolean) ex.execute(/*ex*/null/*principal*/, new Executor.SimpleWork(this, "isGraphCritical") { @Transactional(readOnly = true) public Boolean doWork(Session session, ServiceFactory sf) { return secSys.isGraphCritical(); } }); } /** * Retrieves the pixels corresponding to the specified identifier. * * @param pixelsId The identifier of the pixels. * @return See above. */ private Pixels retrievePixels(final long pixelsId) { return (Pixels) ex.execute(/*ex*/null/*principal*/, new Executor.SimpleWork(this, "retrievePixels") { @Transactional(readOnly = true) public Object doWork(Session session, ServiceFactory sf) { return sf.getPixelsService().retrievePixDescription(pixelsId); } }); } /** * Retrieves the rendering settings corresponding to the specified pixels * set. * * @param pixelsId The identifier of the pixels. * @return See above. */ private RenderingDef retrieveRndSettings(final long pixelsId) { // FIXME check for null here? // Do not move this below errorIfNullPixels() boolean isGraphCritical = isGraphCritical(); errorIfNullPixels(); RenderingDef rd = (RenderingDef) ex.execute(/*ex*/null/*principal*/, new Executor.SimpleWork(this, "retrieveRndDef") { @Transactional(readOnly = true) public Object doWork(Session session, ServiceFactory sf) { return sf.getPixelsService().retrieveRndSettings(pixelsId); } }); long pixOwner = pixelsObj.getDetails().getOwner().getId(); long currentUser = getCurrentEventContext().getCurrentUserId(); if (rd == null && currentUser != pixOwner) { // ticket:1434 and shoola:ticket:1157. If this is graph critical // and we couldn't find a rendering def for the current user, // then we try to find one for the pixels owner. As in the // thumbnail bean we also have to check if we're in a read-only // group and not the owner of the Pixels object as well. Permissions currentGroupPermissions = getCurrentEventContext().getCurrentGroupPermissions(); Permissions readOnly = Permissions.parseString("rwr---"); if (isGraphCritical || currentGroupPermissions.identical(readOnly)) { rd = retrieveRndSettingsFor(pixelsId, pixOwner); } } return rd; } /** * Retrieves the rendering settings corresponding to the specified * pixels set and for a given user. * * @param pixelsId The identifier of the pixels. * @param userId The user's identifier. * @return See above. */ private RenderingDef retrieveRndSettingsFor(final long pixelsId, final long userId) { return (RenderingDef) ex.execute(/*ex*/null/*principal*/, new Executor.SimpleWork(this, "retrieveRndDefFor") { @Transactional(readOnly = true) public Object doWork(Session session, ServiceFactory sf) { return sf.getPixelsService().retrieveRndSettingsFor(pixelsId, userId); } }); } /** * Loads the rendering settings corresponding to the specified identifier. * * @param rdefId The identifier of the settings. * @return See above. */ private RenderingDef loadRndSettings(final long rdefId) { return (RenderingDef) ex.execute(/*ex*/null/*principal*/, new Executor.SimpleWork(this, "loadRndDef") { @Transactional(readOnly = true) public Object doWork(Session session, ServiceFactory sf) { return sf.getPixelsService().loadRndSettings(rdefId); } }); } /** * Retrieves all enumerations of a given type. * * @param k The type of enumerations to retrieve. * @return See above */ private List getAllEnumerations(final Class k) { return (List) ex.execute(/*ex*/null/*principal*/, new Executor.SimpleWork(this, "getAllEnumerations") { @Transactional(readOnly = true) public Object doWork(Session session, ServiceFactory sf) { return sf.getPixelsService().getAllEnumerations(k); } }); } /** * Controls if the pixels sets are compatible. Returns <code>true</code> * if compatible, <code>false</code> otherwise. * * @param pix1 One of the set to handle. * @param pix2 One of the set to handle. * @return */ private boolean sanityCheckPixels(final Pixels pix1, final Pixels pix2) { return (Boolean) ex.execute(/*ex*/null/*principal*/, new Executor.SimpleWork(this, "sanityCheck") { @Transactional(readOnly = true) public Object doWork(Session session, ServiceFactory sf) { return sf.getRenderingSettingsService().sanityCheckPixels(pix1, pix2); } }); } /** * Projects a given stack. * * @param algorithm The projection algorithm. * @param timepoint The selected time point. * @param stepping The step between z-section to project. * @param start The lower z-section to project. * @param end The upper z-section to project. * @param pixelsId The identifier of the pixels set. * @param i The channel. * @return See above. */ private byte[] projectStack(final int algorithm, final int timepoint, final int stepping, final int start, final int end, final long pixelsId, final int i) { return (byte[]) ex.execute(/* ex */null/* principal */, new Executor.SimpleWork(this, "projectStack") { @Transactional(readOnly = true) public Object doWork(Session session, ServiceFactory sf) { return sf.getProjectionService().projectStack(pixelsId, null, algorithm, timepoint, i, stepping, start, end); } }); } /** * Creates new rendering settings for the passed pixels set. * * @param pixels The pixels set to handle. * @return See above. */ private RenderingDef createNewRenderingDef(final Pixels pixels) { return (RenderingDef) ex.execute(null, new Executor.SimpleWork(this, "createNewRenderingDef") { @Transactional(readOnly = true) public Object doWork(Session session, ServiceFactory sf) { return sf.getRenderingSettingsService().createNewRenderingDef(pixels); } }); } /** * Resets the default rendering settings for the pixels set. * * @param def The rendering settings to handle. * @param pixels The pixels set. */ private void _resetDefaults(final RenderingDef def, final Pixels pixels) { ex.execute(null, new Executor.SimpleWork(this, "_resetDefaults") { @Transactional(readOnly = false) // ticket:1434 public Object doWork(Session session, ServiceFactory sf) { sf.getRenderingSettingsService().resetDefaults(def, pixels); return null; } }); } private PixelBuffer getPixelBuffer() { return (PixelBuffer) ex.execute(null, new Executor.SimpleWork(this, "getPixelBuffer") { @Transactional(readOnly = false) // ticket:5232 public Object doWork(Session session, ServiceFactory sf) { return pixDataSrv.getPixelBuffer(pixelsObj, false); } }); } }