/* *------------------------------------------------------------------------------ * Copyright (C) 2006-2013 University of Dundee. 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 org.openmicroscopy.shoola.env.data.views.calls; import java.awt.Dimension; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.openmicroscopy.shoola.env.LookupNames; import org.openmicroscopy.shoola.env.data.OmeroImageService; import org.openmicroscopy.shoola.env.data.login.UserCredentials; import org.openmicroscopy.shoola.env.data.model.ThumbnailData; import omero.gateway.SecurityContext; import omero.gateway.exception.RenderingServiceException; import org.openmicroscopy.shoola.env.data.views.BatchCall; import org.openmicroscopy.shoola.env.data.views.BatchCallTree; import org.openmicroscopy.shoola.util.image.geom.Factory; import omero.gateway.model.DataObject; import omero.gateway.model.ExperimenterData; import omero.gateway.model.FileData; import omero.gateway.model.ImageData; import omero.gateway.model.PixelsData; /** * Command to load a given set of thumbnails. * <p>As thumbnails are retrieved from <i>OMERO</i>, they're posted back to the * caller through <code>DSCallFeedbackEvent</code>s. </p> * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author Donald MacDonald      * <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a> * @version 3.0 * @since OME3.0 */ public class ThumbnailSetLoader extends BatchCallTree { /** * Indicates that the thumbnails are associated to an * <code>ImageData</code>. */ public static final int IMAGE = 0; /** * Indicates that the thumbnails are associated to an * <code>ExperimenterData</code>. */ public static final int EXPERIMENTER = 1; /** * Indicates that the thumbnails are associated to an <code>FileData</code>. */ public static final int FS_FILE = 2; /** Maximum number of thumbnails retrieved asynchronously. */ private static final int FETCH_SIZE = 10; /** * Factor by which the maximum number of thumbnails to fetch * is multiplied with when the connection's speed is <code>Low</code>. */ private static final double FETCH_LOW_SPEED = 0.25; /** * Factor by which the maximum number of thumbnails to fetch * is multiplied with when the connection's speed is <code>Medium</code>. */ private static final double FETCH_MEDIUM_SPEED = 0.5; /** Helper reference to the image service. */ private OmeroImageService service; /** The maximum acceptable length of the thumbnails. */ private int maxLength; /** Collection of list of pixels set to handle. */ private List<List> toHandle; /** Key, value pairs, Key is the pixels set id. */ private Map<Long, DataObject> input; /** Collection of {@link ThumbnailData}s for not valid pixels set. */ private List notValid; /** Collection of current {@link ThumbnailData}s. */ private Object currentThumbs; /** The maximum number of the tumbnails fetched. */ private int fetchSize; /** The type of nodes to handle. */ private Class type; /** The security context.*/ private SecurityContext ctx; /** * Creates a default thumbnail for the passed pixels set. * * @param pxd The pixels set to handle. * @return See above. */ private BufferedImage createDefaultImage(PixelsData pxd) { if (pxd == null) return Factory.createDefaultImageThumbnail(-1); Dimension d = Factory.computeThumbnailSize(maxLength, maxLength, pxd.getSizeX(), pxd.getSizeY()); return Factory.createDefaultImageThumbnail(d.width, d.height); } /** * Computes the maximum number of thumbnails to fetch * depending on the initial value and the speed of the connection. */ private void computeFetchSize() { int value = -1; Object fSize = context.lookup(LookupNames.THUMBNAIL_FETCH_SZ); if (fSize != null && (fSize instanceof Integer)) value = (Integer) fSize; else context.getLogger().warn(this, "Thumbnail fetching size not set"); if (value <= 0) value = FETCH_SIZE; UserCredentials uc = (UserCredentials) context.lookup(LookupNames.USER_CREDENTIALS); double f = 0; Object fSpeed = null; switch (uc.getSpeedLevel()) { case UserCredentials.MEDIUM: fSpeed = context.lookup( LookupNames.THUMBNAIL_FETCH_MEDIUM_SPEED); if (fSpeed != null && (fSpeed instanceof Double)) f = (Double) fSpeed; else context.getLogger().warn(this, "Thumbnail " + "fetching factor not set"); if (f <= 0 || f > 1) f = FETCH_MEDIUM_SPEED; fetchSize = (int) (value*f); break; case UserCredentials.LOW: fSpeed = context.lookup( LookupNames.THUMBNAIL_FETCH_LOW_SPEED); if (fSpeed != null && (fSpeed instanceof Double)) f = (Double) fSpeed; else context.getLogger().warn(this, "Thumbnail " + "fetching factor not set"); if (f <= 0 || f > 1) f = FETCH_LOW_SPEED; fetchSize = (int) (value*f); break; default: fetchSize = value; } } /** * Loads the thumbnails for the passed collection of files. * * @param files The collection of files to handle. */ private void loadFSThumbnails(List files) { List result = new ArrayList(); try { ExperimenterData exp = (ExperimenterData) context.lookup( LookupNames.CURRENT_USER_DETAILS); long id = exp.getId(); Map<DataObject, BufferedImage> m = service.getFSThumbnailSet(ctx, files, maxLength, id); Entry<DataObject, BufferedImage> entry; Iterator<Entry<DataObject, BufferedImage>> i = m.entrySet().iterator(); BufferedImage thumb; DataObject obj; boolean valid = true; FileData f; while (i.hasNext()) { entry = i.next(); obj = entry.getKey(); thumb = entry.getValue(); if (thumb == null) { thumb = Factory.createDefaultImageThumbnail( Factory.IMAGE_ICON); } if (obj.getId() > 0) result.add(new ThumbnailData(obj.getId(), thumb, valid)); else result.add(new ThumbnailData(obj, thumb, valid)); } currentThumbs = result; } catch (Exception e) { currentThumbs = result; context.getLogger().error(this, "Cannot retrieve thumbnail: "+e.getMessage()); } } /** * Loads the thumbnails for the passed collection of experimenters. * * @param experimenters The collection of experimenters to handle. */ private void loadExperimenterThumbnails(List experimenters) { try { ExperimenterData exp = (ExperimenterData) context.lookup( LookupNames.CURRENT_USER_DETAILS); Map<DataObject, BufferedImage> m = service.getExperimenterThumbnailSet(ctx, experimenters, maxLength); List result = new ArrayList(); Entry<DataObject, BufferedImage> entry; Iterator<Entry<DataObject, BufferedImage>> i = m.entrySet().iterator(); BufferedImage thumb; DataObject obj; boolean valid = true; while (i.hasNext()) { entry = i.next(); obj = (DataObject) entry.getKey(); thumb = entry.getValue(); if (thumb == null) thumb = Factory.createDefaultImageThumbnail( Factory.EXPERIMENTER_ICON); if (obj.getId() > 0) result.add(new ThumbnailData(obj.getId(), thumb, valid)); else result.add(new ThumbnailData(obj, thumb, valid)); } currentThumbs = result; } catch (Exception e) { context.getLogger().error(this, "Cannot retrieve thumbnail: "+e.getMessage()); } } /** * Loads the thumbnail for passed collection of pixels set. * * @param ids The collection of pixels set id. */ private void loadThumbnails(List ids) { try { Map<Long, BufferedImage> m = service.getThumbnailSet(ctx, ids, maxLength); List<Object> result = new ArrayList<Object>(); Iterator<Long> i = m.keySet().iterator(); long pixelsID; BufferedImage thumbPix; DataObject obj; boolean valid = true; long imageID = -1; PixelsData pxd = null; while (i.hasNext()) { pixelsID = i.next(); obj = input.get(pixelsID); if (obj instanceof ImageData) { imageID = ((ImageData) obj).getId(); pxd = ((ImageData) obj).getDefaultPixels(); } else if (obj instanceof PixelsData) { pxd = (PixelsData) obj; imageID = pxd.getImage().getId(); } if (pxd != null) { thumbPix = (BufferedImage) m.get(pixelsID); if (thumbPix == null) thumbPix = createDefaultImage(pxd); result.add(new ThumbnailData(imageID, thumbPix, valid)); } } currentThumbs = result; } catch (RenderingServiceException e) { context.getLogger().error(this, "Cannot retrieve thumbnail: "+e.getExtendedMessage()); } } /** * Adds a {@link BatchCall} to the tree for each thumbnail to retrieve. * @see BatchCallTree#buildTree() */ protected void buildTree() { Iterator<List> i = toHandle.iterator(); String description = "Loading collection of thumbnails"; List l; while (i.hasNext()) { l = i.next(); final List ids = l; add(new BatchCall(description) { public void doCall() { if (ImageData.class.equals(type)) { loadThumbnails(ids); } else if (FileData.class.equals(type)) { loadFSThumbnails(ids); } else if (ExperimenterData.class.equals(type)) { loadExperimenterThumbnails(ids); } } }); } currentThumbs = notValid; } /** * Returns the lastly retrieved thumbnail. * This will be packed by the framework into a feedback event and * sent to the provided call observer, if any. * * @return A {@link ThumbnailData} containing the thumbnail pixels. */ protected Object getPartialResult() { return currentThumbs; } /** * Returns <code>null</code> as there's no final result. * In fact, thumbnails are progressively delivered with * feedback events. * @see BatchCallTree#getResult() */ protected Object getResult() { return null; } /** * Creates a new instance. * * @param ctx The security context. * @param images The collection of images to load thumbnails for. * @param maxLength The maximum length of a thumbnail. * @param nodeType One of the constants defined by this class. */ public ThumbnailSetLoader(SecurityContext ctx, Collection<DataObject> images, int maxLength, int nodeType) { if (images == null) throw new NullPointerException("No images."); if (maxLength <= 0) throw new IllegalArgumentException( "Non-positive height: "+maxLength+"."); computeFetchSize(); this.ctx = ctx; this.maxLength = maxLength; service = context.getImageService(); toHandle = new ArrayList<List>(); input = new HashMap<Long, DataObject>(); notValid = new ArrayList(); Iterator<DataObject> i = images.iterator(); ImageData img; DataObject object; int index = 0; List<Object> l = null; PixelsData pxd = null; while (i.hasNext()) { object = i.next(); if (object instanceof ImageData) { if (nodeType == FS_FILE) { input.put(object.getId(), object); type = FileData.class; if (index == 0) l = new ArrayList<Object>(); if (index < fetchSize) { l.add(object); index++; if (index == fetchSize) { toHandle.add(l); index = 0; l = null; } } } else { img = (ImageData) object; type = ImageData.class; try { pxd = img.getDefaultPixels(); input.put(pxd.getId(), img); if (index == 0) l = new ArrayList<Object>(); if (index < fetchSize) { l.add(pxd.getId()); index++; if (index == fetchSize) { toHandle.add(l); index = 0; l = null; } } } catch (Exception e) { notValid.add(new ThumbnailData(img.getId(), createDefaultImage(pxd), false)); } //something went wrong during import } } else if (object instanceof FileData) { input.put(object.getId(), object); type = FileData.class; if (index == 0) l = new ArrayList<Object>(); if (index < fetchSize) { l.add(object); index++; if (index == fetchSize) { toHandle.add(l); index = 0; l = null; } } } else if (object instanceof ExperimenterData) { input.put(object.getId(), object); type = ExperimenterData.class; if (index == 0) l = new ArrayList<Object>(); if (index < fetchSize) { l.add(object); index++; if (index == fetchSize) { toHandle.add(l); index = 0; l = null; } } } else if (object instanceof PixelsData) { pxd = (PixelsData) object; type = ImageData.class; input.put(pxd.getId(), pxd); if (index == 0) l = new ArrayList<Object>(); if (index < fetchSize) { l.add(pxd.getId()); index++; if (index == fetchSize) { toHandle.add(l); index = 0; l = null; } } } } if (l != null && l.size() > 0) toHandle.add(l); } }