/*
*------------------------------------------------------------------------------
* Copyright (C) 2006-2016 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.agents.fsimporter.view;
import ij.ImagePlus;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.filechooser.FileFilter;
import omero.model.ImageI;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FilenameUtils;
import org.openmicroscopy.shoola.agents.fsimporter.AnnotationDataLoader;
import org.openmicroscopy.shoola.agents.fsimporter.DataLoader;
import org.openmicroscopy.shoola.agents.fsimporter.DataObjectCreator;
import org.openmicroscopy.shoola.agents.fsimporter.DiskSpaceLoader;
import org.openmicroscopy.shoola.agents.fsimporter.ImagesImporter;
import org.openmicroscopy.shoola.agents.fsimporter.ImportResultLoader;
import org.openmicroscopy.shoola.agents.fsimporter.ImporterAgent;
import org.openmicroscopy.shoola.agents.fsimporter.MeasurementsSaver;
import org.openmicroscopy.shoola.agents.fsimporter.ROISaver;
import org.openmicroscopy.shoola.agents.fsimporter.TagsLoader;
import org.openmicroscopy.shoola.agents.fsimporter.util.FileImportComponent;
import org.openmicroscopy.shoola.agents.fsimporter.util.ObjectToCreate;
import org.openmicroscopy.shoola.env.LookupNames;
import org.openmicroscopy.shoola.env.data.model.FileObject;
import org.openmicroscopy.shoola.env.data.model.ImportableObject;
import org.openmicroscopy.shoola.env.data.model.ResultsObject;
import omero.gateway.SecurityContext;
import org.openmicroscopy.shoola.util.CommonsLangUtils;
import org.openmicroscopy.shoola.util.roi.io.ROIReader;
import com.google.common.io.Files;
import omero.gateway.model.DataObject;
import omero.gateway.model.ExperimenterData;
import omero.gateway.model.FileAnnotationData;
import omero.gateway.model.GroupData;
import omero.gateway.model.ImageData;
import omero.gateway.model.ProjectData;
import omero.gateway.model.ROIData;
import omero.gateway.model.ScreenData;
/**
* The Model component in the <code>Importer</code> MVC triad.
* This class tracks the <code>Importer</code>'s state and knows how to
* initiate data retrievals. It also knows how to store and manipulate
* the results. This class provide a suitable data loader.
* The {@link ImporterComponent} intercepts the
* results of data loadings, feeds them back to this class and fires state
* transitions as appropriate.
*
* @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 3.0-Beta4
*/
class ImporterModel
{
/** Holds one of the state flags defined by {@link Importer}. */
private int state;
/** Reference to the component that embeds this model. */
protected Importer component;
/** The collection of existing tags. */
private Collection tags;
/** Keeps track of the different loaders. */
private Map<Integer, ImagesImporter> loaders;
/** The id of the selected group of the current user. */
private long groupId;
/** The id of the user currently logged in.*/
private long experimenterId;
/** The security context.*/
private SecurityContext ctx; //to be initialized.
/** Returns <code>true</code> if it is opened as a standalone app.*/
private boolean master;
/** The display mode.*/
private int displayMode;
/** The id of the user to import for.*/
private long userId;
/** Loader for screen/plates, projects/datasets */
private DataLoader containerLoader;
/**
* The result object used to determine setting when saving rois/measurement
* post import.
*/
private ResultsObject object;
/** Initializes the model.*/
private void initialize()
{
groupId = -1;
experimenterId = -1;
userId = -1;
state = Importer.NEW;
loaders = new HashMap<Integer, ImagesImporter>();
checkDefaultDisplayMode();
}
/**
* Invokes the value is not set.
*/
private void checkDefaultDisplayMode()
{
Integer value = (Integer) ImporterAgent.getRegistry().lookup(
LookupNames.DATA_DISPLAY);
if (value == null) setDisplayMode(LookupNames.EXPERIMENTER_DISPLAY);
else setDisplayMode(value.intValue());
}
/**
* Indicates to load all annotations available if the user can annotate
* and is an administrator/group owner or to only load the user's
* annotation.
*
* @return See above
*/
private boolean canRetrieveAll()
{
GroupData group = getGroup(getGroupId());
if (group == null) return false;
if (GroupData.PERMISSIONS_GROUP_READ ==
group.getPermissions().getPermissionsLevel()) {
if (ImporterAgent.isAdministrator()) return true;
Set leaders = group.getLeaders();
Iterator i = leaders.iterator();
long userID = getExperimenterId();
ExperimenterData exp;
while (i.hasNext()) {
exp = (ExperimenterData) i.next();
if (exp.getId() == userID)
return true;
}
return false;
}
return true;
}
/**
* Returns the group corresponding to the specified id or <code>null</code>.
*
* @param groupId The identifier of the group.
* @return See above.
*/
private GroupData getGroup(long groupId)
{
Collection groups = ImporterAgent.getAvailableUserGroups();
if (groups == null) return null;
Iterator i = groups.iterator();
GroupData group;
while (i.hasNext()) {
group = (GroupData) i.next();
if (group.getId() == groupId) return group;
}
return null;
}
/**
* Creates a new instance.
*
* @param groupID The id to the group selected for the current user.
* @param displayMode Group/Experimenter view.
*/
ImporterModel(long groupId, int displayMode)
{
this(groupId, false, displayMode);
}
/**
* Creates a new instance.
*
* @param groupID The id to the group selected for the current user.
* @param master Pass <code>true</code> if the importer is used a stand-alone
* application, <code>false</code> otherwise.
* @param displayMode Group/Experimenter view.
*/
ImporterModel(long groupId, boolean master, int displayMode)
{
this.master = master;
initialize();
setGroupId(groupId);
setDisplayMode(displayMode);
}
/**
* Sets the group's identifier.
*
* @param groupId The group's identifier.
*/
void setGroupId(long groupId)
{
this.groupId = groupId;
ExperimenterData exp = ImporterAgent.getUserDetails();
if (groupId < 0) {
this.groupId = exp.getDefaultGroup().getGroupId();
}
ctx = new SecurityContext(this.groupId);
experimenterId = exp.getId();
tags = null;
}
/**
* Returns the group's identifier.
*
* @return See above.
*/
long getGroupId() { return groupId; }
/**
* Returns the experimenter's identifier.
*
* @return See above.
*/
long getExperimenterId() { return experimenterId; }
/**
* Returns <code>true</code> if the agent is the entry point
* <code>false</code> otherwise.
*
* @return See above.
*/
boolean isMaster() { return master; }
/**
* Called by the <code>FSImporter</code> after creation to allow this
* object to store a back reference to the embedding component.
*
* @param component The embedding component.
*/
void initialize(Importer component)
{
this.component = component;
}
/**
* Returns the current state.
*
* @return One of the flags defined by the {@link Importer} interface.
*/
int getState() { return state; }
/**
* Sets the state of the component.
*
* @param state The value to set.
*/
void setState(int state) { this.state = state; }
/**
* Sets the object in the {@link Importer#DISCARDED} state.
* Any ongoing data loading will be cancelled.
*/
void discard()
{
cancel();
if (loaders.size() > 0) {
Iterator<ImagesImporter> i = loaders.values().iterator();
while (i.hasNext()) {
i.next().cancel();
}
loaders.clear();
}
state = Importer.DISCARDED;
}
/**
* Sets the object in the {@link Importer#READY} state.
* Any ongoing data loading will be cancelled.
*/
void cancel()
{
//state = Importer.READY;
}
/**
* Cancels the specified on-going import.
*
* @param loaderID The identifier of the loader.
*/
void cancel(int loaderID)
{
ImagesImporter loader = loaders.get(loaderID);
if (loader != null) {
//loader.cancel();
loaders.remove(loaderID);
}
}
/**
* Returns the list of the supported file formats.
*
* @return See above.
*/
FileFilter[] getSupportedFormats()
{
return
ImporterAgent.getRegistry().getImageService().getSupportedFileFormats();
}
/**
* Fires an asynchronous call to import the files in the object.
*
* @param data The file to import.
* @param loaderID The identifier of the component this loader is for.
*/
void fireImportData(ImportableObject data, int loaderID)
{
if (data == null) return;
ImagesImporter loader = new ImagesImporter(component, data, loaderID);
loaders.put(loaderID, loader);
loader.load();
state = Importer.IMPORTING;
}
/**
* Notifies that the import has finished.
*
* @param loaderID The identifier of the loader associated to the finished
* import.
*/
void importCompleted(int loaderID)
{
state = Importer.READY;
loaders.remove(loaderID);
}
/**
* Returns the collection of the existing tags.
*
* @return See above.
*/
Collection getTags() { return tags; }
/**
* Sets the collection of the existing tags.
*
* @param The value to set.
*/
void setTags(Collection tags)
{
this.tags = tags;
}
/** Starts an asynchronous call to load the tags. */
void fireTagsLoading()
{
if (tags != null) return; //already loading tags
TagsLoader loader = new TagsLoader(component, ctx, canRetrieveAll());
loader.load();
}
/** Starts an asynchronous call to load the available disk space. */
void fireDiskSpaceLoading()
{
DiskSpaceLoader loader = new DiskSpaceLoader(component, ctx);
loader.load();
}
/**
* Fires an asynchronous call to load the container.
*
* @param rootType The type of nodes to load.
* @param refreshImport Flag indicating to refresh the on-going import.
* @param changeGroup Flag indicating that the group has been modified
* if <code>true</code>, <code>false</code> otherwise.
* @param user The user to load the data for.
*/
void fireContainerLoading(Class rootType, boolean refreshImport, boolean
changeGroup, ExperimenterData user)
{
if (!(ProjectData.class.equals(rootType) ||
ScreenData.class.equals(rootType)))
return;
// cancel the previous loader
if(containerLoader != null && state == Importer.LOADING)
containerLoader.cancel();
state = Importer.LOADING;
containerLoader = new DataLoader(component, ctx, rootType,
refreshImport, changeGroup);
containerLoader.load();
}
/**
* Fires an asynchronous call to load the import log file.
*
* @param fileSetID The fileSet id.
* @param index The index of the UI element.
*/
void fireImportLogFileLoading(long fileSetID, int index)
{
AnnotationDataLoader loader = new AnnotationDataLoader(component, ctx,
fileSetID, index);
loader.load();
}
/**
* Creates a new data object.
*
* @param data The object hosting information about the object to create.
*/
void fireDataCreation(ObjectToCreate data)
{
SecurityContext ctx = new SecurityContext(data.getGroup().getId());
ctx.setServerInformation(this.ctx.getServerInformation());
ctx.setExperimenter(data.getExperimenter());
DataObjectCreator loader = new DataObjectCreator(component, ctx,
data.getChild(), data.getParent());
loader.load();
}
/**
* Returns <code>true</code> if only one group for the user,
* <code>false</code> otherwise.
*
* @return See above.
*/
boolean isSingleGroup()
{
Collection l = ImporterAgent.getAvailableUserGroups();
return (l.size() <= 1);
}
/**
* Returns the display mode.
*
* @return See above.
*/
int getDisplayMode() { return displayMode; }
/**
* Sets the display mode.
*
* @param value The value to set.
*/
void setDisplayMode(int value)
{
if (value < 0) {
checkDefaultDisplayMode();
return;
}
switch (value) {
case LookupNames.EXPERIMENTER_DISPLAY:
case LookupNames.GROUP_DISPLAY:
displayMode = value;
break;
default:
displayMode = LookupNames.EXPERIMENTER_DISPLAY;
}
if (tags != null) {
tags.clear();
tags = null;
}
}
/**
* Returns the security context.
*
* @return See above.
*/
SecurityContext getSecurityContext() { return ctx; }
/**
* Fires a call to load the thumbnails or the plate.
*
* @param pixels The objects to load.
* @param type The type of data to handle.
* @param component The component the result is for.
* @param user The experimenter to import data for.
*/
void fireImportResultLoading(Collection<DataObject> pixels, Class<?> type,
Object component, ExperimenterData user)
{
if (user != null) {
long currentUser = ImporterAgent.getUserDetails().getId();
if (currentUser != user.getId()) {
ctx.setExperimenter(user);
ctx.sudo();
}
}
ImportResultLoader loader = new ImportResultLoader(this.component, ctx,
pixels, type, component);
loader.load();
}
/**
* Returns the id of the user to import data for.
*
* @return See above.
*/
long getImportFor()
{
if (!canImportAs() || userId < 0) return experimenterId;
return userId;
}
/**
* Sets the id of the user to import the data for.
*
* @param userId The id of the user.
*/
void setImportFor(long userId)
{
if (canImportAs()) this.userId = userId;
}
/**
* Returns <code>true</code> if the user currently log in can import as,
* <code>false</code> otherwise.
*
* @return
*/
boolean canImportAs()
{
if (ImporterAgent.isAdministrator()) return true;
return CollectionUtils.isNotEmpty(getGroupsLeaderOf());
}
/**
* Returns <code>true</code> if the group is a system group e.g. System
* <code>false</code> otherwise.
*
* @param id The identifier of the group.
* @param key One of the constants defined by <code>GroupData</code>
* @return See above.
*/
boolean isSystemGroup(long id, String key)
{
return ImporterAgent.getRegistry().getAdminService().isSecuritySystemGroup(id, key);
}
/**
* Returns the collection of groups the current user is the leader of.
*
* @return See above.
*/
Set<GroupData> getGroupsLeaderOf()
{
Set<GroupData> values = new HashSet<GroupData>();
Collection<GroupData> groups = getAvailableGroups();
Iterator<GroupData> i = groups.iterator();
GroupData g;
Set<ExperimenterData> leaders;
ExperimenterData exp = ImporterAgent.getUserDetails();
long id = exp.getId();
Iterator<ExperimenterData> j;
while (i.hasNext()) {
g = (GroupData) i.next();
leaders = g.getLeaders();
if (CollectionUtils.isNotEmpty(leaders)) {
j = leaders.iterator();
while (j.hasNext()) {
exp = (ExperimenterData) j.next();
if (exp.getId() == id)
values.add(g);
}
}
}
return values;
}
/**
* Returns the groups the current user is a member of.
*
* @return See above.
*/
Collection<GroupData> getAvailableGroups()
{
return ImporterAgent.getAvailableUserGroups();
}
/**
* Saves the roi if any associated to the image.
*
* @param c The component to handle.
* @param images The images to handle.
*/
void saveROI(FileImportComponent c, List<ImageData> images)
{
FileObject object = c.getOriginalFile();
if (object.isImagePlus() && CollectionUtils.isNotEmpty(images)) {
ROIReader reader = new ROIReader();
SecurityContext ctx = new SecurityContext(c.getGroupID());
ImagePlus img = (ImagePlus) object.getFile();
List<FileObject> files = object.getAssociatedFiles();
List<ROIData> rois;
Map<Integer, List<ROIData>> indexes =
new HashMap<Integer, List<ROIData>>();
int index;
boolean mif = false;
if (CollectionUtils.isNotEmpty(files)) {
mif = true;
Iterator<FileObject> j = files.iterator();
FileObject o;
while (j.hasNext()) {
o = j.next();
if (o.isImagePlus()) {
index = o.getIndex();
rois = reader.readImageJROI(-1, (ImagePlus) o.getFile());
indexes.put(index, rois);
if (index < 0) {
mif = false;
}
}
}
}
//convert rois from manager.
//rois from manager so we need to link them to all the images
ImageData data;
long id;
Iterator<ImageData> i = images.iterator();
while (i.hasNext()) {
data = i.next();
id = data.getId();
index = data.getSeries();
//First check overlay
rois = null;
if (indexes.containsKey(index)) {
rois = indexes.get(index);
linkRoisToImage(id, rois);
} else {
if (!mif) {
rois = reader.readImageJROI(id, img);
}
}
//check roi manager
if (CollectionUtils.isEmpty(rois)) {
rois = reader.readImageJROI(id);
}
if (CollectionUtils.isNotEmpty(rois)) {
ROISaver saver = new ROISaver(component, ctx, rois, id,
c.getExperimenterID(), c);
saver.load();
}
//Save the measurements
File f = createFile(data.getName());
if (f != null) {
MeasurementsSaver ms = new MeasurementsSaver(
component, ctx, new FileAnnotationData(f),
data, c.getExperimenterID());
ms.load();
}
}
}
}
/**
* Create a temporary file
*
* @param imageName The image object to handle.
* @return See above.
*/
private File createFile(String imageName)
{
File dir = Files.createTempDir();
String name;
String fileName = null;
if (object != null) {
fileName = object.getTableName();
}
if (CommonsLangUtils.isBlank(fileName)) {
name = "ImageJ-"+FilenameUtils.getBaseName(
FilenameUtils.removeExtension(imageName))+"-Results-";
name += new SimpleDateFormat("yyyy-MM-dd").format(new Date());
} else {
name = FilenameUtils.removeExtension(fileName);
}
name += ".csv";
try {
File f = new File(dir, name);
//read data
ROIReader reader = new ROIReader();
if (!reader.readResults(f)) {
f.delete();
dir.delete();
return null;
}
dir.deleteOnExit();
return f;
} catch (Exception e) {
ImporterAgent.getRegistry().getLogger().error(this,
"Cannot create file to save results"+e.getMessage());
}
return null;
}
/**
* Links the rois to the image.
*
* @param imageID The image's id.
* @param rois The rois to link to the image.
*/
private void linkRoisToImage(long imageID, List<ROIData> rois)
{
if (CollectionUtils.isEmpty(rois)) return;
Iterator<ROIData> i = rois.iterator();
while (i.hasNext()) {
i.next().setImage(new ImageI(imageID, false));
}
}
/**
* Sets the results object.
*
* @param object The object to set.
*/
void setResultsObject(ResultsObject object)
{
this.object = object;
}
}