diff --git a/CHANGELOG b/CHANGELOG index 3c6066108aeb4bdc95716999983315a303d2e8fd..03fe24f31ed75a22d0b34ce8f3505942e043696b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -82,12 +82,17 @@ minerva (12.0.0~alpha.0) unstable; urgency=medium -- Piotr Gawron <piotr.gawron@uni.lu> Wed, 21 Feb 2018 12:00:00 +0200 +minerva (11.1.0) stable; urgency=high + * User can provide Google Maps API key that must be registered in google + cloud account + + -- Piotr Gawron <piotr.gawron@uni.lu> Mon, 14 May 2018 9:00:00 +0200 + minerva (11.0.10) stable; urgency=medium * Bug fix: link from overview image to search result was broken -- Piotr Gawron <piotr.gawron@uni.lu> Wed, 16 Apr 2018 15:00:00 +0200 - minerva (11.0.9) stable; urgency=medium * Bug fix: some comment couldn't be loaded * Bug fix: when adding comment it didn't appear on map immediately diff --git a/frontend-js/src/main/js/ConfigurationType.js b/frontend-js/src/main/js/ConfigurationType.js index 1ce31b07fc58d9ba2f6542d09475783019a16062..183411fcea0dadee718eb8984c9874866e201534 100644 --- a/frontend-js/src/main/js/ConfigurationType.js +++ b/frontend-js/src/main/js/ConfigurationType.js @@ -3,6 +3,7 @@ var ConfigurationType = { DEFAULT_MAP: "DEFAULT_MAP", GOOGLE_ANALYTICS_IDENTIFIER: "GOOGLE_ANALYTICS_IDENTIFIER", + GOOGLE_MAPS_API_KEY: "GOOGLE_MAPS_API_KEY", LOGO_IMG: "LOGO_IMG", LOGO_LINK: "LOGO_LINK", LOGO_TEXT: "LOGO_TEXT", diff --git a/frontend-js/src/main/js/ServerConnector.js b/frontend-js/src/main/js/ServerConnector.js index 4ff0fc4f086e3d63e29ff7cb22a449573e513db7..fc653b7e7f7cc06712298959a0f01ac76e3794b6 100644 --- a/frontend-js/src/main/js/ServerConnector.js +++ b/frontend-js/src/main/js/ServerConnector.js @@ -1742,7 +1742,8 @@ ServerConnector.updateOverlay = function (overlay) { description: overlay.getDescription(), creator: overlay.getCreator(), publicOverlay: overlay.getPublicOverlay(), - defaultOverlay: overlay.isDefaultOverlay() + defaultOverlay: overlay.isDefaultOverlay(), + googleLicenseConsent : overlay.isGoogleLicenseConsent() } }; return self.sendPatchRequest(self.updateOverlayUrl(queryParams), filterParams); diff --git a/frontend-js/src/main/js/gui/Panel.js b/frontend-js/src/main/js/gui/Panel.js index d4a1129b38d61d57078b1bab03267e0dcb85536f..3a6c1ca96a6e31e140312c77e58b3f9c3c3a1259 100644 --- a/frontend-js/src/main/js/gui/Panel.js +++ b/frontend-js/src/main/js/gui/Panel.js @@ -166,6 +166,8 @@ Panel.prototype.assignDialogOptions = function (div, params) { dialog.dialog('option', 'dialogClass', params[key]); } else if (key === "title") { dialog.dialog('option', 'title', params[key]); + } else if (key === "width") { + dialog.dialog('option', 'width', params[key]); } else { throw new Error("Unknown dialog param: " + key + " - " + params[key]); } diff --git a/frontend-js/src/main/js/gui/leftPanel/LeftPanel.js b/frontend-js/src/main/js/gui/leftPanel/LeftPanel.js index aac93a0effb65c153f3eb5812d4ec0f8067b078a..ecbe4037d7c18ef3c5d7be97f9d864cfebd15332 100644 --- a/frontend-js/src/main/js/gui/leftPanel/LeftPanel.js +++ b/frontend-js/src/main/js/gui/leftPanel/LeftPanel.js @@ -316,4 +316,12 @@ LeftPanel.prototype.destroy = function () { return Promise.all(promises); }; +LeftPanel.prototype.setGoogleLicenseConsentRequired = function (value) { + this._googleLicenseConsentRequired = value; +}; + +LeftPanel.prototype.isGoogleLicenseConsentRequired = function () { + return this._googleLicenseConsentRequired; +}; + module.exports = LeftPanel; diff --git a/frontend-js/src/main/js/gui/leftPanel/OverlayPanel.js b/frontend-js/src/main/js/gui/leftPanel/OverlayPanel.js index 5e6380700e1799568a0a325a7525c6bcf9cd8be7..9fe91b1abab770bfffe178b339656f68d29be90f 100644 --- a/frontend-js/src/main/js/gui/leftPanel/OverlayPanel.js +++ b/frontend-js/src/main/js/gui/leftPanel/OverlayPanel.js @@ -141,8 +141,6 @@ OverlayPanel.prototype._createOverlayPanelGui = function () { content: "Add overlay" }); centerTag.appendChild(addOverlayButton); - - this.setControlElement(PanelControlElementType.OVERLAY_ADD_OVERLAY_BUTTON, addOverlayButton); }; @@ -183,7 +181,7 @@ OverlayPanel.prototype.createTableHeader = function (edit) { return result; }; -OverlayPanel.prototype.createOverlayRow = function (overlay, checked) { +OverlayPanel.prototype.createOverlayRow = function (overlay, checked, disabled) { var self = this; var guiUtils = self.getGuiUtils(); var result = document.createElement("tr"); @@ -204,6 +202,7 @@ OverlayPanel.prototype.createOverlayRow = function (overlay, checked) { data: overlay.getId() }); checkbox.checked = checked; + $(checkbox).prop("disabled", disabled); viewTd.appendChild(checkbox); } else { var img = guiUtils.createIcon("icons/search.png"); @@ -285,12 +284,21 @@ OverlayPanel.prototype.openEditOverlayDialog = function (overlay) { row = guiUtils.createTableRow([guiUtils.createLabel("Description: "), descriptionInput]); content.appendChild(row); + var consentCheckbox = document.createElement("input"); + consentCheckbox.type = "checkbox"; + consentCheckbox.checked = overlay.isGoogleLicenseConsent(); + row = guiUtils.createTableRow([guiUtils.createLabel("I am aware that this map is displayed under the terms of the <a href='https://cloud.google.com/maps-platform/terms/' target='_blank'>license of Google Maps Platform</a> and I agree to these terms. " + + "In particular, I warrant that this dataset does not contain Protected Health Information (as defined in and subject to HIPAA). "), consentCheckbox]); + content.appendChild(row); + var buttons = [{ text: "SAVE", click: function () { var windowSelf = this; overlay.setName(nameInput.value); overlay.setDescription(descriptionInput.value); + overlay.setGoogleLicenseConsent(consentCheckbox.checked); + return ServerConnector.updateOverlay(overlay).then(function () { return self.refresh(); }).then(function () { @@ -318,6 +326,7 @@ OverlayPanel.prototype.openEditOverlayDialog = function (overlay) { } }]; self.openDialog(content, { + width: "600px", id: overlay.getId(), buttons: buttons, title: "Data overlay: " + overlay.getName(), @@ -377,7 +386,7 @@ OverlayPanel.prototype.refresh = function (showDefault) { table.appendChild(body); for (i = 0; i < generalOverlays.length; i++) { overlay = generalOverlays[i]; - body.appendChild(self.createOverlayRow(overlay, selectedOverlay[overlay.getId()])); + body.appendChild(self.createOverlayRow(overlay, selectedOverlay[overlay.getId()], false)); } var title = self.getControlElement(PanelControlElementType.OVERLAY_CUSTOM_OVERLAY_TITLE); @@ -406,12 +415,16 @@ OverlayPanel.prototype.refresh = function (showDefault) { } }).DataTable(); var data = []; + self.setGoogleLicenseConsentRequired(false); for (i = 0; i < customOverlays.length; i++) { overlay = customOverlays[i]; if (showDefault && overlay.isDefaultOverlay()) { selectedOverlay[overlay.getId()] = true; } - data.push(self.overlayToDataRow(overlay, selectedOverlay[overlay.getId()])); + data.push(self.overlayToDataRow(overlay, selectedOverlay[overlay.getId()], !overlay.isGoogleLicenseConsent())); + if (!overlay.isGoogleLicenseConsent()) { + self.setGoogleLicenseConsentRequired(true); + } } table.clear().rows.add(data).draw(); @@ -532,4 +545,10 @@ OverlayPanel.prototype.destroy = function () { } }; +OverlayPanel.prototype.setGoogleLicenseConsentRequired = function (value) { + var leftPanel = this.getParent(); + if (leftPanel !== undefined) { + leftPanel.setGoogleLicenseConsentRequired(value); + } +}; module.exports = OverlayPanel; diff --git a/frontend-js/src/main/js/map/data/DataOverlay.js b/frontend-js/src/main/js/map/data/DataOverlay.js index a859fe054c2e50dd300201a31b05280bcecc4803..a4d6f2749d3c3412f3f65a79f7334bbf25e3a9bd 100644 --- a/frontend-js/src/main/js/map/data/DataOverlay.js +++ b/frontend-js/src/main/js/map/data/DataOverlay.js @@ -26,6 +26,7 @@ function DataOverlay(overlayId, name) { this.setFilename(object.filename); this.setPublicOverlay(object.publicOverlay); this.setDefaultOverlay(object.defaultOverlay); + this.setGoogleLicenseConsent(object.googleLicenseConsent); this.setInputDataAvailable(object.inputDataAvailable); this.setType(object.type); if (!this.getInputDataAvailable()) { @@ -241,6 +242,14 @@ DataOverlay.prototype.setType = function (type) { this._type = type; }; +DataOverlay.prototype.setGoogleLicenseConsent = function(value) { + this._googleLicenseConsent = value; +}; + +DataOverlay.prototype.isGoogleLicenseConsent = function() { + return this._googleLicenseConsent; +}; + DataOverlay.prototype.update = function (overlay) { this.setName(overlay.getName()); this.setDescription(overlay.getDescription()); diff --git a/frontend-js/src/main/js/minerva.js b/frontend-js/src/main/js/minerva.js index 0cffc5c39049005e18b8ec5debaeaf7a81ccece0..83fa8dd4637dab7d6a685454f130137012b2895c 100644 --- a/frontend-js/src/main/js/minerva.js +++ b/frontend-js/src/main/js/minerva.js @@ -246,12 +246,13 @@ function create(params) { params.getElement().innerHTML = "<div style='vertical-align:middle;display:table-cell;text-align: center'>" + "<img src='resources/images/icons/ajax-loader.gif'/>" + "</div>"; - // make sure that we are logged in // make sure that we are logged in return ServerConnector.createSession().then(function () { return ServerConnector.getConfiguration(); }).then(function (configuration) { params.setConfiguration(configuration); + return functions.loadScript("https://maps.google.com/maps/api/js?libraries=drawing&v=3.26&key=" + configuration.getOption(ConfigurationType.GOOGLE_MAPS_API_KEY)); + }).then(function () { return getProject(params); }).then(function (project) { if (project === null) { @@ -353,6 +354,9 @@ function create(params) { } return Promise.all(promises); }).then(function () { + if (leftPanel.isGoogleLicenseConsentRequired()) { + GuiConnector.alert("Some data overlays doesn't have consent to the terms of the <a href='https://cloud.google.com/maps-platform/terms/' target='_blank'>license of Google Maps Platform</a>. To be able to visualize them you must edit data overlay. ") + } var result = { destroy: function () { return leftPanel.destroy().then(function () { @@ -538,6 +542,8 @@ function createExport(params) { // make sure that we are logged in return ServerConnector.getConfiguration().then(function (configuration) { params.setConfiguration(configuration); + return functions.loadScript("https://maps.google.com/maps/api/js?libraries=drawing&v=3.26&key=" + configuration.getOption(ConfigurationType.GOOGLE_MAPS_API_KEY)); + }).then(function () { return getProject(params); }).then(function (project) { params.setProject(project); @@ -568,6 +574,8 @@ function createAdmin(params) { return ServerConnector.getConfiguration(); }).then(function (configuration) { params.setConfiguration(configuration); + return functions.loadScript("https://maps.google.com/maps/api/js?libraries=drawing&v=3.26&key=" + configuration.getOption(ConfigurationType.GOOGLE_MAPS_API_KEY)); + }).then(function () { result = new Admin(params); return createFooter(); }).then(function (footer) { diff --git a/model/src/main/java/lcsb/mapviewer/model/map/layout/Layout.java b/model/src/main/java/lcsb/mapviewer/model/map/layout/Layout.java index 78add52b8436ccf274e2d53bc497f799b95d1645..6a4efe053f62162f80c2ac17f78eea7f15b336c1 100644 --- a/model/src/main/java/lcsb/mapviewer/model/map/layout/Layout.java +++ b/model/src/main/java/lcsb/mapviewer/model/map/layout/Layout.java @@ -94,6 +94,9 @@ public class Layout implements Serializable { */ private boolean hierarchicalView = false; + @Column(name="google_license_consent") + private boolean googleLicenseConsent = false; + /** * If overlay contain hierarchical view then it might be fixed on some specific * level. This parameter defines the level at which it's fixed or contains null @@ -487,4 +490,12 @@ public class Layout implements Serializable { this.orderIndex = orderIndex; } + public boolean isGoogleLicenseConsent() { + return googleLicenseConsent; + } + + public void setGoogleLicenseConsent(boolean googleLicenseConsent) { + this.googleLicenseConsent = googleLicenseConsent; + } + } diff --git a/model/src/main/java/lcsb/mapviewer/model/user/ConfigurationElementType.java b/model/src/main/java/lcsb/mapviewer/model/user/ConfigurationElementType.java index a9ce9dd9ea74fc3047c5d1323fffd8f9fac070ec..27b2aceee9fb34686cb5d703814b1a9551e3c427 100644 --- a/model/src/main/java/lcsb/mapviewer/model/user/ConfigurationElementType.java +++ b/model/src/main/java/lcsb/mapviewer/model/user/ConfigurationElementType.java @@ -188,6 +188,12 @@ public enum ConfigurationElementType { SHOW_REACTION_TYPE("Show reaction type when browsing map", "true", ConfigurationElementEditType.BOOLEAN, false, ConfigurationElementTypeGroup.LEGEND_AND_LOGO), + GOOGLE_MAPS_API_KEY("By providing this Google Maps Platform API key I declare that I am aware that " + + "I am a Customer of the Google Maps Platform and I agree to the terms of the <a href=\"https://cloud.google.com/maps-platform/terms/\" target='_blank'>license of Google Maps Platform</a>." + + "In particular, I warrant that neither any of the maps nor publicly available data overlays " + + "(\"General overlays\") on this MINERVA server contain Protected Health Information (as defined in and subject to HIPAA).", + "", ConfigurationElementEditType.STRING, false, ConfigurationElementTypeGroup.SERVER_CONFIGURATION), + ; /** @@ -211,7 +217,7 @@ public enum ConfigurationElementType { /** * Default constructor. - * + * * @param commonName * common name used for this parameter * @param editType diff --git a/persist/src/db/11.1.0/fix_db_20180509.sql b/persist/src/db/11.1.0/fix_db_20180509.sql new file mode 100644 index 0000000000000000000000000000000000000000..7b90e53299d30f2657a70e2816afaa6de66c81bc --- /dev/null +++ b/persist/src/db/11.1.0/fix_db_20180509.sql @@ -0,0 +1 @@ +alter table layout add column google_license_consent boolean default false; diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayRestImpl.java b/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayRestImpl.java index ae0568257d0e2ad77737f8a9a61c54d984f63bcb..df87863ca25954c20b43aea180103f7b0c1e9fb4 100644 --- a/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayRestImpl.java +++ b/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayRestImpl.java @@ -214,6 +214,8 @@ public class OverlayRestImpl extends BaseRestImpl { layout.setPublicLayout(parseBoolean(value)); } else if (key.equalsIgnoreCase("defaultOverlay")) { layout.setDefaultOverlay(parseBoolean(value)); + } else if (key.equalsIgnoreCase("googleLicenseConsent")) { + layout.setGoogleLicenseConsent((Boolean) overlayData.get("googleLicenseConsent")); } else if (key.equalsIgnoreCase("creator")) { if ("".equals(value)) { layout.setCreator(null); diff --git a/service/src/main/java/lcsb/mapviewer/services/impl/LayoutService.java b/service/src/main/java/lcsb/mapviewer/services/impl/LayoutService.java index 27a911d583bb0f5e92144e5280bd4571e29fe088..24506ac69c2b717b8013c78449c4ae8a95e64515 100644 --- a/service/src/main/java/lcsb/mapviewer/services/impl/LayoutService.java +++ b/service/src/main/java/lcsb/mapviewer/services/impl/LayoutService.java @@ -1,1007 +1,1008 @@ -package lcsb.mapviewer.services.impl; - -import java.awt.Color; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.annotation.PostConstruct; -import javax.mail.MessagingException; - -import org.apache.commons.io.IOUtils; -import org.apache.log4j.Logger; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.annotation.Transactional; - -import lcsb.mapviewer.annotation.services.MiriamConnector; -import lcsb.mapviewer.commands.ColorExtractor; -import lcsb.mapviewer.commands.ColorModelCommand; -import lcsb.mapviewer.commands.CommandExecutionException; -import lcsb.mapviewer.commands.CopyCommand; -import lcsb.mapviewer.common.IProgressUpdater; -import lcsb.mapviewer.common.Pair; -import lcsb.mapviewer.common.TextFileUtils; -import lcsb.mapviewer.common.exception.InvalidArgumentException; -import lcsb.mapviewer.common.exception.InvalidStateException; -import lcsb.mapviewer.converter.graphics.MapGenerator; -import lcsb.mapviewer.converter.graphics.MapGenerator.MapGeneratorParams; -import lcsb.mapviewer.model.Project; -import lcsb.mapviewer.model.cache.UploadedFileEntry; -import lcsb.mapviewer.model.log.LogType; -import lcsb.mapviewer.model.map.MiriamData; -import lcsb.mapviewer.model.map.layout.ColorSchema; -import lcsb.mapviewer.model.map.layout.GeneVariation; -import lcsb.mapviewer.model.map.layout.GeneVariationColorSchema; -import lcsb.mapviewer.model.map.layout.GenericColorSchema; -import lcsb.mapviewer.model.map.layout.InvalidColorSchemaException; -import lcsb.mapviewer.model.map.layout.Layout; -import lcsb.mapviewer.model.map.layout.LayoutStatus; -import lcsb.mapviewer.model.map.model.Model; -import lcsb.mapviewer.model.map.model.ModelSubmodelConnection; -import lcsb.mapviewer.model.map.reaction.Reaction; -import lcsb.mapviewer.model.map.species.Element; -import lcsb.mapviewer.model.user.ObjectPrivilege; -import lcsb.mapviewer.model.user.PrivilegeType; -import lcsb.mapviewer.model.user.User; -import lcsb.mapviewer.persist.DbUtils; -import lcsb.mapviewer.persist.dao.map.LayoutDao; -import lcsb.mapviewer.services.SecurityException; -import lcsb.mapviewer.services.interfaces.IConfigurationService; -import lcsb.mapviewer.services.interfaces.ILayoutService; -import lcsb.mapviewer.services.interfaces.ILogService; -import lcsb.mapviewer.services.interfaces.ILogService.LogParams; -import lcsb.mapviewer.services.interfaces.IUserService; -import lcsb.mapviewer.services.search.layout.FullLayoutAliasView; -import lcsb.mapviewer.services.search.layout.FullLayoutAliasViewFactory; -import lcsb.mapviewer.services.search.layout.FullLayoutReactionView; -import lcsb.mapviewer.services.search.layout.FullLayoutReactionViewFactory; -import lcsb.mapviewer.services.search.layout.LightLayoutAliasView; -import lcsb.mapviewer.services.search.layout.LightLayoutAliasViewFactory; -import lcsb.mapviewer.services.search.layout.LightLayoutReactionView; -import lcsb.mapviewer.services.search.layout.LightLayoutReactionViewFactory; -import lcsb.mapviewer.services.utils.ColorSchemaReader; -import lcsb.mapviewer.services.utils.EmailSender; -import lcsb.mapviewer.services.utils.data.ColorSchemaColumn; - -/** - * Implementation of the layout service. - * - * @author Piotr Gawron - * - */ -@Transactional(value = "txManager") -public class LayoutService implements ILayoutService { - - /** - * Default class logger. - */ - private Logger logger = Logger.getLogger(LayoutService.class); - - /** - * Layout data access object. - */ - @Autowired - private LayoutDao layoutDao; - - /** - * Service that manages and gives access to user information. - */ - @Autowired - private IUserService userService; - - /** - * Service used to access logs. - * - * @see ILogService - */ - @Autowired - private ILogService logService; - - /** - * Service that manages and gives access to configuration parameters. - */ - @Autowired - private IConfigurationService configurationService; - - /** - * Utility class that helps to manage the sessions in custom multi-threaded - * implementation. - */ - @Autowired - private DbUtils dbUtils; - - /** - * Object that sends emails. - */ - private EmailSender emailSender; - - /** - * Method called after spring initialized all interfaces. - */ - @PostConstruct - public void springInit() { - emailSender = new EmailSender(configurationService); - } - - @Override - public boolean userCanAddLayout(Model model, User user) { - // if we don't have privileges to view the object then we cannot add layouts - if (!userService.userHasPrivilege(user, PrivilegeType.VIEW_PROJECT, model.getProject())) { - return false; - } - long count = getAvailableCustomLayoutsNumber(user); - return count > 0; - } - - @Override - public long getAvailableCustomLayoutsNumber(User user) { - long level = userService.getUserPrivilegeLevel(user, PrivilegeType.CUSTOM_LAYOUTS); - long layouts = layoutDao.getCountByUser(user); - return level - layouts; - - } - - @Override - public boolean userCanRemoveLayout(Layout layout, User user) { - User creator = layout.getCreator(); - Project project = layout.getModel().getProject(); - if (creator == null) { - return userService.userHasPrivilege(user, PrivilegeType.LAYOUT_MANAGEMENT, project); - } else { - return creator.getId().equals(user.getId()) - || userService.userHasPrivilege(user, PrivilegeType.LAYOUT_MANAGEMENT, project); - } - } - - private boolean userCanViewOverlay(Layout overlay, User user) { - if (overlay.isPublicLayout()) { - return true; - } - if (overlay.getCreator() == null) { - return true; - } - if (overlay.getCreator().getLogin().equals(user.getLogin())) { - return true; - } - - if (userService.userHasPrivilege(user, PrivilegeType.LAYOUT_VIEW, overlay)) { - return true; - } - if (userService.userHasPrivilege(user, PrivilegeType.LAYOUT_MANAGEMENT, overlay.getModel().getProject())) { - return true; - } - return false; - } - - @Override - public void removeLayout(Layout layout, final String homeDir) throws IOException { - final String dir; - if (homeDir != null) { - if (layout.getModel().getProject().getDirectory() != null) { - dir = homeDir + "/../map_images/" + layout.getModel().getProject().getDirectory() + "/"; - } else { - dir = homeDir + "/../map_images/"; - } - } else { - dir = null; - } - - String projectId = layout.getModel().getProject().getProjectId(); - final String email; - User user = layout.getCreator(); - if (user != null) { - email = user.getEmail(); - } else { - email = null; - } - - layout.getModel().removeLayout(layout); - layoutDao.delete(layout); - - LogParams params = new LogParams().object(layout).type(LogType.LAYOUT_REMOVED); - logService.log(params); - - if (email != null) { - Thread sendEmailThread = new Thread(new Runnable() { - @Override - public void run() { - try { - sendSuccesfullRemoveEmail(projectId, layout.getTitle(), email); - } catch (MessagingException e) { - logger.error(e); - } - } - }); - sendEmailThread.start(); - } - Thread removeFilesThread = new Thread(new Runnable() { - @Override - public void run() { - MapGenerator generator = new MapGenerator(); - try { - generator.removeLayout(layout, dir); - } catch (IOException e) { - logger.error(e); - } - - } - }); - removeFilesThread.start(); - } - - @Override - public void updateLayout(Layout layout) { - layoutDao.update(layout); - } - - @Override - public void addViewPrivilegeToLayout(Layout layout, User user) { - ObjectPrivilege privilege = new ObjectPrivilege(); - privilege.setIdObject(layout.getId()); - privilege.setLevel(1); - privilege.setType(PrivilegeType.LAYOUT_VIEW); - privilege.setUser(user); - userService.setUserPrivilege(user, privilege); - } - - @Override - public void dropViewPrivilegeFromLayout(Layout layout, User user) { - ObjectPrivilege privilege = new ObjectPrivilege(); - privilege.setIdObject(layout.getId()); - privilege.setLevel(0); - privilege.setType(PrivilegeType.LAYOUT_VIEW); - privilege.setUser(user); - userService.setUserPrivilege(user, privilege); - } - - @Override - public Layout createLayoutWithImages(final CreateLayoutParams params) - throws IOException, InvalidColorSchemaException, CommandExecutionException { - ColorSchemaReader reader = new ColorSchemaReader(); - final Collection<ColorSchema> schemas = reader.readColorSchema(params.getColorInputStream(), - TextFileUtils.getHeaderParametersFromFile(params.getColorInputStream())); - final Model colorModel = new CopyCommand(params.getModel()).execute(); - new ColorModelCommand(params.getModel(), schemas, userService.getColorExtractorForUser(params.getUser())).execute(); - String[] tmp = params.getDirectory().split("[\\\\/]"); - String simpleDir = tmp[tmp.length - 1]; - - layoutDao.flush(); - - boolean customSession = false; - if (params.isAsync()) { - customSession = !dbUtils.isCustomSessionForCurrentThread(); - if (customSession) { - dbUtils.createSessionForCurrentThread(); - } - } - final Map<Model, Integer> layoutIdByModel = new HashMap<>(); - - Layout topLayout = new Layout(params.getName(), simpleDir, false); - if (params.getUser() == null) { - topLayout.setPublicLayout(true); - } else { - topLayout.setPublicLayout(false); - } - topLayout.setStatus(LayoutStatus.NA); - topLayout.setProgress(0.0); - UploadedFileEntry fileEntry = new UploadedFileEntry(); - fileEntry.setFileContent(IOUtils.toByteArray(params.getColorInputStream())); - fileEntry.setOriginalFileName(params.getLayoutFileName()); - fileEntry.setLength(fileEntry.getFileContent().length); - fileEntry.setOwner(params.getUser()); - topLayout.setInputData(fileEntry); - topLayout.setDescription(params.getDescription()); - params.getModel().addLayout(topLayout); - topLayout.setCreator(params.getUser()); - layoutDao.add(topLayout); - topLayout.setDirectory(simpleDir + topLayout.getId()); - - layoutDao.update(topLayout); - - layoutIdByModel.put(colorModel, topLayout.getId()); - - for (ModelSubmodelConnection connection : params.getModel().getSubmodelConnections()) { - Layout layout = new Layout(params.getName(), simpleDir, false); - layout.setStatus(LayoutStatus.NA); - layout.setProgress(0.0); - connection.getSubmodel().getModel().addLayout(layout); - layout.setCreator(params.getUser()); - layout.setPublicLayout(false); - topLayout.addLayout(layout); - layoutDao.add(layout); - layout.setDirectory(simpleDir + layout.getId()); - - layoutDao.update(layout); - - layoutIdByModel.put(colorModel.getSubmodelByConnectionName(connection.getName()), layout.getId()); - } - - if (params.isAsync()) { - if (customSession) { - dbUtils.closeSessionForCurrentThread(); - } else { - layoutDao.commit(); - } - } - final int layoutId = layoutIdByModel.get(colorModel); - - Thread computations = new Thread(new Runnable() { - @Override - public void run() { - if (params.isAsync()) { - // open transaction for this thread - dbUtils.createSessionForCurrentThread(); - } - - try { - MapGenerator generator = new MapGenerator(); - final int models = layoutIdByModel.entrySet().size(); - - int count = 0; - for (Model m : layoutIdByModel.keySet()) { - final int counted = count; - int id = layoutIdByModel.get(m); - MapGeneratorParams imgParams = generator.new MapGeneratorParams().model(m) - .directory(params.getDirectory() + id).updater(new IProgressUpdater() { - private int lastProgress = -1; - - @Override - public void setProgress(double progress) { - progress = progress / ((double) models) + ((double) counted * MAX_PROGRESS) / ((double) models); - if (((int) progress) != lastProgress) { - Layout layout = layoutDao.getById(layoutId); - lastProgress = (int) progress; - layout.setProgress(progress); - layout.setStatus(LayoutStatus.GENERATING); - layoutDao.update(layout); - if (params.isAsync()) { - layoutDao.commit(); - } - } - } - }); - imgParams.sbgn(params.getModel().getProject().isSbgnFormat()); - generator.generateMapImages(imgParams); - count++; - } - Layout layout = layoutDao.getById(layoutId); - layout.setProgress(IProgressUpdater.MAX_PROGRESS); - layout.setStatus(LayoutStatus.OK); - layoutDao.update(layout); - if (params.isAsync()) { - layoutDao.commit(); - } - - try { - sendSuccessfullGenerationEmail(params, schemas); - } catch (MessagingException e) { - logger.error("Problem with sending email", e); - } - logService - .log(new LogParams().object(layout).description("Created successfully.").type(LogType.LAYOUT_CREATED)); - } catch (Exception e) { - logger.error("Problem with creating layout", e); - - Layout layout = layoutDao.getById(layoutId); - layout.setProgress(IProgressUpdater.MAX_PROGRESS); - layout.setStatus(LayoutStatus.FAILURE); - layoutDao.update(layout); - - try { - emailSender.sendEmail("MapViewer status", "There was a problem with generating layout " + params.getName() - + ". Contact administrator to solve this issue.", params.getUser()); - } catch (MessagingException e1) { - logger.error("Problem with sending email", e1); - } - logService - .log(new LogParams().object(layout).description("Problem during creation.").type(LogType.LAYOUT_CREATED)); - } - if (params.isAsync()) { - // close the transaction for this thread - dbUtils.closeSessionForCurrentThread(); - } - } - }); - - if (params.isAsync()) { - computations.start(); - } else { - computations.run(); - } - return topLayout; - } - - @Override - public Layout createLayout(final CreateLayoutParams params) throws IOException, InvalidColorSchemaException { - ColorSchemaReader reader = new ColorSchemaReader(); - final Collection<ColorSchema> schemas = reader.readColorSchema(params.getColorInputStream(), - TextFileUtils.getHeaderParametersFromFile(params.getColorInputStream())); - - // check if we can color our model using this schema, - // if not then exception will be thrown and passed up - try { - Model copy = new CopyCommand(params.getModel()).execute(); - new ColorModelCommand(copy, schemas, userService.getColorExtractorForUser(params.getUser())).execute(); - } catch (CommandExecutionException e) { - throw new InvalidColorSchemaException(e); - } - - String[] tmp = params.getDirectory().split("[\\\\/]"); - String simpleDir = tmp[tmp.length - 1]; - - layoutDao.flush(); - - Layout topLayout = new Layout(params.getName(), simpleDir, false); - if (params.getUser() == null) { - topLayout.setPublicLayout(true); - } else { - topLayout.setPublicLayout(false); - } - topLayout.setStatus(LayoutStatus.OK); - topLayout.setProgress(0.0); - UploadedFileEntry fileEntry = new UploadedFileEntry(); - fileEntry.setFileContent(IOUtils.toByteArray(params.getColorInputStream())); - fileEntry.setOriginalFileName(params.getLayoutFileName()); - fileEntry.setLength(fileEntry.getFileContent().length); - fileEntry.setOwner(params.getUser()); - topLayout.setInputData(fileEntry); - topLayout.setDescription(params.getDescription()); - params.getModel().addLayout(topLayout); - topLayout.setCreator(params.getUser()); - layoutDao.add(topLayout); - topLayout.setDirectory(simpleDir + topLayout.getId()); - - layoutDao.update(topLayout); - - for (ModelSubmodelConnection connection : params.getModel().getSubmodelConnections()) { - Layout layout = new Layout(params.getName(), simpleDir, false); - layout.setStatus(LayoutStatus.OK); - layout.setProgress(0.0); - connection.getSubmodel().getModel().addLayout(layout); - layout.setCreator(params.getUser()); - layout.setPublicLayout(false); - topLayout.addLayout(layout); - layoutDao.add(layout); - layout.setDirectory(simpleDir + layout.getId()); - - layoutDao.update(layout); - } - - Thread computations = new Thread(new Runnable() { - @Override - public void run() { - try { - // open transaction for this thread - dbUtils.createSessionForCurrentThread(); - sendSuccessfullGenerationEmail(params, schemas); - } catch (MessagingException e) { - logger.error("Problem with sending email", e); - } finally { - dbUtils.closeSessionForCurrentThread(); - } - } - }); - - LogParams logParams = new LogParams().object(topLayout).description("Created successfully.") - .type(LogType.LAYOUT_CREATED); - logService.log(logParams); - - computations.start(); - return topLayout; - } - - /** - * @return the configurationService - * @see #configurationService - */ - public IConfigurationService getConfigurationService() { - return configurationService; - } - - /** - * @param configurationService - * the configurationService to set - * @see #configurationService - */ - public void setConfigurationService(IConfigurationService configurationService) { - this.configurationService = configurationService; - } - - /** - * @return the dbUtils - * @see #dbUtils - */ - public DbUtils getDbUtils() { - return dbUtils; - } - - /** - * @param dbUtils - * the dbUtils to set - * @see #dbUtils - */ - public void setDbUtils(DbUtils dbUtils) { - this.dbUtils = dbUtils; - } - - /** - * @return the userService - * @see #userService - */ - public IUserService getUserService() { - return userService; - } - - /** - * @param userService - * the userService to set - * @see #userService - */ - public void setUserService(IUserService userService) { - this.userService = userService; - } - - /** - * Sends notification email that layout was removed. - * - * @param projectId - * identifier of the project - * @param layoutName - * name of the layout - * @param email - * notification email - * @throws MessagingException - * thrown when there is a problem with sending email - */ - protected void sendSuccesfullRemoveEmail(String projectId, String layoutName, String email) - throws MessagingException { - StringBuilder content = new StringBuilder( - "Layout " + layoutName + " in map " + projectId + " was successfully removed.<br/>"); - emailSender.sendEmail("MapViewer notification", content.toString(), email); - } - - /** - * Sends notification email that layout was generated. - * - * @param params - * list of {@link CreateLayoutParams params} used for layout creation - * @param schemas - * set of schemas used in coloring - * @throws MessagingException - * thrown when there is a problem with sending email - */ - protected void sendSuccessfullGenerationEmail(final CreateLayoutParams params, final Collection<ColorSchema> schemas) - throws MessagingException { - StringBuilder content = new StringBuilder("Layout " + params.getName() + " generated successfully.\n"); - content.append("Result of coloring:<br/>"); - - String table = prepareTableResult(schemas, new ColorSchemaReader()); - content.append(table); - String email = params.getModel().getProject().getNotifyEmail(); - if (params.getUser() != null) { // send email to a user who owns the layout - emailSender.sendEmail("MapViewer notification", content.toString(), params.getUser()); - - // send email to the model owner - if (email != null && !email.equals(params.getUser().getEmail())) { - content = new StringBuilder(""); - String username = "UNKNOWN"; - if (params.getUser() != null) { - username = params.getUser().getLogin(); - } - content.append( - "User " + username + " created layout in " + params.getModel().getProject().getProjectId() + " map."); - - emailSender.sendEmail("MapViewer notification", content.toString(), email); - } - } else if (email != null) { - // if nobody owns the layout then send it to the model owner - emailSender.sendEmail("MapViewer notification", content.toString(), email); - } - } - - /** - * Prepares table with statistics about coloring. - * - * @param schemas - * schemas that were used for coloring - * @param scr - * interface that returns list of columns that should be printed - * @return table with statistics about coloring - */ - protected String prepareTableResult(Collection<ColorSchema> schemas, ColorSchemaReader scr) { - StringBuilder sb = new StringBuilder(""); - - Collection<ColorSchemaColumn> columns = scr.getSetColorSchemaColumns(schemas); - - for (ColorSchemaColumn column : ColorSchemaColumn.values()) { - if (columns.contains(column)) { - sb.append(column.getTitle() + "\t"); - } - } - sb.append("matches<br/>\n"); - - for (ColorSchema originalSchema : schemas) { - for (ColorSchema schema : splitColorSchema(originalSchema)) { - sb.append(prepareTableRow(columns, schema)); - } - } - - return sb.toString(); - } - - /** - * {@link ColorSchema} sometimes contains merged value from few rows. This - * method split single {@link ColorSchema} object to simple flat objects that - * can be serialiazed in a simple tab separated file. - * - * @param originalSchema - * original {@link ColorSchema} objcet that we want to spli into flat - * objects - * @return {@link List} of flat schema objects obtained from input - */ - private List<ColorSchema> splitColorSchema(ColorSchema originalSchema) { - List<ColorSchema> result = new ArrayList<>(); - if (originalSchema instanceof GenericColorSchema) { - result.add(originalSchema); - } else if (originalSchema instanceof GeneVariationColorSchema) { - for (GeneVariation gv : ((GeneVariationColorSchema) originalSchema).getGeneVariations()) { - GeneVariationColorSchema copy = (GeneVariationColorSchema) originalSchema.copy(); - copy.getGeneVariations().clear(); - copy.addGeneVariation(gv.copy()); - result.add(copy); - } - } else { - throw new InvalidArgumentException("Unknown class type: " + originalSchema.getClass()); - } - return result; - } - - /** - * Prepares tab separated {@link String} represenation of {@link ColorSchema}. - * - * @param columns - * columns that should be outputed in the result String - * @param schema - * input ColorSchema that is going to be transformed into String - * representation - * @return tab separated {@link String} represenation of {@link ColorSchema} - */ - protected String prepareTableRow(Collection<ColorSchemaColumn> columns, ColorSchema schema) { - StringBuilder sb = new StringBuilder(); - for (ColorSchemaColumn column : ColorSchemaColumn.values()) { - if (columns.contains(column)) { - if (schema instanceof GenericColorSchema) { - sb.append(prepareTableCellForGenericSchema((GenericColorSchema) schema, column)); - } else if (schema instanceof GeneVariationColorSchema) { - sb.append(prepareTableCellForGeneVariationSchema((GeneVariationColorSchema) schema, column)); - } else { - throw new InvalidArgumentException("Unknown schema type: " + schema.getClass()); - } - } - } - sb.append(schema.getMatches() + "<br/>\n"); - return sb.toString(); - } - - /** - * Returns String representing data of {@link GenericColorSchema} that should - * appear in a given {@link ColorSchemaColumn}. - * - * @param schema - * object for which data will be returned - * @param column - * column for which data should be returned - * @return {@link String} representing data of {@link GenericColorSchema} that - * should appear in a given {@link ColorSchemaColumn} - */ - protected String prepareTableCellForGenericSchema(GenericColorSchema schema, ColorSchemaColumn column) { - StringBuilder sb = new StringBuilder(); - if (column.equals(ColorSchemaColumn.COLOR)) { - if (schema.getColor() != null) { - sb.append("#" + Integer.toHexString(schema.getColor().getRGB()).substring(2).toUpperCase()); - } - sb.append("\t"); - } else if (column.equals(ColorSchemaColumn.NAME)) { - sb.append(schema.getName() + "\t"); - } else if (column.equals(ColorSchemaColumn.MODEL_NAME)) { - sb.append(schema.getModelName() + "\t"); - } else if (column.equals(ColorSchemaColumn.VALUE)) { - sb.append(schema.getValue() + "\t"); - } else if (column.equals(ColorSchemaColumn.COMPARTMENT)) { - for (String str : schema.getCompartments()) { - sb.append(str + ", "); - } - sb.append("\t"); - } else if (column.equals(ColorSchemaColumn.TYPE)) { - for (Class<? extends Element> str : schema.getTypes()) { - sb.append(str.getSimpleName() + ", "); - } - sb.append("\t"); - } else if (column.equals(ColorSchemaColumn.IDENTIFIER)) { - for (MiriamData md : schema.getMiriamData()) { - sb.append(md.getDataType().getCommonName() + ": " + md.getResource() + ", "); - } - sb.append("\t"); - } else if (column.equals(ColorSchemaColumn.ELEMENT_IDENTIFIER)) { - sb.append(schema.getElementId() + "\t"); - } else if (column.equals(ColorSchemaColumn.LINE_WIDTH)) { - sb.append(schema.getLineWidth() + "\t"); - } else if (column.equals(ColorSchemaColumn.REVERSE_REACTION)) { - sb.append(schema.getReverseReaction() + "\t"); - } else if (column.equals(ColorSchemaColumn.POSITION)) { - sb.append(schema.getReverseReaction() + "\t"); - } else if (column.equals(ColorSchemaColumn.DESCRIPTION)) { - sb.append(schema.getDescription() + "\t"); - } else { - throw new InvalidArgumentException("Unknown column type: " + column + " for schema type: " + schema.getClass()); - } - return sb.toString(); - } - - /** - * Returns String representing data of {@link GeneVariationColorSchema} that - * should appear in a given {@link ColorSchemaColumn}. - * - * @param schema - * object for which data will be returned - * @param column - * column for which data should be returned - * @return {@link String} representing data of {@link GeneVariationColorSchema} - * that should appear in a given {@link ColorSchemaColumn} - */ - protected String prepareTableCellForGeneVariationSchema(GeneVariationColorSchema schema, ColorSchemaColumn column) { - StringBuilder sb = new StringBuilder(); - if (column.equals(ColorSchemaColumn.COLOR)) { - if (schema.getColor() != null) { - sb.append("#" + Integer.toHexString(schema.getColor().getRGB()).substring(2).toUpperCase()); - } - sb.append("\t"); - } else if (column.equals(ColorSchemaColumn.NAME)) { - sb.append(schema.getName() + "\t"); - } else if (column.equals(ColorSchemaColumn.MODEL_NAME)) { - sb.append(schema.getModelName() + "\t"); - } else if (column.equals(ColorSchemaColumn.VALUE)) { - sb.append(schema.getValue() + "\t"); - } else if (column.equals(ColorSchemaColumn.COMPARTMENT)) { - for (String str : schema.getCompartments()) { - sb.append(str + ", "); - } - sb.append("\t"); - } else if (column.equals(ColorSchemaColumn.TYPE)) { - for (Class<? extends Element> str : schema.getTypes()) { - sb.append(str.getSimpleName() + ", "); - } - sb.append("\t"); - } else if (column.equals(ColorSchemaColumn.IDENTIFIER)) { - for (MiriamData md : schema.getMiriamData()) { - sb.append(md.getDataType().getCommonName() + ": " + md.getResource() + ", "); - } - sb.append("\t"); - } else if (column.equals(ColorSchemaColumn.ELEMENT_IDENTIFIER)) { - sb.append(schema.getElementId() + "\t"); - } else if (column.equals(ColorSchemaColumn.LINE_WIDTH)) { - sb.append(schema.getLineWidth() + "\t"); - } else if (column.equals(ColorSchemaColumn.REVERSE_REACTION)) { - sb.append(schema.getReverseReaction() + "\t"); - } else if (column.equals(ColorSchemaColumn.POSITION)) { - sb.append(schema.getGeneVariations().get(0).getPosition() + "\t"); - } else if (column.equals(ColorSchemaColumn.DESCRIPTION)) { - sb.append(schema.getDescription() + "\t"); - } else if (column.equals(ColorSchemaColumn.ORIGINAL_DNA)) { - sb.append(schema.getGeneVariations().get(0).getOriginalDna() + "\t"); - } else if (column.equals(ColorSchemaColumn.ALTERNATIVE_DNA)) { - sb.append(schema.getGeneVariations().get(0).getModifiedDna() + "\t"); - } else if (column.equals(ColorSchemaColumn.REFERENCE_GENOME_TYPE)) { - sb.append(schema.getGeneVariations().get(0).getReferenceGenomeType() + "\t"); - } else if (column.equals(ColorSchemaColumn.REFERENCE_GENOME_VERSION)) { - sb.append(schema.getGeneVariations().get(0).getReferenceGenomeVersion() + "\t"); - } else if (column.equals(ColorSchemaColumn.CONTIG)) { - sb.append(schema.getGeneVariations().get(0).getContig() + "\t"); - } else if (column.equals(ColorSchemaColumn.REFERENCES)) { - MiriamConnector mc = new MiriamConnector(); - for (MiriamData md : schema.getGeneVariations().get(0).getReferences()) { - sb.append(mc.miriamDataToUri(md) + "\t"); - } - } else { - throw new InvalidArgumentException("Unknown column type: " + column + " for schema type: " + schema.getClass()); - } - return sb.toString(); - } - - /** - * Returns byte array containing data from original input file that was used to - * generate the layout. - * - * @param layoutId - * identifier of layout for which we want to retrieve original file - * data - * @return original data file for given layout, if such file is not stored in - * database (compatibility reasons) then null is returned - * @throws SecurityException - */ - private byte[] getInputDataForLayout(int layoutId, String token) throws SecurityException { - Layout layout = getLayoutById(layoutId, token); - if (layout == null) { - return null; - } else { - if (layout.getInputData() != null) { - return layout.getInputData().getFileContent(); - } else { - return null; - } - } - } - - @Override - public List<LightLayoutAliasView> getAliasesForLayout(Model model, int layoutId, String token) - throws SecurityException { - try { - ColorSchemaReader reader = new ColorSchemaReader(); - Collection<ColorSchema> schemas = reader.readColorSchema(getInputDataForLayout(layoutId, token)); - // colors here are not important - ColorModelCommand command = new ColorModelCommand(model, schemas, - new ColorExtractor(Color.BLACK, Color.BLACK, Color.BLACK)); - LightLayoutAliasViewFactory factory = new LightLayoutAliasViewFactory(); - List<LightLayoutAliasView> result = new ArrayList<>(); - for (Map.Entry<Object, ColorSchema> entry : command.getModifiedElements().entrySet()) { - if (entry.getKey() instanceof Element) { - result.add(factory.create(new Pair<Element, ColorSchema>((Element) entry.getKey(), entry.getValue()))); - } - } - return result; - } catch (InvalidColorSchemaException e) { - throw new InvalidStateException(e); - } catch (IOException e) { - throw new InvalidStateException(e); - } - } - - @Override - public List<LightLayoutReactionView> getReactionsForLayout(Model model, int layoutId, String token) - throws SecurityException { - try { - ColorSchemaReader reader = new ColorSchemaReader(); - Collection<ColorSchema> schemas = reader.readColorSchema(getInputDataForLayout(layoutId, token)); - // colors here are not important - ColorModelCommand command = new ColorModelCommand(model, schemas, - new ColorExtractor(Color.BLACK, Color.BLACK, Color.BLACK)); - LightLayoutReactionViewFactory factory = new LightLayoutReactionViewFactory(); - List<LightLayoutReactionView> result = new ArrayList<>(); - for (Map.Entry<Object, ColorSchema> entry : command.getModifiedElements().entrySet()) { - if (entry.getKey() instanceof Reaction) { - result.add(factory.create(new Pair<Reaction, ColorSchema>((Reaction) entry.getKey(), entry.getValue()))); - } - } - return result; - } catch (InvalidColorSchemaException e) { - throw new InvalidStateException(e); - } catch (IOException e) { - throw new InvalidStateException(e); - } - } - - @Override - public Map<Object, ColorSchema> getElementsForLayout(Model model, Integer layoutId, String token) - throws SecurityException { - try { - ColorSchemaReader reader = new ColorSchemaReader(); - Collection<ColorSchema> schemas; - schemas = reader.readColorSchema(getInputDataForLayout(layoutId, token)); - // colors here are not important - ColorModelCommand command = new ColorModelCommand(model, schemas, - new ColorExtractor(Color.BLACK, Color.BLACK, Color.BLACK)); - return command.getModifiedElements(); - } catch (InvalidColorSchemaException e) { - throw new InvalidStateException(e); - } catch (IOException e) { - throw new InvalidStateException(e); - } - } - - @Override - public EmailSender getEmailSender() { - return emailSender; - } - - @Override - public void setEmailSender(EmailSender emailSender) { - this.emailSender = emailSender; - } - - @Override - public List<Layout> getCustomLayouts(Model model, String token, Boolean publicOverlay, User creator) - throws SecurityException { - User user = userService.getUserByToken(token); - List<Layout> result = new ArrayList<>(); - if (model == null || user == null) { - return result; - } - List<Layout> overlays = layoutDao.getLayoutsByModel(model, creator, publicOverlay); - for (Layout overlay : overlays) { - if (userCanViewOverlay(overlay, user)) { - result.add(overlay); - } - } - Collections.sort(result, Layout.ID_COMPARATOR); - return result; - } - - @Override - public Layout getLayoutById(int overlayId, String token) throws SecurityException { - Layout layout = layoutDao.getById(overlayId); - if (layout == null) { - return null; - } - User user = userService.getUserByToken(token); - if (!userCanViewOverlay(layout, user)) { - throw new SecurityException("User doesn't have access to overlay"); - } - return layout; - } - - @Override - public FullLayoutAliasView getFullAliasForLayout(Model model, Integer id, int layoutId, String token) - throws SecurityException { - try { - ColorSchemaReader reader = new ColorSchemaReader(); - Collection<ColorSchema> schemas; - schemas = reader.readColorSchema(getInputDataForLayout(layoutId, token)); - // colors here are not important - ColorModelCommand command = new ColorModelCommand(model, schemas, - new ColorExtractor(Color.BLACK, Color.BLACK, Color.BLACK)); - FullLayoutAliasViewFactory factory = new FullLayoutAliasViewFactory(); - - for (Map.Entry<Object, ColorSchema> entry : command.getModifiedElements().entrySet()) { - if (entry.getKey() instanceof Element) { - Element alias = (Element) entry.getKey(); - if (id.equals(alias.getId())) { - return factory.create(new Pair<Element, ColorSchema>(alias, entry.getValue())); - } - } - } - return null; - } catch (InvalidColorSchemaException e) { - throw new InvalidStateException(e); - } catch (IOException e) { - throw new InvalidStateException(e); - } - } - - @Override - public FullLayoutReactionView getFullReactionForLayout(Model model, Integer id, int layoutId, String token) - throws SecurityException { - try { - ColorSchemaReader reader = new ColorSchemaReader(); - Collection<ColorSchema> schemas; - schemas = reader.readColorSchema(getInputDataForLayout(layoutId, token)); - // colors here are not important - ColorModelCommand command = new ColorModelCommand(model, schemas, - new ColorExtractor(Color.BLACK, Color.BLACK, Color.BLACK)); - FullLayoutReactionViewFactory factory = new FullLayoutReactionViewFactory(); - - for (Map.Entry<Object, ColorSchema> entry : command.getModifiedElements().entrySet()) { - if (entry.getKey() instanceof Reaction) { - Reaction alias = (Reaction) entry.getKey(); - if (id.equals(alias.getId())) { - return factory.create(new Pair<Reaction, ColorSchema>(alias, entry.getValue())); - } - } - } - return null; - } catch (InvalidColorSchemaException e) { - throw new InvalidStateException(e); - } catch (IOException e) { - throw new InvalidStateException(e); - } - } - - @Override - public boolean userCanRemoveLayout(Layout layout, String authenticationToken) throws SecurityException { - User user = userService.getUserByToken(authenticationToken); - return userCanRemoveLayout(layout, user); - } -} +package lcsb.mapviewer.services.impl; + +import java.awt.Color; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.mail.MessagingException; + +import org.apache.commons.io.IOUtils; +import org.apache.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import lcsb.mapviewer.annotation.services.MiriamConnector; +import lcsb.mapviewer.commands.ColorExtractor; +import lcsb.mapviewer.commands.ColorModelCommand; +import lcsb.mapviewer.commands.CommandExecutionException; +import lcsb.mapviewer.commands.CopyCommand; +import lcsb.mapviewer.common.IProgressUpdater; +import lcsb.mapviewer.common.Pair; +import lcsb.mapviewer.common.TextFileUtils; +import lcsb.mapviewer.common.exception.InvalidArgumentException; +import lcsb.mapviewer.common.exception.InvalidStateException; +import lcsb.mapviewer.converter.graphics.MapGenerator; +import lcsb.mapviewer.converter.graphics.MapGenerator.MapGeneratorParams; +import lcsb.mapviewer.model.Project; +import lcsb.mapviewer.model.cache.UploadedFileEntry; +import lcsb.mapviewer.model.log.LogType; +import lcsb.mapviewer.model.map.MiriamData; +import lcsb.mapviewer.model.map.layout.ColorSchema; +import lcsb.mapviewer.model.map.layout.GeneVariation; +import lcsb.mapviewer.model.map.layout.GeneVariationColorSchema; +import lcsb.mapviewer.model.map.layout.GenericColorSchema; +import lcsb.mapviewer.model.map.layout.InvalidColorSchemaException; +import lcsb.mapviewer.model.map.layout.Layout; +import lcsb.mapviewer.model.map.layout.LayoutStatus; +import lcsb.mapviewer.model.map.model.Model; +import lcsb.mapviewer.model.map.model.ModelSubmodelConnection; +import lcsb.mapviewer.model.map.reaction.Reaction; +import lcsb.mapviewer.model.map.species.Element; +import lcsb.mapviewer.model.user.ObjectPrivilege; +import lcsb.mapviewer.model.user.PrivilegeType; +import lcsb.mapviewer.model.user.User; +import lcsb.mapviewer.persist.DbUtils; +import lcsb.mapviewer.persist.dao.map.LayoutDao; +import lcsb.mapviewer.services.SecurityException; +import lcsb.mapviewer.services.interfaces.IConfigurationService; +import lcsb.mapviewer.services.interfaces.ILayoutService; +import lcsb.mapviewer.services.interfaces.ILogService; +import lcsb.mapviewer.services.interfaces.ILogService.LogParams; +import lcsb.mapviewer.services.interfaces.IUserService; +import lcsb.mapviewer.services.search.layout.FullLayoutAliasView; +import lcsb.mapviewer.services.search.layout.FullLayoutAliasViewFactory; +import lcsb.mapviewer.services.search.layout.FullLayoutReactionView; +import lcsb.mapviewer.services.search.layout.FullLayoutReactionViewFactory; +import lcsb.mapviewer.services.search.layout.LightLayoutAliasView; +import lcsb.mapviewer.services.search.layout.LightLayoutAliasViewFactory; +import lcsb.mapviewer.services.search.layout.LightLayoutReactionView; +import lcsb.mapviewer.services.search.layout.LightLayoutReactionViewFactory; +import lcsb.mapviewer.services.utils.ColorSchemaReader; +import lcsb.mapviewer.services.utils.EmailSender; +import lcsb.mapviewer.services.utils.data.ColorSchemaColumn; + +/** + * Implementation of the layout service. + * + * @author Piotr Gawron + * + */ +@Transactional(value = "txManager") +public class LayoutService implements ILayoutService { + + /** + * Default class logger. + */ + private Logger logger = Logger.getLogger(LayoutService.class); + + /** + * Layout data access object. + */ + @Autowired + private LayoutDao layoutDao; + + /** + * Service that manages and gives access to user information. + */ + @Autowired + private IUserService userService; + + /** + * Service used to access logs. + * + * @see ILogService + */ + @Autowired + private ILogService logService; + + /** + * Service that manages and gives access to configuration parameters. + */ + @Autowired + private IConfigurationService configurationService; + + /** + * Utility class that helps to manage the sessions in custom multi-threaded + * implementation. + */ + @Autowired + private DbUtils dbUtils; + + /** + * Object that sends emails. + */ + private EmailSender emailSender; + + /** + * Method called after spring initialized all interfaces. + */ + @PostConstruct + public void springInit() { + emailSender = new EmailSender(configurationService); + } + + @Override + public boolean userCanAddLayout(Model model, User user) { + // if we don't have privileges to view the object then we cannot add layouts + if (!userService.userHasPrivilege(user, PrivilegeType.VIEW_PROJECT, model.getProject())) { + return false; + } + long count = getAvailableCustomLayoutsNumber(user); + return count > 0; + } + + @Override + public long getAvailableCustomLayoutsNumber(User user) { + long level = userService.getUserPrivilegeLevel(user, PrivilegeType.CUSTOM_LAYOUTS); + long layouts = layoutDao.getCountByUser(user); + return level - layouts; + + } + + @Override + public boolean userCanRemoveLayout(Layout layout, User user) { + User creator = layout.getCreator(); + Project project = layout.getModel().getProject(); + if (creator == null) { + return userService.userHasPrivilege(user, PrivilegeType.LAYOUT_MANAGEMENT, project); + } else { + return creator.getId().equals(user.getId()) + || userService.userHasPrivilege(user, PrivilegeType.LAYOUT_MANAGEMENT, project); + } + } + + private boolean userCanViewOverlay(Layout overlay, User user) { + if (overlay.isPublicLayout()) { + return true; + } + if (overlay.getCreator() == null) { + return true; + } + if (overlay.getCreator().getLogin().equals(user.getLogin())) { + return true; + } + + if (userService.userHasPrivilege(user, PrivilegeType.LAYOUT_VIEW, overlay)) { + return true; + } + if (userService.userHasPrivilege(user, PrivilegeType.LAYOUT_MANAGEMENT, overlay.getModel().getProject())) { + return true; + } + return false; + } + + @Override + public void removeLayout(Layout layout, final String homeDir) throws IOException { + final String dir; + if (homeDir != null) { + if (layout.getModel().getProject().getDirectory() != null) { + dir = homeDir + "/../map_images/" + layout.getModel().getProject().getDirectory() + "/"; + } else { + dir = homeDir + "/../map_images/"; + } + } else { + dir = null; + } + + String projectId = layout.getModel().getProject().getProjectId(); + final String email; + User user = layout.getCreator(); + if (user != null) { + email = user.getEmail(); + } else { + email = null; + } + + layout.getModel().removeLayout(layout); + layoutDao.delete(layout); + + LogParams params = new LogParams().object(layout).type(LogType.LAYOUT_REMOVED); + logService.log(params); + + if (email != null) { + Thread sendEmailThread = new Thread(new Runnable() { + @Override + public void run() { + try { + sendSuccesfullRemoveEmail(projectId, layout.getTitle(), email); + } catch (MessagingException e) { + logger.error(e); + } + } + }); + sendEmailThread.start(); + } + Thread removeFilesThread = new Thread(new Runnable() { + @Override + public void run() { + MapGenerator generator = new MapGenerator(); + try { + generator.removeLayout(layout, dir); + } catch (IOException e) { + logger.error(e); + } + + } + }); + removeFilesThread.start(); + } + + @Override + public void updateLayout(Layout layout) { + layoutDao.update(layout); + } + + @Override + public void addViewPrivilegeToLayout(Layout layout, User user) { + ObjectPrivilege privilege = new ObjectPrivilege(); + privilege.setIdObject(layout.getId()); + privilege.setLevel(1); + privilege.setType(PrivilegeType.LAYOUT_VIEW); + privilege.setUser(user); + userService.setUserPrivilege(user, privilege); + } + + @Override + public void dropViewPrivilegeFromLayout(Layout layout, User user) { + ObjectPrivilege privilege = new ObjectPrivilege(); + privilege.setIdObject(layout.getId()); + privilege.setLevel(0); + privilege.setType(PrivilegeType.LAYOUT_VIEW); + privilege.setUser(user); + userService.setUserPrivilege(user, privilege); + } + + @Override + public Layout createLayoutWithImages(final CreateLayoutParams params) + throws IOException, InvalidColorSchemaException, CommandExecutionException { + ColorSchemaReader reader = new ColorSchemaReader(); + final Collection<ColorSchema> schemas = reader.readColorSchema(params.getColorInputStream(), + TextFileUtils.getHeaderParametersFromFile(params.getColorInputStream())); + final Model colorModel = new CopyCommand(params.getModel()).execute(); + new ColorModelCommand(params.getModel(), schemas, userService.getColorExtractorForUser(params.getUser())).execute(); + String[] tmp = params.getDirectory().split("[\\\\/]"); + String simpleDir = tmp[tmp.length - 1]; + + layoutDao.flush(); + + boolean customSession = false; + if (params.isAsync()) { + customSession = !dbUtils.isCustomSessionForCurrentThread(); + if (customSession) { + dbUtils.createSessionForCurrentThread(); + } + } + final Map<Model, Integer> layoutIdByModel = new HashMap<>(); + + Layout topLayout = new Layout(params.getName(), simpleDir, false); + if (params.getUser() == null) { + topLayout.setPublicLayout(true); + } else { + topLayout.setPublicLayout(false); + } + topLayout.setStatus(LayoutStatus.NA); + topLayout.setProgress(0.0); + UploadedFileEntry fileEntry = new UploadedFileEntry(); + fileEntry.setFileContent(IOUtils.toByteArray(params.getColorInputStream())); + fileEntry.setOriginalFileName(params.getLayoutFileName()); + fileEntry.setLength(fileEntry.getFileContent().length); + fileEntry.setOwner(params.getUser()); + topLayout.setInputData(fileEntry); + topLayout.setDescription(params.getDescription()); + params.getModel().addLayout(topLayout); + topLayout.setCreator(params.getUser()); + layoutDao.add(topLayout); + topLayout.setDirectory(simpleDir + topLayout.getId()); + + layoutDao.update(topLayout); + + layoutIdByModel.put(colorModel, topLayout.getId()); + + for (ModelSubmodelConnection connection : params.getModel().getSubmodelConnections()) { + Layout layout = new Layout(params.getName(), simpleDir, false); + layout.setStatus(LayoutStatus.NA); + layout.setProgress(0.0); + connection.getSubmodel().getModel().addLayout(layout); + layout.setCreator(params.getUser()); + layout.setPublicLayout(false); + topLayout.addLayout(layout); + layoutDao.add(layout); + layout.setDirectory(simpleDir + layout.getId()); + + layoutDao.update(layout); + + layoutIdByModel.put(colorModel.getSubmodelByConnectionName(connection.getName()), layout.getId()); + } + + if (params.isAsync()) { + if (customSession) { + dbUtils.closeSessionForCurrentThread(); + } else { + layoutDao.commit(); + } + } + final int layoutId = layoutIdByModel.get(colorModel); + + Thread computations = new Thread(new Runnable() { + @Override + public void run() { + if (params.isAsync()) { + // open transaction for this thread + dbUtils.createSessionForCurrentThread(); + } + + try { + MapGenerator generator = new MapGenerator(); + final int models = layoutIdByModel.entrySet().size(); + + int count = 0; + for (Model m : layoutIdByModel.keySet()) { + final int counted = count; + int id = layoutIdByModel.get(m); + MapGeneratorParams imgParams = generator.new MapGeneratorParams().model(m) + .directory(params.getDirectory() + id).updater(new IProgressUpdater() { + private int lastProgress = -1; + + @Override + public void setProgress(double progress) { + progress = progress / ((double) models) + ((double) counted * MAX_PROGRESS) / ((double) models); + if (((int) progress) != lastProgress) { + Layout layout = layoutDao.getById(layoutId); + lastProgress = (int) progress; + layout.setProgress(progress); + layout.setStatus(LayoutStatus.GENERATING); + layoutDao.update(layout); + if (params.isAsync()) { + layoutDao.commit(); + } + } + } + }); + imgParams.sbgn(params.getModel().getProject().isSbgnFormat()); + generator.generateMapImages(imgParams); + count++; + } + Layout layout = layoutDao.getById(layoutId); + layout.setProgress(IProgressUpdater.MAX_PROGRESS); + layout.setStatus(LayoutStatus.OK); + layoutDao.update(layout); + if (params.isAsync()) { + layoutDao.commit(); + } + + try { + sendSuccessfullGenerationEmail(params, schemas); + } catch (MessagingException e) { + logger.error("Problem with sending email", e); + } + logService + .log(new LogParams().object(layout).description("Created successfully.").type(LogType.LAYOUT_CREATED)); + } catch (Exception e) { + logger.error("Problem with creating layout", e); + + Layout layout = layoutDao.getById(layoutId); + layout.setProgress(IProgressUpdater.MAX_PROGRESS); + layout.setStatus(LayoutStatus.FAILURE); + layoutDao.update(layout); + + try { + emailSender.sendEmail("MapViewer status", "There was a problem with generating layout " + params.getName() + + ". Contact administrator to solve this issue.", params.getUser()); + } catch (MessagingException e1) { + logger.error("Problem with sending email", e1); + } + logService + .log(new LogParams().object(layout).description("Problem during creation.").type(LogType.LAYOUT_CREATED)); + } + if (params.isAsync()) { + // close the transaction for this thread + dbUtils.closeSessionForCurrentThread(); + } + } + }); + + if (params.isAsync()) { + computations.start(); + } else { + computations.run(); + } + return topLayout; + } + + @Override + public Layout createLayout(final CreateLayoutParams params) throws IOException, InvalidColorSchemaException { + ColorSchemaReader reader = new ColorSchemaReader(); + final Collection<ColorSchema> schemas = reader.readColorSchema(params.getColorInputStream(), + TextFileUtils.getHeaderParametersFromFile(params.getColorInputStream())); + + // check if we can color our model using this schema, + // if not then exception will be thrown and passed up + try { + Model copy = new CopyCommand(params.getModel()).execute(); + new ColorModelCommand(copy, schemas, userService.getColorExtractorForUser(params.getUser())).execute(); + } catch (CommandExecutionException e) { + throw new InvalidColorSchemaException(e); + } + + String[] tmp = params.getDirectory().split("[\\\\/]"); + String simpleDir = tmp[tmp.length - 1]; + + layoutDao.flush(); + + Layout topLayout = new Layout(params.getName(), simpleDir, false); + if (params.getUser() == null) { + topLayout.setPublicLayout(true); + } else { + topLayout.setPublicLayout(false); + } + topLayout.setGoogleLicenseConsent(params.isGoogleLicenseConsent()); + topLayout.setStatus(LayoutStatus.OK); + topLayout.setProgress(0.0); + UploadedFileEntry fileEntry = new UploadedFileEntry(); + fileEntry.setFileContent(IOUtils.toByteArray(params.getColorInputStream())); + fileEntry.setOriginalFileName(params.getLayoutFileName()); + fileEntry.setLength(fileEntry.getFileContent().length); + fileEntry.setOwner(params.getUser()); + topLayout.setInputData(fileEntry); + topLayout.setDescription(params.getDescription()); + params.getModel().addLayout(topLayout); + topLayout.setCreator(params.getUser()); + layoutDao.add(topLayout); + topLayout.setDirectory(simpleDir + topLayout.getId()); + + layoutDao.update(topLayout); + + for (ModelSubmodelConnection connection : params.getModel().getSubmodelConnections()) { + Layout layout = new Layout(params.getName(), simpleDir, false); + layout.setStatus(LayoutStatus.OK); + layout.setProgress(0.0); + connection.getSubmodel().getModel().addLayout(layout); + layout.setCreator(params.getUser()); + layout.setPublicLayout(false); + topLayout.addLayout(layout); + layoutDao.add(layout); + layout.setDirectory(simpleDir + layout.getId()); + + layoutDao.update(layout); + } + + Thread computations = new Thread(new Runnable() { + @Override + public void run() { + try { + // open transaction for this thread + dbUtils.createSessionForCurrentThread(); + sendSuccessfullGenerationEmail(params, schemas); + } catch (MessagingException e) { + logger.error("Problem with sending email", e); + } finally { + dbUtils.closeSessionForCurrentThread(); + } + } + }); + + LogParams logParams = new LogParams().object(topLayout).description("Created successfully.") + .type(LogType.LAYOUT_CREATED); + logService.log(logParams); + + computations.start(); + return topLayout; + } + + /** + * @return the configurationService + * @see #configurationService + */ + public IConfigurationService getConfigurationService() { + return configurationService; + } + + /** + * @param configurationService + * the configurationService to set + * @see #configurationService + */ + public void setConfigurationService(IConfigurationService configurationService) { + this.configurationService = configurationService; + } + + /** + * @return the dbUtils + * @see #dbUtils + */ + public DbUtils getDbUtils() { + return dbUtils; + } + + /** + * @param dbUtils + * the dbUtils to set + * @see #dbUtils + */ + public void setDbUtils(DbUtils dbUtils) { + this.dbUtils = dbUtils; + } + + /** + * @return the userService + * @see #userService + */ + public IUserService getUserService() { + return userService; + } + + /** + * @param userService + * the userService to set + * @see #userService + */ + public void setUserService(IUserService userService) { + this.userService = userService; + } + + /** + * Sends notification email that layout was removed. + * + * @param projectId + * identifier of the project + * @param layoutName + * name of the layout + * @param email + * notification email + * @throws MessagingException + * thrown when there is a problem with sending email + */ + protected void sendSuccesfullRemoveEmail(String projectId, String layoutName, String email) + throws MessagingException { + StringBuilder content = new StringBuilder( + "Layout " + layoutName + " in map " + projectId + " was successfully removed.<br/>"); + emailSender.sendEmail("MapViewer notification", content.toString(), email); + } + + /** + * Sends notification email that layout was generated. + * + * @param params + * list of {@link CreateLayoutParams params} used for layout creation + * @param schemas + * set of schemas used in coloring + * @throws MessagingException + * thrown when there is a problem with sending email + */ + protected void sendSuccessfullGenerationEmail(final CreateLayoutParams params, final Collection<ColorSchema> schemas) + throws MessagingException { + StringBuilder content = new StringBuilder("Layout " + params.getName() + " generated successfully.\n"); + content.append("Result of coloring:<br/>"); + + String table = prepareTableResult(schemas, new ColorSchemaReader()); + content.append(table); + String email = params.getModel().getProject().getNotifyEmail(); + if (params.getUser() != null) { // send email to a user who owns the layout + emailSender.sendEmail("MapViewer notification", content.toString(), params.getUser()); + + // send email to the model owner + if (email != null && !email.equals(params.getUser().getEmail())) { + content = new StringBuilder(""); + String username = "UNKNOWN"; + if (params.getUser() != null) { + username = params.getUser().getLogin(); + } + content.append( + "User " + username + " created layout in " + params.getModel().getProject().getProjectId() + " map."); + + emailSender.sendEmail("MapViewer notification", content.toString(), email); + } + } else if (email != null) { + // if nobody owns the layout then send it to the model owner + emailSender.sendEmail("MapViewer notification", content.toString(), email); + } + } + + /** + * Prepares table with statistics about coloring. + * + * @param schemas + * schemas that were used for coloring + * @param scr + * interface that returns list of columns that should be printed + * @return table with statistics about coloring + */ + protected String prepareTableResult(Collection<ColorSchema> schemas, ColorSchemaReader scr) { + StringBuilder sb = new StringBuilder(""); + + Collection<ColorSchemaColumn> columns = scr.getSetColorSchemaColumns(schemas); + + for (ColorSchemaColumn column : ColorSchemaColumn.values()) { + if (columns.contains(column)) { + sb.append(column.getTitle() + "\t"); + } + } + sb.append("matches<br/>\n"); + + for (ColorSchema originalSchema : schemas) { + for (ColorSchema schema : splitColorSchema(originalSchema)) { + sb.append(prepareTableRow(columns, schema)); + } + } + + return sb.toString(); + } + + /** + * {@link ColorSchema} sometimes contains merged value from few rows. This + * method split single {@link ColorSchema} object to simple flat objects that + * can be serialiazed in a simple tab separated file. + * + * @param originalSchema + * original {@link ColorSchema} objcet that we want to spli into flat + * objects + * @return {@link List} of flat schema objects obtained from input + */ + private List<ColorSchema> splitColorSchema(ColorSchema originalSchema) { + List<ColorSchema> result = new ArrayList<>(); + if (originalSchema instanceof GenericColorSchema) { + result.add(originalSchema); + } else if (originalSchema instanceof GeneVariationColorSchema) { + for (GeneVariation gv : ((GeneVariationColorSchema) originalSchema).getGeneVariations()) { + GeneVariationColorSchema copy = (GeneVariationColorSchema) originalSchema.copy(); + copy.getGeneVariations().clear(); + copy.addGeneVariation(gv.copy()); + result.add(copy); + } + } else { + throw new InvalidArgumentException("Unknown class type: " + originalSchema.getClass()); + } + return result; + } + + /** + * Prepares tab separated {@link String} represenation of {@link ColorSchema}. + * + * @param columns + * columns that should be outputed in the result String + * @param schema + * input ColorSchema that is going to be transformed into String + * representation + * @return tab separated {@link String} represenation of {@link ColorSchema} + */ + protected String prepareTableRow(Collection<ColorSchemaColumn> columns, ColorSchema schema) { + StringBuilder sb = new StringBuilder(); + for (ColorSchemaColumn column : ColorSchemaColumn.values()) { + if (columns.contains(column)) { + if (schema instanceof GenericColorSchema) { + sb.append(prepareTableCellForGenericSchema((GenericColorSchema) schema, column)); + } else if (schema instanceof GeneVariationColorSchema) { + sb.append(prepareTableCellForGeneVariationSchema((GeneVariationColorSchema) schema, column)); + } else { + throw new InvalidArgumentException("Unknown schema type: " + schema.getClass()); + } + } + } + sb.append(schema.getMatches() + "<br/>\n"); + return sb.toString(); + } + + /** + * Returns String representing data of {@link GenericColorSchema} that should + * appear in a given {@link ColorSchemaColumn}. + * + * @param schema + * object for which data will be returned + * @param column + * column for which data should be returned + * @return {@link String} representing data of {@link GenericColorSchema} that + * should appear in a given {@link ColorSchemaColumn} + */ + protected String prepareTableCellForGenericSchema(GenericColorSchema schema, ColorSchemaColumn column) { + StringBuilder sb = new StringBuilder(); + if (column.equals(ColorSchemaColumn.COLOR)) { + if (schema.getColor() != null) { + sb.append("#" + Integer.toHexString(schema.getColor().getRGB()).substring(2).toUpperCase()); + } + sb.append("\t"); + } else if (column.equals(ColorSchemaColumn.NAME)) { + sb.append(schema.getName() + "\t"); + } else if (column.equals(ColorSchemaColumn.MODEL_NAME)) { + sb.append(schema.getModelName() + "\t"); + } else if (column.equals(ColorSchemaColumn.VALUE)) { + sb.append(schema.getValue() + "\t"); + } else if (column.equals(ColorSchemaColumn.COMPARTMENT)) { + for (String str : schema.getCompartments()) { + sb.append(str + ", "); + } + sb.append("\t"); + } else if (column.equals(ColorSchemaColumn.TYPE)) { + for (Class<? extends Element> str : schema.getTypes()) { + sb.append(str.getSimpleName() + ", "); + } + sb.append("\t"); + } else if (column.equals(ColorSchemaColumn.IDENTIFIER)) { + for (MiriamData md : schema.getMiriamData()) { + sb.append(md.getDataType().getCommonName() + ": " + md.getResource() + ", "); + } + sb.append("\t"); + } else if (column.equals(ColorSchemaColumn.ELEMENT_IDENTIFIER)) { + sb.append(schema.getElementId() + "\t"); + } else if (column.equals(ColorSchemaColumn.LINE_WIDTH)) { + sb.append(schema.getLineWidth() + "\t"); + } else if (column.equals(ColorSchemaColumn.REVERSE_REACTION)) { + sb.append(schema.getReverseReaction() + "\t"); + } else if (column.equals(ColorSchemaColumn.POSITION)) { + sb.append(schema.getReverseReaction() + "\t"); + } else if (column.equals(ColorSchemaColumn.DESCRIPTION)) { + sb.append(schema.getDescription() + "\t"); + } else { + throw new InvalidArgumentException("Unknown column type: " + column + " for schema type: " + schema.getClass()); + } + return sb.toString(); + } + + /** + * Returns String representing data of {@link GeneVariationColorSchema} that + * should appear in a given {@link ColorSchemaColumn}. + * + * @param schema + * object for which data will be returned + * @param column + * column for which data should be returned + * @return {@link String} representing data of {@link GeneVariationColorSchema} + * that should appear in a given {@link ColorSchemaColumn} + */ + protected String prepareTableCellForGeneVariationSchema(GeneVariationColorSchema schema, ColorSchemaColumn column) { + StringBuilder sb = new StringBuilder(); + if (column.equals(ColorSchemaColumn.COLOR)) { + if (schema.getColor() != null) { + sb.append("#" + Integer.toHexString(schema.getColor().getRGB()).substring(2).toUpperCase()); + } + sb.append("\t"); + } else if (column.equals(ColorSchemaColumn.NAME)) { + sb.append(schema.getName() + "\t"); + } else if (column.equals(ColorSchemaColumn.MODEL_NAME)) { + sb.append(schema.getModelName() + "\t"); + } else if (column.equals(ColorSchemaColumn.VALUE)) { + sb.append(schema.getValue() + "\t"); + } else if (column.equals(ColorSchemaColumn.COMPARTMENT)) { + for (String str : schema.getCompartments()) { + sb.append(str + ", "); + } + sb.append("\t"); + } else if (column.equals(ColorSchemaColumn.TYPE)) { + for (Class<? extends Element> str : schema.getTypes()) { + sb.append(str.getSimpleName() + ", "); + } + sb.append("\t"); + } else if (column.equals(ColorSchemaColumn.IDENTIFIER)) { + for (MiriamData md : schema.getMiriamData()) { + sb.append(md.getDataType().getCommonName() + ": " + md.getResource() + ", "); + } + sb.append("\t"); + } else if (column.equals(ColorSchemaColumn.ELEMENT_IDENTIFIER)) { + sb.append(schema.getElementId() + "\t"); + } else if (column.equals(ColorSchemaColumn.LINE_WIDTH)) { + sb.append(schema.getLineWidth() + "\t"); + } else if (column.equals(ColorSchemaColumn.REVERSE_REACTION)) { + sb.append(schema.getReverseReaction() + "\t"); + } else if (column.equals(ColorSchemaColumn.POSITION)) { + sb.append(schema.getGeneVariations().get(0).getPosition() + "\t"); + } else if (column.equals(ColorSchemaColumn.DESCRIPTION)) { + sb.append(schema.getDescription() + "\t"); + } else if (column.equals(ColorSchemaColumn.ORIGINAL_DNA)) { + sb.append(schema.getGeneVariations().get(0).getOriginalDna() + "\t"); + } else if (column.equals(ColorSchemaColumn.ALTERNATIVE_DNA)) { + sb.append(schema.getGeneVariations().get(0).getModifiedDna() + "\t"); + } else if (column.equals(ColorSchemaColumn.REFERENCE_GENOME_TYPE)) { + sb.append(schema.getGeneVariations().get(0).getReferenceGenomeType() + "\t"); + } else if (column.equals(ColorSchemaColumn.REFERENCE_GENOME_VERSION)) { + sb.append(schema.getGeneVariations().get(0).getReferenceGenomeVersion() + "\t"); + } else if (column.equals(ColorSchemaColumn.CONTIG)) { + sb.append(schema.getGeneVariations().get(0).getContig() + "\t"); + } else if (column.equals(ColorSchemaColumn.REFERENCES)) { + MiriamConnector mc = new MiriamConnector(); + for (MiriamData md : schema.getGeneVariations().get(0).getReferences()) { + sb.append(mc.miriamDataToUri(md) + "\t"); + } + } else { + throw new InvalidArgumentException("Unknown column type: " + column + " for schema type: " + schema.getClass()); + } + return sb.toString(); + } + + /** + * Returns byte array containing data from original input file that was used to + * generate the layout. + * + * @param layoutId + * identifier of layout for which we want to retrieve original file + * data + * @return original data file for given layout, if such file is not stored in + * database (compatibility reasons) then null is returned + * @throws SecurityException + */ + private byte[] getInputDataForLayout(int layoutId, String token) throws SecurityException { + Layout layout = getLayoutById(layoutId, token); + if (layout == null) { + return null; + } else { + if (layout.getInputData() != null) { + return layout.getInputData().getFileContent(); + } else { + return null; + } + } + } + + @Override + public List<LightLayoutAliasView> getAliasesForLayout(Model model, int layoutId, String token) + throws SecurityException { + try { + ColorSchemaReader reader = new ColorSchemaReader(); + Collection<ColorSchema> schemas = reader.readColorSchema(getInputDataForLayout(layoutId, token)); + // colors here are not important + ColorModelCommand command = new ColorModelCommand(model, schemas, + new ColorExtractor(Color.BLACK, Color.BLACK, Color.BLACK)); + LightLayoutAliasViewFactory factory = new LightLayoutAliasViewFactory(); + List<LightLayoutAliasView> result = new ArrayList<>(); + for (Map.Entry<Object, ColorSchema> entry : command.getModifiedElements().entrySet()) { + if (entry.getKey() instanceof Element) { + result.add(factory.create(new Pair<Element, ColorSchema>((Element) entry.getKey(), entry.getValue()))); + } + } + return result; + } catch (InvalidColorSchemaException e) { + throw new InvalidStateException(e); + } catch (IOException e) { + throw new InvalidStateException(e); + } + } + + @Override + public List<LightLayoutReactionView> getReactionsForLayout(Model model, int layoutId, String token) + throws SecurityException { + try { + ColorSchemaReader reader = new ColorSchemaReader(); + Collection<ColorSchema> schemas = reader.readColorSchema(getInputDataForLayout(layoutId, token)); + // colors here are not important + ColorModelCommand command = new ColorModelCommand(model, schemas, + new ColorExtractor(Color.BLACK, Color.BLACK, Color.BLACK)); + LightLayoutReactionViewFactory factory = new LightLayoutReactionViewFactory(); + List<LightLayoutReactionView> result = new ArrayList<>(); + for (Map.Entry<Object, ColorSchema> entry : command.getModifiedElements().entrySet()) { + if (entry.getKey() instanceof Reaction) { + result.add(factory.create(new Pair<Reaction, ColorSchema>((Reaction) entry.getKey(), entry.getValue()))); + } + } + return result; + } catch (InvalidColorSchemaException e) { + throw new InvalidStateException(e); + } catch (IOException e) { + throw new InvalidStateException(e); + } + } + + @Override + public Map<Object, ColorSchema> getElementsForLayout(Model model, Integer layoutId, String token) + throws SecurityException { + try { + ColorSchemaReader reader = new ColorSchemaReader(); + Collection<ColorSchema> schemas; + schemas = reader.readColorSchema(getInputDataForLayout(layoutId, token)); + // colors here are not important + ColorModelCommand command = new ColorModelCommand(model, schemas, + new ColorExtractor(Color.BLACK, Color.BLACK, Color.BLACK)); + return command.getModifiedElements(); + } catch (InvalidColorSchemaException e) { + throw new InvalidStateException(e); + } catch (IOException e) { + throw new InvalidStateException(e); + } + } + + @Override + public EmailSender getEmailSender() { + return emailSender; + } + + @Override + public void setEmailSender(EmailSender emailSender) { + this.emailSender = emailSender; + } + + @Override + public List<Layout> getCustomLayouts(Model model, String token, Boolean publicOverlay, User creator) + throws SecurityException { + User user = userService.getUserByToken(token); + List<Layout> result = new ArrayList<>(); + if (model == null || user == null) { + return result; + } + List<Layout> overlays = layoutDao.getLayoutsByModel(model, creator, publicOverlay); + for (Layout overlay : overlays) { + if (userCanViewOverlay(overlay, user)) { + result.add(overlay); + } + } + Collections.sort(result, Layout.ID_COMPARATOR); + return result; + } + + @Override + public Layout getLayoutById(int overlayId, String token) throws SecurityException { + Layout layout = layoutDao.getById(overlayId); + if (layout == null) { + return null; + } + User user = userService.getUserByToken(token); + if (!userCanViewOverlay(layout, user)) { + throw new SecurityException("User doesn't have access to overlay"); + } + return layout; + } + + @Override + public FullLayoutAliasView getFullAliasForLayout(Model model, Integer id, int layoutId, String token) + throws SecurityException { + try { + ColorSchemaReader reader = new ColorSchemaReader(); + Collection<ColorSchema> schemas; + schemas = reader.readColorSchema(getInputDataForLayout(layoutId, token)); + // colors here are not important + ColorModelCommand command = new ColorModelCommand(model, schemas, + new ColorExtractor(Color.BLACK, Color.BLACK, Color.BLACK)); + FullLayoutAliasViewFactory factory = new FullLayoutAliasViewFactory(); + + for (Map.Entry<Object, ColorSchema> entry : command.getModifiedElements().entrySet()) { + if (entry.getKey() instanceof Element) { + Element alias = (Element) entry.getKey(); + if (id.equals(alias.getId())) { + return factory.create(new Pair<Element, ColorSchema>(alias, entry.getValue())); + } + } + } + return null; + } catch (InvalidColorSchemaException e) { + throw new InvalidStateException(e); + } catch (IOException e) { + throw new InvalidStateException(e); + } + } + + @Override + public FullLayoutReactionView getFullReactionForLayout(Model model, Integer id, int layoutId, String token) + throws SecurityException { + try { + ColorSchemaReader reader = new ColorSchemaReader(); + Collection<ColorSchema> schemas; + schemas = reader.readColorSchema(getInputDataForLayout(layoutId, token)); + // colors here are not important + ColorModelCommand command = new ColorModelCommand(model, schemas, + new ColorExtractor(Color.BLACK, Color.BLACK, Color.BLACK)); + FullLayoutReactionViewFactory factory = new FullLayoutReactionViewFactory(); + + for (Map.Entry<Object, ColorSchema> entry : command.getModifiedElements().entrySet()) { + if (entry.getKey() instanceof Reaction) { + Reaction alias = (Reaction) entry.getKey(); + if (id.equals(alias.getId())) { + return factory.create(new Pair<Reaction, ColorSchema>(alias, entry.getValue())); + } + } + } + return null; + } catch (InvalidColorSchemaException e) { + throw new InvalidStateException(e); + } catch (IOException e) { + throw new InvalidStateException(e); + } + } + + @Override + public boolean userCanRemoveLayout(Layout layout, String authenticationToken) throws SecurityException { + User user = userService.getUserByToken(authenticationToken); + return userCanRemoveLayout(layout, user); + } +} diff --git a/service/src/main/java/lcsb/mapviewer/services/interfaces/ILayoutService.java b/service/src/main/java/lcsb/mapviewer/services/interfaces/ILayoutService.java index 8ee8ed38a7440e9dabfe636d7744b6373abe3e3b..018939ca4bb57f9f9f0d2a00d978b78c13e02d97 100644 --- a/service/src/main/java/lcsb/mapviewer/services/interfaces/ILayoutService.java +++ b/service/src/main/java/lcsb/mapviewer/services/interfaces/ILayoutService.java @@ -1,473 +1,494 @@ -package lcsb.mapviewer.services.interfaces; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import java.util.Map; - -import lcsb.mapviewer.commands.CommandExecutionException; -import lcsb.mapviewer.model.map.layout.ColorSchema; -import lcsb.mapviewer.model.map.layout.InvalidColorSchemaException; -import lcsb.mapviewer.model.map.layout.Layout; -import lcsb.mapviewer.model.map.model.Model; -import lcsb.mapviewer.model.user.User; -import lcsb.mapviewer.services.SecurityException; -import lcsb.mapviewer.services.search.layout.FullLayoutAliasView; -import lcsb.mapviewer.services.search.layout.FullLayoutReactionView; -import lcsb.mapviewer.services.search.layout.LightLayoutAliasView; -import lcsb.mapviewer.services.search.layout.LightLayoutReactionView; -import lcsb.mapviewer.services.utils.EmailSender; -import lcsb.mapviewer.services.utils.data.ColorSchemaType; - -/** - * Service that manages layouts of the map. - * - * @author Piotr Gawron - * - */ -public interface ILayoutService { - - /** - * Parameters used for creation of the layout. - * - * @author Piotr Gawron - * - */ - class CreateLayoutParams { - - /** - * Size of the buffer used to copy stream data. - */ - private static final int BUFFER_SIZE = 1024; - - /** - * Name of the layout. - * - */ - private String name; - - /** - * Description of the layout. - * - */ - private String description; - - /** - * Where the graphic files should be saved. - * - */ - private String directory; - - /** - * - * {@link Model} for which the layout is created. - */ - private Model model; - - /** - * Who creates the layout. - * - */ - private User user; - - /** - * - * Input stream with coloring information data in the stream should correspond - * to a file that can be parsed by - * {@link lcsb.mapviewer.services.utils.ColorSchemaXlsxReader ColorSchemaReader} - * . This is a copy of original input stream, so we can read this input stream - * multiple times. - */ - private ByteArrayOutputStream colorInputStreamCopy; - - /** - * Name of the file used as input (if available). - */ - private String layoutFileName; - - /** - * Determines if creation of the output files should be done asynchronously who - * creates the layout. - * - */ - private boolean async = false; - - /** - * Type of the uploaded file. - */ - private ColorSchemaType colorSchemaType; - - /** - * @return the name - * @see #name - */ - public String getName() { - return name; - } - - /** - * @param name - * the name to set - * @see #name - * @return {@link CreateLayoutParams} object - */ - public CreateLayoutParams name(String name) { - this.name = name; - return this; - } - - /** - * @return the directory - * @see #directory - */ - public String getDirectory() { - return directory; - } - - /** - * @param directory - * the directory to set - * @see #directory - * @return {@link CreateLayoutParams} object - */ - public CreateLayoutParams directory(String directory) { - this.directory = directory; - return this; - } - - /** - * @return the model - * @see #model - */ - public Model getModel() { - return model; - } - - /** - * @param model - * the model to set - * @see #model - * @return {@link CreateLayoutParams} object - */ - public CreateLayoutParams model(Model model) { - this.model = model; - return this; - } - - /** - * @return the user - * @see #user - */ - public User getUser() { - return user; - } - - /** - * @param user - * the user to set - * @see #user - * @return {@link CreateLayoutParams} object - */ - public CreateLayoutParams user(User user) { - this.user = user; - return this; - } - - /** - * @return the colorInputStream - * @see #colorInputStream - */ - public InputStream getColorInputStream() { - return new ByteArrayInputStream(colorInputStreamCopy.toByteArray()); - } - - /** - * @param colorInputStream - * the colorInputStream to set - * @see #colorInputStream - * @return {@link CreateLayoutParams} object - * @throws IOException - * thrown when there is a problem with accessing input stream - */ - public CreateLayoutParams colorInputStream(InputStream colorInputStream) throws IOException { - if (colorInputStream == null) { - this.colorInputStreamCopy = null; - } else { - this.colorInputStreamCopy = new ByteArrayOutputStream(); - - byte[] buffer = new byte[BUFFER_SIZE]; - int len; - while ((len = colorInputStream.read(buffer)) > -1) { - this.colorInputStreamCopy.write(buffer, 0, len); - } - this.colorInputStreamCopy.flush(); - } - return this; - } - - /** - * @return the async - * @see #async - */ - public boolean isAsync() { - return async; - } - - /** - * @param async - * the async to set - * @see #async - * @return {@link CreateLayoutParams} object - */ - public CreateLayoutParams async(boolean async) { - this.async = async; - return this; - } - - /** - * @return the description - * @see #description - */ - public String getDescription() { - return description; - } - - /** - * @param description - * the description to set - * @see #description - * @return {@link CreateLayoutParams} object - */ - public CreateLayoutParams description(String description) { - this.description = description; - return this; - } - - /** - * - * @return {@link #colorSchemaType} - */ - public ColorSchemaType getColorSchemaType() { - return colorSchemaType; - } - - /** - * @param colorSchemaType - * the colorSchemaType to set - * @see #colorSchemaType - * @return {@link CreateLayoutParams} object - */ - public CreateLayoutParams colorSchemaType(ColorSchemaType colorSchemaType) { - this.colorSchemaType = colorSchemaType; - return this; - } - - /** - * @return the layoutFileName - * @see #layoutFileName - */ - public String getLayoutFileName() { - return layoutFileName; - } - - /** - * @param layoutFileName - * the layoutFileName to set - * @return {@link CreateLayoutParams} object - * @see #layoutFileName - */ - public CreateLayoutParams layoutFileName(String layoutFileName) { - this.layoutFileName = layoutFileName; - return this; - } - } - - /** - * Returns true if user can add layout to the model. - * - * @param model - * to which model user wants to add layout - * @param user - * who wants to add layout - * @return <code>true</code> if user can add layout, <code>false</code> - * otherwise - */ - boolean userCanAddLayout(Model model, User user); - - /** - * Returns true if user can remove layout from the model. - * - * @param layout - * which layout user want to remove - * @param user - * who wants to remove layout - * @return <code>true</code> if user can remove layout, <code>false</code> - * otherwise - */ - boolean userCanRemoveLayout(Layout layout, User user); - - /** - * Removes layout from the system. - * - * @param layout - * layout to remove - * @param homeDir - * directory where the system is deployed - * @throws IOException - * thrown when there are problems with removing layout files - */ - void removeLayout(Layout layout, String homeDir) throws IOException; - - /** - * Updates data about the layout. - * - * @param layout - * layout to update - */ - void updateLayout(Layout layout); - - /** - * Adds view privilege to the layout for the user. - * - * @param layout - * layout for privilege - * @param user - * who should own the privilege - */ - void addViewPrivilegeToLayout(Layout layout, User user); - - /** - * Removes view privilege to the layout from the user. - * - * @param layout - * layout for privilege remove - * @param user - * who shouldn't have the privilege - */ - void dropViewPrivilegeFromLayout(Layout layout, User user); - - /** - * Create layout based on the data in the parameter. - * - * @param params - * list of {@link CreateLayoutParams params} necessary to create layout - * @return object that refers to created layout - * @throws IOException - * thrown when there are problems with files - * @throws InvalidColorSchemaException - * if the coloring source is invalid - * @throws CommandExecutionException - */ - Layout createLayout(CreateLayoutParams params) throws IOException, InvalidColorSchemaException; - - /** - * Create layout based on the data in the parameter. Layout will contain set of - * images that can be further visualized in goolge maps api. - * - * @param params - * list of {@link CreateLayoutParams params} necessary to create layout - * @return object that refers to created layout - * @throws IOException - * thrown when there are problems with files - * @throws CommandExecutionException - * if the coloring source is invalid - * @throws InvalidColorSchemaException - * if the coloring source is invalid - */ - Layout createLayoutWithImages(CreateLayoutParams params) - throws IOException, CommandExecutionException, InvalidColorSchemaException; - - /** - * Returns number of still available custom layouts for the user. - * - * @param user - * user for which the number of available custom layouts will be - * returned - * @return number of available custom layouts - */ - long getAvailableCustomLayoutsNumber(User user); - - /** - * Returns a list of {@link LightLayoutAliasView aliases} that are visualized in - * a {@link lcsb.mapviewer.model.map.layout.Layout}. - * - * @param model - * model where data is located - * @param layoutId - * identifier of the layout - * @return a list of {@link LightLayoutAliasView aliases} that are visualized in - * a {@link lcsb.mapviewer.model.map.layout.Layout} - * @throws SecurityException - */ - List<LightLayoutAliasView> getAliasesForLayout(Model model, int layoutId, String token) throws SecurityException; - - /** - * Returns a list of {@link LightLayoutReactionView reactions} that are - * visualized in a {@link lcsb.mapviewer.model.map.layout.Layout}. - * - * @param model - * model where data is located - * @param layoutId - * identifier of the layout - * @return a list of {@link LightLayoutReactionView reactions} that are - * visualized in a {@link lcsb.mapviewer.model.map.layout.Layout} - * @throws SecurityException - */ - List<LightLayoutReactionView> getReactionsForLayout(Model model, int layoutId, String token) throws SecurityException; - - /** - * Returns mapping between {@link lcsb.mapviewer.model.map.species.Element - * Alias}/ {@link lcsb.mapviewer.model.map.reaction.Reaction Reaction} and - * {@link ColorSchema} used for coloring object in the layout given in the - * parameter. - * - * @param model - * model where data is located - * @param layoutId - * identifier of the layout - * @return a list of {@link LightLayoutReactionView reactions} that are - * visualized in a {@link lcsb.mapviewer.model.map.layout.Layout} - * @throws SecurityException - */ - Map<Object, ColorSchema> getElementsForLayout(Model model, Integer layoutId, String token) throws SecurityException; - - FullLayoutAliasView getFullAliasForLayout(Model model, Integer id, int layoutId, String token) - throws SecurityException; - - FullLayoutReactionView getFullReactionForLayout(Model model, Integer id, int layoutId, String token) - throws SecurityException; - - /** - * Returns {@link EmailSender} used by the service. - * - * @return {@link EmailSender} used by the service - */ - EmailSender getEmailSender(); - - /** - * Sets {@link EmailSender} used by the service. - * - * @param emailSender - * {@link EmailSender} used by the service - */ - void setEmailSender(EmailSender emailSender); - - /** - * Returns list of custom layouts. - * - * @param model - * model where the layouts lay on - * @param user - * user who asks for the layouts - * @return list of custom layouts - */ - List<Layout> getCustomLayouts(Model model, String token, Boolean publicOverlay, User creator) - throws SecurityException; - - Layout getLayoutById(int overlayId, String token) throws SecurityException; - - boolean userCanRemoveLayout(Layout layout, String authenticationToken) throws SecurityException; - -} +package lcsb.mapviewer.services.interfaces; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +import lcsb.mapviewer.commands.CommandExecutionException; +import lcsb.mapviewer.model.map.layout.ColorSchema; +import lcsb.mapviewer.model.map.layout.InvalidColorSchemaException; +import lcsb.mapviewer.model.map.layout.Layout; +import lcsb.mapviewer.model.map.model.Model; +import lcsb.mapviewer.model.user.User; +import lcsb.mapviewer.services.SecurityException; +import lcsb.mapviewer.services.search.layout.FullLayoutAliasView; +import lcsb.mapviewer.services.search.layout.FullLayoutReactionView; +import lcsb.mapviewer.services.search.layout.LightLayoutAliasView; +import lcsb.mapviewer.services.search.layout.LightLayoutReactionView; +import lcsb.mapviewer.services.utils.EmailSender; +import lcsb.mapviewer.services.utils.data.ColorSchemaType; + +/** + * Service that manages layouts of the map. + * + * @author Piotr Gawron + * + */ +public interface ILayoutService { + + /** + * Parameters used for creation of the layout. + * + * @author Piotr Gawron + * + */ + class CreateLayoutParams { + + /** + * Size of the buffer used to copy stream data. + */ + private static final int BUFFER_SIZE = 1024; + + /** + * Name of the layout. + * + */ + private String name; + + /** + * Description of the layout. + * + */ + private String description; + + /** + * Where the graphic files should be saved. + * + */ + private String directory; + + /** + * + * {@link Model} for which the layout is created. + */ + private Model model; + + /** + * Who creates the layout. + * + */ + private User user; + + /** + * + * Input stream with coloring information data in the stream should correspond + * to a file that can be parsed by + * {@link lcsb.mapviewer.services.utils.ColorSchemaXlsxReader ColorSchemaReader} + * . This is a copy of original input stream, so we can read this input stream + * multiple times. + */ + private ByteArrayOutputStream colorInputStreamCopy; + + /** + * Name of the file used as input (if available). + */ + private String layoutFileName; + + /** + * Determines if creation of the output files should be done asynchronously who + * creates the layout. + * + */ + private boolean async = false; + + private boolean googleLicenseConsent = false; + + /** + * Type of the uploaded file. + */ + private ColorSchemaType colorSchemaType; + + /** + * @return the name + * @see #name + */ + public String getName() { + return name; + } + + /** + * @param name + * the name to set + * @see #name + * @return {@link CreateLayoutParams} object + */ + public CreateLayoutParams name(String name) { + this.name = name; + return this; + } + + /** + * @return the directory + * @see #directory + */ + public String getDirectory() { + return directory; + } + + /** + * @param directory + * the directory to set + * @see #directory + * @return {@link CreateLayoutParams} object + */ + public CreateLayoutParams directory(String directory) { + this.directory = directory; + return this; + } + + /** + * @return the model + * @see #model + */ + public Model getModel() { + return model; + } + + /** + * @param model + * the model to set + * @see #model + * @return {@link CreateLayoutParams} object + */ + public CreateLayoutParams model(Model model) { + this.model = model; + return this; + } + + /** + * @return the user + * @see #user + */ + public User getUser() { + return user; + } + + /** + * @param user + * the user to set + * @see #user + * @return {@link CreateLayoutParams} object + */ + public CreateLayoutParams user(User user) { + this.user = user; + return this; + } + + /** + * @return the colorInputStream + * @see #colorInputStream + */ + public InputStream getColorInputStream() { + return new ByteArrayInputStream(colorInputStreamCopy.toByteArray()); + } + + /** + * @param colorInputStream + * the colorInputStream to set + * @see #colorInputStream + * @return {@link CreateLayoutParams} object + * @throws IOException + * thrown when there is a problem with accessing input stream + */ + public CreateLayoutParams colorInputStream(InputStream colorInputStream) throws IOException { + if (colorInputStream == null) { + this.colorInputStreamCopy = null; + } else { + this.colorInputStreamCopy = new ByteArrayOutputStream(); + + byte[] buffer = new byte[BUFFER_SIZE]; + int len; + while ((len = colorInputStream.read(buffer)) > -1) { + this.colorInputStreamCopy.write(buffer, 0, len); + } + this.colorInputStreamCopy.flush(); + } + return this; + } + + /** + * @return the async + * @see #async + */ + public boolean isAsync() { + return async; + } + + /** + * @param async + * the async to set + * @see #async + * @return {@link CreateLayoutParams} object + */ + public CreateLayoutParams async(boolean async) { + this.async = async; + return this; + } + + /** + * @return the googleLicenseConsent + * @see #googleLicenseConsent + */ + public boolean isGoogleLicenseConsent() { + return googleLicenseConsent; + } + + /** + * @param googleLicenseConsent + * the googleLicenseConsent to set + * @see #googleLicenseConsent + * @return {@link CreateLayoutParams} object + */ + public CreateLayoutParams googleLicenseConsent(boolean googleLicenseConsent) { + this.googleLicenseConsent = googleLicenseConsent; + return this; + } + + /** + * @return the description + * @see #description + */ + public String getDescription() { + return description; + } + + /** + * @param description + * the description to set + * @see #description + * @return {@link CreateLayoutParams} object + */ + public CreateLayoutParams description(String description) { + this.description = description; + return this; + } + + /** + * + * @return {@link #colorSchemaType} + */ + public ColorSchemaType getColorSchemaType() { + return colorSchemaType; + } + + /** + * @param colorSchemaType + * the colorSchemaType to set + * @see #colorSchemaType + * @return {@link CreateLayoutParams} object + */ + public CreateLayoutParams colorSchemaType(ColorSchemaType colorSchemaType) { + this.colorSchemaType = colorSchemaType; + return this; + } + + /** + * @return the layoutFileName + * @see #layoutFileName + */ + public String getLayoutFileName() { + return layoutFileName; + } + + /** + * @param layoutFileName + * the layoutFileName to set + * @return {@link CreateLayoutParams} object + * @see #layoutFileName + */ + public CreateLayoutParams layoutFileName(String layoutFileName) { + this.layoutFileName = layoutFileName; + return this; + } + } + + /** + * Returns true if user can add layout to the model. + * + * @param model + * to which model user wants to add layout + * @param user + * who wants to add layout + * @return <code>true</code> if user can add layout, <code>false</code> + * otherwise + */ + boolean userCanAddLayout(Model model, User user); + + /** + * Returns true if user can remove layout from the model. + * + * @param layout + * which layout user want to remove + * @param user + * who wants to remove layout + * @return <code>true</code> if user can remove layout, <code>false</code> + * otherwise + */ + boolean userCanRemoveLayout(Layout layout, User user); + + /** + * Removes layout from the system. + * + * @param layout + * layout to remove + * @param homeDir + * directory where the system is deployed + * @throws IOException + * thrown when there are problems with removing layout files + */ + void removeLayout(Layout layout, String homeDir) throws IOException; + + /** + * Updates data about the layout. + * + * @param layout + * layout to update + */ + void updateLayout(Layout layout); + + /** + * Adds view privilege to the layout for the user. + * + * @param layout + * layout for privilege + * @param user + * who should own the privilege + */ + void addViewPrivilegeToLayout(Layout layout, User user); + + /** + * Removes view privilege to the layout from the user. + * + * @param layout + * layout for privilege remove + * @param user + * who shouldn't have the privilege + */ + void dropViewPrivilegeFromLayout(Layout layout, User user); + + /** + * Create layout based on the data in the parameter. + * + * @param params + * list of {@link CreateLayoutParams params} necessary to create layout + * @return object that refers to created layout + * @throws IOException + * thrown when there are problems with files + * @throws InvalidColorSchemaException + * if the coloring source is invalid + * @throws CommandExecutionException + */ + Layout createLayout(CreateLayoutParams params) throws IOException, InvalidColorSchemaException; + + /** + * Create layout based on the data in the parameter. Layout will contain set of + * images that can be further visualized in goolge maps api. + * + * @param params + * list of {@link CreateLayoutParams params} necessary to create layout + * @return object that refers to created layout + * @throws IOException + * thrown when there are problems with files + * @throws CommandExecutionException + * if the coloring source is invalid + * @throws InvalidColorSchemaException + * if the coloring source is invalid + */ + Layout createLayoutWithImages(CreateLayoutParams params) + throws IOException, CommandExecutionException, InvalidColorSchemaException; + + /** + * Returns number of still available custom layouts for the user. + * + * @param user + * user for which the number of available custom layouts will be + * returned + * @return number of available custom layouts + */ + long getAvailableCustomLayoutsNumber(User user); + + /** + * Returns a list of {@link LightLayoutAliasView aliases} that are visualized in + * a {@link lcsb.mapviewer.model.map.layout.Layout}. + * + * @param model + * model where data is located + * @param layoutId + * identifier of the layout + * @return a list of {@link LightLayoutAliasView aliases} that are visualized in + * a {@link lcsb.mapviewer.model.map.layout.Layout} + * @throws SecurityException + */ + List<LightLayoutAliasView> getAliasesForLayout(Model model, int layoutId, String token) throws SecurityException; + + /** + * Returns a list of {@link LightLayoutReactionView reactions} that are + * visualized in a {@link lcsb.mapviewer.model.map.layout.Layout}. + * + * @param model + * model where data is located + * @param layoutId + * identifier of the layout + * @return a list of {@link LightLayoutReactionView reactions} that are + * visualized in a {@link lcsb.mapviewer.model.map.layout.Layout} + * @throws SecurityException + */ + List<LightLayoutReactionView> getReactionsForLayout(Model model, int layoutId, String token) throws SecurityException; + + /** + * Returns mapping between {@link lcsb.mapviewer.model.map.species.Element + * Alias}/ {@link lcsb.mapviewer.model.map.reaction.Reaction Reaction} and + * {@link ColorSchema} used for coloring object in the layout given in the + * parameter. + * + * @param model + * model where data is located + * @param layoutId + * identifier of the layout + * @return a list of {@link LightLayoutReactionView reactions} that are + * visualized in a {@link lcsb.mapviewer.model.map.layout.Layout} + * @throws SecurityException + */ + Map<Object, ColorSchema> getElementsForLayout(Model model, Integer layoutId, String token) throws SecurityException; + + FullLayoutAliasView getFullAliasForLayout(Model model, Integer id, int layoutId, String token) + throws SecurityException; + + FullLayoutReactionView getFullReactionForLayout(Model model, Integer id, int layoutId, String token) + throws SecurityException; + + /** + * Returns {@link EmailSender} used by the service. + * + * @return {@link EmailSender} used by the service + */ + EmailSender getEmailSender(); + + /** + * Sets {@link EmailSender} used by the service. + * + * @param emailSender + * {@link EmailSender} used by the service + */ + void setEmailSender(EmailSender emailSender); + + /** + * Returns list of custom layouts. + * + * @param model + * model where the layouts lay on + * @param user + * user who asks for the layouts + * @return list of custom layouts + */ + List<Layout> getCustomLayouts(Model model, String token, Boolean publicOverlay, User creator) + throws SecurityException; + + Layout getLayoutById(int overlayId, String token) throws SecurityException; + + boolean userCanRemoveLayout(Layout layout, String authenticationToken) throws SecurityException; + +} diff --git a/web/src/main/webapp/index.xhtml b/web/src/main/webapp/index.xhtml index 87b3437924fdbf96cee1613a7a1c06f6354487b4..385a0661cc00cc1b9b47dab145170db49ddc30e8 100644 --- a/web/src/main/webapp/index.xhtml +++ b/web/src/main/webapp/index.xhtml @@ -10,9 +10,6 @@ <h:head> - - <!-- Google Maps API version 3.20 --> - <script src="https://maps.google.com/maps/api/js?libraries=drawing" type="text/javascript"/> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" type="text/javascript"/> <script src="https://twitter.github.io/typeahead.js/releases/latest/typeahead.bundle.min.js" type="text/javascript"/> @@ -20,12 +17,12 @@ <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> <script src="https://cdn.datatables.net/1.10.13/js/jquery.dataTables.min.js"></script> <script src="https://cdn.datatables.net/rowreorder/1.2.3/js/dataTables.rowReorder.min.js"></script> - + <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.13/css/jquery.dataTables.min.css"/> <link rel="stylesheet" type="text/css" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"/> <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/rowreorder/1.2.3/css/rowReorder.dataTables.min.css"/> - + <link rel="shortcut icon" href="./resources/images/favicon.png" type="image/png" /> <h:outputScript library="js" name="minerva.js" /> diff --git a/web/src/main/webapp/login.xhtml b/web/src/main/webapp/login.xhtml index af2062f2a1a49b216dd87cd5af4fccbdec895add..918f7b88217a0bdea2ae8e0d4b2d04d297e1e043 100644 --- a/web/src/main/webapp/login.xhtml +++ b/web/src/main/webapp/login.xhtml @@ -11,10 +11,6 @@ <link rel="shortcut icon" href="./resources/images/favicon.png" type="image/png" /> - <script - src="https://maps.google.com/maps/api/js?libraries=drawing&v=3.26" - type="text/javascript" /> - <script src="https://code.jquery.com/jquery-1.12.1.min.js"></script> <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> @@ -36,7 +32,7 @@ function init() { var element = document.getElementById('minervaAppDiv'); return minerva.createLogin({ - element : element, + element : element, }).catch(function(rejectReason){ minerva.GuiConnector.alert(rejectReason, true); });