From 9cbe83f7fb07d06de4002501f16f9c8d446108a4 Mon Sep 17 00:00:00 2001 From: Piotr Gawron <piotr.gawron@uni.lu> Date: Tue, 21 Nov 2017 16:53:06 +0100 Subject: [PATCH] every map has optional default center coordinates where map should be focused after first open during session --- frontend-js/src/main/js/ServerConnector.js | 25 + .../main/js/gui/admin/EditProjectDialog.js | 1508 ++++++------ .../src/main/js/map/AbstractCustomMap.js | 2121 +++++++++-------- frontend-js/src/main/js/map/CustomMap.js | 18 +- frontend-js/src/main/js/map/data/MapModel.js | 1626 ++++++------- .../js/gui/admin/EditProjectDialog-test.js | 246 +- .../PATCH_model.id=15781&token=MOCK_TOKEN_ID& | 1 + .../lcsb/mapviewer/model/map/model/Model.java | 1100 ++++----- .../mapviewer/model/map/model/ModelData.java | 1428 +++++------ .../model/map/model/ModelFullIndexed.java | 1207 +++++----- persist/src/db/12.0.0/fix_db_20171121.sql | 5 + .../api/projects/models/ModelController.java | 108 +- .../api/projects/models/ModelMetaData.java | 33 + .../api/projects/models/ModelRestImpl.java | 88 + .../mapviewer/services/impl/ModelService.java | 1132 ++++----- .../services/interfaces/IModelService.java | 258 +- 16 files changed, 5688 insertions(+), 5216 deletions(-) create mode 100644 frontend-js/testFiles/apiCalls/projects/sample/models/15781/PATCH_model.id=15781&token=MOCK_TOKEN_ID& diff --git a/frontend-js/src/main/js/ServerConnector.js b/frontend-js/src/main/js/ServerConnector.js index da7011f327..470e139378 100644 --- a/frontend-js/src/main/js/ServerConnector.js +++ b/frontend-js/src/main/js/ServerConnector.js @@ -408,6 +408,12 @@ ServerConnector.updateOverlayUrl = function (queryParams) { }); }; +ServerConnector.updateModelUrl = function (queryParams) { + return this.getApiUrl({ + url: this.getModelsUrl(queryParams) + }); +}; + ServerConnector.deleteOverlayUrl = function (queryParams) { return this.getApiUrl({ url: this.getOverlayByIdUrl(queryParams) @@ -1638,6 +1644,25 @@ ServerConnector.updateOverlay = function (overlay) { return self.sendPatchRequest(self.updateOverlayUrl(queryParams), filterParams); }; +ServerConnector.updateModel = function (params) { + var self = this; + var model = params.model; + var queryParams = { + projectId: params.projectId, + modelId: model.getId() + }; + var filterParams = { + model: { + id: model.getId(), + defaultCenterX: model.getDefaultCenterX(), + defaultCenterY: model.getDefaultCenterY(), + defaultZoomLevel: model.getDefaultZoomLevel() + } + }; + return self.sendPatchRequest(self.updateModelUrl(queryParams), filterParams); +}; + + ServerConnector.removeOverlay = function (params) { var self = this; var queryParams = { diff --git a/frontend-js/src/main/js/gui/admin/EditProjectDialog.js b/frontend-js/src/main/js/gui/admin/EditProjectDialog.js index e2787d5492..9101aa7659 100644 --- a/frontend-js/src/main/js/gui/admin/EditProjectDialog.js +++ b/frontend-js/src/main/js/gui/admin/EditProjectDialog.js @@ -1,687 +1,821 @@ -"use strict"; - -/* exported logger */ -var Promise = require("bluebird"); - -var AbstractGuiElement = require('../AbstractGuiElement'); -var AddOverlayDialog = require('../AddOverlayDialog'); -var Annotation = require('../../map/data/Annotation'); -var GuiConnector = require('../../GuiConnector'); - -var Functions = require('../../Functions'); -var logger = require('../../logger'); - -var guiUtils = new (require('../leftPanel/GuiUtils'))(); - -function EditProjectDialog(params) { - AbstractGuiElement.call(this, params); - var self = this; - $(self.getElement()).addClass("minerva-edit-project-dialog"); - - self.createGui(); -} - -EditProjectDialog.prototype = Object.create(AbstractGuiElement.prototype); -EditProjectDialog.prototype.constructor = EditProjectDialog; - -EditProjectDialog.prototype.createGui = function () { - var self = this; - var element = self.getElement(); - - var tabDiv = Functions.createElement({ - type: "div", - name: "tabView", - className: "tabbable boxed parentTabs" - }); - element.appendChild(tabDiv); - - var tabMenuDiv = Functions.createElement({ - type: "ul", - className: "nav nav-tabs" - }); - tabDiv.appendChild(tabMenuDiv); - - var tabContentDiv = Functions.createElement({ - type: "div", - className: "tab-content" - }); - tabDiv.appendChild(tabContentDiv); - - self.createGeneralTab(tabMenuDiv, tabContentDiv); - self.createOverlaysTab(tabMenuDiv, tabContentDiv); - self.createUsersTab(tabMenuDiv, tabContentDiv); -}; - -EditProjectDialog.prototype.createGeneralTab = function (tabMenuDiv, tabContentDiv) { - var self = this; - self.addTab({ - tabMenuDiv: tabMenuDiv, - tabContentDiv: tabContentDiv, - name: "GENERAL", - id: self.getProject().getProjectId() + "_general_tab", - content: self.createGeneralTabContent() - }); - -}; - -EditProjectDialog.prototype.addTab = function (params) { - var navLi = guiUtils.createTabMenuObject({ - id: params.id, - name: params.name, - navigationBar: params.tabMenuDiv - }); - params.tabMenuDiv.appendChild(navLi); - - var contentDiv = guiUtils.createTabContentObject({ - id: params.id, - navigationObject: navLi, - navigationBar: params.tabMenuDiv - }); - - if (params.content !== undefined) { - contentDiv.appendChild(params.content); - } - - params.tabContentDiv.appendChild(contentDiv); -}; - -EditProjectDialog.prototype.createGeneralTabContent = function () { - var self = this; - var project = self.getProject(); - - var result = new Functions.createElement({ - type: "div" - }); - - var table = new Functions.createElement({ - type: "div", - style: "display:table" - }); - result.appendChild(table); - - var projectIdRow = new Functions.createElement({ - type: "div", - style: "display:table-row" - }); - table.appendChild(projectIdRow); - projectIdRow.appendChild(new Functions.createElement({ - type: "div", - style: "display:table-cell", - content: "ProjectId" - })); - projectIdRow.appendChild(new Functions.createElement({ - type: "div", - style: "display:table-cell", - content: project.getProjectId() - })); - - var nameRow = new Functions.createElement({ - type: "div", - style: "display:table-row" - }); - table.appendChild(nameRow); - nameRow.appendChild(new Functions.createElement({ - type: "div", - style: "display:table-cell", - content: "Name" - })); - nameRow.appendChild(new Functions.createElement({ - type: "div", - style: "display:table-cell", - content: "<input name='projectName' value='" + project.getName() + "'/>" - })); - - var versionRow = new Functions.createElement({ - type: "div", - style: "display:table-row" - }); - table.appendChild(versionRow); - versionRow.appendChild(new Functions.createElement({ - type: "div", - style: "display:table-cell", - content: "Version" - })); - versionRow.appendChild(new Functions.createElement({ - type: "div", - style: "display:table-cell", - content: "<input name='projectVersion' value='" + project.getVersion() + "'/>" - })); - - var diseaseRow = new Functions.createElement({ - type: "div", - style: "display:table-row" - }); - table.appendChild(diseaseRow); - diseaseRow.appendChild(new Functions.createElement({ - type: "div", - style: "display:table-cell", - content: "Disease" - })); - var disease = ""; - if (project.getDisease() !== undefined) { - disease = project.getDisease().getResource(); - } - diseaseRow.appendChild(new Functions.createElement({ - type: "div", - style: "display:table-cell", - content: "<input name='projectDisease' value='" + disease + "'/>" - })); - - var organismRow = new Functions.createElement({ - type: "div", - style: "display:table-row" - }); - table.appendChild(organismRow); - organismRow.appendChild(new Functions.createElement({ - type: "div", - style: "display:table-cell", - content: "Organism" - })); - var organism = ""; - if (project.getOrganism() !== undefined) { - organism = project.getOrganism().getResource(); - } - organismRow.appendChild(new Functions.createElement({ - type: "div", - style: "display:table-cell", - content: "<input name='projectOrganism' value='" + organism + "'/>" - })); - - var emailRow = new Functions.createElement({ - type: "div", - style: "display:table-row" - }); - table.appendChild(emailRow); - emailRow.appendChild(new Functions.createElement({ - type: "div", - style: "display:table-cell", - content: "Notify email" - })); - var email = ""; - if (project.getNotifyEmail() !== undefined) { - email = project.getNotifyEmail(); - } - emailRow.appendChild(new Functions.createElement({ - type: "div", - style: "display:table-cell", - content: "<input name='projectNotifyEmail' value='" + email + "'/>" - })); - - var menuRow = Functions.createElement({ - type: "div", - className: "minerva-menu-row", - style: "display:table-row; margin:10px" - }); - result.appendChild(menuRow); - - var saveProjectButton = Functions.createElement({ - type: "button", - name: "saveProject", - content: '<span class="ui-icon ui-icon-disk"></span> SAVE', - onclick: function () { - return self.onSaveClicked().then(function () { - return self.close(); - }, GuiConnector.alert); - } - }); - var cancelButton = Functions.createElement({ - type: "button", - name: "cancelProject", - content: '<span class="ui-icon ui-icon-cancel"></span> CANCEL', - onclick: function () { - return self.close(); - } - }); - menuRow.appendChild(saveProjectButton); - menuRow.appendChild(cancelButton); - - return result; - -}; - -EditProjectDialog.prototype.createOverlaysTab = function (tabMenuDiv, tabContentDiv) { - var self = this; - self.addTab({ - tabMenuDiv: tabMenuDiv, - tabContentDiv: tabContentDiv, - name: "OVERLAYS", - id: self.getProject().getProjectId() + "_overlays_tab", - content: self.createOverlaysTabContent() - }); -}; - -EditProjectDialog.prototype.createOverlaysTabContent = function () { - var self = this; - var result = Functions.createElement({ - type: "div" - }); - result.appendChild(self._createOverlayTable()); - return result; -}; - -EditProjectDialog.prototype._createOverlayTable = function () { - var self = this; - - var result = Functions.createElement({ - type: "div", - style: "margin-top:10px;" - }); - - var overlaysTable = Functions.createElement({ - type: "table", - name: "overlaysTable", - className: "display", - style: "width:100%" - }); - result.appendChild(overlaysTable); - - $(overlaysTable).DataTable({ - fnRowCallback: function (nRow, aData) { - nRow.setAttribute('id', "overlay-" + aData[0]); - }, - columns: [{ - title: 'Id' - }, { - title: 'Name' - }, { - title: 'Description' - }, { - title: 'Public' - }, { - title: 'Default' - }, { - title: 'Owner' - }, { - title: 'Data' - }, { - title: 'Update' - }, { - title: 'Remove' - }], - dom: '<"minerva-datatable-toolbar">frtip', - initComplete: function () { - $("div.minerva-datatable-toolbar", $(result)).html('<button name="addOverlay">Add overlay</button>'); - } - - }); - - $(overlaysTable).on("click", "[name='removeOverlay']", function () { - var button = this; - return self.removeOverlay($(button).attr("data")).then(null, GuiConnector.alert); - }); - - $(overlaysTable).on("click", "[name='saveOverlay']", function () { - var button = this; - GuiConnector.showProcessing("Updating"); - return self.saveOverlay($(button).attr("data")).then(function () { - GuiConnector.hideProcessing(); - GuiConnector.info("Overlay updated successfully"); - }, function (error) { - GuiConnector.hideProcessing(); - GuiConnector.alert(error); - }); - }); - - $(overlaysTable).on("click", "[name='downloadSource']", function () { - var button = this; - return ServerConnector.getOverlaySourceDownloadUrl({ - overlayId: $(button).attr("data") - }).then(function (url) { - return self.downloadFile(url); - }).then(null, GuiConnector.alert); - }); - - $(result).on("click", "[name='addOverlay']", function () { - return self.openAddOverlayDialog(); - }); - - return result; -}; - -EditProjectDialog.prototype.createUsersTab = function (tabMenuDiv, tabContentDiv) { - var self = this; - self.addTab({ - tabMenuDiv: tabMenuDiv, - tabContentDiv: tabContentDiv, - name: "USERS", - id: self.getProject().getProjectId() + "_users_tab", - content: self.createUsersTabContent() - }); -}; - -EditProjectDialog.prototype.createUsersTabContent = function () { - var self = this; - - var result = Functions.createElement({ - type: "div", - style: "margin-top:10px;" - }); - - var usersTable = Functions.createElement({ - type: "table", - name: "usersTable", - className: "display", - style: "width:100%" - }); - result.appendChild(usersTable); - - $(usersTable).on("click", "[name='saveUser']", function () { - var button = this; - GuiConnector.showProcessing("Updating"); - return self.saveUser($(button).attr("data")).then(function () { - GuiConnector.hideProcessing(); - GuiConnector.info("User updated successfully"); - }, function (error) { - GuiConnector.hideProcessing(); - GuiConnector.alert(error); - }); - }); - - return result; -}; - -EditProjectDialog.prototype.createUserPrivilegeColumns = function () { - var self = this; - - if (self._userPrivilegeColumns !== undefined) { - return Promise.resolve(self._userPrivilegeColumns); - } - - return ServerConnector.getConfiguration().then(function (configuration) { - self._userPrivilegeColumns = [{ - title: "Name" - }]; - var privilegeTypes = configuration.getPrivilegeTypes(); - for (var i = 0; i < privilegeTypes.length; i++) { - var type = privilegeTypes[i]; - if (type.getObjectType() === "Project") { - self._userPrivilegeColumns.push({ - "title": type.getCommonName(), - privilegeType: type - }); - } - } - self._userPrivilegeColumns.push({ - "title": "Update" - }); - return self._userPrivilegeColumns; - }); - -}; - -EditProjectDialog.prototype.init = function () { - var self = this; - return self.initUsersTab().then(function () { - return self.refreshUsers(); - }).then(function () { - return self.refreshOverlays(); - }).then(function () { - $(window).trigger('resize'); - }); -}; - -EditProjectDialog.prototype.initUsersTab = function () { - var self = this; - - var usersTable = $("[name=usersTable]", self.getElement())[0]; - - return self.createUserPrivilegeColumns().then(function (columns) { - $(usersTable).DataTable({ - columns: columns - }); - }); -}; - -EditProjectDialog.prototype.refreshOverlays = function () { - var self = this; - return ServerConnector.getOverlays({ - projectId: self.getProject().getProjectId() - }).then(function (overlays) { - return self.setOverlays(overlays); - }); -}; - -EditProjectDialog.prototype.refreshUsers = function () { - var self = this; - return ServerConnector.getUsers().then(function (users) { - return self.setUsers(users); - }); -}; - -EditProjectDialog.prototype.setOverlays = function (overlays) { - var self = this; - self._overlayById = []; - return ServerConnector.getUsers().then(function (users) { - var dataTable = $($("[name='overlaysTable']", self.getElement())[0]).DataTable(); - var data = []; - for (var i = 0; i < overlays.length; i++) { - var overlay = overlays[i]; - self._overlayById[overlay.getId()] = overlay; - var rowData = self.overlayToTableRow(overlay, users); - data.push(rowData); - } - dataTable.clear().rows.add(data).draw(); - }); -}; - -EditProjectDialog.prototype.setUsers = function (users) { - var self = this; - self._userByLogin = []; - return self.createUserPrivilegeColumns().then(function (columns) { - var dataTable = $($("[name='usersTable']", self.getElement())[0]).DataTable(); - var data = []; - for (var i = 0; i < users.length; i++) { - var user = users[i]; - self._userByLogin[user.getLogin()] = user; - var rowData = self.userToTableRow(user, columns); - data.push(rowData); - } - dataTable.clear().rows.add(data).draw(); - }); -}; - -EditProjectDialog.prototype.userToTableRow = function (user, columns) { - var self = this; - var row = []; - var login = user.getLogin(); - - row[0] = user.getName() + " " + user.getSurname() + " (" + login + ")"; - for (var i = 1; i < columns.length; i++) { - var column = columns[i]; - if (column.privilegeType !== undefined) { - if (column.privilegeType.getValueType() === "boolean") { - var checked = ''; - if (user.hasPrivilege(column.privilegeType, self.getProject().getId())) { - checked = 'checked'; - } - row[i] = "<input type='checkbox' name='privilege-" + login + "' data='" + column.privilegeType.getName() + "' " - + checked + "/>"; - } else { - throw new Error("Unsupported type: " + column.privilegeType.getValueType()); - } - } - } - - row.push("<button name='saveUser' data='" + login + "'>SAVE</button>"); - - return row; -}; - -EditProjectDialog.prototype.overlayToTableRow = function (overlay, users) { - var row = []; - var id = overlay.getId(); - var creatorSelect; - if (overlay.getCreator() === "") { - creatorSelect = "<select name='creator-" + id + "' value=''>"; - } else { - creatorSelect = "<select name='creator-" + id + "'>"; - } - - creatorSelect += "<option value='' >---</option>"; - for (var i = 0; i < users.length; i++) { - var selected = ""; - var user = users[i]; - if (overlay.getCreator() === user.getLogin()) { - selected = "selected"; - } else { - selected = ""; - } - - creatorSelect += "<option value='" + user.getLogin() + "' " + selected + ">" + user.getLogin() + "(" - + user.getName() + " " + user.getSurname() + ")</option>"; - } - creatorSelect += "</select>"; - - var checked = ''; - if (overlay.getPublicOverlay()) { - checked = "checked"; - } - var publicOverlayCheckbox = "<input type='checkbox' name='publicOverlay-" + id + "' " + checked + "/>"; - - checked = ''; - if (overlay.isDefaultOverlay()) { - checked = "checked"; - } - var defaultOverlayCheckbox = "<input type='checkbox' name='defaultOverlay-" + id + "' " + checked + "/>"; - - var downloadSourceButton; - if (overlay.getInputDataAvailable()) { - downloadSourceButton = "<button name='downloadSource' data='" + id + "'>" - + "<span class='ui-icon ui-icon-arrowthickstop-1-s'></span>" + "</button>"; - } else { - downloadSourceButton = "N/A"; - } - - row[0] = id; - row[1] = "<input name='name-" + id + "' value='" + overlay.getName() + "'/>"; - row[2] = "<input name='description-" + id + "' value='" + overlay.getDescription() + "'/>"; - row[3] = publicOverlayCheckbox; - row[4] = defaultOverlayCheckbox; - row[5] = creatorSelect; - row[6] = downloadSourceButton; - row[7] = "<button name='saveOverlay' data='" + id + "'>SAVE</button>"; - row[8] = "<button name='removeOverlay' data='" + id + "'>REMOVE</button>"; - - return row; -}; - -EditProjectDialog.prototype.destroy = function () { - var self = this; - var div = self.getElement(); - var usersTable = $("[name=usersTable]", self.getElement())[0]; - var overlaysTable = $("[name=overlaysTable]", self.getElement())[0]; - if ($.fn.DataTable.isDataTable(usersTable)) { - $(usersTable).DataTable().destroy(); - } - if ($.fn.DataTable.isDataTable(overlaysTable)) { - $(overlaysTable).DataTable().destroy(); - } - - if ($(div).hasClass("ui-dialog-content")) { - $(div).dialog("destroy"); - } - if (self._addOverlayDialog !== undefined) { - self._addOverlayDialog.destroy(); - delete self._addOverlayDialog; - } -}; - -EditProjectDialog.prototype.open = function () { - var self = this; - var div = self.getElement(); - if (!$(div).hasClass("ui-dialog-content")) { - $(div).dialog({ - title: self.getProject().getProjectId(), - width: window.innerWidth / 2, - height: window.innerHeight / 2 - }); - } - $(div).dialog("open"); -}; - -var prepareMiriamData = function (type, resource) { - if (resource === "" || resource === undefined || resource === null) { - return null; - } else { - return new Annotation({ - type: type, - resource: resource - }); - } -}; - -EditProjectDialog.prototype.onSaveClicked = function () { - var self = this; - var project = self.getProject(); - var element = self.getElement(); - project.setName($("[name='projectName']", element)[0].value); - project.setVersion($("[name='projectVersion']", element)[0].value); - project.setNotifyEmail($("[name='projectNotifyEmail']", element)[0].value); - var organism = prepareMiriamData("TAXONOMY", $("[name='projectOrganism']", element)[0].value); - project.setOrganism(organism); - var disease = prepareMiriamData("MESH_2012", $("[name='projectDisease']", element)[0].value); - project.setDisease(disease); - return ServerConnector.updateProject(project); -}; -EditProjectDialog.prototype.close = function () { - var self = this; - $(self.getElement()).dialog("close"); -}; - -EditProjectDialog.prototype.saveOverlay = function (overlayId) { - var self = this; - var overlay = self._overlayById[overlayId]; - overlay.setName($("[name='name-" + overlayId + "']", self.getElement())[0].value); - overlay.setDescription($("[name='description-" + overlayId + "']", self.getElement())[0].value); - overlay.setPublicOverlay($("[name='publicOverlay-" + overlayId + "']", self.getElement())[0].checked); - overlay.setDefaultOverlay($("[name='defaultOverlay-" + overlayId + "']", self.getElement())[0].checked); - overlay.setCreator($("[name='creator-" + overlayId + "']", self.getElement())[0].value); - - return ServerConnector.updateOverlay(overlay); -}; - -EditProjectDialog.prototype.saveUser = function (login) { - var self = this; - var checkboxes = $("[name='privilege-" + login + "']", self.getElement()); - var privileges = {}; - for (var i = 0; i < checkboxes.length; i++) { - var checkbox = checkboxes[i]; - var privilege = {}; - privilege[self.getProject().getId()] = checkbox.checked; - privileges[$(checkbox).attr("data")] = privilege; - } - - return ServerConnector.updateUserPrivileges({ - user: self._userByLogin[login], - privileges: privileges - }); -}; - -EditProjectDialog.prototype.removeOverlay = function (overlayId) { - var self = this; - return ServerConnector.removeOverlay({ - overlayId: overlayId - }).then(function () { - return self.refreshOverlays(); - }); -}; - -EditProjectDialog.prototype.openAddOverlayDialog = function () { - var self = this; - if (self._addOverlayDialog !== undefined) { - self._addOverlayDialog.destroy(); - } - self._addOverlayDialog = new AddOverlayDialog({ - project: self.getProject(), - customMap: null, - element: document.createElement("div") - }); - self._addOverlayDialog.addListener("onAddOverlay", function () { - return self.refreshOverlays(); - }); - return self._addOverlayDialog.init().then(function () { - return self._addOverlayDialog.open(); - }); -}; - -module.exports = EditProjectDialog; +"use strict"; + +/* exported logger */ +var Promise = require("bluebird"); + +var AbstractGuiElement = require('../AbstractGuiElement'); +var AddOverlayDialog = require('../AddOverlayDialog'); +var Annotation = require('../../map/data/Annotation'); +var GuiConnector = require('../../GuiConnector'); + +var Functions = require('../../Functions'); +// noinspection JSUnusedLocalSymbols +var logger = require('../../logger'); + +var guiUtils = new (require('../leftPanel/GuiUtils'))(); + +function EditProjectDialog(params) { + AbstractGuiElement.call(this, params); + var self = this; + $(self.getElement()).addClass("minerva-edit-project-dialog"); + + self.createGui(); +} + +EditProjectDialog.prototype = Object.create(AbstractGuiElement.prototype); +EditProjectDialog.prototype.constructor = EditProjectDialog; + +EditProjectDialog.prototype.createGui = function () { + var self = this; + var element = self.getElement(); + + var tabDiv = Functions.createElement({ + type: "div", + name: "tabView", + className: "tabbable boxed parentTabs" + }); + element.appendChild(tabDiv); + + var tabMenuDiv = Functions.createElement({ + type: "ul", + className: "nav nav-tabs" + }); + tabDiv.appendChild(tabMenuDiv); + + var tabContentDiv = Functions.createElement({ + type: "div", + className: "tab-content" + }); + tabDiv.appendChild(tabContentDiv); + + self.createGeneralTab(tabMenuDiv, tabContentDiv); + self.createOverlaysTab(tabMenuDiv, tabContentDiv); + self.createMapsTab(tabMenuDiv, tabContentDiv); + self.createUsersTab(tabMenuDiv, tabContentDiv); +}; + +EditProjectDialog.prototype.createGeneralTab = function (tabMenuDiv, tabContentDiv) { + var self = this; + self.addTab({ + tabMenuDiv: tabMenuDiv, + tabContentDiv: tabContentDiv, + name: "GENERAL", + id: self.getProject().getProjectId() + "_general_tab", + content: self.createGeneralTabContent() + }); + +}; + +EditProjectDialog.prototype.addTab = function (params) { + var navLi = guiUtils.createTabMenuObject({ + id: params.id, + name: params.name, + navigationBar: params.tabMenuDiv + }); + params.tabMenuDiv.appendChild(navLi); + + var contentDiv = guiUtils.createTabContentObject({ + id: params.id, + navigationObject: navLi, + navigationBar: params.tabMenuDiv + }); + + if (params.content !== undefined) { + contentDiv.appendChild(params.content); + } + + params.tabContentDiv.appendChild(contentDiv); +}; + +EditProjectDialog.prototype.createGeneralTabContent = function () { + var self = this; + var project = self.getProject(); + + var result = new Functions.createElement({ + type: "div" + }); + + var table = new Functions.createElement({ + type: "div", + style: "display:table" + }); + result.appendChild(table); + + var projectIdRow = new Functions.createElement({ + type: "div", + style: "display:table-row" + }); + table.appendChild(projectIdRow); + projectIdRow.appendChild(new Functions.createElement({ + type: "div", + style: "display:table-cell", + content: "ProjectId" + })); + projectIdRow.appendChild(new Functions.createElement({ + type: "div", + style: "display:table-cell", + content: project.getProjectId() + })); + + var nameRow = new Functions.createElement({ + type: "div", + style: "display:table-row" + }); + table.appendChild(nameRow); + nameRow.appendChild(new Functions.createElement({ + type: "div", + style: "display:table-cell", + content: "Name" + })); + nameRow.appendChild(new Functions.createElement({ + type: "div", + style: "display:table-cell", + content: "<input name='projectName' value='" + project.getName() + "'/>" + })); + + var versionRow = new Functions.createElement({ + type: "div", + style: "display:table-row" + }); + table.appendChild(versionRow); + versionRow.appendChild(new Functions.createElement({ + type: "div", + style: "display:table-cell", + content: "Version" + })); + versionRow.appendChild(new Functions.createElement({ + type: "div", + style: "display:table-cell", + content: "<input name='projectVersion' value='" + project.getVersion() + "'/>" + })); + + var diseaseRow = new Functions.createElement({ + type: "div", + style: "display:table-row" + }); + table.appendChild(diseaseRow); + diseaseRow.appendChild(new Functions.createElement({ + type: "div", + style: "display:table-cell", + content: "Disease" + })); + var disease = ""; + if (project.getDisease() !== undefined) { + disease = project.getDisease().getResource(); + } + diseaseRow.appendChild(new Functions.createElement({ + type: "div", + style: "display:table-cell", + content: "<input name='projectDisease' value='" + disease + "'/>" + })); + + var organismRow = new Functions.createElement({ + type: "div", + style: "display:table-row" + }); + table.appendChild(organismRow); + organismRow.appendChild(new Functions.createElement({ + type: "div", + style: "display:table-cell", + content: "Organism" + })); + var organism = ""; + if (project.getOrganism() !== undefined) { + organism = project.getOrganism().getResource(); + } + organismRow.appendChild(new Functions.createElement({ + type: "div", + style: "display:table-cell", + content: "<input name='projectOrganism' value='" + organism + "'/>" + })); + + var emailRow = new Functions.createElement({ + type: "div", + style: "display:table-row" + }); + table.appendChild(emailRow); + emailRow.appendChild(new Functions.createElement({ + type: "div", + style: "display:table-cell", + content: "Notify email" + })); + var email = ""; + if (project.getNotifyEmail() !== undefined) { + email = project.getNotifyEmail(); + } + emailRow.appendChild(new Functions.createElement({ + type: "div", + style: "display:table-cell", + content: "<input name='projectNotifyEmail' value='" + email + "'/>" + })); + + var menuRow = Functions.createElement({ + type: "div", + className: "minerva-menu-row", + style: "display:table-row; margin:10px" + }); + result.appendChild(menuRow); + + var saveProjectButton = Functions.createElement({ + type: "button", + name: "saveProject", + content: '<span class="ui-icon ui-icon-disk"></span> SAVE', + onclick: function () { + return self.onSaveClicked().then(function () { + return self.close(); + }, GuiConnector.alert); + } + }); + var cancelButton = Functions.createElement({ + type: "button", + name: "cancelProject", + content: '<span class="ui-icon ui-icon-cancel"></span> CANCEL', + onclick: function () { + return self.close(); + } + }); + menuRow.appendChild(saveProjectButton); + menuRow.appendChild(cancelButton); + + return result; + +}; + +EditProjectDialog.prototype.createMapsTab = function (tabMenuDiv, tabContentDiv) { + var self = this; + self.addTab({ + tabMenuDiv: tabMenuDiv, + tabContentDiv: tabContentDiv, + name: "MAPS", + id: self.getProject().getProjectId() + "_maps_tab", + content: self.createMapsTabContent() + }); +}; + +EditProjectDialog.prototype.createMapsTabContent = function () { + var self = this; + var result = Functions.createElement({ + type: "div" + }); + result.appendChild(self._createMapsTable()); + return result; +}; + +EditProjectDialog.prototype._createMapsTable = function () { + var self = this; + + var result = Functions.createElement({ + type: "div", + style: "margin-top:10px;" + }); + + var mapsTable = Functions.createElement({ + type: "table", + name: "mapsTable", + className: "display", + style: "width:100%" + }); + result.appendChild(mapsTable); + + $(mapsTable).DataTable({ + fnRowCallback: function (nRow, aData) { + nRow.setAttribute('id', "map-" + aData[0]); + }, + columns: [{ + title: 'Id' + }, { + title: 'Name' + }, { + title: 'Default center x' + }, { + title: 'Default center y' + }, { + title: 'Default zoom level' + }, { + title: 'Update' + }] + }); + + $(mapsTable).on("click", "[name='saveMap']", function () { + var button = this; + GuiConnector.showProcessing("Updating"); + return self.saveMap($(button).attr("data")).then(function () { + GuiConnector.hideProcessing(); + GuiConnector.info("Map updated successfully"); + }, function (error) { + GuiConnector.hideProcessing(); + GuiConnector.alert(error); + }); + }); + + return result; +}; + + +EditProjectDialog.prototype.createOverlaysTab = function (tabMenuDiv, tabContentDiv) { + var self = this; + self.addTab({ + tabMenuDiv: tabMenuDiv, + tabContentDiv: tabContentDiv, + name: "OVERLAYS", + id: self.getProject().getProjectId() + "_overlays_tab", + content: self.createOverlaysTabContent() + }); +}; + +EditProjectDialog.prototype.createOverlaysTabContent = function () { + var self = this; + var result = Functions.createElement({ + type: "div" + }); + result.appendChild(self._createOverlayTable()); + return result; +}; + +EditProjectDialog.prototype._createOverlayTable = function () { + var self = this; + + var result = Functions.createElement({ + type: "div", + style: "margin-top:10px;" + }); + + var overlaysTable = Functions.createElement({ + type: "table", + name: "overlaysTable", + className: "display", + style: "width:100%" + }); + result.appendChild(overlaysTable); + + $(overlaysTable).DataTable({ + fnRowCallback: function (nRow, aData) { + nRow.setAttribute('id', "overlay-" + aData[0]); + }, + columns: [{ + title: 'Id' + }, { + title: 'Name' + }, { + title: 'Description' + }, { + title: 'Public' + }, { + title: 'Default' + }, { + title: 'Owner' + }, { + title: 'Data' + }, { + title: 'Update' + }, { + title: 'Remove' + }], + dom: '<"minerva-datatable-toolbar">frtip', + initComplete: function () { + $("div.minerva-datatable-toolbar", $(result)).html('<button name="addOverlay">Add overlay</button>'); + } + + }); + + $(overlaysTable).on("click", "[name='removeOverlay']", function () { + var button = this; + return self.removeOverlay($(button).attr("data")).then(null, GuiConnector.alert); + }); + + $(overlaysTable).on("click", "[name='saveOverlay']", function () { + var button = this; + GuiConnector.showProcessing("Updating"); + return self.saveOverlay($(button).attr("data")).then(function () { + GuiConnector.hideProcessing(); + GuiConnector.info("Overlay updated successfully"); + }, function (error) { + GuiConnector.hideProcessing(); + GuiConnector.alert(error); + }); + }); + + $(overlaysTable).on("click", "[name='downloadSource']", function () { + var button = this; + return ServerConnector.getOverlaySourceDownloadUrl({ + overlayId: $(button).attr("data") + }).then(function (url) { + return self.downloadFile(url); + }).then(null, GuiConnector.alert); + }); + + $(result).on("click", "[name='addOverlay']", function () { + return self.openAddOverlayDialog(); + }); + + return result; +}; + +EditProjectDialog.prototype.createUsersTab = function (tabMenuDiv, tabContentDiv) { + var self = this; + self.addTab({ + tabMenuDiv: tabMenuDiv, + tabContentDiv: tabContentDiv, + name: "USERS", + id: self.getProject().getProjectId() + "_users_tab", + content: self.createUsersTabContent() + }); +}; + +EditProjectDialog.prototype.createUsersTabContent = function () { + var self = this; + + var result = Functions.createElement({ + type: "div", + style: "margin-top:10px;" + }); + + var usersTable = Functions.createElement({ + type: "table", + name: "usersTable", + className: "display", + style: "width:100%" + }); + result.appendChild(usersTable); + + $(usersTable).on("click", "[name='saveUser']", function () { + var button = this; + GuiConnector.showProcessing("Updating"); + return self.saveUser($(button).attr("data")).then(function () { + GuiConnector.hideProcessing(); + GuiConnector.info("User updated successfully"); + }, function (error) { + GuiConnector.hideProcessing(); + GuiConnector.alert(error); + }); + }); + + return result; +}; + +EditProjectDialog.prototype.createUserPrivilegeColumns = function () { + var self = this; + + if (self._userPrivilegeColumns !== undefined) { + return Promise.resolve(self._userPrivilegeColumns); + } + + return ServerConnector.getConfiguration().then(function (configuration) { + self._userPrivilegeColumns = [{ + title: "Name" + }]; + var privilegeTypes = configuration.getPrivilegeTypes(); + for (var i = 0; i < privilegeTypes.length; i++) { + var type = privilegeTypes[i]; + if (type.getObjectType() === "Project") { + self._userPrivilegeColumns.push({ + "title": type.getCommonName(), + privilegeType: type + }); + } + } + self._userPrivilegeColumns.push({ + "title": "Update" + }); + return self._userPrivilegeColumns; + }); + +}; + +EditProjectDialog.prototype.init = function () { + var self = this; + return self.initUsersTab().then(function () { + return self.refreshUsers(); + }).then(function () { + return self.refreshMaps(); + }).then(function () { + return self.refreshOverlays(); + }).then(function () { + $(window).trigger('resize'); + }); +}; + +EditProjectDialog.prototype.initUsersTab = function () { + var self = this; + + var usersTable = $("[name=usersTable]", self.getElement())[0]; + + return self.createUserPrivilegeColumns().then(function (columns) { + $(usersTable).DataTable({ + columns: columns + }); + }); +}; + +EditProjectDialog.prototype.refreshOverlays = function () { + var self = this; + return ServerConnector.getOverlays({ + projectId: self.getProject().getProjectId() + }).then(function (overlays) { + return self.setOverlays(overlays); + }); +}; + +EditProjectDialog.prototype.refreshMaps = function () { + var self = this; + return ServerConnector.getModels(self.getProject().getProjectId()).then(function (maps) { + return self.setMaps(maps); + }); +}; + +EditProjectDialog.prototype.refreshUsers = function () { + var self = this; + return ServerConnector.getUsers().then(function (users) { + return self.setUsers(users); + }); +}; + +EditProjectDialog.prototype.setOverlays = function (overlays) { + var self = this; + self._overlayById = []; + return ServerConnector.getUsers().then(function (users) { + var dataTable = $($("[name='overlaysTable']", self.getElement())[0]).DataTable(); + var data = []; + for (var i = 0; i < overlays.length; i++) { + var overlay = overlays[i]; + self._overlayById[overlay.getId()] = overlay; + var rowData = self.overlayToTableRow(overlay, users); + data.push(rowData); + } + dataTable.clear().rows.add(data).draw(); + }); +}; + +EditProjectDialog.prototype.setMaps = function (maps) { + var self = this; + self._mapsById = []; + var dataTable = $($("[name='mapsTable']", self.getElement())[0]).DataTable(); + var data = []; + for (var i = 0; i < maps.length; i++) { + var map = maps[i]; + self._mapsById[map.getId()] = map; + var rowData = self.mapToTableRow(map); + data.push(rowData); + } + dataTable.clear().rows.add(data).draw(); +}; + +EditProjectDialog.prototype.setUsers = function (users) { + var self = this; + self._userByLogin = []; + return self.createUserPrivilegeColumns().then(function (columns) { + var dataTable = $($("[name='usersTable']", self.getElement())[0]).DataTable(); + var data = []; + for (var i = 0; i < users.length; i++) { + var user = users[i]; + self._userByLogin[user.getLogin()] = user; + var rowData = self.userToTableRow(user, columns); + data.push(rowData); + } + dataTable.clear().rows.add(data).draw(); + }); +}; + +EditProjectDialog.prototype.userToTableRow = function (user, columns) { + var self = this; + var row = []; + var login = user.getLogin(); + + row[0] = user.getName() + " " + user.getSurname() + " (" + login + ")"; + for (var i = 1; i < columns.length; i++) { + var column = columns[i]; + if (column.privilegeType !== undefined) { + if (column.privilegeType.getValueType() === "boolean") { + var checked = ''; + if (user.hasPrivilege(column.privilegeType, self.getProject().getId())) { + checked = 'checked'; + } + row[i] = "<input type='checkbox' name='privilege-" + login + "' data='" + column.privilegeType.getName() + "' " + + checked + "/>"; + } else { + throw new Error("Unsupported type: " + column.privilegeType.getValueType()); + } + } + } + + row.push("<button name='saveUser' data='" + login + "'>SAVE</button>"); + + return row; +}; + +EditProjectDialog.prototype.overlayToTableRow = function (overlay, users) { + var row = []; + var id = overlay.getId(); + var creatorSelect; + if (overlay.getCreator() === "") { + creatorSelect = "<select name='creator-" + id + "' value=''>"; + } else { + creatorSelect = "<select name='creator-" + id + "'>"; + } + + creatorSelect += "<option value='' >---</option>"; + for (var i = 0; i < users.length; i++) { + var selected = ""; + var user = users[i]; + if (overlay.getCreator() === user.getLogin()) { + selected = "selected"; + } else { + selected = ""; + } + + creatorSelect += "<option value='" + user.getLogin() + "' " + selected + ">" + user.getLogin() + "(" + + user.getName() + " " + user.getSurname() + ")</option>"; + } + creatorSelect += "</select>"; + + var checked = ''; + if (overlay.getPublicOverlay()) { + checked = "checked"; + } + var publicOverlayCheckbox = "<input type='checkbox' name='publicOverlay-" + id + "' " + checked + "/>"; + + checked = ''; + if (overlay.isDefaultOverlay()) { + checked = "checked"; + } + var defaultOverlayCheckbox = "<input type='checkbox' name='defaultOverlay-" + id + "' " + checked + "/>"; + + var downloadSourceButton; + if (overlay.getInputDataAvailable()) { + downloadSourceButton = "<button name='downloadSource' data='" + id + "'>" + + "<span class='ui-icon ui-icon-arrowthickstop-1-s'></span>" + "</button>"; + } else { + downloadSourceButton = "N/A"; + } + + row[0] = id; + row[1] = "<input name='name-" + id + "' value='" + overlay.getName() + "'/>"; + row[2] = "<input name='description-" + id + "' value='" + overlay.getDescription() + "'/>"; + row[3] = publicOverlayCheckbox; + row[4] = defaultOverlayCheckbox; + row[5] = creatorSelect; + row[6] = downloadSourceButton; + row[7] = "<button name='saveOverlay' data='" + id + "'>SAVE</button>"; + row[8] = "<button name='removeOverlay' data='" + id + "'>REMOVE</button>"; + + return row; +}; + +function getValueOrEmpty(value) { + if (value !== undefined && value !== null) { + return value; + } else { + return ""; + } +} + +EditProjectDialog.prototype.mapToTableRow = function (map, users) { + var row = []; + var id = map.getId(); + var centerX = getValueOrEmpty(map.getDefaultCenterX()); + var centerY = getValueOrEmpty(map.getDefaultCenterY()); + var zoomLevel = getValueOrEmpty(map.getDefaultZoomLevel()); + row[0] = id; + row[1] = map.getName(); + row[2] = "<input name='defaultCenterX-" + id + "' value='" + centerX + "'/>"; + row[3] = "<input name='defaultCenterY-" + id + "' value='" + centerY + "'/>"; + row[4] = "<input name='defaultZoomLevel-" + id + "' value='" + zoomLevel + "'/>"; + row[5] = "<button name='saveMap' data='" + id + "'>SAVE</button>"; + + return row; +}; + +EditProjectDialog.prototype.destroy = function () { + var self = this; + var div = self.getElement(); + var usersTable = $("[name=usersTable]", self.getElement())[0]; + var overlaysTable = $("[name=overlaysTable]", self.getElement())[0]; + var mapsTable = $("[name=mapsTable]", self.getElement())[0]; + if ($.fn.DataTable.isDataTable(usersTable)) { + $(usersTable).DataTable().destroy(); + } + if ($.fn.DataTable.isDataTable(overlaysTable)) { + $(overlaysTable).DataTable().destroy(); + } + if ($.fn.DataTable.isDataTable(mapsTable)) { + $(mapsTable).DataTable().destroy(); + } + + if ($(div).hasClass("ui-dialog-content")) { + $(div).dialog("destroy"); + } + if (self._addOverlayDialog !== undefined) { + self._addOverlayDialog.destroy(); + delete self._addOverlayDialog; + } +}; + +EditProjectDialog.prototype.open = function () { + var self = this; + var div = self.getElement(); + if (!$(div).hasClass("ui-dialog-content")) { + $(div).dialog({ + title: self.getProject().getProjectId(), + width: window.innerWidth / 2, + height: window.innerHeight / 2 + }); + } + $(div).dialog("open"); +}; + +var prepareMiriamData = function (type, resource) { + if (resource === "" || resource === undefined || resource === null) { + return null; + } else { + return new Annotation({ + type: type, + resource: resource + }); + } +}; + +EditProjectDialog.prototype.onSaveClicked = function () { + var self = this; + var project = self.getProject(); + var element = self.getElement(); + project.setName($("[name='projectName']", element)[0].value); + project.setVersion($("[name='projectVersion']", element)[0].value); + project.setNotifyEmail($("[name='projectNotifyEmail']", element)[0].value); + var organism = prepareMiriamData("TAXONOMY", $("[name='projectOrganism']", element)[0].value); + project.setOrganism(organism); + var disease = prepareMiriamData("MESH_2012", $("[name='projectDisease']", element)[0].value); + project.setDisease(disease); + return ServerConnector.updateProject(project); +}; +EditProjectDialog.prototype.close = function () { + var self = this; + $(self.getElement()).dialog("close"); +}; + +EditProjectDialog.prototype.saveOverlay = function (overlayId) { + var self = this; + var overlay = self._overlayById[overlayId]; + overlay.setName($("[name='name-" + overlayId + "']", self.getElement())[0].value); + overlay.setDescription($("[name='description-" + overlayId + "']", self.getElement())[0].value); + overlay.setPublicOverlay($("[name='publicOverlay-" + overlayId + "']", self.getElement())[0].checked); + overlay.setDefaultOverlay($("[name='defaultOverlay-" + overlayId + "']", self.getElement())[0].checked); + overlay.setCreator($("[name='creator-" + overlayId + "']", self.getElement())[0].value); + + return ServerConnector.updateOverlay(overlay); +}; + +EditProjectDialog.prototype.saveMap = function (mapId) { + var self = this; + var map = self._mapsById[mapId]; + map.setDefaultCenterX($("[name='defaultCenterX-" + mapId + "']", self.getElement())[0].value); + map.setDefaultCenterY($("[name='defaultCenterY-" + mapId + "']", self.getElement())[0].value); + map.setDefaultZoomLevel($("[name='defaultZoomLevel-" + mapId + "']", self.getElement())[0].value); + + return ServerConnector.updateModel({projectId: self.getProject().getProjectId(), model: map}); +}; + +EditProjectDialog.prototype.saveUser = function (login) { + var self = this; + var checkboxes = $("[name='privilege-" + login + "']", self.getElement()); + var privileges = {}; + for (var i = 0; i < checkboxes.length; i++) { + var checkbox = checkboxes[i]; + var privilege = {}; + privilege[self.getProject().getId()] = checkbox.checked; + privileges[$(checkbox).attr("data")] = privilege; + } + + return ServerConnector.updateUserPrivileges({ + user: self._userByLogin[login], + privileges: privileges + }); +}; + +EditProjectDialog.prototype.removeOverlay = function (overlayId) { + var self = this; + return ServerConnector.removeOverlay({ + overlayId: overlayId + }).then(function () { + return self.refreshOverlays(); + }); +}; + +EditProjectDialog.prototype.openAddOverlayDialog = function () { + var self = this; + if (self._addOverlayDialog !== undefined) { + self._addOverlayDialog.destroy(); + } + self._addOverlayDialog = new AddOverlayDialog({ + project: self.getProject(), + customMap: null, + element: document.createElement("div") + }); + self._addOverlayDialog.addListener("onAddOverlay", function () { + return self.refreshOverlays(); + }); + return self._addOverlayDialog.init().then(function () { + return self._addOverlayDialog.open(); + }); +}; + +module.exports = EditProjectDialog; diff --git a/frontend-js/src/main/js/map/AbstractCustomMap.js b/frontend-js/src/main/js/map/AbstractCustomMap.js index d67620f0bd..78f7c7216d 100644 --- a/frontend-js/src/main/js/map/AbstractCustomMap.js +++ b/frontend-js/src/main/js/map/AbstractCustomMap.js @@ -1,1053 +1,1068 @@ -"use strict"; - -var Promise = require("bluebird"); - -var logger = require('../logger'); -var functions = require('../Functions'); - -var AliasInfoWindow = require('./window/AliasInfoWindow'); -var AliasSurface = require('./surface/AliasSurface'); -var GuiConnector = require('../GuiConnector'); -var IdentifiedElement = require('./data/IdentifiedElement'); -var ObjectWithListeners = require('../ObjectWithListeners'); -var PointInfoWindow = require('./window/PointInfoWindow'); -var ReactionInfoWindow = require('./window/ReactionInfoWindow'); -var ReactionSurface = require('./surface/ReactionSurface'); - -var MarkerSurfaceCollection = require('./marker/MarkerSurfaceCollection'); - -/** - * Default constructor. - */ -function AbstractCustomMap(model, options) { - // call super constructor - ObjectWithListeners.call(this); - - if (model === undefined) { - throw Error("Model must be defined"); - } - - this.setElement(options.getElement()); - this.setConfiguration(options.getConfiguration()); - - this.setModel(model); - - // this array contains elements that are presented on a specific layout (set - // of google map object representing lines/areas that are associated with - // layout) - this.selectedLayoutOverlays = []; - - // following fields are used in conversion between x,y coordinates and lat,lng - // coordinates - this.pixelOrigin_ = new google.maps.Point(this.getTileSize() / 2, this.getTileSize() / 2); - this.pixelsPerLonDegree_ = this.getTileSize() / 360; - this.pixelsPerLonRadian_ = this.getTileSize() / (2 * Math.PI); - - /* jshint bitwise: false */ - this.zoomFactor = this.getPictureSize() / (this.getTileSize() / (1 << this.getMinZoom())); - - // array with info windows for Marker pointing to aliases - this._aliasInfoWindow = []; - - // array with info windows for Marker pointing to points - this._pointInfoWindow = []; - - // array with info windows for reactions - this._reactionInfoWindow = []; - - this._markerSurfaceCollection = new MarkerSurfaceCollection({map: this}); - - // this is google.maps.drawing.DrawingManager that will allow user to draw - // elements in the client - this._drawingManager = null; - - // this is the polygon that was selected (clicked) last time on the map - this._selectedArea = null; - - // markers should be optimized by default, - // however, for testing purpose this function could be turned of by javascript - // the other possibility is that it should be off in the touch mode - // (bigButtons=true) - this._markerOptimization = options.isMarkerOptimization(); - - this._bigLogo = options.isBigLogo(); - this._customTouchInterface = options.isCustomTouchInterface(); - - this.setDebug(options.isDebug()); -} - -// define super constructor -AbstractCustomMap.prototype = Object.create(ObjectWithListeners.prototype); -AbstractCustomMap.prototype.constructor = AbstractCustomMap; - - -AbstractCustomMap.prototype.getMarkerSurfaceCollection = function () { - return this._markerSurfaceCollection; -}; -/** - * Assigns layouts with images to the google map (which set of images should be - * used by google maps api for which layout). - * - */ -AbstractCustomMap.prototype.setupLayouts = function () { - for (var i = 0; i < this.getLayouts().length; i++) { - var layout = this.getLayouts()[i]; - var typeOptions = this.createTypeOptions(layout); - var mapType = new google.maps.ImageMapType(typeOptions); - this.getGoogleMap().mapTypes.set(layout.getId().toString(), mapType); - } - this.getGoogleMap().setMapTypeId(this.getLayouts()[0].getId().toString()); -}; - -/** - * Creates general google maps options used in this map. - * - */ -AbstractCustomMap.prototype.createMapOptions = function () { - var self = this; - - var centerPoint = this.getModel().getCenterLatLng(); - - // if we have coordinate data stored in session then restore it - var point = ServerConnector.getSessionData(self.getProject()).getCenter(self.getModel()); - if (point !== undefined) { - centerPoint = self.fromPointToLatLng(point); - } - - var result = { - center: centerPoint, - rotateControl: true, - panControl: true, - mapTypeControl: false, - zoom: this.getMinZoom(), - streetViewControl: false, - - panControlOptions: { - position: google.maps.ControlPosition.LEFT_TOP - }, - zoomControlOptions: { - style: google.maps.ZoomControlStyle.LARGE, - position: google.maps.ControlPosition.LEFT_TOP - } - - }; - return result; -}; - -/** - * Create google maps configuration options object for a specific layout. - * - * @param param - * object with information about layout - */ -AbstractCustomMap.prototype.createTypeOptions = function (param) { - var self = this; - var result = { - // this is a function that will retrieve valid url to png images for - // tiles on different zoom levels - getTileUrl: function (coord, zoom) { - // we have 1 tile on self.getConfiguration().MIN_ZOOM and - // therefore must limit tails according to this - /* jshint bitwise: false */ - var tileRange = 1 << (zoom - self.getMinZoom()); - if (coord.y < 0 || coord.y >= tileRange || coord.x < 0 || coord.x >= tileRange) { - return null; - } - var addr = "../map_images/" + param.getDirectory() + "/" + zoom + "/" + coord.x + "/" + coord.y + ".PNG"; - return addr; - }, - tileSize: new google.maps.Size(this.getTileSize(), this.getTileSize()), - maxZoom: this.getMaxZoom(), - minZoom: this.getMinZoom(), - radius: 360, - name: param.name - }; - return result; -}; - -/** - * Sets maximum zoom level on google map. - * - */ -AbstractCustomMap.prototype.setMaxZoomLevel = function () { - this.getGoogleMap().setZoom(this.getMaxZoom()); -}; - -/** - * Returns mouse coordinate on the map in lat,lng system. - * - */ -AbstractCustomMap.prototype.getMouseLatLng = function () { - // this method is tricky, the main problem is how to map mouse coordinate to - // google map - // to do that we need a point of reference in both systems (x,y and lat,lng) - // this will be center of the map that is currently visible - // next, we will have to find distance from this point in x,y coordinates and - // transform it to lat,lng - - var self = this; - // center point visible on the map - var latLngCoordinates = self.getGoogleMap().getCenter(); - var point = self.fromLatLngToPoint(latLngCoordinates); - - // this is magic :) - // find offset of the div where google map is located related to top left - // corner of the browser - var el = self.getGoogleMap().getDiv(); - for (var lx = 0, ly = 0; el !== null && el !== undefined; lx += el.offsetLeft, ly += el.offsetTop, el = el.offsetParent) { - } - - var offset = { - x: lx, - y: ly - }; - - var center = { - x: self.getGoogleMap().getDiv().offsetWidth / 2, - y: self.getGoogleMap().getDiv().offsetHeight / 2 - }; - - // and now find how far from center point we are (in pixels) - var relativeDist = { - x: (GuiConnector.xPos - offset.x - center.x), - y: (GuiConnector.yPos - offset.y - center.y) - }; - - // transform pixels into x,y distance - var pointDist = self.fromPixelsToPoint(relativeDist, self.getGoogleMap().getZoom()); - - // now we have offset in x,y and center point on the map in x,y, so we have - // final position in x,y - var newCoordinates = new google.maps.Point(point.x + pointDist.x, point.y + pointDist.y); - - // change it to lat,lng - var latLngResult = self.fromPointToLatLng(newCoordinates); - - return latLngResult; -}; - -/** - * Transform distance (coordinates) in pixels into x,y distance on the map. - * - * @param pixels - * x,y distance in pixels - * @param zoomLevel - * at which zoom level this pixels where measured - * - */ -AbstractCustomMap.prototype.fromPixelsToPoint = function (pixels, zoomLevel) { - var zoomScale = this.getPictureSize() / (Math.pow(2, zoomLevel - this.getMinZoom()) * this.getTileSize()); - var pointX = pixels.x * zoomScale; - var pointY = pixels.y * zoomScale; - return new google.maps.Point(pointX, pointY); -}; - -/** - * Transforms coordinates on the map from google.maps.LatLng to - * google.maps.Point - * - * @param latLng - * in lat,lng format - * @param coordinates in x,y format - * - */ -AbstractCustomMap.prototype.fromLatLngToPoint = function (latLng) { - var me = this; - var point = new google.maps.Point(0, 0); - var origin = me.pixelOrigin_; - - point.x = origin.x + latLng.lng() * me.pixelsPerLonDegree_; - - // Truncating to 0.9999 effectively limits latitude to 89.189. This is - // about a third of a tile past the edge of the world tile. - var siny = functions.bound(Math.sin(functions.degreesToRadians(latLng.lat())), -0.9999, 0.9999); - point.y = origin.y + 0.5 * Math.log((1 + siny) / (1 - siny)) * -me.pixelsPerLonRadian_; - - // rescale the point (all computations are done assuming that we work on - // TILE_SIZE square) - point.x *= me.zoomFactor; - point.y *= me.zoomFactor; - return point; -}; - -/** - * Transforms coordinates on the map from google.maps.Point to - * google.maps.LatLng - * - * @param point - * coordinates in standard x,y format - * @return coordinates in lat,lng format - */ -AbstractCustomMap.prototype.fromPointToLatLng = function (point) { - var me = this; - - // rescale the point (all computations are done assuming that we work on - // TILE_SIZE square) - var p = new google.maps.Point(point.x / me.zoomFactor, point.y / me.zoomFactor); - var origin = me.pixelOrigin_; - var lng = (p.x - origin.x) / me.pixelsPerLonDegree_; - var latRadians = (p.y - origin.y) / -me.pixelsPerLonRadian_; - var lat = functions.radiansToDegrees(2 * Math.atan(Math.exp(latRadians)) - Math.PI / 2); - return new google.maps.LatLng(lat, lng); -}; - -/** - * Transforms google.maps.LatLng to tile coordinate (for instance on which tile - * mouse clicked). - * - * - * @param latLng - * coordinates in latlng format - * @param z - * zoom level at which we want to find coordinates of tile - * @return coordinates of a tile - */ -AbstractCustomMap.prototype.latLngToTile = function (latLng, z) { - var worldCoordinate = this.fromLatLngToPoint(latLng); - var pixelCoordinate = new google.maps.Point(worldCoordinate.x * Math.pow(2, z), worldCoordinate.y * Math.pow(2, z)); - var tileCoordinate = new google.maps.Point(Math.floor(pixelCoordinate.x / this.getTileSize()), Math - .floor(pixelCoordinate.y / this.getTileSize())); - return tileCoordinate; -}; - -/** - * Register events responsible for click events - */ -AbstractCustomMap.prototype.registerMapClickEvents = function () { - - // find top map (CustomMap) - // - var customMap = this.getTopMap(); - - var self = this; - - // search event - google.maps.event.addListener(this.getGoogleMap(), 'click', function (mouseEvent) { - var point = self.fromLatLngToPoint(mouseEvent.latLng); - var searchDb = customMap.getOverlayByName('search'); - if (searchDb !== undefined) { - return searchDb.searchByCoordinates({ - modelId: self.getModel().getId(), - coordinates: point, - zoom: self.getGoogleMap().getZoom() - }).then(null, GuiConnector.alert); - } else { - logger.warn("Search is impossible because search db is not present"); - } - }); - - // select last clicked map - google.maps.event.addListener(this.getGoogleMap(), 'click', function (mouseEvent) { - customMap.setActiveSubmapId(self.getId()); - customMap.setActiveSubmapClickCoordinates(self.fromLatLngToPoint(mouseEvent.latLng)); - }); - - // select last clicked map - google.maps.event.addListener(this.getGoogleMap(), 'rightclick', function (mouseEvent) { - customMap.setActiveSubmapId(self.getId()); - customMap.setActiveSubmapClickCoordinates(self.fromLatLngToPoint(mouseEvent.latLng)); - }); - - // prepare for image export - google.maps.event.addListener(this.getGoogleMap(), 'rightclick', function () { - var bounds = self.getGoogleMap().getBounds(); - var polygon = ""; - - var ne = bounds.getNorthEast(); - var sw = bounds.getSouthWest(); - - var westLng = sw.lng(); - var eastLng = ne.lng(); - - if (westLng > 0) { - westLng = -180; - } - if (eastLng - westLng > 90) { - eastLng = -90; - } else if (eastLng > -90) { - eastLng = -90; - } - - polygon += ne.lat() + "," + westLng + ";"; - polygon += ne.lat() + "," + eastLng + ";"; - polygon += sw.lat() + "," + eastLng + ";"; - polygon += sw.lat() + "," + westLng + ";"; - self.getTopMap().setSelectedPolygon(polygon); - }); - - // context menu event - google.maps.event.addListener(this.getGoogleMap(), 'rightclick', function () { - self.getTopMap().getContextMenu().open(GuiConnector.xPos, GuiConnector.yPos, new Date().getTime()); - }); -}; - -/** - * It toggle drawing manager used on the map: if it's on then it will turn it - * off, if it's off it will turn it on - * - */ -AbstractCustomMap.prototype._turnOnOffDrawing = function () { - if (this.isDrawingOn()) { - this.turnOffDrawing(); - } else { - this.turnOnDrawing(); - } -}; - -/** - * Checks if the drawing manager for the map is on. - * - */ -AbstractCustomMap.prototype.isDrawingOn = function () { - return this._drawingManager !== null; -}; - -/** - * Turns on drawing manager on the map. - */ -AbstractCustomMap.prototype.turnOnDrawing = function () { - if (this.isDrawingOn()) { - logger.warn("Trying to turn on drawing manager, but it is already available."); - return; - } - var customMap = this.getTopMap(); - var self = this; - this._drawingManager = new google.maps.drawing.DrawingManager({ - drawingMode: google.maps.drawing.OverlayType.MARKER, - drawingControl: true, - drawingControlOptions: { - position: google.maps.ControlPosition.TOP_CENTER, - drawingModes: [google.maps.drawing.OverlayType.POLYGON] - }, - markerOptions: { - icon: 'images/beachflag.png' - }, - circleOptions: { - fillColor: '#ffff00', - fillOpacity: 1, - strokeWeight: 5, - clickable: false, - editable: true, - zIndex: 1 - } - }); - this._drawingManager.setMap(this.getGoogleMap()); - this._drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON); - - google.maps.event.addListener(this._drawingManager, 'overlaycomplete', function (e) { - if (e.type !== google.maps.drawing.OverlayType.MARKER) { - // Switch back to non-drawing mode after drawing a shape. - self._drawingManager.setDrawingMode(null); - - // Add an event listener that selects the newly-drawn shape when the - // user mouses down on it. - var newShape = e.overlay; - newShape.type = e.type; - google.maps.event.addListener(newShape, 'rightclick', function (e) { - // select map that was clicked - customMap.setActiveSubmapId(self.getId()); - - self.setSelectedArea(newShape); - newShape.position = e.latLng; - - self.getTopMap().setSelectedPolygon(self.areaToString(newShape)); - - self.getTopMap().getSelectionContextMenu().open(GuiConnector.xPos, GuiConnector.yPos, new Date().getTime()); - }); - } - }); - -}; - -/** - * Sets selectedArea on this map. - * - */ -AbstractCustomMap.prototype.setSelectedArea = function (area) { - this._selectedArea = area; -}; - -/** - * Returns selectedArea on this map. - * - */ -AbstractCustomMap.prototype.getSelectedArea = function () { - return this._selectedArea; -}; - -/** - * Transforms google.maps.Polygon into string with coordinates. - * - */ -AbstractCustomMap.prototype.areaToString = function (area) { - var len = area.getPath().length; - var path = area.getPath(); - var res = ""; - for (var i = 0; i < len; i++) { - var latLng = path.getAt(i); - res += latLng.lat() + "," + latLng.lng() + ";"; - } - return res; -}; - -/** - * Removes selected area (polygon) from the map. - */ -AbstractCustomMap.prototype._removeSelection = function () { - if (this._selectedArea) { - this._selectedArea.setMap(null); - this._selectedArea = null; - } else { - logger.warn("Cannot remove selected area. No area was selected"); - } -}; - -/** - * Turns off drawing manager on the map. - */ -AbstractCustomMap.prototype.turnOffDrawing = function () { - if (this.isDrawingOn()) { - this._drawingManager.setMap(null); - this._drawingManager = null; - } else { - logger.warn("Trying to turn off drawing manager, but it is not available."); - } -}; - -/** - * Returns top map. TODO implementation of this function should be probably - * moved to Submap and CustomMap classes and here only abstract function - * definition - * - * @returns {CustomMap} - */ -AbstractCustomMap.prototype.getTopMap = function () { - logger.fatal("Not implemented"); -}; - -/** - * Method that should be called when number of layouts to visualize changed to - * modify boundaries of the elements to visualize. When few layouts are - * visualized at the same time then index contains information where this new - * layout is placed in the list (starting from 0) and length contains - * information how many layouts we visualize in total. - * - * @param layoutId - * identifier of a layout - * @param index - * when visualizing more than one layout at the same time index - * contains information at which position in the list this layout is - * placed - * @param length - * number of layouts that are currently visualized - */ -AbstractCustomMap.prototype._resizeSelectedLayout = function (layoutId, index, length) { - var self = this; - return new Promise(function (resolve) { - // if map is not initialized then don't perform this operation - if (!self.initialized) { - logger.debug("Model " + self.getId() + " not initialized"); - resolve(); - } - logger.debug("Resize layout: " + layoutId); - // start ratio - var startX = index * (1.0 / length); - // end ratio - var endX = (index + 1) * (1.0 / length); - - for (var i = 0; i < self.selectedLayoutOverlays[layoutId].length; i++) { - self.selectedLayoutOverlays[layoutId][i].setBoundsForAlias(startX, endX); - } - resolve(); - }); -}; - -/** - * Shows all elements from a given layout. When few layouts are visualized at - * the same time then index contains information where this new layout is placed - * in the list (starting from 0) and length contains information how many - * layouts we visualize in total. - * - * @param layoutId - * identifier of a layout - * @param index - * when visualizing more than one layout at the same time index - * contains information at which position in the list this layout is - * placed - * @param length - * number of layouts that are currently visualized - */ -AbstractCustomMap.prototype._showSelectedLayout = function (layoutId, index, length) { - var self = this; - // if map is not initialized then don't perform this operation - return new Promise(function (resolve, reject) { - if (!self.initialized) { - logger.debug("Model " + self.getId() + " not initialized"); - resolve(); - return; - } else { - logger.debug("Showing model " + self.getId()); - } - - self.selectedLayoutOverlays[layoutId] = []; - - // start ratio - var startX = index * (1.0 / length); - // end ratio - var endX = (index + 1) * (1.0 / length); - - var layoutAliases; - var layoutReactions; - - return self.getTopMap().getModel().getLayoutDataById(layoutId).then(function (layout) { - layoutAliases = layout.getAliases(); - layoutReactions = layout.getReactions(); - - var identifiedElements = []; - var i; - for (i = 0; i < layoutAliases.length; i++) { - if (layoutAliases[i].getModelId() === self.getId()) { - identifiedElements.push(new IdentifiedElement(layoutAliases[i])); - } - } - var reactionIds = []; - for (i = 0; i < layoutReactions.length; i++) { - if (layoutReactions[i].getModelId() === self.getId()) { - identifiedElements.push(new IdentifiedElement(layoutReactions[i])); - } - } - return self.getModel().getByIdentifiedElements(identifiedElements, false); - }).then(function () { - return Promise.each(layoutAliases, function (layoutAlias) { - if (layoutAlias.getModelId() === self.getId()) { - var surface; - var element; - return self.getModel().getAliasById(layoutAlias.getId()).then(function (aliasData) { - element = new IdentifiedElement(aliasData); - return AliasSurface.create({ - overlayAlias: layoutAlias, - alias: aliasData, - map: self, - startX: startX, - endX: endX, - onClick: [function () { - return self._openInfoWindowForAlias(aliasData, surface.getGoogleMarker()); - }, function () { - return self.getTopMap().callListeners("onBioEntityClick", element); - }] - }); - }).then(function (result) { - surface = result; - self.selectedLayoutOverlays[layoutId].push(surface); - }); - } - }); - }).then(function () { - return Promise.each(layoutReactions, function (layoutReaction) { - if (layoutReaction.getModelId() === self.getId()) { - return self.getModel().getReactionById(layoutReaction.getId()).then(function (reactionData) { - var surface; - var element = new IdentifiedElement(reactionData); - return ReactionSurface.create({ - layoutReaction: layoutReaction, - reaction: reactionData, - map: self, - onClick: [function () { - return self._openInfoWindowForReaction(reactionData, surface.getGoogleMarker()); - }, function () { - return self.getTopMap().callListeners("onBioEntityClick", element); - }], - customized: (length === 1) - }).then(function (result) { - surface = result; - self.selectedLayoutOverlays[layoutId].push(surface); - surface.show(); - }); - }); - } - }); - }).then(function () { - resolve(); - }).then(null, reject); - }); -}; - -/** - * Hides all elements from layout. - * - * @param layoutId - * identifier of a layout - */ -AbstractCustomMap.prototype._hideSelectedLayout = function (layoutId) { - // if map is not initialized then don't perform this operation - if (!this.initialized) { - logger.debug("Model " + this.getId() + " not initialized"); - return; - } - for (var i = 0; i < this.selectedLayoutOverlays[layoutId].length; i++) { - this.selectedLayoutOverlays[layoutId][i].setMap(null); - } - this.selectedLayoutOverlays[layoutId] = []; -}; - -/** - * Opens {@link AliasInfoWindow} for given alias. - * - * @param aliasId - * identifier of the alias - */ -AbstractCustomMap.prototype._openInfoWindowForAlias = function (alias, googleMarker) { - var self = this; - - var infoWindow = this.getAliasInfoWindowById(alias.getId()); - if (infoWindow !== null && infoWindow !== undefined) { - if (!infoWindow.isOpened()) { - infoWindow.open(googleMarker); - } else { - logger.warn("Info window for alias: " + alias.getId() + " is already opened"); - } - return Promise.resolve(infoWindow); - } else { - self._aliasInfoWindow[alias.getId()] = new AliasInfoWindow({ - alias: alias, - map: self, - marker: googleMarker, - }); - return self._aliasInfoWindow[alias.getId()].init(); - } -}; - -/** - * Returns promise of a list of {@link LayoutAlias} information for a given - * {@link Alias} in all currently visualized layouts. - * - * @param aliasId - * identifier of the {@link Alias} - * @returns promise of an {Array} with list of {@link LayoutAlias} information - * for a given {@link Alias} in all currently visualized layouts - */ -AbstractCustomMap.prototype.getAliasVisibleLayoutsData = function (aliasId) { - var self = this; - return self.getTopMap().getVisibleDataOverlays().then(function (visibleDataOverlays) { - var result = []; - for (var i = 0; i < visibleDataOverlays.length; i++) { - var layout = visibleDataOverlays[i]; - result.push(layout.getFullAliasById(aliasId)); - } - return Promise.all(result); - }); -}; - -/** - * Refresh content of all {@link AliasInfoWindow} in this map. - */ -AbstractCustomMap.prototype._refreshInfoWindows = function () { - var promises = []; - for (var key in this._pointInfoWindow) { - if (this._pointInfoWindow.hasOwnProperty(key)) { - if (this._pointInfoWindow[key].isOpened()) { - promises.push(this._pointInfoWindow[key].update()); - } - } - } - for (var aliasKey in this._aliasInfoWindow) { - if (this._aliasInfoWindow.hasOwnProperty(aliasKey)) { - if (this._aliasInfoWindow[aliasKey].isOpened()) { - promises.push(this._aliasInfoWindow[aliasKey].update()); - } - } - } - return Promise.all(promises); -}; - - -/** - * Opens {@link ReactionInfoWindow} for given reaction identifier. - * - * @param reactionId - * reaction identifier - */ -AbstractCustomMap.prototype._openInfoWindowForReaction = function (reaction, googleMarker) { - var infoWindow = this.getReactionInfoWindowById(reaction.getId()); - var self = this; - if (infoWindow !== null && infoWindow !== undefined) { - if (!infoWindow.isOpened()) { - infoWindow.open(googleMarker); - } else { - logger.warn("Info window for reaction: " + reaction.getId() + " is already opened"); - } - return Promise.resolve(infoWindow); - } else { - return self.getModel().getReactionById(reaction.getId()).then(function (reaction) { - infoWindow = new ReactionInfoWindow({ - reaction: reaction, - map: self, - marker: googleMarker, - }); - self._reactionInfoWindow[reaction.getId()] = infoWindow; - return infoWindow.init(); - }).then(function () { - return infoWindow; - }); - } -}; - -AbstractCustomMap.prototype._openInfoWindowForPoint = function (pointData, googleMarker) { - var self = this; - - var infoWindow = this.getPointInfoWindowById(pointData.getId()); - if (infoWindow !== null && infoWindow !== undefined) { - if (!infoWindow.isOpened()) { - infoWindow.open(googleMarker); - } else { - logger.warn("Info window for point: " + pointData.getId() + " is already opened"); - } - } else { - infoWindow = new PointInfoWindow({ - point: pointData, - map: self, - marker: googleMarker, - }); - this._pointInfoWindow[pointData.getId()] = infoWindow; - } - return Promise.resolve(infoWindow); -}; - -/** - * Opens {@link AbstractInfoWindow} for a marker. - * - * @param marker - * marker for which we are opening window - */ -AbstractCustomMap.prototype.returnInfoWindowForIdentifiedElement = function (element) { - var markerId = element.getId(); - if (element.getType() === "ALIAS") { - return this.getAliasInfoWindowById(markerId); - } else if (element.getType() === "POINT") { - return this.getPointInfoWindowById(markerId); - } else if (element.getType() === "REACTION") { - return this.getReactionInfoWindowById(markerId); - } else { - throw new Error("Unknown marker type: ", element); - } -}; - -/** - * Returns identifier. - * - * @returns identifier - */ -AbstractCustomMap.prototype.getId = function () { - return this.getModel().getId(); -}; - -AbstractCustomMap.prototype.getConfiguration = function () { - return this._configuration; -}; - -AbstractCustomMap.prototype.setConfiguration = function (configuration) { - this._configuration = configuration; -}; - -AbstractCustomMap.prototype._createMapChangedCallbacks = function () { - var self = this; - var sessionData = ServerConnector.getSessionData(self.getTopMap().getProject()); - // listener for changing zoom level - google.maps.event.addListener(this.getGoogleMap(), 'zoom_changed', function () { - sessionData.setZoomLevel(self.getModel(), self.getGoogleMap().getZoom()); - }); - // listener for changing location of the map (moving left/right/top/bottom - google.maps.event.addListener(this.getGoogleMap(), 'center_changed', function () { - var coord = self.getGoogleMap().getCenter(); - var point = self.fromLatLngToPoint(coord); - sessionData.setCenter(self.getModel(), point); - }); -}; - -/** - * Returns {@link ReactionInfoWindow} for given reaction identifier - * - * @param reactionId - * reaction identifier - * @returns {@link ReactionInfoWindow} for given reaction identifier - */ -AbstractCustomMap.prototype.getReactionInfoWindowById = function (reactionId) { - return this._reactionInfoWindow[reactionId]; -}; - -/** - * Returns {@link AliasInfoWindow} for given alias identifier - * - * @param aliasId - * alias identifier - * @returns {@link AliasInfoWindow} for given alias identifier - */ -AbstractCustomMap.prototype.getAliasInfoWindowById = function (aliasId) { - return this._aliasInfoWindow[aliasId]; -}; - -/** - * Returns {@link PointInfoWindow} for given point identifier - * - * @param pointId - * point identifier - * @returns {@link PointInfoWindow} for given point identifier - */ -AbstractCustomMap.prototype.getPointInfoWindowById = function (pointId) { - return this._pointInfoWindow[pointId]; -}; - -AbstractCustomMap.prototype.getModel = function () { - return this._model; -}; - -AbstractCustomMap.prototype.setModel = function (model) { - this._model = model; -}; - -AbstractCustomMap.prototype.getTileSize = function () { - return this.getModel().getTileSize(); -}; - -AbstractCustomMap.prototype.getMinZoom = function () { - return this.getModel().getMinZoom(); -}; - -AbstractCustomMap.prototype.getMaxZoom = function () { - return this.getModel().getMaxZoom(); -}; - -AbstractCustomMap.prototype.getLayouts = function () { - return this.getModel().getLayouts(); -}; - -AbstractCustomMap.prototype.getPictureSize = function () { - return this.getModel().getPictureSize(); -}; - -/** - * Returns array containing elements that are presented on a specific layout - * (set of google map objects representing lines/areas that are associated with - * layout). - * - * @returns {Array} containing elements that are presented on a specific - * layout (set of google map objects representing lines/areas that are - * associated with layout). - */ -AbstractCustomMap.prototype.getSelectedLayoutOverlays = function () { - return this.selectedLayoutOverlays; -}; - -/** - * Returns google.maps.map object used to representing data. - * - * @returns google.maps.map object used to representing data - */ -AbstractCustomMap.prototype.getGoogleMap = function () { - return this._map; -}; - -/** - * Sets google.maps.map object used to representing data. - * - */ -AbstractCustomMap.prototype.setGoogleMap = function (googleMap) { - this._map = googleMap; -}; - -AbstractCustomMap.prototype.isMarkerOptimization = function () { - return this._markerOptimization; -}; - -AbstractCustomMap.prototype.isBigLogo = function () { - return this._bigLogo; -}; - -AbstractCustomMap.prototype.isCustomTouchInterface = function () { - return this._customTouchInterface; -}; - -AbstractCustomMap.prototype.setDebug = function (debug) { - if (debug !== undefined) { - if (typeof debug !== "boolean") { - logger.warn("param must be boolean"); - } - this._debug = debug; - } -}; - -AbstractCustomMap.prototype.isDebug = function () { - return this._debug === true; -}; - -AbstractCustomMap.prototype.getTopLeftLatLng = function () { - return this.getModel().getTopLeftLatLng(); -}; - -AbstractCustomMap.prototype.getBottomRightLatLng = function () { - return this.getModel().getBottomRightLatLng(); -}; - -AbstractCustomMap.prototype.getElement = function () { - return this._element; -}; -AbstractCustomMap.prototype.setElement = function (element) { - this._element = element; -}; - -/** - * Sets center point for google maps. - * - * @param coordinates - * new center point on map - */ -AbstractCustomMap.prototype.setCenter = function (coordinates) { - if (coordinates instanceof google.maps.Point) { - coordinates = this.fromPointToLatLng(coordinates); - } - if (this.initialized) { - return Promise.resolve(this.getGoogleMap().setCenter(coordinates)); - } else { - logger.warn("cannot center map that is not opened yet"); - return Promise.resolve(); - } -}; - -/** - * Sets zoom level for google maps. - * - * @param mapIdentifier - * id of the model for which we change zoom level - * @param zoom - * new zoom level on map - */ -AbstractCustomMap.prototype.setZoom = function (zoom) { - if (this.initialized) { - return Promise.resolve(this.getGoogleMap().setZoom(zoom)); - } else { - logger.warn("cannot center map that is not opened yet"); - return Promise.resolve(); - } -}; - -AbstractCustomMap.prototype.fitBounds = function (markers) { - var self = this; - var map = self.getGoogleMap(); - if (map !== undefined) { - var bounds = new google.maps.LatLngBounds(); - - for (var i = 0; i < markers.length; i++) { - var marker = markers[i]; - if (marker.getModelId() === self.getId()) { - var markerBounds = marker.getBounds(); - bounds.extend(markerBounds.getNorthEast()); - bounds.extend(markerBounds.getSouthWest()); - } - } - if (!bounds.isEmpty()) { - map.fitBounds(bounds); - } - } -}; - -module.exports = AbstractCustomMap; +"use strict"; + +var Promise = require("bluebird"); + +var logger = require('../logger'); +var functions = require('../Functions'); + +var AliasInfoWindow = require('./window/AliasInfoWindow'); +var AliasSurface = require('./surface/AliasSurface'); +var GuiConnector = require('../GuiConnector'); +var IdentifiedElement = require('./data/IdentifiedElement'); +var ObjectWithListeners = require('../ObjectWithListeners'); +var PointInfoWindow = require('./window/PointInfoWindow'); +var ReactionInfoWindow = require('./window/ReactionInfoWindow'); +var ReactionSurface = require('./surface/ReactionSurface'); + +var MarkerSurfaceCollection = require('./marker/MarkerSurfaceCollection'); + +/** + * Default constructor. + */ +function AbstractCustomMap(model, options) { + // call super constructor + ObjectWithListeners.call(this); + + if (model === undefined) { + throw Error("Model must be defined"); + } + + this.setElement(options.getElement()); + this.setConfiguration(options.getConfiguration()); + + this.setModel(model); + + // this array contains elements that are presented on a specific layout (set + // of google map object representing lines/areas that are associated with + // layout) + this.selectedLayoutOverlays = []; + + // following fields are used in conversion between x,y coordinates and lat,lng + // coordinates + this.pixelOrigin_ = new google.maps.Point(this.getTileSize() / 2, this.getTileSize() / 2); + this.pixelsPerLonDegree_ = this.getTileSize() / 360; + this.pixelsPerLonRadian_ = this.getTileSize() / (2 * Math.PI); + + /* jshint bitwise: false */ + this.zoomFactor = this.getPictureSize() / (this.getTileSize() / (1 << this.getMinZoom())); + + // array with info windows for Marker pointing to aliases + this._aliasInfoWindow = []; + + // array with info windows for Marker pointing to points + this._pointInfoWindow = []; + + // array with info windows for reactions + this._reactionInfoWindow = []; + + this._markerSurfaceCollection = new MarkerSurfaceCollection({map: this}); + + // this is google.maps.drawing.DrawingManager that will allow user to draw + // elements in the client + this._drawingManager = null; + + // this is the polygon that was selected (clicked) last time on the map + this._selectedArea = null; + + // markers should be optimized by default, + // however, for testing purpose this function could be turned of by javascript + // the other possibility is that it should be off in the touch mode + // (bigButtons=true) + this._markerOptimization = options.isMarkerOptimization(); + + this._bigLogo = options.isBigLogo(); + this._customTouchInterface = options.isCustomTouchInterface(); + + this.setDebug(options.isDebug()); +} + +// define super constructor +AbstractCustomMap.prototype = Object.create(ObjectWithListeners.prototype); +AbstractCustomMap.prototype.constructor = AbstractCustomMap; + + +AbstractCustomMap.prototype.getMarkerSurfaceCollection = function () { + return this._markerSurfaceCollection; +}; +/** + * Assigns layouts with images to the google map (which set of images should be + * used by google maps api for which layout). + * + */ +AbstractCustomMap.prototype.setupLayouts = function () { + for (var i = 0; i < this.getLayouts().length; i++) { + var layout = this.getLayouts()[i]; + var typeOptions = this.createTypeOptions(layout); + var mapType = new google.maps.ImageMapType(typeOptions); + this.getGoogleMap().mapTypes.set(layout.getId().toString(), mapType); + } + this.getGoogleMap().setMapTypeId(this.getLayouts()[0].getId().toString()); +}; + +/** + * Creates general google maps options used in this map. + * + */ +AbstractCustomMap.prototype.createMapOptions = function () { + var self = this; + var model = self.getModel(); + + var centerPoint = this.getModel().getCenterLatLng(); + + var zoom = ServerConnector.getSessionData(self.getProject()).getZoomLevel(model); + if (zoom === undefined) { + zoom = this.getMinZoom(); + } + + // if we have coordinate data stored in session then restore it + var point = ServerConnector.getSessionData(self.getProject()).getCenter(model); + if (point !== undefined) { + centerPoint = self.fromPointToLatLng(point); + // if we have default coordinates defined for model + } else if (model.getDefaultCenterX() !== undefined && + model.getDefaultCenterY() !== undefined && + model.getDefaultZoomLevel() !== undefined && + model.getDefaultCenterX() !== null && + model.getDefaultCenterY() !== null && + model.getDefaultZoomLevel() !== null) { + + centerPoint = self.fromPointToLatLng(new google.maps.Point(model.getDefaultCenterX(), model.getDefaultCenterY())); + zoom = model.getDefaultZoomLevel(); + } + + return { + center: centerPoint, + rotateControl: true, + panControl: true, + mapTypeControl: false, + zoom: zoom, + streetViewControl: false, + + panControlOptions: { + position: google.maps.ControlPosition.LEFT_TOP + }, + zoomControlOptions: { + style: google.maps.ZoomControlStyle.LARGE, + position: google.maps.ControlPosition.LEFT_TOP + } + + }; +}; + +/** + * Create google maps configuration options object for a specific layout. + * + * @param param + * object with information about layout + */ +AbstractCustomMap.prototype.createTypeOptions = function (param) { + var self = this; + var result = { + // this is a function that will retrieve valid url to png images for + // tiles on different zoom levels + getTileUrl: function (coord, zoom) { + // we have 1 tile on self.getConfiguration().MIN_ZOOM and + // therefore must limit tails according to this + /* jshint bitwise: false */ + var tileRange = 1 << (zoom - self.getMinZoom()); + if (coord.y < 0 || coord.y >= tileRange || coord.x < 0 || coord.x >= tileRange) { + return null; + } + var addr = "../map_images/" + param.getDirectory() + "/" + zoom + "/" + coord.x + "/" + coord.y + ".PNG"; + return addr; + }, + tileSize: new google.maps.Size(this.getTileSize(), this.getTileSize()), + maxZoom: this.getMaxZoom(), + minZoom: this.getMinZoom(), + radius: 360, + name: param.name + }; + return result; +}; + +/** + * Sets maximum zoom level on google map. + * + */ +AbstractCustomMap.prototype.setMaxZoomLevel = function () { + this.getGoogleMap().setZoom(this.getMaxZoom()); +}; + +/** + * Returns mouse coordinate on the map in lat,lng system. + * + */ +AbstractCustomMap.prototype.getMouseLatLng = function () { + // this method is tricky, the main problem is how to map mouse coordinate to + // google map + // to do that we need a point of reference in both systems (x,y and lat,lng) + // this will be center of the map that is currently visible + // next, we will have to find distance from this point in x,y coordinates and + // transform it to lat,lng + + var self = this; + // center point visible on the map + var latLngCoordinates = self.getGoogleMap().getCenter(); + var point = self.fromLatLngToPoint(latLngCoordinates); + + // this is magic :) + // find offset of the div where google map is located related to top left + // corner of the browser + var el = self.getGoogleMap().getDiv(); + for (var lx = 0, ly = 0; el !== null && el !== undefined; lx += el.offsetLeft, ly += el.offsetTop, el = el.offsetParent) { + } + + var offset = { + x: lx, + y: ly + }; + + var center = { + x: self.getGoogleMap().getDiv().offsetWidth / 2, + y: self.getGoogleMap().getDiv().offsetHeight / 2 + }; + + // and now find how far from center point we are (in pixels) + var relativeDist = { + x: (GuiConnector.xPos - offset.x - center.x), + y: (GuiConnector.yPos - offset.y - center.y) + }; + + // transform pixels into x,y distance + var pointDist = self.fromPixelsToPoint(relativeDist, self.getGoogleMap().getZoom()); + + // now we have offset in x,y and center point on the map in x,y, so we have + // final position in x,y + var newCoordinates = new google.maps.Point(point.x + pointDist.x, point.y + pointDist.y); + + // change it to lat,lng + var latLngResult = self.fromPointToLatLng(newCoordinates); + + return latLngResult; +}; + +/** + * Transform distance (coordinates) in pixels into x,y distance on the map. + * + * @param pixels + * x,y distance in pixels + * @param zoomLevel + * at which zoom level this pixels where measured + * + */ +AbstractCustomMap.prototype.fromPixelsToPoint = function (pixels, zoomLevel) { + var zoomScale = this.getPictureSize() / (Math.pow(2, zoomLevel - this.getMinZoom()) * this.getTileSize()); + var pointX = pixels.x * zoomScale; + var pointY = pixels.y * zoomScale; + return new google.maps.Point(pointX, pointY); +}; + +/** + * Transforms coordinates on the map from google.maps.LatLng to + * google.maps.Point + * + * @param latLng + * in lat,lng format + * @param coordinates in x,y format + * + */ +AbstractCustomMap.prototype.fromLatLngToPoint = function (latLng) { + var me = this; + var point = new google.maps.Point(0, 0); + var origin = me.pixelOrigin_; + + point.x = origin.x + latLng.lng() * me.pixelsPerLonDegree_; + + // Truncating to 0.9999 effectively limits latitude to 89.189. This is + // about a third of a tile past the edge of the world tile. + var siny = functions.bound(Math.sin(functions.degreesToRadians(latLng.lat())), -0.9999, 0.9999); + point.y = origin.y + 0.5 * Math.log((1 + siny) / (1 - siny)) * -me.pixelsPerLonRadian_; + + // rescale the point (all computations are done assuming that we work on + // TILE_SIZE square) + point.x *= me.zoomFactor; + point.y *= me.zoomFactor; + return point; +}; + +/** + * Transforms coordinates on the map from google.maps.Point to + * google.maps.LatLng + * + * @param point + * coordinates in standard x,y format + * @return coordinates in lat,lng format + */ +AbstractCustomMap.prototype.fromPointToLatLng = function (point) { + var me = this; + + // rescale the point (all computations are done assuming that we work on + // TILE_SIZE square) + var p = new google.maps.Point(point.x / me.zoomFactor, point.y / me.zoomFactor); + var origin = me.pixelOrigin_; + var lng = (p.x - origin.x) / me.pixelsPerLonDegree_; + var latRadians = (p.y - origin.y) / -me.pixelsPerLonRadian_; + var lat = functions.radiansToDegrees(2 * Math.atan(Math.exp(latRadians)) - Math.PI / 2); + return new google.maps.LatLng(lat, lng); +}; + +/** + * Transforms google.maps.LatLng to tile coordinate (for instance on which tile + * mouse clicked). + * + * + * @param latLng + * coordinates in latlng format + * @param z + * zoom level at which we want to find coordinates of tile + * @return coordinates of a tile + */ +AbstractCustomMap.prototype.latLngToTile = function (latLng, z) { + var worldCoordinate = this.fromLatLngToPoint(latLng); + var pixelCoordinate = new google.maps.Point(worldCoordinate.x * Math.pow(2, z), worldCoordinate.y * Math.pow(2, z)); + var tileCoordinate = new google.maps.Point(Math.floor(pixelCoordinate.x / this.getTileSize()), Math + .floor(pixelCoordinate.y / this.getTileSize())); + return tileCoordinate; +}; + +/** + * Register events responsible for click events + */ +AbstractCustomMap.prototype.registerMapClickEvents = function () { + + // find top map (CustomMap) + // + var customMap = this.getTopMap(); + + var self = this; + + // search event + google.maps.event.addListener(this.getGoogleMap(), 'click', function (mouseEvent) { + var point = self.fromLatLngToPoint(mouseEvent.latLng); + var searchDb = customMap.getOverlayByName('search'); + if (searchDb !== undefined) { + return searchDb.searchByCoordinates({ + modelId: self.getModel().getId(), + coordinates: point, + zoom: self.getGoogleMap().getZoom() + }).then(null, GuiConnector.alert); + } else { + logger.warn("Search is impossible because search db is not present"); + } + }); + + // select last clicked map + google.maps.event.addListener(this.getGoogleMap(), 'click', function (mouseEvent) { + customMap.setActiveSubmapId(self.getId()); + customMap.setActiveSubmapClickCoordinates(self.fromLatLngToPoint(mouseEvent.latLng)); + }); + + // select last clicked map + google.maps.event.addListener(this.getGoogleMap(), 'rightclick', function (mouseEvent) { + customMap.setActiveSubmapId(self.getId()); + customMap.setActiveSubmapClickCoordinates(self.fromLatLngToPoint(mouseEvent.latLng)); + }); + + // prepare for image export + google.maps.event.addListener(this.getGoogleMap(), 'rightclick', function () { + var bounds = self.getGoogleMap().getBounds(); + var polygon = ""; + + var ne = bounds.getNorthEast(); + var sw = bounds.getSouthWest(); + + var westLng = sw.lng(); + var eastLng = ne.lng(); + + if (westLng > 0) { + westLng = -180; + } + if (eastLng - westLng > 90) { + eastLng = -90; + } else if (eastLng > -90) { + eastLng = -90; + } + + polygon += ne.lat() + "," + westLng + ";"; + polygon += ne.lat() + "," + eastLng + ";"; + polygon += sw.lat() + "," + eastLng + ";"; + polygon += sw.lat() + "," + westLng + ";"; + self.getTopMap().setSelectedPolygon(polygon); + }); + + // context menu event + google.maps.event.addListener(this.getGoogleMap(), 'rightclick', function () { + self.getTopMap().getContextMenu().open(GuiConnector.xPos, GuiConnector.yPos, new Date().getTime()); + }); +}; + +/** + * It toggle drawing manager used on the map: if it's on then it will turn it + * off, if it's off it will turn it on + * + */ +AbstractCustomMap.prototype._turnOnOffDrawing = function () { + if (this.isDrawingOn()) { + this.turnOffDrawing(); + } else { + this.turnOnDrawing(); + } +}; + +/** + * Checks if the drawing manager for the map is on. + * + */ +AbstractCustomMap.prototype.isDrawingOn = function () { + return this._drawingManager !== null; +}; + +/** + * Turns on drawing manager on the map. + */ +AbstractCustomMap.prototype.turnOnDrawing = function () { + if (this.isDrawingOn()) { + logger.warn("Trying to turn on drawing manager, but it is already available."); + return; + } + var customMap = this.getTopMap(); + var self = this; + this._drawingManager = new google.maps.drawing.DrawingManager({ + drawingMode: google.maps.drawing.OverlayType.MARKER, + drawingControl: true, + drawingControlOptions: { + position: google.maps.ControlPosition.TOP_CENTER, + drawingModes: [google.maps.drawing.OverlayType.POLYGON] + }, + markerOptions: { + icon: 'images/beachflag.png' + }, + circleOptions: { + fillColor: '#ffff00', + fillOpacity: 1, + strokeWeight: 5, + clickable: false, + editable: true, + zIndex: 1 + } + }); + this._drawingManager.setMap(this.getGoogleMap()); + this._drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON); + + google.maps.event.addListener(this._drawingManager, 'overlaycomplete', function (e) { + if (e.type !== google.maps.drawing.OverlayType.MARKER) { + // Switch back to non-drawing mode after drawing a shape. + self._drawingManager.setDrawingMode(null); + + // Add an event listener that selects the newly-drawn shape when the + // user mouses down on it. + var newShape = e.overlay; + newShape.type = e.type; + google.maps.event.addListener(newShape, 'rightclick', function (e) { + // select map that was clicked + customMap.setActiveSubmapId(self.getId()); + + self.setSelectedArea(newShape); + newShape.position = e.latLng; + + self.getTopMap().setSelectedPolygon(self.areaToString(newShape)); + + self.getTopMap().getSelectionContextMenu().open(GuiConnector.xPos, GuiConnector.yPos, new Date().getTime()); + }); + } + }); + +}; + +/** + * Sets selectedArea on this map. + * + */ +AbstractCustomMap.prototype.setSelectedArea = function (area) { + this._selectedArea = area; +}; + +/** + * Returns selectedArea on this map. + * + */ +AbstractCustomMap.prototype.getSelectedArea = function () { + return this._selectedArea; +}; + +/** + * Transforms google.maps.Polygon into string with coordinates. + * + */ +AbstractCustomMap.prototype.areaToString = function (area) { + var len = area.getPath().length; + var path = area.getPath(); + var res = ""; + for (var i = 0; i < len; i++) { + var latLng = path.getAt(i); + res += latLng.lat() + "," + latLng.lng() + ";"; + } + return res; +}; + +/** + * Removes selected area (polygon) from the map. + */ +AbstractCustomMap.prototype._removeSelection = function () { + if (this._selectedArea) { + this._selectedArea.setMap(null); + this._selectedArea = null; + } else { + logger.warn("Cannot remove selected area. No area was selected"); + } +}; + +/** + * Turns off drawing manager on the map. + */ +AbstractCustomMap.prototype.turnOffDrawing = function () { + if (this.isDrawingOn()) { + this._drawingManager.setMap(null); + this._drawingManager = null; + } else { + logger.warn("Trying to turn off drawing manager, but it is not available."); + } +}; + +/** + * Returns top map. TODO implementation of this function should be probably + * moved to Submap and CustomMap classes and here only abstract function + * definition + * + * @returns {CustomMap} + */ +AbstractCustomMap.prototype.getTopMap = function () { + logger.fatal("Not implemented"); +}; + +/** + * Method that should be called when number of layouts to visualize changed to + * modify boundaries of the elements to visualize. When few layouts are + * visualized at the same time then index contains information where this new + * layout is placed in the list (starting from 0) and length contains + * information how many layouts we visualize in total. + * + * @param layoutId + * identifier of a layout + * @param index + * when visualizing more than one layout at the same time index + * contains information at which position in the list this layout is + * placed + * @param length + * number of layouts that are currently visualized + */ +AbstractCustomMap.prototype._resizeSelectedLayout = function (layoutId, index, length) { + var self = this; + return new Promise(function (resolve) { + // if map is not initialized then don't perform this operation + if (!self.initialized) { + logger.debug("Model " + self.getId() + " not initialized"); + resolve(); + } + logger.debug("Resize layout: " + layoutId); + // start ratio + var startX = index * (1.0 / length); + // end ratio + var endX = (index + 1) * (1.0 / length); + + for (var i = 0; i < self.selectedLayoutOverlays[layoutId].length; i++) { + self.selectedLayoutOverlays[layoutId][i].setBoundsForAlias(startX, endX); + } + resolve(); + }); +}; + +/** + * Shows all elements from a given layout. When few layouts are visualized at + * the same time then index contains information where this new layout is placed + * in the list (starting from 0) and length contains information how many + * layouts we visualize in total. + * + * @param layoutId + * identifier of a layout + * @param index + * when visualizing more than one layout at the same time index + * contains information at which position in the list this layout is + * placed + * @param length + * number of layouts that are currently visualized + */ +AbstractCustomMap.prototype._showSelectedLayout = function (layoutId, index, length) { + var self = this; + // if map is not initialized then don't perform this operation + return new Promise(function (resolve, reject) { + if (!self.initialized) { + logger.debug("Model " + self.getId() + " not initialized"); + resolve(); + return; + } else { + logger.debug("Showing model " + self.getId()); + } + + self.selectedLayoutOverlays[layoutId] = []; + + // start ratio + var startX = index * (1.0 / length); + // end ratio + var endX = (index + 1) * (1.0 / length); + + var layoutAliases; + var layoutReactions; + + return self.getTopMap().getModel().getLayoutDataById(layoutId).then(function (layout) { + layoutAliases = layout.getAliases(); + layoutReactions = layout.getReactions(); + + var identifiedElements = []; + var i; + for (i = 0; i < layoutAliases.length; i++) { + if (layoutAliases[i].getModelId() === self.getId()) { + identifiedElements.push(new IdentifiedElement(layoutAliases[i])); + } + } + var reactionIds = []; + for (i = 0; i < layoutReactions.length; i++) { + if (layoutReactions[i].getModelId() === self.getId()) { + identifiedElements.push(new IdentifiedElement(layoutReactions[i])); + } + } + return self.getModel().getByIdentifiedElements(identifiedElements, false); + }).then(function () { + return Promise.each(layoutAliases, function (layoutAlias) { + if (layoutAlias.getModelId() === self.getId()) { + var surface; + var element; + return self.getModel().getAliasById(layoutAlias.getId()).then(function (aliasData) { + element = new IdentifiedElement(aliasData); + return AliasSurface.create({ + overlayAlias: layoutAlias, + alias: aliasData, + map: self, + startX: startX, + endX: endX, + onClick: [function () { + return self._openInfoWindowForAlias(aliasData, surface.getGoogleMarker()); + }, function () { + return self.getTopMap().callListeners("onBioEntityClick", element); + }] + }); + }).then(function (result) { + surface = result; + self.selectedLayoutOverlays[layoutId].push(surface); + }); + } + }); + }).then(function () { + return Promise.each(layoutReactions, function (layoutReaction) { + if (layoutReaction.getModelId() === self.getId()) { + return self.getModel().getReactionById(layoutReaction.getId()).then(function (reactionData) { + var surface; + var element = new IdentifiedElement(reactionData); + return ReactionSurface.create({ + layoutReaction: layoutReaction, + reaction: reactionData, + map: self, + onClick: [function () { + return self._openInfoWindowForReaction(reactionData, surface.getGoogleMarker()); + }, function () { + return self.getTopMap().callListeners("onBioEntityClick", element); + }], + customized: (length === 1) + }).then(function (result) { + surface = result; + self.selectedLayoutOverlays[layoutId].push(surface); + surface.show(); + }); + }); + } + }); + }).then(function () { + resolve(); + }).then(null, reject); + }); +}; + +/** + * Hides all elements from layout. + * + * @param layoutId + * identifier of a layout + */ +AbstractCustomMap.prototype._hideSelectedLayout = function (layoutId) { + // if map is not initialized then don't perform this operation + if (!this.initialized) { + logger.debug("Model " + this.getId() + " not initialized"); + return; + } + for (var i = 0; i < this.selectedLayoutOverlays[layoutId].length; i++) { + this.selectedLayoutOverlays[layoutId][i].setMap(null); + } + this.selectedLayoutOverlays[layoutId] = []; +}; + +/** + * Opens {@link AliasInfoWindow} for given alias. + * + * @param aliasId + * identifier of the alias + */ +AbstractCustomMap.prototype._openInfoWindowForAlias = function (alias, googleMarker) { + var self = this; + + var infoWindow = this.getAliasInfoWindowById(alias.getId()); + if (infoWindow !== null && infoWindow !== undefined) { + if (!infoWindow.isOpened()) { + infoWindow.open(googleMarker); + } else { + logger.warn("Info window for alias: " + alias.getId() + " is already opened"); + } + return Promise.resolve(infoWindow); + } else { + self._aliasInfoWindow[alias.getId()] = new AliasInfoWindow({ + alias: alias, + map: self, + marker: googleMarker, + }); + return self._aliasInfoWindow[alias.getId()].init(); + } +}; + +/** + * Returns promise of a list of {@link LayoutAlias} information for a given + * {@link Alias} in all currently visualized layouts. + * + * @param aliasId + * identifier of the {@link Alias} + * @returns promise of an {Array} with list of {@link LayoutAlias} information + * for a given {@link Alias} in all currently visualized layouts + */ +AbstractCustomMap.prototype.getAliasVisibleLayoutsData = function (aliasId) { + var self = this; + return self.getTopMap().getVisibleDataOverlays().then(function (visibleDataOverlays) { + var result = []; + for (var i = 0; i < visibleDataOverlays.length; i++) { + var layout = visibleDataOverlays[i]; + result.push(layout.getFullAliasById(aliasId)); + } + return Promise.all(result); + }); +}; + +/** + * Refresh content of all {@link AliasInfoWindow} in this map. + */ +AbstractCustomMap.prototype._refreshInfoWindows = function () { + var promises = []; + for (var key in this._pointInfoWindow) { + if (this._pointInfoWindow.hasOwnProperty(key)) { + if (this._pointInfoWindow[key].isOpened()) { + promises.push(this._pointInfoWindow[key].update()); + } + } + } + for (var aliasKey in this._aliasInfoWindow) { + if (this._aliasInfoWindow.hasOwnProperty(aliasKey)) { + if (this._aliasInfoWindow[aliasKey].isOpened()) { + promises.push(this._aliasInfoWindow[aliasKey].update()); + } + } + } + return Promise.all(promises); +}; + + +/** + * Opens {@link ReactionInfoWindow} for given reaction identifier. + * + * @param reactionId + * reaction identifier + */ +AbstractCustomMap.prototype._openInfoWindowForReaction = function (reaction, googleMarker) { + var infoWindow = this.getReactionInfoWindowById(reaction.getId()); + var self = this; + if (infoWindow !== null && infoWindow !== undefined) { + if (!infoWindow.isOpened()) { + infoWindow.open(googleMarker); + } else { + logger.warn("Info window for reaction: " + reaction.getId() + " is already opened"); + } + return Promise.resolve(infoWindow); + } else { + return self.getModel().getReactionById(reaction.getId()).then(function (reaction) { + infoWindow = new ReactionInfoWindow({ + reaction: reaction, + map: self, + marker: googleMarker, + }); + self._reactionInfoWindow[reaction.getId()] = infoWindow; + return infoWindow.init(); + }).then(function () { + return infoWindow; + }); + } +}; + +AbstractCustomMap.prototype._openInfoWindowForPoint = function (pointData, googleMarker) { + var self = this; + + var infoWindow = this.getPointInfoWindowById(pointData.getId()); + if (infoWindow !== null && infoWindow !== undefined) { + if (!infoWindow.isOpened()) { + infoWindow.open(googleMarker); + } else { + logger.warn("Info window for point: " + pointData.getId() + " is already opened"); + } + } else { + infoWindow = new PointInfoWindow({ + point: pointData, + map: self, + marker: googleMarker, + }); + this._pointInfoWindow[pointData.getId()] = infoWindow; + } + return Promise.resolve(infoWindow); +}; + +/** + * Opens {@link AbstractInfoWindow} for a marker. + * + * @param marker + * marker for which we are opening window + */ +AbstractCustomMap.prototype.returnInfoWindowForIdentifiedElement = function (element) { + var markerId = element.getId(); + if (element.getType() === "ALIAS") { + return this.getAliasInfoWindowById(markerId); + } else if (element.getType() === "POINT") { + return this.getPointInfoWindowById(markerId); + } else if (element.getType() === "REACTION") { + return this.getReactionInfoWindowById(markerId); + } else { + throw new Error("Unknown marker type: ", element); + } +}; + +/** + * Returns identifier. + * + * @returns identifier + */ +AbstractCustomMap.prototype.getId = function () { + return this.getModel().getId(); +}; + +AbstractCustomMap.prototype.getConfiguration = function () { + return this._configuration; +}; + +AbstractCustomMap.prototype.setConfiguration = function (configuration) { + this._configuration = configuration; +}; + +AbstractCustomMap.prototype._createMapChangedCallbacks = function () { + var self = this; + var sessionData = ServerConnector.getSessionData(self.getTopMap().getProject()); + // listener for changing zoom level + google.maps.event.addListener(this.getGoogleMap(), 'zoom_changed', function () { + sessionData.setZoomLevel(self.getModel(), self.getGoogleMap().getZoom()); + }); + // listener for changing location of the map (moving left/right/top/bottom + google.maps.event.addListener(this.getGoogleMap(), 'center_changed', function () { + var coord = self.getGoogleMap().getCenter(); + var point = self.fromLatLngToPoint(coord); + sessionData.setCenter(self.getModel(), point); + }); +}; + +/** + * Returns {@link ReactionInfoWindow} for given reaction identifier + * + * @param reactionId + * reaction identifier + * @returns {@link ReactionInfoWindow} for given reaction identifier + */ +AbstractCustomMap.prototype.getReactionInfoWindowById = function (reactionId) { + return this._reactionInfoWindow[reactionId]; +}; + +/** + * Returns {@link AliasInfoWindow} for given alias identifier + * + * @param aliasId + * alias identifier + * @returns {@link AliasInfoWindow} for given alias identifier + */ +AbstractCustomMap.prototype.getAliasInfoWindowById = function (aliasId) { + return this._aliasInfoWindow[aliasId]; +}; + +/** + * Returns {@link PointInfoWindow} for given point identifier + * + * @param pointId + * point identifier + * @returns {@link PointInfoWindow} for given point identifier + */ +AbstractCustomMap.prototype.getPointInfoWindowById = function (pointId) { + return this._pointInfoWindow[pointId]; +}; + +AbstractCustomMap.prototype.getModel = function () { + return this._model; +}; + +AbstractCustomMap.prototype.setModel = function (model) { + this._model = model; +}; + +AbstractCustomMap.prototype.getTileSize = function () { + return this.getModel().getTileSize(); +}; + +AbstractCustomMap.prototype.getMinZoom = function () { + return this.getModel().getMinZoom(); +}; + +AbstractCustomMap.prototype.getMaxZoom = function () { + return this.getModel().getMaxZoom(); +}; + +AbstractCustomMap.prototype.getLayouts = function () { + return this.getModel().getLayouts(); +}; + +AbstractCustomMap.prototype.getPictureSize = function () { + return this.getModel().getPictureSize(); +}; + +/** + * Returns array containing elements that are presented on a specific layout + * (set of google map objects representing lines/areas that are associated with + * layout). + * + * @returns {Array} containing elements that are presented on a specific + * layout (set of google map objects representing lines/areas that are + * associated with layout). + */ +AbstractCustomMap.prototype.getSelectedLayoutOverlays = function () { + return this.selectedLayoutOverlays; +}; + +/** + * Returns google.maps.map object used to representing data. + * + * @returns google.maps.map object used to representing data + */ +AbstractCustomMap.prototype.getGoogleMap = function () { + return this._map; +}; + +/** + * Sets google.maps.map object used to representing data. + * + */ +AbstractCustomMap.prototype.setGoogleMap = function (googleMap) { + this._map = googleMap; +}; + +AbstractCustomMap.prototype.isMarkerOptimization = function () { + return this._markerOptimization; +}; + +AbstractCustomMap.prototype.isBigLogo = function () { + return this._bigLogo; +}; + +AbstractCustomMap.prototype.isCustomTouchInterface = function () { + return this._customTouchInterface; +}; + +AbstractCustomMap.prototype.setDebug = function (debug) { + if (debug !== undefined) { + if (typeof debug !== "boolean") { + logger.warn("param must be boolean"); + } + this._debug = debug; + } +}; + +AbstractCustomMap.prototype.isDebug = function () { + return this._debug === true; +}; + +AbstractCustomMap.prototype.getTopLeftLatLng = function () { + return this.getModel().getTopLeftLatLng(); +}; + +AbstractCustomMap.prototype.getBottomRightLatLng = function () { + return this.getModel().getBottomRightLatLng(); +}; + +AbstractCustomMap.prototype.getElement = function () { + return this._element; +}; +AbstractCustomMap.prototype.setElement = function (element) { + this._element = element; +}; + +/** + * Sets center point for google maps. + * + * @param coordinates + * new center point on map + */ +AbstractCustomMap.prototype.setCenter = function (coordinates) { + if (coordinates instanceof google.maps.Point) { + coordinates = this.fromPointToLatLng(coordinates); + } + if (this.initialized) { + return Promise.resolve(this.getGoogleMap().setCenter(coordinates)); + } else { + logger.warn("cannot center map that is not opened yet"); + return Promise.resolve(); + } +}; + +/** + * Sets zoom level for google maps. + * + * @param mapIdentifier + * id of the model for which we change zoom level + * @param zoom + * new zoom level on map + */ +AbstractCustomMap.prototype.setZoom = function (zoom) { + if (this.initialized) { + return Promise.resolve(this.getGoogleMap().setZoom(zoom)); + } else { + logger.warn("cannot center map that is not opened yet"); + return Promise.resolve(); + } +}; + +AbstractCustomMap.prototype.fitBounds = function (markers) { + var self = this; + var map = self.getGoogleMap(); + if (map !== undefined) { + var bounds = new google.maps.LatLngBounds(); + + for (var i = 0; i < markers.length; i++) { + var marker = markers[i]; + if (marker.getModelId() === self.getId()) { + var markerBounds = marker.getBounds(); + bounds.extend(markerBounds.getNorthEast()); + bounds.extend(markerBounds.getSouthWest()); + } + } + if (!bounds.isEmpty()) { + map.fitBounds(bounds); + } + } +}; + +module.exports = AbstractCustomMap; diff --git a/frontend-js/src/main/js/map/CustomMap.js b/frontend-js/src/main/js/map/CustomMap.js index 931fb1673a..82e5f7fef8 100644 --- a/frontend-js/src/main/js/map/CustomMap.js +++ b/frontend-js/src/main/js/map/CustomMap.js @@ -441,7 +441,14 @@ CustomMap.prototype.customizeGoogleMapView = function (div) { // center map and zoom in to fit into browser window if there is no // information about coordinates in the session - if (ServerConnector.getSessionData(this.getProject()).getCenter(this.getModel()) === undefined) { + if (ServerConnector.getSessionData(this.getProject()).getCenter(this.getModel()) === undefined && + (this.getModel().getDefaultCenterX() === undefined || + this.getModel().getDefaultCenterY() === undefined || + this.getModel().getDefaultZoomLevel() === undefined || + this.getModel().getDefaultCenterX() === null || + this.getModel().getDefaultCenterY() === null || + this.getModel().getDefaultZoomLevel() === null + )) { var bounds = new google.maps.LatLngBounds(); bounds.extend(this.getTopLeftLatLng()); bounds.extend(this.getBottomRightLatLng()); @@ -459,15 +466,6 @@ CustomMap.prototype.createMapChangedCallbacks = function () { var self = this; var sessionData = ServerConnector.getSessionData(self.getProject()); - // if we have zoom level data stored in session then restore it - var level = sessionData.getZoomLevel(self.getModel()); - if (parseInt(level) > 0) { - level = parseInt(level); - this.getGoogleMap().setZoom(level); - } else { - sessionData.setZoomLevel(self.getModel(), self.getGoogleMap().getZoom()); - } - // listener for changing type of layout google.maps.event.addListener(self.getGoogleMap(), 'maptypeid_changed', function () { sessionData.setSelectedBackgroundOverlay(self.getGoogleMap().getMapTypeId()); diff --git a/frontend-js/src/main/js/map/data/MapModel.js b/frontend-js/src/main/js/map/data/MapModel.js index 000602857d..7bbd84fc8d 100644 --- a/frontend-js/src/main/js/map/data/MapModel.js +++ b/frontend-js/src/main/js/map/data/MapModel.js @@ -1,798 +1,828 @@ -"use strict"; - -var Promise = require("bluebird"); - -var logger = require('../../logger'); - -var Alias = require('./Alias'); -var IdentifiedElement = require('./IdentifiedElement'); -var LayoutData = require('./LayoutData'); -var PointData = require('./PointData'); -var Reaction = require('./Reaction'); - -// This file describes javascript representation of Java Model class that -// represents content of the map. - -/** - * Default constructor. - * - */ - -function MapModel(configuration) { - - // list of aliases is empty (it will be filled dynamically - when necessary) - this._aliases = []; - - // list of reactions is empty (it will be filled dynamically - when necessary) - this._reactions = []; - this._reactionsByParticipantElementId = []; - - // list of aliases that should be updated from server side during the next - // connection - this._missingAliases = []; - - // list of reactions that should be updated from server side during the next - // connection - this._missingReactions = []; - - // list of layouts is empty (it will be filled when necessary) - this._layoutsData = []; - - // information about points and associated data (for now we have only comments - // associated to the point, - // but it can be extended) - this._pointsData = []; - - this._submodels = []; - - if (configuration !== undefined) { - if (configuration instanceof MapModel) { - this.setId(configuration.getId()); - this.setName(configuration.getName()); - this.setTileSize(configuration.getTileSize()); - this.setWidth(configuration.getWidth()); - this.setHeight(configuration.getHeight()); - this.setMinZoom(configuration.getMinZoom()); - this.setMaxZoom(configuration.getMaxZoom()); - this.addLayouts(configuration.getLayouts()); - this.addSubmodels(configuration.getSubmodels()); - this.setCenterLatLng(configuration.getCenterLatLng()); - this.setTopLeftLatLng(configuration.getTopLeftLatLng()); - this.setBottomRightLatLng(configuration.getBottomRightLatLng()); - this.setSubmodelType(configuration.getSubmodelType()); - } else { - this.setId(configuration.idObject); - this.setName(configuration.name); - this.setTileSize(configuration.tileSize); - this.setWidth(configuration.width); - this.setHeight(configuration.height); - this.setMinZoom(configuration.minZoom); - this.setMaxZoom(configuration.maxZoom); - this.addLayouts(configuration.layouts); - this.addSubmodels(configuration.submodels); - this.setCenterLatLng(configuration.centerLatLng); - this.setTopLeftLatLng(configuration.topLeftLatLng); - this.setBottomRightLatLng(configuration.bottomRightLatLng); - this.setSubmodelType(configuration.submodelType); - } - } -} - -/** - * Returns list of {@link LayoutData} on this model. - * - * @returns {Array} with list of {@link LayoutData} on this model - */ -MapModel.prototype.getLayoutsData = function() { - var result = []; - for ( var id in this._layoutsData) { - if (this._layoutsData.hasOwnProperty(id)) { - result.push(this._layoutsData[id]); - } - } - return result; -}; - -MapModel.prototype.getLayouts = function() { - return this.getLayoutsData(); -}; - -/** - * Return list of all aliases that were added to the model. - */ -MapModel.prototype.getAliases = function(params) { - var self = this; - return ServerConnector.getAliases({ - columns : "id,modelId", - type : params.type, - modelId : self.getId(), - includedCompartmentIds : params.includedCompartmentIds, - excludedCompartmentIds : params.excludedCompartmentIds, - - }).then(function(lightAliases) { - var identifiedElements = []; - for (var i = 0; i < lightAliases.length; i++) { - self.addAlias(lightAliases[i]); - identifiedElements.push(new IdentifiedElement(lightAliases[i])); - } - return self.getByIdentifiedElements(identifiedElements, params.complete); - }); -}; - -/** - * Returns {@link Alias} by identifier. - * - * @param id - * identifier of the {@link Alias} - * @returns {@link Alias} by identifier - */ -MapModel.prototype.getAliasById = function(id, complete) { - var self = this; - if (complete) { - return this.getCompleteAliasById(id); - } - if (self._aliases[id] !== undefined) { - return Promise.resolve(self._aliases[id]); - } else { - return self.getMissingElements({ - aliasIds : [ id ] - }).then(function() { - return self._aliases[id]; - }); - } -}; - -MapModel.prototype.getCompleteAliasById = function(id) { - var self = this; - if (self._aliases[id] !== undefined && self._aliases[id].isComplete()) { - return Promise.resolve(self._aliases[id]); - } else { - return ServerConnector.getAliases({ - ids : id - }).then(function(aliases) { - if (self._aliases[id] === undefined) { - self._aliases[id] = aliases[0]; - } else { - self._aliases[id].update(aliases[0]); - } - return self._aliases[id]; - }); - } -}; - -/** - * Returns {@link Reaction} by identifier. - * - * @param id - * identifier of the {@link Reaction} - * @returns {@link Reaction} by identifier - */ -MapModel.prototype.getReactionById = function(id, complete) { - var self = this; - if (complete) { - return this.getCompleteReactionById(id); - } - if (self._reactions[id] !== undefined) { - return Promise.resolve(self._reactions[id]); - } else { - return self.getMissingElements({ - reactionIds : [ id ] - }).then(function() { - return self._reactions[id]; - }); - } -}; - -MapModel.prototype._getMissingReactionsElementIds = function(reactions) { - var self = this; - var result = []; - var ids = []; - for (var i = 0; i < reactions.length; i++) { - var reaction = reactions[i]; - var elements = reaction.getElements(); - for (var j = 0; j < elements.length; j++) { - var element = elements[j]; - if (!(element instanceof Alias)) { - if (self._aliases[element] === undefined || !self._aliases[element].isComplete()) { - if (!ids[element]) { - ids[element] = true; - result.push(element); - } - } - } - } - } - return result; -}; - -MapModel.prototype.getCompleteReactionById = function(id) { - var self = this; - if (self._reactions[id] instanceof Reaction && self._reactions[id].isComplete()) { - return Promise.resolve(self._reactions[id]); - } else { - var result; - return self.getReactionById(id).then(function(result) { - var ids = self._getMissingReactionsElementIds([ result ]); - return self.getMissingElements({ - aliasIds : ids, - complete : true - }); - }).then(function() { - var i; - result = self._reactions[id]; - for (i = 0; i < result.getReactants().length; i++) { - if (!(result.getReactants()[i] instanceof Alias)) { - result.getReactants()[i] = self._aliases[result.getReactants()[i]]; - } - } - for (i = 0; i < result.getProducts().length; i++) { - if (!(result.getProducts()[i] instanceof Alias)) { - result.getProducts()[i] = self._aliases[result.getProducts()[i]]; - } - } - for (i = 0; i < result.getModifiers().length; i++) { - if (!(result.getModifiers()[i] instanceof Alias)) { - result.getModifiers()[i] = self._aliases[result.getModifiers()[i]]; - } - } - return result; - }); - } -}; - -MapModel.prototype.getMissingElements = function(elements) { - var self = this; - - var layouts = this._getLayouts(); - var aliasIds = []; - var reactionIds = []; - - var i = 0; - if (elements.reactionIds !== undefined) { - reactionIds.push.apply(reactionIds, elements.reactionIds); - for (i = 0; i < reactionIds.length; i++) { - this._missingReactions[reactionIds[i]] = reactionIds[i]; - } - - } - if (elements.aliasIds !== undefined) { - aliasIds.push.apply(aliasIds, elements.aliasIds); - for (i = 0; i < aliasIds.length; i++) { - this._missingAliases[aliasIds[i]] = aliasIds[i]; - } - } - - for (i = 0; i < layouts.length; i++) { - var layout = layouts[i]; - if (layout.isInitialized()) { - var aliases = layout.getAliases(); - for (var j = 0; j < aliases.length; j++) { - var alias = aliases[j]; - if (this._aliases[alias.getId()] === undefined && this._missingAliases[alias.getId()] === undefined) { - this._missingAliases[alias.getId()] = alias.getId(); - aliasIds.push(alias.getId()); - } - } - - var reactions = layout.getReactions(); - for (var k = 0; k < reactions.length; k++) { - var reaction = reactions[k]; - if (this._reactions[reaction.getId()] === undefined && this._missingReactions[reaction.getId()] === undefined) { - this._missingReactions[reaction.getId()] = reaction.getId(); - reactionIds.push(reaction.getId()); - } - } - } - } - var reactionPromise = null; - if (reactionIds.length > 0) { - reactionPromise = ServerConnector.getReactions({ - ids : reactionIds, - complete : elements.complete - }); - } - - var aliasPromise = null; - if (aliasIds.length > 0) { - if (elements.complete) { - aliasPromise = ServerConnector.getAliases({ - ids : aliasIds - }); - } else { - aliasPromise = ServerConnector.getAliases({ - ids : aliasIds, - columns : "id,bounds,modelId" - }); - - } - } - - var result = []; - return Promise.all([ reactionPromise, aliasPromise ]).then(function(values) { - var i; - var reactions = values[0]; - var aliases = values[1]; - var ids = []; - - if (reactions !== null) { - for (i = 0; i < reactions.length; i++) { - var reaction = reactions[i]; - self.addReaction(reaction); - result.push(reaction); - } - ids = self._getMissingReactionsElementIds(reactions); - } - if (aliases !== null) { - for (i = 0; i < aliases.length; i++) { - var alias = aliases[i]; - self.addAlias(alias); - result.push(alias); - } - } - if (ids.length > 0) { - return self.getMissingElements({ - aliasIds : ids, - complete : true - }); - } else { - return Promise.resolve([]); - } - }).then(function() { - return result; - }); -}; - -/** - * Returns layout data for a given layout identifier. - * - * @param layoutId - * layout identifier - * @returns {LayoutData} for a given layout identifier - */ -MapModel.prototype.getLayoutDataById = function(layoutId) { - var self = this; - if (self._layoutsData[layoutId] !== undefined) { - return Promise.resolve(self._layoutsData[layoutId]); - } else { - return ServerConnector.getOverlayById(layoutId).then(function(layout) { - self.addLayout(layout); - return self._layoutsData[layoutId]; - }); - } -}; - -/** - * Adds information about alias. - * - * @param aliasData - * raw data about alias - */ -MapModel.prototype.addAlias = function(aliasData) { - var alias = aliasData; - if (!(aliasData instanceof Alias)) { - alias = new Alias(aliasData); - } - if (this._aliases[alias.getId()] !== undefined) { - this._aliases[alias.getId()].update(alias); - } else { - this._aliases[alias.getId()] = alias; - if (this._missingAliases[alias.getId()] !== undefined) { - this._missingAliases[alias.getId()] = null; - delete this._missingAliases[alias.getId()]; - } - } -}; - -/** - * Adds information about reaction. - * - * @param reactionData - * raw data about reaction - */ -MapModel.prototype.addReaction = function(reactionData) { - var reaction = null; - if (reactionData instanceof Reaction) { - reaction = reactionData; - } else { - reaction = new Reaction(reactionData); - } - if (this._reactions[reaction.getId()] !== undefined) { - logger.warn("Reaction with id: " + reaction.getId() + " already exists"); - } else { - this._reactions[reaction.getId()] = reaction; - if (this._missingReactions[reaction.getId()] !== undefined) { - this._missingReactions[reaction.getId()] = null; - delete this._missingReactions[reaction.getId()]; - } - } -}; - -/** - * Returns {@link PointData} for a given point on the map. - * - * @param point - * {@link google.maps.Point} where we are requesting data - * @returns {@link PointData} for a given point on the map - */ -MapModel.prototype.getPointDataByPoint = function(inputPoint) { - if (inputPoint instanceof google.maps.Point) { - var point = this._roundPoint(inputPoint); - var id = this._pointToId(point); - var result = this._pointsData[id]; - if (result === undefined) { - result = new PointData(point, this.getId()); - this._pointsData[id] = result; - } - return result; - } else { - logger.warn("point must be of class: google.maps.Point"); - return null; - } -}; - -/** - * Returns point where x and y coordinate are rounded to 2 decimal places. - * - * @param point - * input point - * @returns {google.maps.Point} point where x and y coordinate are rounded to 2 - * decimal places - */ -MapModel.prototype._roundPoint = function(point) { - var x = parseFloat(point.x).toFixed(2); - var y = parseFloat(point.y).toFixed(2); - return new google.maps.Point(x, y); -}; - -/** - * Transform point into string identifier. - * - * @param point - * {google.maps.Point} to transform - * @returns {String} string identifier for a given point - */ -MapModel.prototype._pointToId = function(point) { - if (point instanceof google.maps.Point) { - return "(" + point.x + ", " + point.y + ")"; - } else { - return point.replace(/ /g, ''); - } -}; - -MapModel.prototype.getId = function() { - return this.id; -}; - -MapModel.prototype.setId = function(id) { - this.id = parseInt(id); -}; - -MapModel.prototype.getWidth = function() { - return this._width; -}; - -MapModel.prototype.setWidth = function(width) { - this._width = width; -}; - -MapModel.prototype.getHeight = function() { - return this._height; -}; - -MapModel.prototype.setHeight = function(height) { - this._height = height; -}; - -MapModel.prototype.getName = function() { - return this._name; -}; - -MapModel.prototype.setName = function(name) { - this._name = name; -}; - -MapModel.prototype.getMinZoom = function() { - return this._minZoom; -}; - -MapModel.prototype.setMinZoom = function(minZoom) { - this._minZoom = minZoom; -}; - -MapModel.prototype.getSubmodelType = function() { - return this._submodelType; -}; - -MapModel.prototype.setSubmodelType = function(submodelType) { - this._submodelType = submodelType; -}; - -function createLatLng(param) { - if (param === undefined) { - return null; - } - if (param instanceof google.maps.LatLng) { - return new google.maps.LatLng(param.lat(), param.lng()); - } else { - return new google.maps.LatLng(param.lat, param.lng); - } -} - -MapModel.prototype.setCenterLatLng = function(centerLatLng) { - var newVal = createLatLng(centerLatLng); - if (newVal === null) { - logger.warn("centerLatLng is invalid"); - } else { - this._centerLatLng = newVal; - } -}; - -MapModel.prototype.getCenterLatLng = function() { - return this._centerLatLng; -}; - -MapModel.prototype.setTopLeftLatLng = function(topLeftLatLng) { - var newVal = createLatLng(topLeftLatLng); - if (newVal === null) { - logger.warn("topLeftLatLng is invalid"); - } else { - this._topLeftLatLng = newVal; - } -}; - -MapModel.prototype.getTopLeftLatLng = function() { - return this._topLeftLatLng; -}; - -MapModel.prototype.setBottomRightLatLng = function(bottomRightLatLng) { - var newVal = createLatLng(bottomRightLatLng); - if (newVal === null) { - logger.warn("bottomRightLatLng is invalid"); - } else { - this._bottomRightLatLng = newVal; - } -}; - -MapModel.prototype.getBottomRightLatLng = function() { - return this._bottomRightLatLng; -}; - -MapModel.prototype.getMaxZoom = function() { - return this._maxZoom; -}; - -MapModel.prototype.setMaxZoom = function(maxZoom) { - this._maxZoom = maxZoom; -}; - -MapModel.prototype.getTileSize = function() { - return this._tileSize; -}; - -MapModel.prototype.getPictureSize = function() { - return Math.max(this.getWidth(), this.getHeight()); -}; - -MapModel.prototype.setTileSize = function(tileSize) { - this._tileSize = tileSize; -}; - -MapModel.prototype.addLayouts = function(layouts) { - if (layouts === undefined) { - logger.warn("Layouts are undefined..."); - } else { - for (var i = 0; i < layouts.length; i++) { - this.addLayout(layouts[i]); - } - } -}; -MapModel.prototype.addLayout = function(layout) { - var layoutData = null; - if (layout instanceof LayoutData) { - layoutData = layout; - } else { - layoutData = new LayoutData(layout); - } - var object = this._layoutsData[layoutData.getId()]; - if (object === undefined) { - this._layoutsData[layoutData.getId()] = layoutData; - } else { - logger.warn("Layout " + layoutData.getId() + " already exists in a model: " + this.getId()); - } -}; - -MapModel.prototype.addSubmodels = function(submodels) { - if (submodels !== undefined) { - for (var i = 0; i < submodels.length; i++) { - this.addSubmodel(submodels[i]); - } - } -}; - -MapModel.prototype.addSubmodel = function(submodel) { - if (!(submodel instanceof MapModel)) { - submodel = new MapModel(submodel); - } - this._submodels.push(submodel); -}; - -MapModel.prototype.getSubmodels = function() { - return this._submodels; -}; - -MapModel.prototype.getSubmodelById = function(id) { - if (this.getId() === id) { - return this; - } - for (var i = 0; i < this._submodels.length; i++) { - if (this._submodels[i].getId() === id) { - return this._submodels[i]; - } - } - return null; -}; - -MapModel.prototype._getLayouts = function() { - var result = []; - for ( var id in this._layoutsData) { - if (this._layoutsData.hasOwnProperty(id)) { - result.push(this._layoutsData[id]); - } - } - return result; -}; - -MapModel.prototype.getByIdentifiedElement = function(ie, complete) { - var self = this; - if (ie.getType() === "ALIAS") { - return self.getAliasById(ie.getId(), complete); - } else if (ie.getType() === "REACTION") { - return self.getReactionById(ie.getId(), complete); - } else if (ie.getType() === "POINT") { - var id = self._pointToId(ie.getId()); - var result = this._pointsData[id]; - if (result === undefined) { - result = new PointData(ie); - this._pointsData[id] = result; - } - return Promise.resolve(result); - } else { - throw new Error("Unknown type: " + ie.getType()); - } -}; - -MapModel.prototype.getByIdentifiedElements = function(identifiedElements, complete) { - var self = this; - var missingAliases = []; - var missingReactions = []; - - for (var i = 0; i < identifiedElements.length; i++) { - var ie = identifiedElements[i]; - if (!this.isAvailable(ie, complete)) { - if (ie.getType() === "ALIAS") { - missingAliases.push(ie.getId()); - } else if (ie.getType() === "REACTION") { - missingReactions.push(ie.getId()); - } else { - throw new Error("Unknown type " + ie); - } - } - } - - return self.getMissingElements({ - aliasIds : missingAliases, - reactionIds : missingReactions, - complete : complete - }).then(function() { - var promises = []; - for (var i = 0; i < identifiedElements.length; i++) { - promises.push(self.getByIdentifiedElement(identifiedElements[i], complete)); - } - return Promise.all(promises); - }); - -}; - -MapModel.prototype.isAvailable = function(ie, complete) { - var element; - if (ie.getType() === "ALIAS") { - element = this._aliases[ie.getId()]; - } else if (ie.getType() === "REACTION") { - element = this._reactions[ie.getId()]; - } else if (ie.getType() === "POINT") { - var id = this._pointToId(ie.getId()); - var result = this._pointsData[id]; - if (result === undefined) { - result = new PointData(ie); - this._pointsData[id] = result; - } - element = this._pointsData[id]; - } else { - throw new Error("Unknown type: " + ie.getType(), complete); - } - if (element === undefined) { - return false; - } else if (complete) { - return element.isComplete(); - } else { - return true; - } -}; - -MapModel.prototype.getReactionsForElement = function(element, complete) { - return this.getReactionsForElements([ element ], complete); -}; - -MapModel.prototype.getReactionsForElements = function(elements, complete) { - var self = this; - var ids = []; - var i; - for (i = 0; i < elements.length; i++) { - ids.push(elements[i].getId()); - } - var idString = ids.join(); - if (this._reactionsByParticipantElementId[idString]) { - var reactions = self._reactionsByParticipantElementId[idString]; - if (!complete) { - return Promise.resolve(reactions); - } else { - var promises = []; - for (i = 0; i < reactions.length; i++) { - promises.push(self.getCompleteReactionById(reactions[i].getId())); - } - return Promise.all(promises); - } - } - - var result = []; - return ServerConnector.getReactions({ - modelId: self.getId(), - participantId : ids - }).then(function(reactions) { - result = reactions; - - for (var i = 0; i < reactions.length; i++) { - var reaction = reactions[i]; - var id = reaction.getId(); - if (self._reactions[id] === undefined) { - self._reactions[id] = reaction; - } else { - self._reactions[id].update(reaction); - } - } - var ids = self._getMissingReactionsElementIds(reactions); - return self.getMissingElements({ - aliasIds : ids, - complete : true - }); - }).then(function() { - var promises = []; - for (var i = 0; i < result.length; i++) { - promises.push(self.getCompleteReactionById(result[i].getId())); - } - return Promise.all(promises); - }); -}; - -MapModel.prototype.getCompartments = function() { - var self = this; - - var promise = Promise.resolve(); - if (self._compartments === undefined) { - promise = ServerConnector.getAliases({ - columns : "id,bounds,modelId", - type : "Compartment", - modelId : self.getId() - }).then(function(compartments) { - self._compartments = []; - for (var i = 0; i < compartments.length; i++) { - self._compartments.push(new IdentifiedElement(compartments[i])); - } - }); - } - return promise.then(function() { - return self.getByIdentifiedElements(self._compartments, true); - }); -}; - -module.exports = MapModel; +"use strict"; + +var Promise = require("bluebird"); + +var logger = require('../../logger'); + +var Alias = require('./Alias'); +var IdentifiedElement = require('./IdentifiedElement'); +var LayoutData = require('./LayoutData'); +var PointData = require('./PointData'); +var Reaction = require('./Reaction'); + +// This file describes javascript representation of Java Model class that +// represents content of the map. + +/** + * Default constructor. + * + */ + +function MapModel(configuration) { + + // list of aliases is empty (it will be filled dynamically - when necessary) + this._aliases = []; + + // list of reactions is empty (it will be filled dynamically - when necessary) + this._reactions = []; + this._reactionsByParticipantElementId = []; + + // list of aliases that should be updated from server side during the next + // connection + this._missingAliases = []; + + // list of reactions that should be updated from server side during the next + // connection + this._missingReactions = []; + + // list of layouts is empty (it will be filled when necessary) + this._layoutsData = []; + + // information about points and associated data (for now we have only comments + // associated to the point, + // but it can be extended) + this._pointsData = []; + + this._submodels = []; + + if (configuration !== undefined) { + if (configuration instanceof MapModel) { + this.setId(configuration.getId()); + this.setName(configuration.getName()); + this.setTileSize(configuration.getTileSize()); + this.setWidth(configuration.getWidth()); + this.setHeight(configuration.getHeight()); + this.setMinZoom(configuration.getMinZoom()); + this.setMaxZoom(configuration.getMaxZoom()); + this.addLayouts(configuration.getLayouts()); + this.addSubmodels(configuration.getSubmodels()); + this.setCenterLatLng(configuration.getCenterLatLng()); + this.setTopLeftLatLng(configuration.getTopLeftLatLng()); + this.setBottomRightLatLng(configuration.getBottomRightLatLng()); + this.setSubmodelType(configuration.getSubmodelType()); + this.setDefaultCenterX(configuration.getDefaultCenterX()); + this.setDefaultCenterY(configuration.getDefaultCenterY()); + this.setDefaultZoomLevel(configuration.getDefaultZoomLevel()); + } else { + this.setId(configuration.idObject); + this.setName(configuration.name); + this.setTileSize(configuration.tileSize); + this.setWidth(configuration.width); + this.setHeight(configuration.height); + this.setMinZoom(configuration.minZoom); + this.setMaxZoom(configuration.maxZoom); + this.addLayouts(configuration.layouts); + this.addSubmodels(configuration.submodels); + this.setCenterLatLng(configuration.centerLatLng); + this.setTopLeftLatLng(configuration.topLeftLatLng); + this.setBottomRightLatLng(configuration.bottomRightLatLng); + this.setSubmodelType(configuration.submodelType); + this.setDefaultCenterX(configuration.defaultCenterX); + this.setDefaultCenterY(configuration.defaultCenterY); + this.setDefaultZoomLevel(configuration.defaultZoomLevel); + } + } +} + +/** + * Returns list of {@link LayoutData} on this model. + * + * @returns {Array} with list of {@link LayoutData} on this model + */ +MapModel.prototype.getLayoutsData = function() { + var result = []; + for ( var id in this._layoutsData) { + if (this._layoutsData.hasOwnProperty(id)) { + result.push(this._layoutsData[id]); + } + } + return result; +}; + +MapModel.prototype.getLayouts = function() { + return this.getLayoutsData(); +}; + +/** + * Return list of all aliases that were added to the model. + */ +MapModel.prototype.getAliases = function(params) { + var self = this; + return ServerConnector.getAliases({ + columns : "id,modelId", + type : params.type, + modelId : self.getId(), + includedCompartmentIds : params.includedCompartmentIds, + excludedCompartmentIds : params.excludedCompartmentIds, + + }).then(function(lightAliases) { + var identifiedElements = []; + for (var i = 0; i < lightAliases.length; i++) { + self.addAlias(lightAliases[i]); + identifiedElements.push(new IdentifiedElement(lightAliases[i])); + } + return self.getByIdentifiedElements(identifiedElements, params.complete); + }); +}; + +/** + * Returns {@link Alias} by identifier. + * + * @param id + * identifier of the {@link Alias} + * @returns {@link Alias} by identifier + */ +MapModel.prototype.getAliasById = function(id, complete) { + var self = this; + if (complete) { + return this.getCompleteAliasById(id); + } + if (self._aliases[id] !== undefined) { + return Promise.resolve(self._aliases[id]); + } else { + return self.getMissingElements({ + aliasIds : [ id ] + }).then(function() { + return self._aliases[id]; + }); + } +}; + +MapModel.prototype.getCompleteAliasById = function(id) { + var self = this; + if (self._aliases[id] !== undefined && self._aliases[id].isComplete()) { + return Promise.resolve(self._aliases[id]); + } else { + return ServerConnector.getAliases({ + ids : id + }).then(function(aliases) { + if (self._aliases[id] === undefined) { + self._aliases[id] = aliases[0]; + } else { + self._aliases[id].update(aliases[0]); + } + return self._aliases[id]; + }); + } +}; + +/** + * Returns {@link Reaction} by identifier. + * + * @param id + * identifier of the {@link Reaction} + * @returns {@link Reaction} by identifier + */ +MapModel.prototype.getReactionById = function(id, complete) { + var self = this; + if (complete) { + return this.getCompleteReactionById(id); + } + if (self._reactions[id] !== undefined) { + return Promise.resolve(self._reactions[id]); + } else { + return self.getMissingElements({ + reactionIds : [ id ] + }).then(function() { + return self._reactions[id]; + }); + } +}; + +MapModel.prototype._getMissingReactionsElementIds = function(reactions) { + var self = this; + var result = []; + var ids = []; + for (var i = 0; i < reactions.length; i++) { + var reaction = reactions[i]; + var elements = reaction.getElements(); + for (var j = 0; j < elements.length; j++) { + var element = elements[j]; + if (!(element instanceof Alias)) { + if (self._aliases[element] === undefined || !self._aliases[element].isComplete()) { + if (!ids[element]) { + ids[element] = true; + result.push(element); + } + } + } + } + } + return result; +}; + +MapModel.prototype.getCompleteReactionById = function(id) { + var self = this; + if (self._reactions[id] instanceof Reaction && self._reactions[id].isComplete()) { + return Promise.resolve(self._reactions[id]); + } else { + var result; + return self.getReactionById(id).then(function(result) { + var ids = self._getMissingReactionsElementIds([ result ]); + return self.getMissingElements({ + aliasIds : ids, + complete : true + }); + }).then(function() { + var i; + result = self._reactions[id]; + for (i = 0; i < result.getReactants().length; i++) { + if (!(result.getReactants()[i] instanceof Alias)) { + result.getReactants()[i] = self._aliases[result.getReactants()[i]]; + } + } + for (i = 0; i < result.getProducts().length; i++) { + if (!(result.getProducts()[i] instanceof Alias)) { + result.getProducts()[i] = self._aliases[result.getProducts()[i]]; + } + } + for (i = 0; i < result.getModifiers().length; i++) { + if (!(result.getModifiers()[i] instanceof Alias)) { + result.getModifiers()[i] = self._aliases[result.getModifiers()[i]]; + } + } + return result; + }); + } +}; + +MapModel.prototype.getMissingElements = function(elements) { + var self = this; + + var layouts = this._getLayouts(); + var aliasIds = []; + var reactionIds = []; + + var i = 0; + if (elements.reactionIds !== undefined) { + reactionIds.push.apply(reactionIds, elements.reactionIds); + for (i = 0; i < reactionIds.length; i++) { + this._missingReactions[reactionIds[i]] = reactionIds[i]; + } + + } + if (elements.aliasIds !== undefined) { + aliasIds.push.apply(aliasIds, elements.aliasIds); + for (i = 0; i < aliasIds.length; i++) { + this._missingAliases[aliasIds[i]] = aliasIds[i]; + } + } + + for (i = 0; i < layouts.length; i++) { + var layout = layouts[i]; + if (layout.isInitialized()) { + var aliases = layout.getAliases(); + for (var j = 0; j < aliases.length; j++) { + var alias = aliases[j]; + if (this._aliases[alias.getId()] === undefined && this._missingAliases[alias.getId()] === undefined) { + this._missingAliases[alias.getId()] = alias.getId(); + aliasIds.push(alias.getId()); + } + } + + var reactions = layout.getReactions(); + for (var k = 0; k < reactions.length; k++) { + var reaction = reactions[k]; + if (this._reactions[reaction.getId()] === undefined && this._missingReactions[reaction.getId()] === undefined) { + this._missingReactions[reaction.getId()] = reaction.getId(); + reactionIds.push(reaction.getId()); + } + } + } + } + var reactionPromise = null; + if (reactionIds.length > 0) { + reactionPromise = ServerConnector.getReactions({ + ids : reactionIds, + complete : elements.complete + }); + } + + var aliasPromise = null; + if (aliasIds.length > 0) { + if (elements.complete) { + aliasPromise = ServerConnector.getAliases({ + ids : aliasIds + }); + } else { + aliasPromise = ServerConnector.getAliases({ + ids : aliasIds, + columns : "id,bounds,modelId" + }); + + } + } + + var result = []; + return Promise.all([ reactionPromise, aliasPromise ]).then(function(values) { + var i; + var reactions = values[0]; + var aliases = values[1]; + var ids = []; + + if (reactions !== null) { + for (i = 0; i < reactions.length; i++) { + var reaction = reactions[i]; + self.addReaction(reaction); + result.push(reaction); + } + ids = self._getMissingReactionsElementIds(reactions); + } + if (aliases !== null) { + for (i = 0; i < aliases.length; i++) { + var alias = aliases[i]; + self.addAlias(alias); + result.push(alias); + } + } + if (ids.length > 0) { + return self.getMissingElements({ + aliasIds : ids, + complete : true + }); + } else { + return Promise.resolve([]); + } + }).then(function() { + return result; + }); +}; + +/** + * Returns layout data for a given layout identifier. + * + * @param layoutId + * layout identifier + * @returns {LayoutData} for a given layout identifier + */ +MapModel.prototype.getLayoutDataById = function(layoutId) { + var self = this; + if (self._layoutsData[layoutId] !== undefined) { + return Promise.resolve(self._layoutsData[layoutId]); + } else { + return ServerConnector.getOverlayById(layoutId).then(function(layout) { + self.addLayout(layout); + return self._layoutsData[layoutId]; + }); + } +}; + +/** + * Adds information about alias. + * + * @param aliasData + * raw data about alias + */ +MapModel.prototype.addAlias = function(aliasData) { + var alias = aliasData; + if (!(aliasData instanceof Alias)) { + alias = new Alias(aliasData); + } + if (this._aliases[alias.getId()] !== undefined) { + this._aliases[alias.getId()].update(alias); + } else { + this._aliases[alias.getId()] = alias; + if (this._missingAliases[alias.getId()] !== undefined) { + this._missingAliases[alias.getId()] = null; + delete this._missingAliases[alias.getId()]; + } + } +}; + +/** + * Adds information about reaction. + * + * @param reactionData + * raw data about reaction + */ +MapModel.prototype.addReaction = function(reactionData) { + var reaction = null; + if (reactionData instanceof Reaction) { + reaction = reactionData; + } else { + reaction = new Reaction(reactionData); + } + if (this._reactions[reaction.getId()] !== undefined) { + logger.warn("Reaction with id: " + reaction.getId() + " already exists"); + } else { + this._reactions[reaction.getId()] = reaction; + if (this._missingReactions[reaction.getId()] !== undefined) { + this._missingReactions[reaction.getId()] = null; + delete this._missingReactions[reaction.getId()]; + } + } +}; + +/** + * Returns {@link PointData} for a given point on the map. + * + * @param point + * {@link google.maps.Point} where we are requesting data + * @returns {@link PointData} for a given point on the map + */ +MapModel.prototype.getPointDataByPoint = function(inputPoint) { + if (inputPoint instanceof google.maps.Point) { + var point = this._roundPoint(inputPoint); + var id = this._pointToId(point); + var result = this._pointsData[id]; + if (result === undefined) { + result = new PointData(point, this.getId()); + this._pointsData[id] = result; + } + return result; + } else { + logger.warn("point must be of class: google.maps.Point"); + return null; + } +}; + +/** + * Returns point where x and y coordinate are rounded to 2 decimal places. + * + * @param point + * input point + * @returns {google.maps.Point} point where x and y coordinate are rounded to 2 + * decimal places + */ +MapModel.prototype._roundPoint = function(point) { + var x = parseFloat(point.x).toFixed(2); + var y = parseFloat(point.y).toFixed(2); + return new google.maps.Point(x, y); +}; + +/** + * Transform point into string identifier. + * + * @param point + * {google.maps.Point} to transform + * @returns {String} string identifier for a given point + */ +MapModel.prototype._pointToId = function(point) { + if (point instanceof google.maps.Point) { + return "(" + point.x + ", " + point.y + ")"; + } else { + return point.replace(/ /g, ''); + } +}; + +MapModel.prototype.getId = function() { + return this.id; +}; + +MapModel.prototype.setId = function(id) { + this.id = parseInt(id); +}; + +MapModel.prototype.getWidth = function() { + return this._width; +}; + +MapModel.prototype.setWidth = function(width) { + this._width = width; +}; + +MapModel.prototype.getHeight = function() { + return this._height; +}; + +MapModel.prototype.setHeight = function(height) { + this._height = height; +}; + +MapModel.prototype.getName = function() { + return this._name; +}; + +MapModel.prototype.setName = function(name) { + this._name = name; +}; + +MapModel.prototype.getMinZoom = function() { + return this._minZoom; +}; + +MapModel.prototype.setMinZoom = function(minZoom) { + this._minZoom = minZoom; +}; + +MapModel.prototype.getDefaultZoomLevel = function() { + return this._defaultZoomLevel; +}; + +MapModel.prototype.setDefaultZoomLevel = function(defaultZoomLevel) { + this._defaultZoomLevel = defaultZoomLevel; +}; + +MapModel.prototype.getDefaultCenterX = function() { + return this._defaultCenterX; +}; + +MapModel.prototype.setDefaultCenterX = function(defaultCenterX) { + this._defaultCenterX = defaultCenterX; +}; + +MapModel.prototype.getDefaultCenterY = function() { + return this._defaultCenterY; +}; + +MapModel.prototype.setDefaultCenterY = function(defaultCenterY) { + this._defaultCenterY = defaultCenterY; +}; + +MapModel.prototype.getSubmodelType = function() { + return this._submodelType; +}; + +MapModel.prototype.setSubmodelType = function(submodelType) { + this._submodelType = submodelType; +}; + +function createLatLng(param) { + if (param === undefined) { + return null; + } + if (param instanceof google.maps.LatLng) { + return new google.maps.LatLng(param.lat(), param.lng()); + } else { + return new google.maps.LatLng(param.lat, param.lng); + } +} + +MapModel.prototype.setCenterLatLng = function(centerLatLng) { + var newVal = createLatLng(centerLatLng); + if (newVal === null) { + logger.warn("centerLatLng is invalid"); + } else { + this._centerLatLng = newVal; + } +}; + +MapModel.prototype.getCenterLatLng = function() { + return this._centerLatLng; +}; + +MapModel.prototype.setTopLeftLatLng = function(topLeftLatLng) { + var newVal = createLatLng(topLeftLatLng); + if (newVal === null) { + logger.warn("topLeftLatLng is invalid"); + } else { + this._topLeftLatLng = newVal; + } +}; + +MapModel.prototype.getTopLeftLatLng = function() { + return this._topLeftLatLng; +}; + +MapModel.prototype.setBottomRightLatLng = function(bottomRightLatLng) { + var newVal = createLatLng(bottomRightLatLng); + if (newVal === null) { + logger.warn("bottomRightLatLng is invalid"); + } else { + this._bottomRightLatLng = newVal; + } +}; + +MapModel.prototype.getBottomRightLatLng = function() { + return this._bottomRightLatLng; +}; + +MapModel.prototype.getMaxZoom = function() { + return this._maxZoom; +}; + +MapModel.prototype.setMaxZoom = function(maxZoom) { + this._maxZoom = maxZoom; +}; + +MapModel.prototype.getTileSize = function() { + return this._tileSize; +}; + +MapModel.prototype.getPictureSize = function() { + return Math.max(this.getWidth(), this.getHeight()); +}; + +MapModel.prototype.setTileSize = function(tileSize) { + this._tileSize = tileSize; +}; + +MapModel.prototype.addLayouts = function(layouts) { + if (layouts === undefined) { + logger.warn("Layouts are undefined..."); + } else { + for (var i = 0; i < layouts.length; i++) { + this.addLayout(layouts[i]); + } + } +}; +MapModel.prototype.addLayout = function(layout) { + var layoutData = null; + if (layout instanceof LayoutData) { + layoutData = layout; + } else { + layoutData = new LayoutData(layout); + } + var object = this._layoutsData[layoutData.getId()]; + if (object === undefined) { + this._layoutsData[layoutData.getId()] = layoutData; + } else { + logger.warn("Layout " + layoutData.getId() + " already exists in a model: " + this.getId()); + } +}; + +MapModel.prototype.addSubmodels = function(submodels) { + if (submodels !== undefined) { + for (var i = 0; i < submodels.length; i++) { + this.addSubmodel(submodels[i]); + } + } +}; + +MapModel.prototype.addSubmodel = function(submodel) { + if (!(submodel instanceof MapModel)) { + submodel = new MapModel(submodel); + } + this._submodels.push(submodel); +}; + +MapModel.prototype.getSubmodels = function() { + return this._submodels; +}; + +MapModel.prototype.getSubmodelById = function(id) { + if (this.getId() === id) { + return this; + } + for (var i = 0; i < this._submodels.length; i++) { + if (this._submodels[i].getId() === id) { + return this._submodels[i]; + } + } + return null; +}; + +MapModel.prototype._getLayouts = function() { + var result = []; + for ( var id in this._layoutsData) { + if (this._layoutsData.hasOwnProperty(id)) { + result.push(this._layoutsData[id]); + } + } + return result; +}; + +MapModel.prototype.getByIdentifiedElement = function(ie, complete) { + var self = this; + if (ie.getType() === "ALIAS") { + return self.getAliasById(ie.getId(), complete); + } else if (ie.getType() === "REACTION") { + return self.getReactionById(ie.getId(), complete); + } else if (ie.getType() === "POINT") { + var id = self._pointToId(ie.getId()); + var result = this._pointsData[id]; + if (result === undefined) { + result = new PointData(ie); + this._pointsData[id] = result; + } + return Promise.resolve(result); + } else { + throw new Error("Unknown type: " + ie.getType()); + } +}; + +MapModel.prototype.getByIdentifiedElements = function(identifiedElements, complete) { + var self = this; + var missingAliases = []; + var missingReactions = []; + + for (var i = 0; i < identifiedElements.length; i++) { + var ie = identifiedElements[i]; + if (!this.isAvailable(ie, complete)) { + if (ie.getType() === "ALIAS") { + missingAliases.push(ie.getId()); + } else if (ie.getType() === "REACTION") { + missingReactions.push(ie.getId()); + } else { + throw new Error("Unknown type " + ie); + } + } + } + + return self.getMissingElements({ + aliasIds : missingAliases, + reactionIds : missingReactions, + complete : complete + }).then(function() { + var promises = []; + for (var i = 0; i < identifiedElements.length; i++) { + promises.push(self.getByIdentifiedElement(identifiedElements[i], complete)); + } + return Promise.all(promises); + }); + +}; + +MapModel.prototype.isAvailable = function(ie, complete) { + var element; + if (ie.getType() === "ALIAS") { + element = this._aliases[ie.getId()]; + } else if (ie.getType() === "REACTION") { + element = this._reactions[ie.getId()]; + } else if (ie.getType() === "POINT") { + var id = this._pointToId(ie.getId()); + var result = this._pointsData[id]; + if (result === undefined) { + result = new PointData(ie); + this._pointsData[id] = result; + } + element = this._pointsData[id]; + } else { + throw new Error("Unknown type: " + ie.getType(), complete); + } + if (element === undefined) { + return false; + } else if (complete) { + return element.isComplete(); + } else { + return true; + } +}; + +MapModel.prototype.getReactionsForElement = function(element, complete) { + return this.getReactionsForElements([ element ], complete); +}; + +MapModel.prototype.getReactionsForElements = function(elements, complete) { + var self = this; + var ids = []; + var i; + for (i = 0; i < elements.length; i++) { + ids.push(elements[i].getId()); + } + var idString = ids.join(); + if (this._reactionsByParticipantElementId[idString]) { + var reactions = self._reactionsByParticipantElementId[idString]; + if (!complete) { + return Promise.resolve(reactions); + } else { + var promises = []; + for (i = 0; i < reactions.length; i++) { + promises.push(self.getCompleteReactionById(reactions[i].getId())); + } + return Promise.all(promises); + } + } + + var result = []; + return ServerConnector.getReactions({ + modelId: self.getId(), + participantId : ids + }).then(function(reactions) { + result = reactions; + + for (var i = 0; i < reactions.length; i++) { + var reaction = reactions[i]; + var id = reaction.getId(); + if (self._reactions[id] === undefined) { + self._reactions[id] = reaction; + } else { + self._reactions[id].update(reaction); + } + } + var ids = self._getMissingReactionsElementIds(reactions); + return self.getMissingElements({ + aliasIds : ids, + complete : true + }); + }).then(function() { + var promises = []; + for (var i = 0; i < result.length; i++) { + promises.push(self.getCompleteReactionById(result[i].getId())); + } + return Promise.all(promises); + }); +}; + +MapModel.prototype.getCompartments = function() { + var self = this; + + var promise = Promise.resolve(); + if (self._compartments === undefined) { + promise = ServerConnector.getAliases({ + columns : "id,bounds,modelId", + type : "Compartment", + modelId : self.getId() + }).then(function(compartments) { + self._compartments = []; + for (var i = 0; i < compartments.length; i++) { + self._compartments.push(new IdentifiedElement(compartments[i])); + } + }); + } + return promise.then(function() { + return self.getByIdentifiedElements(self._compartments, true); + }); +}; + +module.exports = MapModel; diff --git a/frontend-js/src/test/js/gui/admin/EditProjectDialog-test.js b/frontend-js/src/test/js/gui/admin/EditProjectDialog-test.js index 886be16557..d5d1638bba 100644 --- a/frontend-js/src/test/js/gui/admin/EditProjectDialog-test.js +++ b/frontend-js/src/test/js/gui/admin/EditProjectDialog-test.js @@ -1,114 +1,132 @@ -"use strict"; - -require("../../mocha-config"); - -var EditProjectDialog = require('../../../../main/js/gui/admin/EditProjectDialog'); -var ServerConnector = require('../../ServerConnector-mock'); -var logger = require('../../logger'); - -var assert = require('assert'); - -describe('EditProjectDialog', function () { - - it('open', function () { - var dialog; - var project; - return ServerConnector.getProject().then(function (result) { - project = result; - dialog = new EditProjectDialog({ - element: testDiv, - project: project, - customMap: null - }); - return dialog.open(); - }).then(function () { - assert.equal(0, logger.getWarnings().length); - dialog.destroy(); - }); - }); - - it('init', function () { - var dialog; - var project; - return ServerConnector.getProject().then(function (result) { - project = result; - dialog = new EditProjectDialog({ - element: testDiv, - project: project, - customMap: null - }); - return dialog.init(); - }).then(function () { - dialog.destroy(); - }); - }); - it('saveOverlay', function () { - var dialog; - var project; - return ServerConnector.getProject().then(function (result) { - project = result; - dialog = new EditProjectDialog({ - element: testDiv, - project: project, - customMap: null - }); - return dialog.init(); - }).then(function () { - return dialog.saveOverlay(14081); - }).then(function () { - dialog.destroy(); - }); - }); - - it('saveUser', function () { - var dialog; - var project; - return ServerConnector.getProject().then(function (result) { - project = result; - dialog = new EditProjectDialog({ - element: testDiv, - project: project, - customMap: null - }); - return dialog.init(); - }).then(function () { - return dialog.saveUser("anonymous"); - }).then(function () { - dialog.destroy(); - }); - }); - - - it('onSaveClicked', function () { - var dialog; - var project; - return ServerConnector.getProject().then(function (result) { - project = result; - project.setVersion("2.01"); - dialog = new EditProjectDialog({ - element: testDiv, - project: project, - customMap: null - }); - return dialog.onSaveClicked(); - }).then(function (result) { - assert.ok(project === result); - dialog.destroy(); - }); - }); - - it('openAddOverlayDialog', function () { - var dialog; - return ServerConnector.getProject().then(function (project) { - dialog = new EditProjectDialog({ - element: testDiv, - project: project, - customMap: null - }); - return dialog.openAddOverlayDialog(); - }).then(function () { - dialog.destroy(); - }); - }); - -}); +"use strict"; + +require("../../mocha-config"); + +var EditProjectDialog = require('../../../../main/js/gui/admin/EditProjectDialog'); +var ServerConnector = require('../../ServerConnector-mock'); +var logger = require('../../logger'); + +var assert = require('assert'); + +describe('EditProjectDialog', function () { + + it('open', function () { + var dialog; + var project; + return ServerConnector.getProject().then(function (result) { + project = result; + dialog = new EditProjectDialog({ + element: testDiv, + project: project, + customMap: null + }); + return dialog.open(); + }).then(function () { + assert.equal(0, logger.getWarnings().length); + dialog.destroy(); + }); + }); + + it('init', function () { + var dialog; + var project; + return ServerConnector.getProject().then(function (result) { + project = result; + dialog = new EditProjectDialog({ + element: testDiv, + project: project, + customMap: null + }); + return dialog.init(); + }).then(function () { + dialog.destroy(); + }); + }); + it('saveOverlay', function () { + var dialog; + var project; + return ServerConnector.getProject().then(function (result) { + project = result; + dialog = new EditProjectDialog({ + element: testDiv, + project: project, + customMap: null + }); + return dialog.init(); + }).then(function () { + return dialog.saveOverlay(14081); + }).then(function () { + dialog.destroy(); + }); + }); + + it('saveMap', function () { + var dialog; + var project; + return ServerConnector.getProject().then(function (result) { + project = result; + dialog = new EditProjectDialog({ + element: testDiv, + project: project, + customMap: null + }); + return dialog.init(); + }).then(function () { + return dialog.saveMap(15781); + }).then(function () { + dialog.destroy(); + }); + }); + + it('saveUser', function () { + var dialog; + var project; + return ServerConnector.getProject().then(function (result) { + project = result; + dialog = new EditProjectDialog({ + element: testDiv, + project: project, + customMap: null + }); + return dialog.init(); + }).then(function () { + return dialog.saveUser("anonymous"); + }).then(function () { + dialog.destroy(); + }); + }); + + + it('onSaveClicked', function () { + var dialog; + var project; + return ServerConnector.getProject().then(function (result) { + project = result; + project.setVersion("2.01"); + dialog = new EditProjectDialog({ + element: testDiv, + project: project, + customMap: null + }); + return dialog.onSaveClicked(); + }).then(function (result) { + assert.ok(project === result); + dialog.destroy(); + }); + }); + + it('openAddOverlayDialog', function () { + var dialog; + return ServerConnector.getProject().then(function (project) { + dialog = new EditProjectDialog({ + element: testDiv, + project: project, + customMap: null + }); + return dialog.openAddOverlayDialog(); + }).then(function () { + dialog.destroy(); + }); + }); + +}); diff --git a/frontend-js/testFiles/apiCalls/projects/sample/models/15781/PATCH_model.id=15781&token=MOCK_TOKEN_ID& b/frontend-js/testFiles/apiCalls/projects/sample/models/15781/PATCH_model.id=15781&token=MOCK_TOKEN_ID& new file mode 100644 index 0000000000..689b1eb3d8 --- /dev/null +++ b/frontend-js/testFiles/apiCalls/projects/sample/models/15781/PATCH_model.id=15781&token=MOCK_TOKEN_ID& @@ -0,0 +1 @@ +{"version":null,"name":"UNKNOWN DISEASE MAP","idObject":15781,"tileSize":256,"width":1305,"height":473,"defaultCenterX":3.0,"defaultCenterY":4.0,"defaultZoomLevel":null,"minZoom":2,"maxZoom":5,"layouts":[{"idObject":14081,"modelId":15781,"name":"Pathways and compartments","description":"","status":"Not available","publicOverlay":true,"defaultOverlay":false,"progress":"0.00","directory":"5e8ff9bf55ba3508199d22e984129be6/_nested0","creator":"","inputDataAvailable":"false"},{"idObject":14082,"modelId":15781,"name":"Network","description":"","status":"Not available","publicOverlay":true,"defaultOverlay":false,"progress":"0.00","directory":"5e8ff9bf55ba3508199d22e984129be6/_normal0","creator":"","inputDataAvailable":"false"},{"idObject":14083,"modelId":15781,"name":"Empty","description":"","status":"Not available","publicOverlay":true,"defaultOverlay":true,"progress":"0.00","directory":"5e8ff9bf55ba3508199d22e984129be6/_empty0","creator":"","inputDataAvailable":"false"},{"idObject":18076,"modelId":15781,"name":"C:\\fakepath\\test.txt","description":"xxx","status":"OK","publicOverlay":true,"defaultOverlay":false,"progress":"0.00","directory":"5e8ff9bf55ba3508199d22e984129be6/.18076","creator":"","inputDataAvailable":"true"},{"idObject":18077,"modelId":15781,"name":"xxx","description":"yyy","status":"OK","publicOverlay":true,"defaultOverlay":false,"progress":"0.00","directory":"5e8ff9bf55ba3508199d22e984129be6/.18077","creator":"","inputDataAvailable":"true"},{"idObject":22750,"modelId":15781,"name":"new-global","description":"","status":"OK","publicOverlay":true,"defaultOverlay":false,"progress":"0.00","directory":"5e8ff9bf55ba3508199d22e984129be6/.22750","creator":"","inputDataAvailable":"true"}],"submodels":[],"centerLatLng":{"lat":79.18277721779353,"lng":-135.06093781915757},"topLeftLatLng":{"lat":85.05112877980659,"lng":-180.0},"bottomRightLatLng":{"lat":81.26928406550978,"lng":-90.0},"submodelType":"UNKNOWN"} \ No newline at end of file diff --git a/model/src/main/java/lcsb/mapviewer/model/map/model/Model.java b/model/src/main/java/lcsb/mapviewer/model/map/model/Model.java index f5d4a32637..f20bbd35a4 100644 --- a/model/src/main/java/lcsb/mapviewer/model/map/model/Model.java +++ b/model/src/main/java/lcsb/mapviewer/model/map/model/Model.java @@ -21,7 +21,7 @@ import lcsb.mapviewer.model.map.species.Species; /** * This interface defines functionality that the model container class should - * implement to access data effitiently. It shouldn't be implemented by data + * implement to access data efficiently. It shouldn't be implemented by data * model. * * @author Piotr Gawron @@ -29,543 +29,563 @@ import lcsb.mapviewer.model.map.species.Species; */ public interface Model { - /** - * Adds element to the model. - * - * @param element - * element to add - */ - void addElement(Element element); - - /** - * - * @return model width - */ - Double getWidth(); - - /** - * Sets model width. - * - * @param width - * new model width - */ - void setWidth(Double width); - - /** - * - * @return model hieght - */ - Double getHeight(); - - /** - * Sets model height. - * - * @param height - * new model height - */ - void setHeight(Double height); - - /** - * Sets model width. - * - * @param text - * new model width - */ - void setWidth(String text); - - /** - * Sets model height. - * - * @param text - * new model height - */ - void setHeight(String text); - - /** - * Returns set of all alisaes. - * - * @return set of all alisaes - */ - Set<Element> getElements(); - - /** - * Returns element with the given element identfier ({@link Element#elementId} - * ). - * - * @param idElement - * element identifier - * @param <T> - * type of the object to be returned - * @return {@link Element} with the given id - */ - <T extends Element> T getElementByElementId(String idElement); - - /** - * Adds reaction to the model. - * - * @param reaction - * reaction to add - */ - void addReaction(Reaction reaction); - - /** - * Returns set of reactions. - * - * @return set of reaction in the model - */ - Set<Reaction> getReactions(); - - /** - * @return list of compartments - */ - List<Compartment> getCompartments(); - - /** - * Adds layer to the model. - * - * @param layer - * object to add - */ - void addLayer(Layer layer); - - /** - * - * @return set of layers - */ - Set<Layer> getLayers(); - - /** - * Adds list of elements into model. - * - * @param elements - * list of elements - */ - void addElements(List<? extends Element> elements); - - /** - * Sets new short description of the model. - * - * @param notes - * new short description - */ - void setNotes(String notes); - - /** - * - * @return short description of the model - */ - String getNotes(); - - /** - * Returns reaction with the id given in the parameter. - * - * @param idReaction - * reaction identifier ({@link Reaction#idReaction}) - * @return reaction with the id given in the parameter - */ - Reaction getReactionByReactionId(String idReaction); - - /** - * Adds set of layers to the model. - * - * @param layers - * object to add - */ - void addLayers(Collection<Layer> layers); - - /** - * Adds {@link ElementGroup} to the model. - * - * @param elementGroup - * object to add - */ - void addElementGroup(ElementGroup elementGroup); - - /** - * Adds {@link BlockDiagram} to the model. - * - * @param blockDiagram - * object to add - */ - void addBlockDiagream(BlockDiagram blockDiagram); - - /** - * @param idModel - * the idModel to set - * @see Model#idModel - */ - void setIdModel(String idModel); - - /** - * @return the idModel - * @see Model#idModel - */ - String getIdModel(); - - /** - * @param tileSize - * the tileSize to set - * @see ModelData#tileSize - */ - void setTileSize(int tileSize); - - /** - * @return the tileSize - * @see ModelData#tileSize - */ - int getTileSize(); - - /** - * @param zoomLevels - * the zoomLevels to set - * @see ModelData#zoomLevels - */ - void setZoomLevels(int zoomLevels); - - /** - * @return the zoomLevels - * @see ModelData#zoomLevels - */ - int getZoomLevels(); - - /** - * Removes reaction from model. - * - * @param reaction - * reaction to remove - */ - void removeReaction(Reaction reaction); - - /** - * Removes {@link Element} from the model. - * - * @param element - * element to remove - */ - void removeElement(Element element); - - /** - * Returns list of reactions sorted by reaction id. - * - * @return list of reactions sorted by reaction id - */ - List<Reaction> getSortedReactions(); - - /** - * Return list of elements sorted by the size. - * - * @return list of elements sorted by the size - */ - List<Element> getSortedSpeciesList(); - - /** - * Returns collection of all {@link Species} excluding {@link Complex}. - * - * @return collection of all {@link Species} excluding {@link Complex}. - */ - Collection<Species> getNotComplexSpeciesList(); - - /** - * Returns list of all {@link Species} in the model. - * - * @return list of all {@link Species} in the model - */ - List<Species> getSpeciesList(); - - /** - * Returns collection of {@link Complex}. - * - * @return collection of {@link Complex} - */ - Collection<Complex> getComplexList(); - - /** - * Adds reactions to model. - * - * @param reactions2 - * list of reaction to add - */ - void addReactions(List<Reaction> reactions2); - - /** - * Returns list of elements annotated by the {@link MiriamData}. - * - * @param miriamData - * {@link MiriamData} - * @return list of elements - */ - Set<BioEntity> getElementsByAnnotation(MiriamData miriamData); - - /** - * Returns list of elements with given name. - * - * @param name - * name of the element - * @return list of elements with given name - */ - List<Element> getElementsByName(String name); - - /** - * - * @param height - * new {@link ModelData#height} - */ - void setHeight(int height); - - /** - * - * @param width - * new {@link ModelData#width} - */ - void setWidth(int width); - - /** - * Adds layout to model. - * - * @param layout - * layout to add - */ - void addLayout(Layout layout); - - /** - * - * @param layouts - * new {@link ModelData#layouts} - */ - void setLayouts(List<Layout> layouts); - - /** - * - * @return {@link ModelData#layouts} - */ - List<Layout> getLayouts(); - - /** - * - * @param creationDate - * new {@link ModelData#creationDate} - */ - void setCreationDate(Calendar creationDate); - - /** - * - * @return {@link ModelData#creationDate} - */ - Calendar getCreationDate(); - - /** - * Returns {@link Element} for given database identifier. - * - * @param dbId - * element database identifier ({@link Element#id}) - * @return {@link Element} for a given id - */ - <T extends Element> T getElementByDbId(Integer dbId); - - /** - * Returns {@link Reaction} for given database identifier. - * - * @param dbId - * reaction database identifier ({@link Reaction#id}) - * @return {@link Reaction} for a given id - */ - Reaction getReactionByDbId(Integer dbId); - - /** - * Returns sorted by size list of compartments. - * - * @return list of compartment sorted by size - */ - List<Compartment> getSortedCompartments(); - - /** - * Returns list of elements sorted by the size. - * - * @return list of elements sorted by the size - */ - List<Element> getElementsSortedBySize(); - - /** - * - * @param project - * new {@link ModelData#project} - */ - void setProject(Project project); - - /** - * - * @return {@link ModelData#project} - */ - Project getProject(); - - /** - * - * @param elements - * new {@link ModelData#elements} collection - */ - void setElements(Set<Element> elements); - - /** - * @return the modelData - */ - ModelData getModelData(); - - /** - * - * @return {@link ModelData#id} - */ - Integer getId(); - - /** - * Adds submodel connection. - * - * @param submodel - * submodel to add - */ - void addSubmodelConnection(ModelSubmodelConnection submodel); - - /** - * Returns set of submodel connections. - * - * @return collection of submodels - */ - Collection<ModelSubmodelConnection> getSubmodelConnections(); - - /** - * Returns name of the model. - * - * @return name of the model - */ - String getName(); - - /** - * Sets name of the model. - * - * @param name - * name of the model - */ - void setName(String name); - - /** - * Returns {@link Model submodel} by the {@link ModelData#id database - * identifier} given in the parameter. - * - * @param idObject - * the {@link ModelData#id database identifier} that identifies - * submodel - * @return {@link Model submodel} by the {@link ModelData#id database - * identifier} given in the parameter - */ - Model getSubmodelById(Integer idObject); - - /** - * Returns set of connections that point to this model. Be very carefoul with - * using this function as the implementation forces lazy loading of the maps. - * - * @return set of connections that point to this model - */ - Collection<SubmodelConnection> getParentModels(); - - /** - * Returns connection to a submodel identified by connection name. - * - * @param name - * name of the connection - * @return connection to a submodel identified by connection name - */ - Model getSubmodelByConnectionName(String name); - - /** - * Returns connection to a submodel identified by connection identifier. - * - * @param id - * id of the connection - * @return connection to a submodel identified by connection identifier - */ - SubmodelConnection getSubmodelConnectionById(Integer id); - - /** - * Returns submodel identified by submodel identifier. - * - * @param identifier - * identifier of the model - * @return submodel identified by identifiere - */ - Model getSubmodelById(String identifier); - - /** - * Returns collection of {@link Model submodels}. - * - * @return collection of {@link Model submodels} - */ - Collection<Model> getSubmodels(); - - /** - * Returns {@link Model submodel} identified by the {@link ModelData#name - * model name}. It returns this 'parent' object when the names matches. - * - * @param name - * name of the submodel that should be returned - * @return {@link Model submodel} identified by the {@link ModelData#name - * model name} - */ - Model getSubmodelByName(String name); - - /** - * Adds layout to list of layouts on a given position. - * - * @param index - * where the new layout should be added - * @param layout - * object to add - */ - void addLayout(int index, Layout layout); - - /** - * Adds new {@link DataMiningSet} to the model. - * - * @param dataMiningSet - * object to add - */ - void addDataMiningSet(DataMiningSet dataMiningSet); - - /** - * Returns list of {@link DataMiningSet} associated with the model. - * - * @return list of {@link DataMiningSet} associated with the model - */ - List<DataMiningSet> getDataMiningSets(); - - /** - * Adds set of {@link DataMiningSet} to the model. - * - * @param dataMiningSets - * objects to add - */ - void addDataMiningSets(Collection<DataMiningSet> dataMiningSets); - - /** - * Returns layout identified by {@link Layout#id database identifier}. - * - * @param layoutIdentfier - * layout {@link Layout#id database identifier} - * @return layout identified by {@link Layout#id database identifier} - */ - Layout getLayoutByIdentifier(Integer layoutIdentfier); - - /** - * Sets database identifier of the model. - * - * @param id - * database identifier - */ - void setId(int id); - - /** - * Return list od all {@link BioEntity} in the map. This includes all - * {@link Reaction reactions} and {@link Element elements}. - * - * @return list od all {@link BioEntity} in the map - */ - List<BioEntity> getBioEntities(); + /** + * Adds element to the model. + * + * @param element + * element to add + */ + void addElement(Element element); + + /** + * + * @return model width + */ + Double getWidth(); + + /** + * Sets model width. + * + * @param width + * new model width + */ + void setWidth(Double width); + + /** + * + * @return model height + */ + Double getHeight(); + + /** + * Sets model height. + * + * @param height + * new model height + */ + void setHeight(Double height); + + /** + * Sets model width. + * + * @param text + * new model width + */ + void setWidth(String text); + + /** + * Sets model height. + * + * @param text + * new model height + */ + void setHeight(String text); + + /** + * Returns set of all elements. + * + * @return set of all elements + */ + Set<Element> getElements(); + + /** + * Returns element with the given element identifier ({@link Element#elementId} + * ). + * + * @param idElement + * element identifier + * @param <T> + * type of the object to be returned + * @return {@link Element} with the given id + */ + <T extends Element> T getElementByElementId(String idElement); + + /** + * Adds reaction to the model. + * + * @param reaction + * reaction to add + */ + void addReaction(Reaction reaction); + + /** + * Returns set of reactions. + * + * @return set of reaction in the model + */ + Set<Reaction> getReactions(); + + /** + * @return list of compartments + */ + List<Compartment> getCompartments(); + + /** + * Adds layer to the model. + * + * @param layer + * object to add + */ + void addLayer(Layer layer); + + /** + * + * @return set of layers + */ + Set<Layer> getLayers(); + + /** + * Adds list of elements into model. + * + * @param elements + * list of elements + */ + void addElements(List<? extends Element> elements); + + /** + * Sets new short description of the model. + * + * @param notes + * new short description + */ + void setNotes(String notes); + + /** + * + * @return short description of the model + */ + String getNotes(); + + /** + * Returns reaction with the id given in the parameter. + * + * @param idReaction + * reaction identifier ({@link Reaction#idReaction}) + * @return reaction with the id given in the parameter + */ + Reaction getReactionByReactionId(String idReaction); + + /** + * Adds set of layers to the model. + * + * @param layers + * object to add + */ + void addLayers(Collection<Layer> layers); + + /** + * Adds {@link ElementGroup} to the model. + * + * @param elementGroup + * object to add + */ + void addElementGroup(ElementGroup elementGroup); + + /** + * Adds {@link BlockDiagram} to the model. + * + * @param blockDiagram + * object to add + */ + void addBlockDiagream(BlockDiagram blockDiagram); + + /** + * @param idModel + * the idModel to set + * @see Model#idModel + */ + void setIdModel(String idModel); + + /** + * @return the idModel + * @see Model#idModel + */ + String getIdModel(); + + /** + * @param tileSize + * the tileSize to set + * @see ModelData#tileSize + */ + void setTileSize(int tileSize); + + /** + * @return the tileSize + * @see ModelData#tileSize + */ + int getTileSize(); + + /** + * @param zoomLevels + * the zoomLevels to set + * @see ModelData#zoomLevels + */ + void setZoomLevels(int zoomLevels); + + /** + * @return the zoomLevels + * @see ModelData#zoomLevels + */ + int getZoomLevels(); + + /** + * Removes reaction from model. + * + * @param reaction + * reaction to remove + */ + void removeReaction(Reaction reaction); + + /** + * Removes {@link Element} from the model. + * + * @param element + * element to remove + */ + void removeElement(Element element); + + /** + * Returns list of reactions sorted by reaction id. + * + * @return list of reactions sorted by reaction id + */ + List<Reaction> getSortedReactions(); + + /** + * Return list of elements sorted by the size. + * + * @return list of elements sorted by the size + */ + List<Element> getSortedSpeciesList(); + + /** + * Returns collection of all {@link Species} excluding {@link Complex}. + * + * @return collection of all {@link Species} excluding {@link Complex}. + */ + Collection<Species> getNotComplexSpeciesList(); + + /** + * Returns list of all {@link Species} in the model. + * + * @return list of all {@link Species} in the model + */ + List<Species> getSpeciesList(); + + /** + * Returns collection of {@link Complex}. + * + * @return collection of {@link Complex} + */ + Collection<Complex> getComplexList(); + + /** + * Adds reactions to model. + * + * @param reactions2 + * list of reaction to add + */ + void addReactions(List<Reaction> reactions2); + + /** + * Returns list of elements annotated by the {@link MiriamData}. + * + * @param miriamData + * {@link MiriamData} + * @return list of elements + */ + Set<BioEntity> getElementsByAnnotation(MiriamData miriamData); + + /** + * Returns list of elements with given name. + * + * @param name + * name of the element + * @return list of elements with given name + */ + List<Element> getElementsByName(String name); + + /** + * + * @param height + * new {@link ModelData#height} + */ + void setHeight(int height); + + /** + * + * @param width + * new {@link ModelData#width} + */ + void setWidth(int width); + + /** + * Adds layout to model. + * + * @param layout + * layout to add + */ + void addLayout(Layout layout); + + /** + * + * @param layouts + * new {@link ModelData#layouts} + */ + void setLayouts(List<Layout> layouts); + + /** + * + * @return {@link ModelData#layouts} + */ + List<Layout> getLayouts(); + + /** + * + * @param creationDate + * new {@link ModelData#creationDate} + */ + void setCreationDate(Calendar creationDate); + + /** + * + * @return {@link ModelData#creationDate} + */ + Calendar getCreationDate(); + + /** + * Returns {@link Element} for given database identifier. + * + * @param dbId + * element database identifier ({@link Element#id}) + * @return {@link Element} for a given id + */ + <T extends Element> T getElementByDbId(Integer dbId); + + /** + * Returns {@link Reaction} for given database identifier. + * + * @param dbId + * reaction database identifier ({@link Reaction#id}) + * @return {@link Reaction} for a given id + */ + Reaction getReactionByDbId(Integer dbId); + + /** + * Returns sorted by size list of compartments. + * + * @return list of compartment sorted by size + */ + List<Compartment> getSortedCompartments(); + + /** + * Returns list of elements sorted by the size. + * + * @return list of elements sorted by the size + */ + List<Element> getElementsSortedBySize(); + + /** + * + * @param project + * new {@link ModelData#project} + */ + void setProject(Project project); + + /** + * + * @return {@link ModelData#project} + */ + Project getProject(); + + /** + * + * @param elements + * new {@link ModelData#elements} collection + */ + void setElements(Set<Element> elements); + + /** + * @return the modelData + */ + ModelData getModelData(); + + /** + * + * @return {@link ModelData#id} + */ + Integer getId(); + + /** + * Adds submodel connection. + * + * @param submodel + * submodel to add + */ + void addSubmodelConnection(ModelSubmodelConnection submodel); + + /** + * Returns set of submodel connections. + * + * @return collection of submodels + */ + Collection<ModelSubmodelConnection> getSubmodelConnections(); + + /** + * Returns name of the model. + * + * @return name of the model + */ + String getName(); + + /** + * Sets name of the model. + * + * @param name + * name of the model + */ + void setName(String name); + + /** + * Returns {@link Model submodel} by the {@link ModelData#id database + * identifier} given in the parameter. + * + * @param idObject + * the {@link ModelData#id database identifier} that identifies + * submodel + * @return {@link Model submodel} by the {@link ModelData#id database + * identifier} given in the parameter + */ + Model getSubmodelById(Integer idObject); + + /** + * Returns set of connections that point to this model. Be very carefoul with + * using this function as the implementation forces lazy loading of the maps. + * + * @return set of connections that point to this model + */ + Collection<SubmodelConnection> getParentModels(); + + /** + * Returns connection to a submodel identified by connection name. + * + * @param name + * name of the connection + * @return connection to a submodel identified by connection name + */ + Model getSubmodelByConnectionName(String name); + + /** + * Returns connection to a submodel identified by connection identifier. + * + * @param id + * id of the connection + * @return connection to a submodel identified by connection identifier + */ + SubmodelConnection getSubmodelConnectionById(Integer id); + + /** + * Returns submodel identified by submodel identifier. + * + * @param identifier + * identifier of the model + * @return submodel identified by identifiere + */ + Model getSubmodelById(String identifier); + + /** + * Returns collection of {@link Model submodels}. + * + * @return collection of {@link Model submodels} + */ + Collection<Model> getSubmodels(); + + /** + * Returns {@link Model submodel} identified by the {@link ModelData#name model + * name}. It returns this 'parent' object when the names matches. + * + * @param name + * name of the submodel that should be returned + * @return {@link Model submodel} identified by the {@link ModelData#name model + * name} + */ + Model getSubmodelByName(String name); + + /** + * Adds layout to list of layouts on a given position. + * + * @param index + * where the new layout should be added + * @param layout + * object to add + */ + void addLayout(int index, Layout layout); + + /** + * Adds new {@link DataMiningSet} to the model. + * + * @param dataMiningSet + * object to add + */ + void addDataMiningSet(DataMiningSet dataMiningSet); + + /** + * Returns list of {@link DataMiningSet} associated with the model. + * + * @return list of {@link DataMiningSet} associated with the model + */ + List<DataMiningSet> getDataMiningSets(); + + /** + * Adds set of {@link DataMiningSet} to the model. + * + * @param dataMiningSets + * objects to add + */ + void addDataMiningSets(Collection<DataMiningSet> dataMiningSets); + + /** + * Returns layout identified by {@link Layout#id database identifier}. + * + * @param layoutIdentfier + * layout {@link Layout#id database identifier} + * @return layout identified by {@link Layout#id database identifier} + */ + Layout getLayoutByIdentifier(Integer layoutIdentfier); + + /** + * Sets database identifier of the model. + * + * @param id + * database identifier + */ + void setId(int id); + + /** + * Return list of all {@link BioEntity} in the map. This includes all + * {@link Reaction reactions} and {@link Element elements}. + * + * @return list of all {@link BioEntity} in the map + */ + List<BioEntity> getBioEntities(); + + Double getDefaultCenterX(); + + void setDefaultCenterX(Double defaultCenterX); + + Double getDefaultCenterY(); + + void setDefaultCenterY(Double defaultCenterY); + + Integer getDefaultZoomLevel(); + + void setDefaultZoomLevel(Integer defaultZoomLevel); + + /** + * Remove layout from model. + * + * @param dbLayout + * layout to remove + */ + void removeLayout(Layout dbLayout); } diff --git a/model/src/main/java/lcsb/mapviewer/model/map/model/ModelData.java b/model/src/main/java/lcsb/mapviewer/model/map/model/ModelData.java index 448470f1d8..e5c17ee798 100644 --- a/model/src/main/java/lcsb/mapviewer/model/map/model/ModelData.java +++ b/model/src/main/java/lcsb/mapviewer/model/map/model/ModelData.java @@ -1,695 +1,735 @@ -package lcsb.mapviewer.model.map.model; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.ManyToOne; -import javax.persistence.OneToMany; -import javax.persistence.OrderBy; -import javax.persistence.Table; -import javax.persistence.Transient; -import javax.xml.bind.annotation.XmlTransient; - -import org.apache.log4j.Logger; -import org.hibernate.annotations.Cascade; -import org.hibernate.annotations.CascadeType; - -import lcsb.mapviewer.common.exception.InvalidArgumentException; -import lcsb.mapviewer.model.Project; -import lcsb.mapviewer.model.map.graph.DataMiningSet; -import lcsb.mapviewer.model.map.layout.BlockDiagram; -import lcsb.mapviewer.model.map.layout.ElementGroup; -import lcsb.mapviewer.model.map.layout.Layout; -import lcsb.mapviewer.model.map.layout.graphics.Layer; -import lcsb.mapviewer.model.map.reaction.Reaction; -import lcsb.mapviewer.model.map.species.Element; - -/** - * Representation of the model data. It contains all information about single - * map: - * <ul> - * <li>species and compartments ({@link #elements} field)</li> - * <li>list of reactions ({@link #reactions})</li> - * <li>layers with additional graphical objects ({@link #layers})</li> - * <li>different graphical visualizations of the whole map ({@link #layouts}) - * </li> - * <li>some other meta data (like: creation date, version, etc)</li> - * </ul> - * - * @author Piotr Gawron - * - */ -@Entity -@Table(name = "model_table") -public class ModelData implements Serializable { - - /** - * - */ - private static final long serialVersionUID = 1L; - - /** - * Default class logger. - */ - private static Logger logger = Logger.getLogger(ModelData.class); - - /** - * Set of all elements in the map. - * - * @see Element - */ - @Cascade({ CascadeType.ALL }) - @OneToMany(mappedBy = "model", orphanRemoval = true) - private Set<Element> elements = new HashSet<>(); - - /** - * Set of all layers in the map. - * - * @see Layer - * - */ - @Cascade({ CascadeType.ALL }) - @OneToMany(mappedBy = "model", orphanRemoval = true) - private Set<Layer> layers = new HashSet<>(); - - /** - * Unique database identifier. - */ - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "idDb", unique = true, nullable = false) - private int id; - - /** - * Collection of all reactions in the map. - * - * @see Reaction - * - */ - @Cascade({ CascadeType.ALL }) - @OneToMany(mappedBy = "model", orphanRemoval = true) - private Set<Reaction> reactions = new HashSet<>(); - - /** - * When the map was created. - */ - private Calendar creationDate = Calendar.getInstance(); - - /** - * Width of the map. - */ - private Double width; - /** - * Height of the map. - */ - private Double height; - - /** - * Description of the map. - */ - @Column(name = "notes", columnDefinition = "TEXT") - private String notes; - - /** - * Name of the map. - */ - private String name; - - /** - * Another CellDesigner identifier. - */ - private String idModel; - - /** - * How many hierarchical levels are in this map. - */ - private int zoomLevels; - - /** - * Size of the image tile that are used in this model. - */ - private int tileSize; - - /** - * List of layouts. - */ - @Cascade({ CascadeType.ALL }) - @OneToMany(fetch = FetchType.EAGER, mappedBy = "model", orphanRemoval = true) - @OrderBy("id") - private List<Layout> layouts = new ArrayList<>(); - - /** - * Project to which this model belong to. - */ - @ManyToOne(fetch = FetchType.LAZY, optional = false) - private Project project; - - // This field should be transient in hibernate and during the transformation - // to xml - /** - * {@link Model} object that is a container where data is being placed. - */ - @Transient - @XmlTransient - private Model model; - - /** - * List of submodels. - */ - @Cascade({ CascadeType.ALL }) - @OneToMany(fetch = FetchType.EAGER, mappedBy = "parentModel", orphanRemoval = true) - private Set<ModelSubmodelConnection> submodels = new HashSet<>(); - - /** - * List of connections with parent model (by definition one map can be a - * submodel of few maps). - */ - @Cascade({ CascadeType.ALL }) - @OneToMany(fetch = FetchType.LAZY, mappedBy = "submodel") - private Set<SubmodelConnection> parentModels = new HashSet<>(); - - /** - * List of {@link DataMiningSet} in the model. These data mining sets are used - * to create suggested (but missing) connections in the model. - */ - @Cascade({ CascadeType.ALL }) - @OneToMany(mappedBy = "model", orphanRemoval = true) - @OrderBy("id") - private List<DataMiningSet> dataMiningSets = new ArrayList<>(); - - /** - * Default constructor. - */ - public ModelData() { - } - - /** - * Adds {@link Element} to model data. - * - * @param element - * element to add - */ - public void addElement(Element element) { - element.setModelData(this); - elements.add(element); - } - - /** - * Adds {@link Reaction} to model data. - * - * @param reaction - * reaction to add - */ - public void addReaction(Reaction reaction) { - reaction.setModelData(this); - reactions.add(reaction); - } - - /** - * Adds {@link Layer} to model data. - * - * @param layer - * layer to add - */ - public void addLayer(Layer layer) { - layer.setModel(this); - layers.add(layer); - } - - /** - * Adds collection of {@link Element elements} to model data. - * - * @param elements - * elements to add - */ - public void addElements(List<? extends Element> elements) { - for (Element element : elements) { - addElement(element); - } - } - - /** - * - * @return {@link #id} - */ - public Integer getId() { - return id; - } - - /** - * - * @param id - * new {@link #id} - */ - public void setId(int id) { - this.id = id; - } - - /** - * - * @param elements - * new {@link #elements} collection - */ - public void setElements(Set<Element> elements) { - this.elements = elements; - } - - /** - * - * @return {@link #project} - */ - public Project getProject() { - return project; - } - - /** - * - * @param project - * new {@link #project} - */ - public void setProject(Project project) { - this.project = project; - } - - /** - * - * @return {@link #creationDate} - */ - public Calendar getCreationDate() { - return creationDate; - } - - /** - * - * @param creationDate - * new {@link #creationDate} - */ - public void setCreationDate(Calendar creationDate) { - this.creationDate = creationDate; - } - - /** - * - * @return {@link #layouts} - */ - public List<Layout> getLayouts() { - return layouts; - } - - /** - * - * @param layouts - * new {@link #layouts} - */ - public void setLayouts(List<Layout> layouts) { - this.layouts = layouts; - } - - /** - * Adds layout to model. - * - * @param layout - * layout to add - */ - public void addLayout(Layout layout) { - layouts.add(layout); - layout.setModel(this); - } - - /** - * - * @param width - * new {@link #width} - */ - public void setWidth(int width) { - setWidth(Double.valueOf(width)); - } - - /** - * - * @param height - * new {@link #height} - */ - public void setHeight(int height) { - setHeight(Double.valueOf(height)); - } - - /** - * Adds reactions to model. - * - * @param reactions2 - * list of reaction to add - */ - public void addReactions(List<Reaction> reactions2) { - for (Reaction reaction : reactions2) { - addReaction(reaction); - } - } - - /** - * Adds collection of {@link Layer layers} to the model data. - * - * @param layers - * objets to add - */ - public void addLayers(Collection<Layer> layers) { - for (Layer layer : layers) { - addLayer(layer); - } - } - - /** - * Remove layout from model. - * - * @param dbLayout - * layout to remove - */ - public void removeLayout(Layout dbLayout) { - List<Layout> toRemove = new ArrayList<Layout>(); - for (Layout layout : layouts) { - if (layout.getId() == dbLayout.getId()) { - toRemove.add(layout); - } - } - - if (toRemove.size() == 0) { - logger.warn("Cannot remove layout: " + dbLayout.getId()); - } else { - layouts.removeAll(toRemove); - } - } - - /** - * Adds {@link ElementGroup} to the model data. - * - * @param elementGroup - * object to add - */ - public void addElementGroup(ElementGroup elementGroup) { - // for now we ignore this information - } - - /** - * Adds {@link BlockDiagram} to the model data. - * - * @param blockDiagram - * object to add - */ - public void addBlockDiagream(BlockDiagram blockDiagram) { - // for now we ignore this information - } - - /** - * Removes {@link Element} from the model. - * - * @param element - * element to remove - */ - public void removeElement(Element element) { - if (element == null) { - throw new InvalidArgumentException("Cannot remove null"); - } - if (!elements.contains(element)) { - logger.warn("Element doesn't exist in the map: " + element.getElementId()); - return; - } - - element.setModelData(null); - elements.remove(element); - } - - /** - * Removes reaction from model. - * - * @param reaction - * reaction to remove - */ - public void removeReaction(Reaction reaction) { - if (!reactions.contains(reaction)) { - logger.warn("Reaction doesn't exist in the model: " + reaction.getIdReaction()); - return; - } - reaction.setModelData(null); - reactions.remove(reaction); - } - - /** - * @return the zoomLevels - * @see #zoomLevels - */ - public int getZoomLevels() { - return zoomLevels; - } - - /** - * @param zoomLevels - * the zoomLevels to set - * @see #zoomLevels - */ - public void setZoomLevels(int zoomLevels) { - this.zoomLevels = zoomLevels; - } - - /** - * @return the tileSize - * @see #tileSize - */ - public int getTileSize() { - return tileSize; - } - - /** - * @param tileSize - * the tileSize to set - * @see #tileSize - */ - public void setTileSize(int tileSize) { - this.tileSize = tileSize; - } - - /** - * @return the idModel - * @see #idModel - */ - public String getIdModel() { - return idModel; - } - - /** - * @param idModel - * the idModel to set - * @see #idModel - */ - public void setIdModel(String idModel) { - this.idModel = idModel; - } - - /** - * @return the model - * @see #model - */ - @XmlTransient - public Model getModel() { - if (model == null) { - logger.warn("Model not set in model data."); - } - return model; - } - - /** - * @param model - * the model to set - * @see #model - */ - public void setModel(Model model) { - this.model = model; - } - - /** - * @return the elements - * @see #elements - */ - public Set<Element> getElements() { - return elements; - } - - /** - * @return the layers - * @see #layers - */ - public Set<Layer> getLayers() { - return layers; - } - - /** - * @param layers - * the layers to set - * @see #layers - */ - public void setLayers(Set<Layer> layers) { - this.layers = layers; - } - - /** - * @return the reactions - * @see #reactions - */ - public Set<Reaction> getReactions() { - return reactions; - } - - /** - * @param reactions - * the reactions to set - * @see #reactions - */ - public void setReactions(Set<Reaction> reactions) { - this.reactions = reactions; - } - - /** - * @return the width - * @see #width - */ - public Double getWidth() { - return width; - } - - /** - * @param width - * the width to set - * @see #width - */ - public void setWidth(Double width) { - this.width = width; - } - - /** - * @return the height - * @see #height - */ - public Double getHeight() { - return height; - } - - /** - * @param height - * the height to set - * @see #height - */ - public void setHeight(Double height) { - this.height = height; - } - - /** - * @return the notes - * @see #notes - */ - public String getNotes() { - return notes; - } - - /** - * @param notes - * the notes to set - * @see #notes - */ - public void setNotes(String notes) { - this.notes = notes; - } - - /** - * Adds submodel. - * - * @param submodel - * object to add - */ - public void addSubmodel(ModelSubmodelConnection submodel) { - this.submodels.add(submodel); - submodel.setParentModel(this); - } - - /** - * Returns collection of submodels. - * - * @return collection of submodels. - */ - public Collection<ModelSubmodelConnection> getSubmodels() { - return this.submodels; - } - - /** - * @return the name - * @see #name - */ - public String getName() { - return name; - } - - /** - * @param name - * the name to set - * @see #name - */ - public void setName(String name) { - this.name = name; - } - - /** - * @return the parentModels - * @see #parentModels - */ - public Set<SubmodelConnection> getParentModels() { - return parentModels; - } - - /** - * @param parentModels - * the parentModels to set - * @see #parentModels - */ - public void setParentModels(Set<SubmodelConnection> parentModels) { - this.parentModels = parentModels; - } - - /** - * @return the dataMining - * @see #dataMiningSets - */ - public List<DataMiningSet> getDataMiningSets() { - return dataMiningSets; - } - - /** - * @param dataMining - * the dataMining to set - * @see #dataMiningSets - */ - public void setDataMiningSets(List<DataMiningSet> dataMining) { - this.dataMiningSets = dataMining; - } - - /** - * Adds layout to the list of layouts at a given position. - * - * @param index - * position at which new object will be inserted - * @param layout - * object to add - */ - public void addLayout(int index, Layout layout) { - layouts.add(index, layout); - layout.setModel(this); - } +package lcsb.mapviewer.model.map.model; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import javax.persistence.Table; +import javax.persistence.Transient; +import javax.xml.bind.annotation.XmlTransient; + +import org.apache.log4j.Logger; +import org.hibernate.annotations.Cascade; +import org.hibernate.annotations.CascadeType; + +import lcsb.mapviewer.common.exception.InvalidArgumentException; +import lcsb.mapviewer.model.Project; +import lcsb.mapviewer.model.map.graph.DataMiningSet; +import lcsb.mapviewer.model.map.layout.BlockDiagram; +import lcsb.mapviewer.model.map.layout.ElementGroup; +import lcsb.mapviewer.model.map.layout.Layout; +import lcsb.mapviewer.model.map.layout.graphics.Layer; +import lcsb.mapviewer.model.map.reaction.Reaction; +import lcsb.mapviewer.model.map.species.Element; + +/** + * Representation of the model data. It contains all information about single + * map: + * <ul> + * <li>species and compartments ({@link #elements} field)</li> + * <li>list of reactions ({@link #reactions})</li> + * <li>layers with additional graphical objects ({@link #layers})</li> + * <li>different graphical visualizations of the whole map ({@link #layouts}) + * </li> + * <li>some other meta data (like: creation date, version, etc)</li> + * </ul> + * + * @author Piotr Gawron + * + */ +@Entity +@Table(name = "model_table") +public class ModelData implements Serializable { + + /** + * + */ + private static final long serialVersionUID = 1L; + + /** + * Default class logger. + */ + private static Logger logger = Logger.getLogger(ModelData.class); + + /** + * Set of all elements in the map. + * + * @see Element + */ + @Cascade({ CascadeType.ALL }) + @OneToMany(mappedBy = "model", orphanRemoval = true) + private Set<Element> elements = new HashSet<>(); + + /** + * Set of all layers in the map. + * + * @see Layer + * + */ + @Cascade({ CascadeType.ALL }) + @OneToMany(mappedBy = "model", orphanRemoval = true) + private Set<Layer> layers = new HashSet<>(); + + /** + * Unique database identifier. + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "idDb", unique = true, nullable = false) + private int id; + + /** + * Collection of all reactions in the map. + * + * @see Reaction + * + */ + @Cascade({ CascadeType.ALL }) + @OneToMany(mappedBy = "model", orphanRemoval = true) + private Set<Reaction> reactions = new HashSet<>(); + + /** + * When the map was created. + */ + private Calendar creationDate = Calendar.getInstance(); + + /** + * Width of the map. + */ + private Double width; + + /** + * Height of the map. + */ + private Double height; + + /** + * X coordinate that should be used when initially showing map. + */ + private Double defaultCenterX; + + /** + * Y coordinate that should be used when initially showing map. + */ + private Double defaultCenterY; + + /** + * Description of the map. + */ + @Column(name = "notes", columnDefinition = "TEXT") + private String notes; + + /** + * Name of the map. + */ + private String name; + + /** + * Another CellDesigner identifier. + */ + private String idModel; + + /** + * How many hierarchical levels are in this map. + */ + private int zoomLevels; + + /** + * Zoom level that should be used when initially showing map. + */ + private Integer defaultZoomLevel; + + /** + * Size of the image tile that are used in this model. + */ + private int tileSize; + + /** + * List of layouts. + */ + @Cascade({ CascadeType.ALL }) + @OneToMany(fetch = FetchType.EAGER, mappedBy = "model", orphanRemoval = true) + @OrderBy("id") + private List<Layout> layouts = new ArrayList<>(); + + /** + * Project to which this model belong to. + */ + @ManyToOne(fetch = FetchType.LAZY, optional = false) + private Project project; + + // This field should be transient in hibernate and during the transformation + // to xml + /** + * {@link Model} object that is a container where data is being placed. + */ + @Transient + @XmlTransient + private Model model; + + /** + * List of submodels. + */ + @Cascade({ CascadeType.ALL }) + @OneToMany(fetch = FetchType.EAGER, mappedBy = "parentModel", orphanRemoval = true) + private Set<ModelSubmodelConnection> submodels = new HashSet<>(); + + /** + * List of connections with parent model (by definition one map can be a + * submodel of few maps). + */ + @Cascade({ CascadeType.ALL }) + @OneToMany(fetch = FetchType.LAZY, mappedBy = "submodel") + private Set<SubmodelConnection> parentModels = new HashSet<>(); + + /** + * List of {@link DataMiningSet} in the model. These data mining sets are used + * to create suggested (but missing) connections in the model. + */ + @Cascade({ CascadeType.ALL }) + @OneToMany(mappedBy = "model", orphanRemoval = true) + @OrderBy("id") + private List<DataMiningSet> dataMiningSets = new ArrayList<>(); + + /** + * Default constructor. + */ + public ModelData() { + } + + /** + * Adds {@link Element} to model data. + * + * @param element + * element to add + */ + public void addElement(Element element) { + element.setModelData(this); + elements.add(element); + } + + /** + * Adds {@link Reaction} to model data. + * + * @param reaction + * reaction to add + */ + public void addReaction(Reaction reaction) { + reaction.setModelData(this); + reactions.add(reaction); + } + + /** + * Adds {@link Layer} to model data. + * + * @param layer + * layer to add + */ + public void addLayer(Layer layer) { + layer.setModel(this); + layers.add(layer); + } + + /** + * Adds collection of {@link Element elements} to model data. + * + * @param elements + * elements to add + */ + public void addElements(List<? extends Element> elements) { + for (Element element : elements) { + addElement(element); + } + } + + /** + * + * @return {@link #id} + */ + public Integer getId() { + return id; + } + + /** + * + * @param id + * new {@link #id} + */ + public void setId(int id) { + this.id = id; + } + + /** + * + * @param elements + * new {@link #elements} collection + */ + public void setElements(Set<Element> elements) { + this.elements = elements; + } + + /** + * + * @return {@link #project} + */ + public Project getProject() { + return project; + } + + /** + * + * @param project + * new {@link #project} + */ + public void setProject(Project project) { + this.project = project; + } + + /** + * + * @return {@link #creationDate} + */ + public Calendar getCreationDate() { + return creationDate; + } + + /** + * + * @param creationDate + * new {@link #creationDate} + */ + public void setCreationDate(Calendar creationDate) { + this.creationDate = creationDate; + } + + /** + * + * @return {@link #layouts} + */ + public List<Layout> getLayouts() { + return layouts; + } + + /** + * + * @param layouts + * new {@link #layouts} + */ + public void setLayouts(List<Layout> layouts) { + this.layouts = layouts; + } + + /** + * Adds layout to model. + * + * @param layout + * layout to add + */ + public void addLayout(Layout layout) { + layouts.add(layout); + layout.setModel(this); + } + + /** + * + * @param width + * new {@link #width} + */ + public void setWidth(int width) { + setWidth(Double.valueOf(width)); + } + + /** + * + * @param height + * new {@link #height} + */ + public void setHeight(int height) { + setHeight(Double.valueOf(height)); + } + + /** + * Adds reactions to model. + * + * @param reactions2 + * list of reaction to add + */ + public void addReactions(List<Reaction> reactions2) { + for (Reaction reaction : reactions2) { + addReaction(reaction); + } + } + + /** + * Adds collection of {@link Layer layers} to the model data. + * + * @param layers + * objets to add + */ + public void addLayers(Collection<Layer> layers) { + for (Layer layer : layers) { + addLayer(layer); + } + } + + /** + * Remove layout from model. + * + * @param dbLayout + * layout to remove + */ + public void removeLayout(Layout dbLayout) { + List<Layout> toRemove = new ArrayList<Layout>(); + for (Layout layout : layouts) { + if (layout.getId() == dbLayout.getId()) { + toRemove.add(layout); + } + } + + if (toRemove.size() == 0) { + logger.warn("Cannot remove layout: " + dbLayout.getId()); + } else { + layouts.removeAll(toRemove); + } + } + + /** + * Adds {@link ElementGroup} to the model data. + * + * @param elementGroup + * object to add + */ + public void addElementGroup(ElementGroup elementGroup) { + // for now we ignore this information + } + + /** + * Adds {@link BlockDiagram} to the model data. + * + * @param blockDiagram + * object to add + */ + public void addBlockDiagream(BlockDiagram blockDiagram) { + // for now we ignore this information + } + + /** + * Removes {@link Element} from the model. + * + * @param element + * element to remove + */ + public void removeElement(Element element) { + if (element == null) { + throw new InvalidArgumentException("Cannot remove null"); + } + if (!elements.contains(element)) { + logger.warn("Element doesn't exist in the map: " + element.getElementId()); + return; + } + + element.setModelData(null); + elements.remove(element); + } + + /** + * Removes reaction from model. + * + * @param reaction + * reaction to remove + */ + public void removeReaction(Reaction reaction) { + if (!reactions.contains(reaction)) { + logger.warn("Reaction doesn't exist in the model: " + reaction.getIdReaction()); + return; + } + reaction.setModelData(null); + reactions.remove(reaction); + } + + /** + * @return the zoomLevels + * @see #zoomLevels + */ + public int getZoomLevels() { + return zoomLevels; + } + + /** + * @param zoomLevels + * the zoomLevels to set + * @see #zoomLevels + */ + public void setZoomLevels(int zoomLevels) { + this.zoomLevels = zoomLevels; + } + + /** + * @return the tileSize + * @see #tileSize + */ + public int getTileSize() { + return tileSize; + } + + /** + * @param tileSize + * the tileSize to set + * @see #tileSize + */ + public void setTileSize(int tileSize) { + this.tileSize = tileSize; + } + + /** + * @return the idModel + * @see #idModel + */ + public String getIdModel() { + return idModel; + } + + /** + * @param idModel + * the idModel to set + * @see #idModel + */ + public void setIdModel(String idModel) { + this.idModel = idModel; + } + + /** + * @return the model + * @see #model + */ + @XmlTransient + public Model getModel() { + if (model == null) { + logger.warn("Model not set in model data."); + } + return model; + } + + /** + * @param model + * the model to set + * @see #model + */ + public void setModel(Model model) { + this.model = model; + } + + /** + * @return the elements + * @see #elements + */ + public Set<Element> getElements() { + return elements; + } + + /** + * @return the layers + * @see #layers + */ + public Set<Layer> getLayers() { + return layers; + } + + /** + * @param layers + * the layers to set + * @see #layers + */ + public void setLayers(Set<Layer> layers) { + this.layers = layers; + } + + /** + * @return the reactions + * @see #reactions + */ + public Set<Reaction> getReactions() { + return reactions; + } + + /** + * @param reactions + * the reactions to set + * @see #reactions + */ + public void setReactions(Set<Reaction> reactions) { + this.reactions = reactions; + } + + /** + * @return the width + * @see #width + */ + public Double getWidth() { + return width; + } + + /** + * @param width + * the width to set + * @see #width + */ + public void setWidth(Double width) { + this.width = width; + } + + /** + * @return the height + * @see #height + */ + public Double getHeight() { + return height; + } + + /** + * @param height + * the height to set + * @see #height + */ + public void setHeight(Double height) { + this.height = height; + } + + /** + * @return the notes + * @see #notes + */ + public String getNotes() { + return notes; + } + + /** + * @param notes + * the notes to set + * @see #notes + */ + public void setNotes(String notes) { + this.notes = notes; + } + + /** + * Adds submodel. + * + * @param submodel + * object to add + */ + public void addSubmodel(ModelSubmodelConnection submodel) { + this.submodels.add(submodel); + submodel.setParentModel(this); + } + + /** + * Returns collection of submodels. + * + * @return collection of submodels. + */ + public Collection<ModelSubmodelConnection> getSubmodels() { + return this.submodels; + } + + /** + * @return the name + * @see #name + */ + public String getName() { + return name; + } + + /** + * @param name + * the name to set + * @see #name + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the parentModels + * @see #parentModels + */ + public Set<SubmodelConnection> getParentModels() { + return parentModels; + } + + /** + * @param parentModels + * the parentModels to set + * @see #parentModels + */ + public void setParentModels(Set<SubmodelConnection> parentModels) { + this.parentModels = parentModels; + } + + /** + * @return the dataMining + * @see #dataMiningSets + */ + public List<DataMiningSet> getDataMiningSets() { + return dataMiningSets; + } + + /** + * @param dataMining + * the dataMining to set + * @see #dataMiningSets + */ + public void setDataMiningSets(List<DataMiningSet> dataMining) { + this.dataMiningSets = dataMining; + } + + /** + * Adds layout to the list of layouts at a given position. + * + * @param index + * position at which new object will be inserted + * @param layout + * object to add + */ + public void addLayout(int index, Layout layout) { + layouts.add(index, layout); + layout.setModel(this); + } + + public Double getDefaultCenterX() { + return defaultCenterX; + } + + public void setDefaultCenterX(Double defaultCenterX) { + this.defaultCenterX = defaultCenterX; + } + + public Double getDefaultCenterY() { + return defaultCenterY; + } + + public void setDefaultCenterY(Double defaultCenterY) { + this.defaultCenterY = defaultCenterY; + } + + public Integer getDefaultZoomLevel() { + return defaultZoomLevel; + } + + public void setDefaultZoomLevel(Integer defaultZoomLevel) { + this.defaultZoomLevel = defaultZoomLevel; + } } \ No newline at end of file diff --git a/model/src/main/java/lcsb/mapviewer/model/map/model/ModelFullIndexed.java b/model/src/main/java/lcsb/mapviewer/model/map/model/ModelFullIndexed.java index cef95d6fd2..9a5c504bc3 100644 --- a/model/src/main/java/lcsb/mapviewer/model/map/model/ModelFullIndexed.java +++ b/model/src/main/java/lcsb/mapviewer/model/map/model/ModelFullIndexed.java @@ -28,7 +28,7 @@ import lcsb.mapviewer.model.map.species.Element; import lcsb.mapviewer.model.map.species.Species; /** - * This class implemets {@link Model} interface. It's is very simple + * This class implements {@link Model} interface. It's is very simple * implementation containing structures that index the data from * {@link ModelData} structure and provide access method to this indexed data. * @@ -37,594 +37,619 @@ import lcsb.mapviewer.model.map.species.Species; */ public class ModelFullIndexed implements Model { - /** - * Default class logger. - */ - @SuppressWarnings("unused") - private static Logger logger = Logger.getLogger(ModelFullIndexed.class); - - /** - * Object that map {@link Element#elementId element identifier} into - * {@link Element}. - */ - private Map<String, Element> elementByElementId = new HashMap<>(); - - /** - * Object that map {@link Element#id element database identifier} into - * {@link Element}. - */ - private Map<Integer, Element> elementByDbId = new HashMap<>(); - - /** - * Object that map {@link Reaction#idReaction reaction identifier} into - * {@link Reaction}. - */ - private Map<String, Reaction> reactionByReactionId = new HashMap<>(); - - /** - * Object that map {@link Reaction#id reaction database identifier} into - * {@link Reaction}. - */ - private Map<Integer, Reaction> reactionByDbId = new HashMap<>(); - - /** - * {@link ModelData} object containing "raw" data about the model. - */ - private ModelData modelData = null; - - /** - * Default constructor. - * - * @param model - * {@link ModelData} object containing "raw" data about the model - */ - public ModelFullIndexed(ModelData model) { - if (model == null) { - this.modelData = new ModelData(); - } else { - this.modelData = model; - for (Element element : model.getElements()) { - elementByElementId.put(element.getElementId(), element); - elementByDbId.put(element.getId(), element); - } - for (Reaction reaction : model.getReactions()) { - reactionByReactionId.put(reaction.getIdReaction(), reaction); - reactionByDbId.put(reaction.getId(), reaction); - } - if (getProject() != null) { - getProject().getProjectId(); - } - - for (ModelSubmodelConnection connection : model.getSubmodels()) { - if (connection.getSubmodel().getModel() == null) { - connection.getSubmodel().setModel(new ModelFullIndexed(connection.getSubmodel())); - } - } - // fetch creators of layouts - for (Layout layout : modelData.getLayouts()) { - if (layout.getCreator() != null) { - layout.getCreator().getId(); - } - } - } - modelData.setModel(this); - } - - @Override - public void addElement(Element element) { - if (element.getElementId() == null || element.getElementId().isEmpty()) { - throw new InvalidArgumentException("Element identifier cannot be empty"); - } - Element element2 = elementByElementId.get(element.getElementId()); - if (element2 == null) { - modelData.addElement(element); - elementByElementId.put(element.getElementId(), element); - elementByDbId.put(element.getId(), element); - } else { - throw new InvalidArgumentException("Element with duplicated id: " + element.getElementId()); - } - } - - @Override - public Double getWidth() { - return modelData.getWidth(); - } - - @Override - public void setWidth(Double width) { - modelData.setWidth(width); - } - - @Override - public Double getHeight() { - return modelData.getHeight(); - } - - @Override - public void setHeight(Double height) { - modelData.setHeight(height); - } - - @Override - public void setWidth(String text) { - modelData.setWidth(Double.parseDouble(text)); - } - - @Override - public void setHeight(String text) { - modelData.setHeight(Double.parseDouble(text)); - } - - @Override - public Set<Element> getElements() { - return modelData.getElements(); - } - - @SuppressWarnings("unchecked") - @Override - public <T extends Element> T getElementByElementId(String elementId) { - return (T) elementByElementId.get(elementId); - } - - @Override - public void addReaction(Reaction reaction) { - modelData.addReaction(reaction); - reactionByReactionId.put(reaction.getIdReaction(), reaction); - reactionByDbId.put(reaction.getId(), reaction); - } - - @Override - public Set<Reaction> getReactions() { - return modelData.getReactions(); - } - - @Override - public List<Compartment> getCompartments() { - List<Compartment> result = new ArrayList<Compartment>(); - for (Element element : modelData.getElements()) { - if (element instanceof Compartment) { - result.add((Compartment) element); - } - } - return result; - } - - @Override - public void addLayer(Layer layer) { - modelData.addLayer(layer); - } - - @Override - public Set<Layer> getLayers() { - return modelData.getLayers(); - } - - @Override - public void addElements(List<? extends Element> elements) { - for (Element element : elements) { - addElement(element); - } - } - - @Override - public void setNotes(String notes) { - if (notes != null && notes.contains("<html")) { - throw new InvalidArgumentException("notes cannot contain <html> tag"); - } - modelData.setNotes(notes); - } - - @Override - public String getNotes() { - return modelData.getNotes(); - } - - @Override - public void setElements(Set<Element> elements) { - this.modelData.setElements(elements); - } - - @Override - public Reaction getReactionByReactionId(String idReaction) { - return reactionByReactionId.get(idReaction); - } - - @Override - public Project getProject() { - return modelData.getProject(); - } - - @Override - public void setProject(Project project) { - modelData.setProject(project); - } - - @Override - public List<Element> getElementsSortedBySize() { - List<Element> sortedElements = new ArrayList<>(); - sortedElements.addAll(getElements()); - Collections.sort(sortedElements, Element.SIZE_COMPARATOR); - return sortedElements; - } - - @Override - public List<Compartment> getSortedCompartments() { - List<Compartment> result = getCompartments(); - Collections.sort(result, Element.SIZE_COMPARATOR); - return result; - } - - @Override - public Reaction getReactionByDbId(Integer dbId) { - return reactionByDbId.get(dbId); - } - - @Override - public Element getElementByDbId(Integer dbId) { - return elementByDbId.get(dbId); - } - - @Override - public Calendar getCreationDate() { - return modelData.getCreationDate(); - } - - @Override - public void setCreationDate(Calendar creationDate) { - this.modelData.setCreationDate(creationDate); - } - - @Override - public List<Layout> getLayouts() { - return modelData.getLayouts(); - } - - @Override - public void setLayouts(List<Layout> layouts) { - this.modelData.setLayouts(layouts); - } - - @Override - public void addLayout(Layout layout) { - modelData.addLayout(layout); - } - - @Override - public void setWidth(int width) { - setWidth(Double.valueOf(width)); - } - - @Override - public void setHeight(int height) { - setHeight(Double.valueOf(height)); - } - - @Override - public Set<BioEntity> getElementsByAnnotation(MiriamData miriamData) { - Set<BioEntity> result = new HashSet<>(); - for (Element element : getElements()) { - for (MiriamData md : element.getMiriamData()) { - if (md.equals(miriamData)) { - result.add(element); - } - } - } - - for (Reaction element : getReactions()) { - for (MiriamData md : element.getMiriamData()) { - if (md.equals(miriamData)) { - result.add(element); - } - } - } - - return result; - } - - @Override - public void addReactions(List<Reaction> reactions2) { - for (Reaction reaction : reactions2) { - addReaction(reaction); - } - } - - @Override - public Collection<Complex> getComplexList() { - List<Complex> result = new ArrayList<>(); - for (Element element : modelData.getElements()) { - if (element instanceof Complex) { - result.add((Complex) element); - } - } - return result; - } - - @Override - public Collection<Species> getNotComplexSpeciesList() { - List<Species> result = new ArrayList<>(); - for (Element element : modelData.getElements()) { - if (element instanceof Species && !(element instanceof Complex)) { - result.add((Species) element); - } - } - return result; - } - - @Override - public void addLayers(Collection<Layer> layers) { - for (Layer layer : layers) { - addLayer(layer); - } - } - - @Override - public List<Element> getSortedSpeciesList() { - List<Element> result = new ArrayList<>(); - result.addAll(getElements()); - Collections.sort(result, Element.SIZE_COMPARATOR); - return result; - } - - @Override - public List<Reaction> getSortedReactions() { - List<Reaction> result = new ArrayList<Reaction>(); - result.addAll(getReactions()); - Collections.sort(result, Reaction.ID_COMPARATOR); - return result; - } - - /** - * Remove layout from model. - * - * @param dbLayout - * layout to remove - */ - public void removeLayout(Layout dbLayout) { - modelData.removeLayout(dbLayout); - } - - @Override - public void addElementGroup(ElementGroup elementGroup) { - modelData.addElementGroup(elementGroup); - } - - @Override - public void addBlockDiagream(BlockDiagram blockDiagram) { - modelData.addBlockDiagream(blockDiagram); - } - - @Override - public void removeElement(Element element) { - modelData.removeElement(element); - elementByElementId.remove(element.getElementId()); - elementByDbId.remove(element.getId()); - - if (element.getCompartment() != null) { - Compartment ca = element.getCompartment(); - ca.removeElement(element); - } - - if (element instanceof Species) { - Species al = (Species) element; - if (al.getComplex() != null) { - Complex ca = ((Species) element).getComplex(); - ca.removeElement(al); - } - } - } - - @Override - public void removeReaction(Reaction reaction) { - modelData.removeReaction(reaction); - reactionByReactionId.remove(reaction.getIdReaction()); - reactionByDbId.remove(reaction.getId()); - } - - @Override - public int getZoomLevels() { - return modelData.getZoomLevels(); - } - - @Override - public void setZoomLevels(int zoomLevels) { - this.modelData.setZoomLevels(zoomLevels); - } - - @Override - public int getTileSize() { - return modelData.getTileSize(); - } - - @Override - public void setTileSize(int tileSize) { - this.modelData.setTileSize(tileSize); - } - - @Override - public String getIdModel() { - return modelData.getIdModel(); - } - - @Override - public void setIdModel(String idModel) { - this.modelData.setIdModel(idModel); - } - - @Override - public ModelData getModelData() { - return modelData; - } - - @Override - public Integer getId() { - return modelData.getId(); - } - - @Override - public void addSubmodelConnection(ModelSubmodelConnection submodel) { - modelData.addSubmodel(submodel); - submodel.setParentModel(this); - } - - @Override - public Collection<ModelSubmodelConnection> getSubmodelConnections() { - return modelData.getSubmodels(); - } - - @Override - public String getName() { - return modelData.getName(); - } - - @Override - public void setName(String name) { - modelData.setName(name); - } - - @Override - public Model getSubmodelById(Integer id) { - if (id == null) { - return null; - } - if (id.equals(getId())) { - return this; - } - SubmodelConnection connection = getSubmodelConnectionById(id); - if (connection != null) { - return connection.getSubmodel().getModel(); - } else { - return null; - } - } - - @Override - public Collection<SubmodelConnection> getParentModels() { - return modelData.getParentModels(); - } - - @Override - public Model getSubmodelByConnectionName(String name) { - if (name == null) { - return null; - } - for (ModelSubmodelConnection connection : getSubmodelConnections()) { - if (name.equals(connection.getName())) { - return connection.getSubmodel().getModel(); - } - } - return null; - } - - @Override - public SubmodelConnection getSubmodelConnectionById(Integer id) { - for (ModelSubmodelConnection connection : getSubmodelConnections()) { - if (id.equals(connection.getSubmodel().getId())) { - return connection; - } - } - return null; - } - - @Override - public Model getSubmodelById(String identifier) { - if (identifier == null) { - return null; - } - Integer id = Integer.parseInt(identifier); - return getSubmodelById(id); - } - - @Override - public Collection<Model> getSubmodels() { - List<Model> models = new ArrayList<Model>(); - for (ModelSubmodelConnection connection : getSubmodelConnections()) { - models.add(connection.getSubmodel().getModel()); - } - return models; - } - - @Override - public Model getSubmodelByName(String name) { - if (name == null) { - return null; - } - for (ModelSubmodelConnection connection : getSubmodelConnections()) { - if (name.equals(connection.getSubmodel().getName())) { - return connection.getSubmodel().getModel(); - } - } - if (name.equals(getName())) { - return this; - } - return null; - } - - @Override - public void addLayout(int index, Layout layout) { - this.modelData.getLayouts().add(index, layout); - layout.setModel(this); - } - - @Override - public void addDataMiningSet(DataMiningSet dataMiningSet) { - this.modelData.getDataMiningSets().add(dataMiningSet); - dataMiningSet.setModel(this); - } - - @Override - public List<DataMiningSet> getDataMiningSets() { - return modelData.getDataMiningSets(); - } - - @Override - public void addDataMiningSets(Collection<DataMiningSet> dataMiningSets) { - for (DataMiningSet dataMiningSet : dataMiningSets) { - addDataMiningSet(dataMiningSet); - } - } - - @Override - public Layout getLayoutByIdentifier(Integer layoutDbIdentfier) { - for (Layout layout : getLayouts()) { - if (layout.getId() == layoutDbIdentfier) { - return layout; - } - } - return null; - } - - @Override - public void setId(int id) { - modelData.setId(id); - } - - @Override - public List<BioEntity> getBioEntities() { - List<BioEntity> result = new ArrayList<>(); - result.addAll(getElements()); - result.addAll(getReactions()); - return result; - } - - @Override - public List<Element> getElementsByName(String name) { - List<Element> result = new ArrayList<>(); - for (Element element : getElements()) { - if (element.getName().equalsIgnoreCase(name)) { - result.add(element); - } - } - return result; - } - - @Override - public List<Species> getSpeciesList() { - List<Species> result = new ArrayList<>(); - for (Element element : modelData.getElements()) { - if (element instanceof Species) { - result.add((Species) element); - } - } - return result; - } + /** + * Default class logger. + */ + @SuppressWarnings("unused") + private static Logger logger = Logger.getLogger(ModelFullIndexed.class); + + /** + * Object that map {@link Element#elementId element identifier} into + * {@link Element}. + */ + private Map<String, Element> elementByElementId = new HashMap<>(); + + /** + * Object that map {@link Element#id element database identifier} into + * {@link Element}. + */ + private Map<Integer, Element> elementByDbId = new HashMap<>(); + + /** + * Object that map {@link Reaction#idReaction reaction identifier} into + * {@link Reaction}. + */ + private Map<String, Reaction> reactionByReactionId = new HashMap<>(); + + /** + * Object that map {@link Reaction#id reaction database identifier} into + * {@link Reaction}. + */ + private Map<Integer, Reaction> reactionByDbId = new HashMap<>(); + + /** + * {@link ModelData} object containing "raw" data about the model. + */ + private ModelData modelData = null; + + /** + * Default constructor. + * + * @param model + * {@link ModelData} object containing "raw" data about the model + */ + public ModelFullIndexed(ModelData model) { + if (model == null) { + this.modelData = new ModelData(); + } else { + this.modelData = model; + for (Element element : model.getElements()) { + elementByElementId.put(element.getElementId(), element); + elementByDbId.put(element.getId(), element); + } + for (Reaction reaction : model.getReactions()) { + reactionByReactionId.put(reaction.getIdReaction(), reaction); + reactionByDbId.put(reaction.getId(), reaction); + } + if (getProject() != null) { + getProject().getProjectId(); + } + + for (ModelSubmodelConnection connection : model.getSubmodels()) { + if (connection.getSubmodel().getModel() == null) { + connection.getSubmodel().setModel(new ModelFullIndexed(connection.getSubmodel())); + } + } + // fetch creators of layouts + for (Layout layout : modelData.getLayouts()) { + if (layout.getCreator() != null) { + layout.getCreator().getId(); + } + } + } + modelData.setModel(this); + } + + @Override + public void addElement(Element element) { + if (element.getElementId() == null || element.getElementId().isEmpty()) { + throw new InvalidArgumentException("Element identifier cannot be empty"); + } + Element element2 = elementByElementId.get(element.getElementId()); + if (element2 == null) { + modelData.addElement(element); + elementByElementId.put(element.getElementId(), element); + elementByDbId.put(element.getId(), element); + } else { + throw new InvalidArgumentException("Element with duplicated id: " + element.getElementId()); + } + } + + @Override + public Double getWidth() { + return modelData.getWidth(); + } + + @Override + public void setWidth(Double width) { + modelData.setWidth(width); + } + + @Override + public Double getHeight() { + return modelData.getHeight(); + } + + @Override + public void setHeight(Double height) { + modelData.setHeight(height); + } + + @Override + public void setWidth(String text) { + modelData.setWidth(Double.parseDouble(text)); + } + + @Override + public void setHeight(String text) { + modelData.setHeight(Double.parseDouble(text)); + } + + @Override + public Set<Element> getElements() { + return modelData.getElements(); + } + + @SuppressWarnings("unchecked") + @Override + public <T extends Element> T getElementByElementId(String elementId) { + return (T) elementByElementId.get(elementId); + } + + @Override + public void addReaction(Reaction reaction) { + modelData.addReaction(reaction); + reactionByReactionId.put(reaction.getIdReaction(), reaction); + reactionByDbId.put(reaction.getId(), reaction); + } + + @Override + public Set<Reaction> getReactions() { + return modelData.getReactions(); + } + + @Override + public List<Compartment> getCompartments() { + List<Compartment> result = new ArrayList<Compartment>(); + for (Element element : modelData.getElements()) { + if (element instanceof Compartment) { + result.add((Compartment) element); + } + } + return result; + } + + @Override + public void addLayer(Layer layer) { + modelData.addLayer(layer); + } + + @Override + public Set<Layer> getLayers() { + return modelData.getLayers(); + } + + @Override + public void addElements(List<? extends Element> elements) { + for (Element element : elements) { + addElement(element); + } + } + + @Override + public void setNotes(String notes) { + if (notes != null && notes.contains("<html")) { + throw new InvalidArgumentException("notes cannot contain <html> tag"); + } + modelData.setNotes(notes); + } + + @Override + public String getNotes() { + return modelData.getNotes(); + } + + @Override + public void setElements(Set<Element> elements) { + this.modelData.setElements(elements); + } + + @Override + public Reaction getReactionByReactionId(String idReaction) { + return reactionByReactionId.get(idReaction); + } + + @Override + public Project getProject() { + return modelData.getProject(); + } + + @Override + public void setProject(Project project) { + modelData.setProject(project); + } + + @Override + public List<Element> getElementsSortedBySize() { + List<Element> sortedElements = new ArrayList<>(); + sortedElements.addAll(getElements()); + Collections.sort(sortedElements, Element.SIZE_COMPARATOR); + return sortedElements; + } + + @Override + public List<Compartment> getSortedCompartments() { + List<Compartment> result = getCompartments(); + Collections.sort(result, Element.SIZE_COMPARATOR); + return result; + } + + @Override + public Reaction getReactionByDbId(Integer dbId) { + return reactionByDbId.get(dbId); + } + + @Override + public Element getElementByDbId(Integer dbId) { + return elementByDbId.get(dbId); + } + + @Override + public Calendar getCreationDate() { + return modelData.getCreationDate(); + } + + @Override + public void setCreationDate(Calendar creationDate) { + this.modelData.setCreationDate(creationDate); + } + + @Override + public List<Layout> getLayouts() { + return modelData.getLayouts(); + } + + @Override + public void setLayouts(List<Layout> layouts) { + this.modelData.setLayouts(layouts); + } + + @Override + public void addLayout(Layout layout) { + modelData.addLayout(layout); + } + + @Override + public void setWidth(int width) { + setWidth(Double.valueOf(width)); + } + + @Override + public void setHeight(int height) { + setHeight(Double.valueOf(height)); + } + + @Override + public Set<BioEntity> getElementsByAnnotation(MiriamData miriamData) { + Set<BioEntity> result = new HashSet<>(); + for (Element element : getElements()) { + for (MiriamData md : element.getMiriamData()) { + if (md.equals(miriamData)) { + result.add(element); + } + } + } + + for (Reaction element : getReactions()) { + for (MiriamData md : element.getMiriamData()) { + if (md.equals(miriamData)) { + result.add(element); + } + } + } + + return result; + } + + @Override + public void addReactions(List<Reaction> reactions2) { + for (Reaction reaction : reactions2) { + addReaction(reaction); + } + } + + @Override + public Collection<Complex> getComplexList() { + List<Complex> result = new ArrayList<>(); + for (Element element : modelData.getElements()) { + if (element instanceof Complex) { + result.add((Complex) element); + } + } + return result; + } + + @Override + public Collection<Species> getNotComplexSpeciesList() { + List<Species> result = new ArrayList<>(); + for (Element element : modelData.getElements()) { + if (element instanceof Species && !(element instanceof Complex)) { + result.add((Species) element); + } + } + return result; + } + + @Override + public void addLayers(Collection<Layer> layers) { + for (Layer layer : layers) { + addLayer(layer); + } + } + + @Override + public List<Element> getSortedSpeciesList() { + List<Element> result = new ArrayList<>(); + result.addAll(getElements()); + Collections.sort(result, Element.SIZE_COMPARATOR); + return result; + } + + @Override + public List<Reaction> getSortedReactions() { + List<Reaction> result = new ArrayList<Reaction>(); + result.addAll(getReactions()); + Collections.sort(result, Reaction.ID_COMPARATOR); + return result; + } + + @Override + public void removeLayout(Layout dbLayout) { + modelData.removeLayout(dbLayout); + } + + @Override + public void addElementGroup(ElementGroup elementGroup) { + modelData.addElementGroup(elementGroup); + } + + @Override + public void addBlockDiagream(BlockDiagram blockDiagram) { + modelData.addBlockDiagream(blockDiagram); + } + + @Override + public void removeElement(Element element) { + modelData.removeElement(element); + elementByElementId.remove(element.getElementId()); + elementByDbId.remove(element.getId()); + + if (element.getCompartment() != null) { + Compartment ca = element.getCompartment(); + ca.removeElement(element); + } + + if (element instanceof Species) { + Species al = (Species) element; + if (al.getComplex() != null) { + Complex ca = ((Species) element).getComplex(); + ca.removeElement(al); + } + } + } + + @Override + public void removeReaction(Reaction reaction) { + modelData.removeReaction(reaction); + reactionByReactionId.remove(reaction.getIdReaction()); + reactionByDbId.remove(reaction.getId()); + } + + @Override + public int getZoomLevels() { + return modelData.getZoomLevels(); + } + + @Override + public void setZoomLevels(int zoomLevels) { + this.modelData.setZoomLevels(zoomLevels); + } + + @Override + public int getTileSize() { + return modelData.getTileSize(); + } + + @Override + public void setTileSize(int tileSize) { + this.modelData.setTileSize(tileSize); + } + + @Override + public String getIdModel() { + return modelData.getIdModel(); + } + + @Override + public void setIdModel(String idModel) { + this.modelData.setIdModel(idModel); + } + + @Override + public ModelData getModelData() { + return modelData; + } + + @Override + public Integer getId() { + return modelData.getId(); + } + + @Override + public void addSubmodelConnection(ModelSubmodelConnection submodel) { + modelData.addSubmodel(submodel); + submodel.setParentModel(this); + } + + @Override + public Collection<ModelSubmodelConnection> getSubmodelConnections() { + return modelData.getSubmodels(); + } + + @Override + public String getName() { + return modelData.getName(); + } + + @Override + public void setName(String name) { + modelData.setName(name); + } + + @Override + public Model getSubmodelById(Integer id) { + if (id == null) { + return null; + } + if (id.equals(getId())) { + return this; + } + SubmodelConnection connection = getSubmodelConnectionById(id); + if (connection != null) { + return connection.getSubmodel().getModel(); + } else { + return null; + } + } + + @Override + public Collection<SubmodelConnection> getParentModels() { + return modelData.getParentModels(); + } + + @Override + public Model getSubmodelByConnectionName(String name) { + if (name == null) { + return null; + } + for (ModelSubmodelConnection connection : getSubmodelConnections()) { + if (name.equals(connection.getName())) { + return connection.getSubmodel().getModel(); + } + } + return null; + } + + @Override + public SubmodelConnection getSubmodelConnectionById(Integer id) { + for (ModelSubmodelConnection connection : getSubmodelConnections()) { + if (id.equals(connection.getSubmodel().getId())) { + return connection; + } + } + return null; + } + + @Override + public Model getSubmodelById(String identifier) { + if (identifier == null) { + return null; + } + Integer id = Integer.parseInt(identifier); + return getSubmodelById(id); + } + + @Override + public Collection<Model> getSubmodels() { + List<Model> models = new ArrayList<Model>(); + for (ModelSubmodelConnection connection : getSubmodelConnections()) { + models.add(connection.getSubmodel().getModel()); + } + return models; + } + + @Override + public Model getSubmodelByName(String name) { + if (name == null) { + return null; + } + for (ModelSubmodelConnection connection : getSubmodelConnections()) { + if (name.equals(connection.getSubmodel().getName())) { + return connection.getSubmodel().getModel(); + } + } + if (name.equals(getName())) { + return this; + } + return null; + } + + @Override + public void addLayout(int index, Layout layout) { + this.modelData.getLayouts().add(index, layout); + layout.setModel(this); + } + + @Override + public void addDataMiningSet(DataMiningSet dataMiningSet) { + this.modelData.getDataMiningSets().add(dataMiningSet); + dataMiningSet.setModel(this); + } + + @Override + public List<DataMiningSet> getDataMiningSets() { + return modelData.getDataMiningSets(); + } + + @Override + public void addDataMiningSets(Collection<DataMiningSet> dataMiningSets) { + for (DataMiningSet dataMiningSet : dataMiningSets) { + addDataMiningSet(dataMiningSet); + } + } + + @Override + public Layout getLayoutByIdentifier(Integer layoutDbIdentfier) { + for (Layout layout : getLayouts()) { + if (layout.getId() == layoutDbIdentfier) { + return layout; + } + } + return null; + } + + @Override + public void setId(int id) { + modelData.setId(id); + } + + @Override + public List<BioEntity> getBioEntities() { + List<BioEntity> result = new ArrayList<>(); + result.addAll(getElements()); + result.addAll(getReactions()); + return result; + } + + @Override + public List<Element> getElementsByName(String name) { + List<Element> result = new ArrayList<>(); + for (Element element : getElements()) { + if (element.getName().equalsIgnoreCase(name)) { + result.add(element); + } + } + return result; + } + + @Override + public List<Species> getSpeciesList() { + List<Species> result = new ArrayList<>(); + for (Element element : modelData.getElements()) { + if (element instanceof Species) { + result.add((Species) element); + } + } + return result; + } + + @Override + public Double getDefaultCenterX() { + return modelData.getDefaultCenterX(); + } + + @Override + public void setDefaultCenterX(Double defaultCenterX) { + modelData.setDefaultCenterX(defaultCenterX); + } + + @Override + public Double getDefaultCenterY() { + return modelData.getDefaultCenterY(); + } + + @Override + public void setDefaultCenterY(Double defaultCenterY) { + modelData.setDefaultCenterY(defaultCenterY); + } + + @Override + public Integer getDefaultZoomLevel() { + return modelData.getDefaultZoomLevel(); + } + + @Override + public void setDefaultZoomLevel(Integer defaultZoomLevel) { + modelData.setDefaultZoomLevel(defaultZoomLevel); + } } diff --git a/persist/src/db/12.0.0/fix_db_20171121.sql b/persist/src/db/12.0.0/fix_db_20171121.sql index 98a7685a7e..3769716cac 100644 --- a/persist/src/db/12.0.0/fix_db_20171121.sql +++ b/persist/src/db/12.0.0/fix_db_20171121.sql @@ -1,2 +1,7 @@ -- column which indicates that this overlay should be shown on startup alter table layout add column defaultoverlay boolean default false; + +-- default positioning of map when session data is not available +alter table model_table add column defaultzoomlevel integer default null; +alter table model_table add column defaultcenterx double precision default null; +alter table model_table add column defaultcentery double precision default null; diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/projects/models/ModelController.java b/rest-api/src/main/java/lcsb/mapviewer/api/projects/models/ModelController.java index 1cecb23458..93beeb54c1 100644 --- a/rest-api/src/main/java/lcsb/mapviewer/api/projects/models/ModelController.java +++ b/rest-api/src/main/java/lcsb/mapviewer/api/projects/models/ModelController.java @@ -1,44 +1,66 @@ -package lcsb.mapviewer.api.projects.models; - -import java.util.List; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.CookieValue; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import lcsb.mapviewer.api.BaseController; -import lcsb.mapviewer.api.ObjectNotFoundException; -import lcsb.mapviewer.common.Configuration; -import lcsb.mapviewer.services.SecurityException; - -@RestController -public class ModelController extends BaseController { - @Autowired - private ModelRestImpl modelController; - - @RequestMapping(value = "/projects/{projectId:.+}/models/", method = { RequestMethod.GET }, produces = { MediaType.APPLICATION_JSON_VALUE }) - public List<ModelMetaData> getModels(// - @PathVariable(value = "projectId") String projectId, // - @CookieValue(value = Configuration.AUTH_TOKEN) String token // - ) throws SecurityException, ObjectNotFoundException { - return modelController.getModels(projectId, token); - } - - @RequestMapping(value = "/projects/{projectId:.+}/models/{modelId:.+}", method = { RequestMethod.GET }, produces = { MediaType.APPLICATION_JSON_VALUE }) - public Object getModel(// - @PathVariable(value = "modelId") String modelId, // - @PathVariable(value = "projectId") String projectId, // - @CookieValue(value = Configuration.AUTH_TOKEN) String token // - ) throws SecurityException, ObjectNotFoundException { - if (modelId.equals("*")) { - return modelController.getModels(projectId, token); - } else { - return modelController.getModel(projectId, modelId, token); - } - } - +package lcsb.mapviewer.api.projects.models; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.CookieValue; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; + +import lcsb.mapviewer.api.BaseController; +import lcsb.mapviewer.api.ObjectNotFoundException; +import lcsb.mapviewer.api.QueryException; +import lcsb.mapviewer.common.Configuration; +import lcsb.mapviewer.services.SecurityException; + +@RestController +public class ModelController extends BaseController { + @Autowired + private ModelRestImpl modelController; + + @RequestMapping(value = "/projects/{projectId:.+}/models/", method = { RequestMethod.GET }, produces = { + MediaType.APPLICATION_JSON_VALUE }) + public List<ModelMetaData> getModels(// + @PathVariable(value = "projectId") String projectId, // + @CookieValue(value = Configuration.AUTH_TOKEN) String token // + ) throws SecurityException, ObjectNotFoundException { + return modelController.getModels(projectId, token); + } + + @RequestMapping(value = "/projects/{projectId:.+}/models/{modelId:.+}", method = { RequestMethod.GET }, produces = { + MediaType.APPLICATION_JSON_VALUE }) + public Object getModel(// + @PathVariable(value = "modelId") String modelId, // + @PathVariable(value = "projectId") String projectId, // + @CookieValue(value = Configuration.AUTH_TOKEN) String token // + ) throws SecurityException, ObjectNotFoundException { + if (modelId.equals("*")) { + return modelController.getModels(projectId, token); + } else { + return modelController.getModel(projectId, modelId, token); + } + } + + @RequestMapping(value = "/projects/{projectId:.+}/models/{modelId:.+}", method = { RequestMethod.PATCH }, produces = { + MediaType.APPLICATION_JSON_VALUE }) + public Object updateModel(// + @PathVariable(value = "modelId") String modelId, // + @PathVariable(value = "projectId") String projectId, // + @RequestBody String body, // + @CookieValue(value = Configuration.AUTH_TOKEN) String token // + ) throws SecurityException, JsonParseException, JsonMappingException, IOException, QueryException { + Map<String, Object> node = parseBody(body); + Map<String, Object> data = getData(node, "model"); + return modelController.updateModel(projectId, modelId, data, token); + } + } \ No newline at end of file diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/projects/models/ModelMetaData.java b/rest-api/src/main/java/lcsb/mapviewer/api/projects/models/ModelMetaData.java index 2ff2bc4932..e7effa77c3 100644 --- a/rest-api/src/main/java/lcsb/mapviewer/api/projects/models/ModelMetaData.java +++ b/rest-api/src/main/java/lcsb/mapviewer/api/projects/models/ModelMetaData.java @@ -46,6 +46,12 @@ public class ModelMetaData implements Serializable { private Integer height; + private Double defaultCenterX; + + private Double defaultCenterY; + + private Integer defaultZoomLevel; + /** * Minimum zoom level that should be allowed by the Google Maps API. */ @@ -104,6 +110,9 @@ public class ModelMetaData implements Serializable { this.setCenterLatLng(cConverter.toLatLng(new Point2D.Double(size / 2, size / 2))); this.setBottomRightLatLng(cConverter.toLatLng(new Point2D.Double(model.getWidth(), model.getHeight()))); this.setTopLeftLatLng(cConverter.toLatLng(new Point2D.Double(0, 0))); + this.setDefaultCenterX(model.getDefaultCenterX()); + this.setDefaultCenterY(model.getDefaultCenterY()); + this.setDefaultZoomLevel(model.getDefaultZoomLevel()); List<ModelMetaData> submodels = new ArrayList<>(); for (ModelSubmodelConnection connection : model.getSubmodels()) { @@ -362,4 +371,28 @@ public class ModelMetaData implements Serializable { this.submodelType = submodelType; } + public Integer getDefaultZoomLevel() { + return defaultZoomLevel; + } + + public void setDefaultZoomLevel(Integer defaultZoomLevel) { + this.defaultZoomLevel = defaultZoomLevel; + } + + public Double getDefaultCenterX() { + return defaultCenterX; + } + + public void setDefaultCenterX(Double defaultCenterX) { + this.defaultCenterX = defaultCenterX; + } + + public Double getDefaultCenterY() { + return defaultCenterY; + } + + public void setDefaultCenterY(Double defaultCenterY) { + this.defaultCenterY = defaultCenterY; + } + } diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/projects/models/ModelRestImpl.java b/rest-api/src/main/java/lcsb/mapviewer/api/projects/models/ModelRestImpl.java index 7e26e7430e..2fd1d56ad5 100644 --- a/rest-api/src/main/java/lcsb/mapviewer/api/projects/models/ModelRestImpl.java +++ b/rest-api/src/main/java/lcsb/mapviewer/api/projects/models/ModelRestImpl.java @@ -2,6 +2,8 @@ package lcsb.mapviewer.api.projects.models; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Set; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; @@ -9,8 +11,12 @@ import org.springframework.transaction.annotation.Transactional; import lcsb.mapviewer.api.BaseRestImpl; import lcsb.mapviewer.api.ObjectNotFoundException; +import lcsb.mapviewer.api.QueryException; +import lcsb.mapviewer.common.exception.InvalidArgumentException; import lcsb.mapviewer.model.Project; import lcsb.mapviewer.model.map.model.Model; +import lcsb.mapviewer.model.map.model.ModelData; +import lcsb.mapviewer.model.user.PrivilegeType; import lcsb.mapviewer.services.SecurityException; import lcsb.mapviewer.services.interfaces.ILayoutService; import lcsb.mapviewer.services.view.AuthenticationToken; @@ -72,4 +78,86 @@ public class ModelRestImpl extends BaseRestImpl { } return result; } + + public ModelMetaData updateModel(String projectId, String modelId, Map<String, Object> data, String token) + throws SecurityException, QueryException { + AuthenticationToken authenticationToken = getUserService().getToken(token); + Project project = getProjectService().getProjectByProjectId(projectId, authenticationToken); + if (project == null) { + throw new ObjectNotFoundException("Project with given id doesn't exist"); + } + boolean canModify = getUserService().userHasPrivilege(authenticationToken, PrivilegeType.ADD_MAP); + if (!canModify) { + throw new SecurityException("You cannot update projects"); + } + ModelData model = null; + Integer id = Integer.valueOf(modelId); + for (ModelData m : project.getModels()) { + if (m.getId().equals(id)) { + model = m; + } + } + if (model == null) { + throw new ObjectNotFoundException("Model with given id doesn't exist"); + } + + Set<String> fields = data.keySet(); + for (String fieldName : fields) { + Object value = data.get(fieldName); + if (fieldName.equalsIgnoreCase("defaultCenterX")) { + model.setDefaultCenterX(parseDouble(value)); + } else if (fieldName.equalsIgnoreCase("defaultCenterY")) { + model.setDefaultCenterY(parseDouble(value)); + } else if (fieldName.equalsIgnoreCase("defaultZoomLevel")) { + model.setDefaultZoomLevel(parseInteger(value)); + } else if (fieldName.equalsIgnoreCase("id")) { + if (!model.getId().equals(parseInteger(value))) { + throw new QueryException("Id doesn't match: " + value + ", " + model.getId()); + } + } else { + throw new QueryException("Unknown field: " + fieldName); + } + } + getModelService().updateModel(model, authenticationToken); + return getModel(projectId, modelId, token); + } + + private Integer parseInteger(Object value) throws QueryException { + if (value instanceof Integer) { + return (Integer) value; + } else if (value instanceof String) { + if (((String) value).equalsIgnoreCase("")) { + return null; + } else { + try { + return Integer.parseInt((String) value); + } catch (NumberFormatException e) { + throw new QueryException("Invalid double value: " + value); + } + } + } else { + throw new InvalidArgumentException(); + } + + } + + private Double parseDouble(Object value) throws QueryException { + if (value instanceof Double) { + return (Double) value; + } else if (value instanceof String) { + if (((String) value).equalsIgnoreCase("")) { + return null; + } else { + try { + return Double.parseDouble((String) value); + } catch (NumberFormatException e) { + throw new QueryException("Invalid double value: " + value); + } + } + } else if (value == null) { + return null; + } else { + throw new InvalidArgumentException(); + } + } } diff --git a/service/src/main/java/lcsb/mapviewer/services/impl/ModelService.java b/service/src/main/java/lcsb/mapviewer/services/impl/ModelService.java index 4d7a63ac46..d3634f6b8d 100644 --- a/service/src/main/java/lcsb/mapviewer/services/impl/ModelService.java +++ b/service/src/main/java/lcsb/mapviewer/services/impl/ModelService.java @@ -1,558 +1,574 @@ -package lcsb.mapviewer.services.impl; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.apache.log4j.Logger; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.annotation.Transactional; - -import lcsb.mapviewer.annotation.data.Article; -import lcsb.mapviewer.annotation.services.MeSHParser; -import lcsb.mapviewer.annotation.services.MiriamConnector; -import lcsb.mapviewer.annotation.services.PubmedParser; -import lcsb.mapviewer.annotation.services.PubmedSearchException; -import lcsb.mapviewer.annotation.services.TaxonomyBackend; -import lcsb.mapviewer.annotation.services.TaxonomySearchException; -import lcsb.mapviewer.annotation.services.annotators.AnnotatorException; -import lcsb.mapviewer.commands.CopyCommand; -import lcsb.mapviewer.common.IProgressUpdater; -import lcsb.mapviewer.common.Pair; -import lcsb.mapviewer.common.exception.InvalidArgumentException; -import lcsb.mapviewer.model.map.MiriamData; -import lcsb.mapviewer.model.map.MiriamType; -import lcsb.mapviewer.model.map.layout.Layout; -import lcsb.mapviewer.model.map.model.Model; -import lcsb.mapviewer.model.map.model.ModelData; -import lcsb.mapviewer.model.map.model.ModelFullIndexed; -import lcsb.mapviewer.model.map.reaction.Reaction; -import lcsb.mapviewer.model.map.species.Element; -import lcsb.mapviewer.model.user.PrivilegeType; -import lcsb.mapviewer.model.user.User; -import lcsb.mapviewer.persist.dao.ProjectDao; -import lcsb.mapviewer.persist.dao.map.ModelDao; -import lcsb.mapviewer.services.interfaces.ILayoutService; -import lcsb.mapviewer.services.interfaces.IModelService; -import lcsb.mapviewer.services.interfaces.IUserService; -import lcsb.mapviewer.services.search.data.LightAliasView; -import lcsb.mapviewer.services.search.data.LightAliasViewFactory; -import lcsb.mapviewer.services.search.data.LightReactionView; -import lcsb.mapviewer.services.view.AuthenticationToken; -import lcsb.mapviewer.services.view.LayoutView; -import lcsb.mapviewer.services.view.ModelView; -import lcsb.mapviewer.services.view.ModelViewFactory; -import lcsb.mapviewer.services.view.ProjectView; - -/** - * Implementation of the service that manages models. - * - * @author Piotr Gawron - * - */ -@Transactional(value = "txManager") -public class ModelService implements IModelService { - - /** - * Default class logger. - */ - private Logger logger = Logger.getLogger(ModelService.class); - - /** - * List of cached models. - */ - private static Map<String, Model> models = new HashMap<String, Model>(); - - /** - * List of models that are currently being loaded from databsae. - */ - private static Set<String> modelsInLoadStage = new HashSet<>(); - - /** - * Service that manages and gives access to user information. - */ - @Autowired - private IUserService userService; - - /** - * Data access object for models. - */ - @Autowired - private ModelDao modelDao; - - @Autowired - private ProjectDao projectDao; - - /** - * Object allowing access to the mesh database. - */ - @Autowired - private MeSHParser meshParser; - - /** - * Local backend to the pubmed data. - */ - @Autowired - private PubmedParser backend; - - /** - * Service used for managing layouts. - */ - @Autowired - private ILayoutService layoutService; - - /** - * Connector used for accessing data from miriam registry. - */ - @Autowired - private MiriamConnector miriamConnector; - - /** - * Factory object used for creation of {@link ModelView} elements. - */ - @Autowired - private ModelViewFactory modelViewFactory; - - /** - * Access point and parser for the online ctd database. - */ - @Autowired - private TaxonomyBackend taxonomyBackend; - - @Override - public Model getLastModelByProjectId(String projectName, AuthenticationToken token) { - if (projectName == null) { - return null; - } - - // check if model is being loaded by another thread - boolean waitForModel = false; - do { - synchronized (modelsInLoadStage) { - waitForModel = modelsInLoadStage.contains(projectName); - } - // if model is being loaded then wait until it's loaded - if (waitForModel) { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - logger.fatal(e, e); - } - } - } while (waitForModel); - - Model model = models.get(projectName); - if (model == null) { - try { - // mark model as being load stage, so other threads that want to access - // it will wait - synchronized (modelsInLoadStage) { - modelsInLoadStage.add(projectName); - } - logger.debug("Unknown model, trying to load the model into memory."); - - ModelData modelData = modelDao.getLastModelForProjectIdentifier(projectName, false); - - if (modelData == null) { - logger.debug("Model doesn't exist"); - return null; - } - logger.debug("Model loaded from db."); - model = new ModelFullIndexed(modelData); - - // this is a trick to load all required subelements of the model... ;/ - // lets copy model - it will access all elements... - new CopyCommand(model).execute(); - - for (ModelData m : model.getProject().getModels()) { - new CopyCommand(m.getModel()).execute(); - } - - logger.debug("Model loaded successfullly"); - models.put(projectName, model); - } finally { - // model is not being loaded anymore - synchronized (modelsInLoadStage) { - modelsInLoadStage.remove(projectName); - } - } - } - if (userService.userHasPrivilege(token, PrivilegeType.VIEW_PROJECT, model.getProject())) { - return model; - } else if (userService.userHasPrivilege(token, PrivilegeType.ADD_MAP)) { - return model; - } else { - logger.debug(userService.getUserByToken(token).getLogin()); - throw new SecurityException("User doesn't have access to project"); - } - } - - @Override - public void cacheAllPubmedIds(Model model, IProgressUpdater updater) { - logger.debug("Caching pubmed ids..."); - if (model != null) { - Set<Integer> pubmedIds = new HashSet<>(); - - for (Element element : model.getElements()) { - for (MiriamData md : element.getMiriamData()) { - if (MiriamType.PUBMED.equals(md.getDataType())) { - try { - pubmedIds.add(Integer.parseInt(md.getResource())); - } catch (NumberFormatException e) { - logger.error("Problem with parsing: " + e.getMessage(), e); - } - } - } - } - for (Reaction reaction : model.getReactions()) { - for (MiriamData md : reaction.getMiriamData()) { - if (MiriamType.PUBMED.equals(md.getDataType())) { - try { - pubmedIds.add(Integer.parseInt(md.getResource())); - } catch (NumberFormatException e) { - logger.error("Problem with parsing: " + e.getMessage(), e); - } - } - } - } - double amount = pubmedIds.size(); - double counter = 0; - for (Integer id : pubmedIds) { - try { - Article art = backend.getPubmedArticleById(id); - if (art == null) { - logger.warn("Cannot find pubmed article. Pubmed_id = " + id); - } - } catch (PubmedSearchException e) { - logger.warn("Problem with accessing info about pubmed: " + id, e); - } - counter++; - updater.setProgress(IProgressUpdater.MAX_PROGRESS * counter / amount); - } - } - logger.debug("Caching finished"); - } - - @Override - public void updateModel(ModelData model, ProjectView selectedProject) { - - for (Model cachedModel : models.values()) { - if (cachedModel.getId().equals(selectedProject.getModelId())) { - setModel(cachedModel.getModelData(), selectedProject); - } - } - setModel(model, selectedProject); - modelDao.update(model); - } - - /** - * Sets the data from selectedProject into model. - * - * @param model - * destination of the data - * @param selectedProject - * source of the data - */ - private void setModel(ModelData model, ProjectView selectedProject) { - model.setNotes(selectedProject.getDescription()); - List<Layout> toRemove = new ArrayList<Layout>(); - for (Layout layout : model.getLayouts()) { - boolean exists = false; - for (LayoutView row : selectedProject.getLayouts()) { - if (layout.getId() == row.getIdObject()) { - exists = true; - // don't allow client side to edit layouts - // layout.setDirectory(row.getDirectory()); - layout.setTitle(row.getName()); - layout.setDescription(row.getDescription()); - } - } - if (!exists) { - toRemove.add(layout); - } - } - model.getLayouts().removeAll(toRemove); - - MiriamData disease = null; - if (selectedProject.getNewDiseaseName() != null) { - disease = new MiriamData(MiriamType.MESH_2012, selectedProject.getNewDiseaseName()); - } - - try { - if (meshParser.isValidMeshId(disease)) { - model.getProject().setDisease(disease); - } else { - model.getProject().setDisease(null); - selectedProject.setNewDiseaseName(null); - } - } catch (AnnotatorException e) { - logger.warn("Problem with accessing mesh db"); - } - - if (model.getProject() != null) { - MiriamData organism = null; - if (selectedProject.getNewOrganismName() != null && !selectedProject.getNewOrganismName().isEmpty()) { - organism = new MiriamData(MiriamType.TAXONOMY, selectedProject.getNewOrganismName()); - } - - try { - if (taxonomyBackend.getNameForTaxonomy(organism) != null) { - model.getProject().setOrganism(organism); - } else { - model.getProject().setOrganism(null); - } - } catch (TaxonomySearchException e) { - logger.error("Problem with accessing taxonomy db", e); - model.getProject().setOrganism(null); - } - } - - } - - @Override - public void removeModelFromCache(Model model) { - models.remove(model.getProject().getProjectId()); - - } - - /** - * @return the modelDao - * @see #modelDao - */ - public ModelDao getModelDao() { - return modelDao; - } - - /** - * @param modelDao - * the modelDao to set - * @see #modelDao - */ - public void setModelDao(ModelDao modelDao) { - this.modelDao = modelDao; - } - - /** - * @return the backend - * @see #backend - */ - public PubmedParser getBackend() { - return backend; - } - - /** - * @param backend - * the backend to set - * @see #backend - */ - public void setBackend(PubmedParser backend) { - this.backend = backend; - } - - @Override - public void cacheAllMiriamLinks(Model model, IProgressUpdater updater) { - logger.debug("Caching miriam ids..."); - if (model != null) { - Set<MiriamData> pubmedIds = new HashSet<MiriamData>(); - - for (Element element : model.getElements()) { - pubmedIds.addAll(element.getMiriamData()); - } - for (Reaction reaction : model.getReactions()) { - pubmedIds.addAll(reaction.getMiriamData()); - } - double amount = pubmedIds.size(); - double counter = 0; - for (MiriamData md : pubmedIds) { - miriamConnector.getUrlString(md); - counter++; - updater.setProgress(IProgressUpdater.MAX_PROGRESS * counter / amount); - } - } - logger.debug("Caching finished"); - - } - - @Override - public ModelView getModelView(Model model, User user) { - ModelView result = modelViewFactory.create(model); - if (user != null) { - result.setCustomLayouts(layoutService.getCustomLayouts(model, user, true, user)); - for (ModelView view : result.getSubmodels()) { - view.setCustomLayouts(layoutService.getCustomLayouts(model.getSubmodelById(view.getIdObject()), user, true, user)); - } - } - return result; - } - - @Override - public void removeModelFromCache(ModelData model) { - models.remove(model.getProject().getProjectId()); - } - - /** - * @return the layoutService - * @see #layoutService - */ - public ILayoutService getLayoutService() { - return layoutService; - } - - /** - * @param layoutService - * the layoutService to set - * @see #layoutService - */ - public void setLayoutService(ILayoutService layoutService) { - this.layoutService = layoutService; - } - - @Override - public void removeModelFromCacheByProjectId(String projectId) { - models.remove(projectId); - } - - @Override - public List<LightAliasView> getLightAliasesByIds(Model model, List<Pair<Integer, Integer>> identifiers) { - LightAliasViewFactory lightAliasViewFactory = new LightAliasViewFactory(); - List<LightAliasView> result = lightAliasViewFactory.createList(getAliasesByIds(model, identifiers)); - return result; - } - - /** - * Returns list of {@link Element aliases} for given identifiers. - * - * @param model - * model where aliases are located - * @param identifiers - * list of alias identifiers in a given submodel. Every {@link Pair} - * contains information about {@link Model#getId() model identifier} - * (in {@link Pair#left}) and - * {@link lcsb.mapviewer.model.map.species.Element#getId() alias - * identifier} (in {@link Pair#right}). - * @return list of {@link Element aliases} for given identifiers - */ - private List<Element> getAliasesByIds(Model model, List<Pair<Integer, Integer>> identifiers) { - Map<Integer, Set<Integer>> identifiersForMap = new HashMap<>(); - for (Pair<Integer, Integer> pair : identifiers) { - Set<Integer> set = identifiersForMap.get(pair.getLeft()); - if (set == null) { - set = new HashSet<>(); - identifiersForMap.put(pair.getLeft(), set); - } - set.add(pair.getRight()); - } - - List<Element> result = new ArrayList<>(); - List<Model> models = new ArrayList<>(); - models.add(model); - models.addAll(model.getSubmodels()); - for (Model model2 : models) { - Set<Integer> set = identifiersForMap.get(model2.getModelData().getId()); - if (set != null) { - for (Element alias : model2.getElements()) { - if (set.contains(alias.getId())) { - result.add(alias); - set.remove(alias.getId()); - } - if (set.size() == 0) { - break; - } - } - if (set.size() > 0) { - Integer aliasId = set.iterator().next(); - throw new InvalidArgumentException("Cannot find alias (id: " + aliasId + ") in a model (id: " + model2.getModelData().getId() + ")"); - } - identifiersForMap.remove(model2.getModelData().getId()); - } - } - if (identifiersForMap.keySet().size() > 0) { - Integer modelId = identifiersForMap.keySet().iterator().next(); - Integer aliasId = identifiersForMap.get(modelId).iterator().next(); - throw new InvalidArgumentException("Cannot find alias (id: " + aliasId + ") in a model (id: " + modelId + "). Model doesn't exist."); - } - return result; - } - - @Override - public List<LightReactionView> getLightReactionsByIds(Model model, List<Pair<Integer, Integer>> identifiers) { - - Map<Integer, Set<Integer>> identifiersForMap = new HashMap<>(); - for (Pair<Integer, Integer> pair : identifiers) { - Set<Integer> set = identifiersForMap.get(pair.getLeft()); - if (set == null) { - set = new HashSet<>(); - identifiersForMap.put(pair.getLeft(), set); - } - set.add(pair.getRight()); - } - - List<LightReactionView> result = new ArrayList<>(); - List<Model> models = new ArrayList<>(); - models.add(model); - models.addAll(model.getSubmodels()); - for (Model model2 : models) { - Set<Integer> set = identifiersForMap.get(model2.getModelData().getId()); - if (set != null) { - for (Reaction reaction : model2.getReactions()) { - if (set.contains(reaction.getId())) { - result.add(new LightReactionView(reaction)); - set.remove(reaction.getId()); - } - if (set.size() == 0) { - break; - } - } - if (set.size() > 0) { - Integer aliasId = set.iterator().next(); - throw new InvalidArgumentException("Cannot find reaction (id: " + aliasId + ") in a model (id: " + model2.getModelData().getId() + ")"); - } - identifiersForMap.remove(model2.getModelData().getId()); - } - } - if (identifiersForMap.keySet().size() > 0) { - Integer modelId = identifiersForMap.keySet().iterator().next(); - Integer aliasId = identifiersForMap.get(modelId).iterator().next(); - throw new InvalidArgumentException("Cannot find reaction (id: " + aliasId + ") in a model (id: " + modelId + "). Model doesn't exist."); - } - return result; - } - - /** - * @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; - } - - /** - * @return the projectDao - * @see #projectDao - */ - public ProjectDao getProjectDao() { - return projectDao; - } - - /** - * @param projectDao - * the projectDao to set - * @see #projectDao - */ - public void setProjectDao(ProjectDao projectDao) { - this.projectDao = projectDao; - } - -} +package lcsb.mapviewer.services.impl; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import lcsb.mapviewer.annotation.data.Article; +import lcsb.mapviewer.annotation.services.MeSHParser; +import lcsb.mapviewer.annotation.services.MiriamConnector; +import lcsb.mapviewer.annotation.services.PubmedParser; +import lcsb.mapviewer.annotation.services.PubmedSearchException; +import lcsb.mapviewer.annotation.services.TaxonomyBackend; +import lcsb.mapviewer.annotation.services.TaxonomySearchException; +import lcsb.mapviewer.annotation.services.annotators.AnnotatorException; +import lcsb.mapviewer.commands.CopyCommand; +import lcsb.mapviewer.common.IProgressUpdater; +import lcsb.mapviewer.common.Pair; +import lcsb.mapviewer.common.exception.InvalidArgumentException; +import lcsb.mapviewer.model.map.MiriamData; +import lcsb.mapviewer.model.map.MiriamType; +import lcsb.mapviewer.model.map.layout.Layout; +import lcsb.mapviewer.model.map.model.Model; +import lcsb.mapviewer.model.map.model.ModelData; +import lcsb.mapviewer.model.map.model.ModelFullIndexed; +import lcsb.mapviewer.model.map.reaction.Reaction; +import lcsb.mapviewer.model.map.species.Element; +import lcsb.mapviewer.model.user.PrivilegeType; +import lcsb.mapviewer.model.user.User; +import lcsb.mapviewer.persist.dao.ProjectDao; +import lcsb.mapviewer.persist.dao.map.ModelDao; +import lcsb.mapviewer.services.interfaces.ILayoutService; +import lcsb.mapviewer.services.interfaces.IModelService; +import lcsb.mapviewer.services.interfaces.IUserService; +import lcsb.mapviewer.services.search.data.LightAliasView; +import lcsb.mapviewer.services.search.data.LightAliasViewFactory; +import lcsb.mapviewer.services.search.data.LightReactionView; +import lcsb.mapviewer.services.view.AuthenticationToken; +import lcsb.mapviewer.services.view.LayoutView; +import lcsb.mapviewer.services.view.ModelView; +import lcsb.mapviewer.services.view.ModelViewFactory; +import lcsb.mapviewer.services.view.ProjectView; + +/** + * Implementation of the service that manages models. + * + * @author Piotr Gawron + * + */ +@Transactional(value = "txManager") +public class ModelService implements IModelService { + + /** + * Default class logger. + */ + private Logger logger = Logger.getLogger(ModelService.class); + + /** + * List of cached models. + */ + private static Map<String, Model> models = new HashMap<String, Model>(); + + /** + * List of models that are currently being loaded from database. + */ + private static Set<String> modelsInLoadStage = new HashSet<>(); + + /** + * Service that manages and gives access to user information. + */ + @Autowired + private IUserService userService; + + /** + * Data access object for models. + */ + @Autowired + private ModelDao modelDao; + + @Autowired + private ProjectDao projectDao; + + /** + * Object allowing access to the mesh database. + */ + @Autowired + private MeSHParser meshParser; + + /** + * Local backend to the pubmed data. + */ + @Autowired + private PubmedParser backend; + + /** + * Service used for managing layouts. + */ + @Autowired + private ILayoutService layoutService; + + /** + * Connector used for accessing data from miriam registry. + */ + @Autowired + private MiriamConnector miriamConnector; + + /** + * Factory object used for creation of {@link ModelView} elements. + */ + @Autowired + private ModelViewFactory modelViewFactory; + + /** + * Access point and parser for the online ctd database. + */ + @Autowired + private TaxonomyBackend taxonomyBackend; + + @Override + public Model getLastModelByProjectId(String projectName, AuthenticationToken token) { + if (projectName == null) { + return null; + } + + // check if model is being loaded by another thread + boolean waitForModel = false; + do { + synchronized (modelsInLoadStage) { + waitForModel = modelsInLoadStage.contains(projectName); + } + // if model is being loaded then wait until it's loaded + if (waitForModel) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + logger.fatal(e, e); + } + } + } while (waitForModel); + + Model model = models.get(projectName); + if (model == null) { + try { + // mark model as being load stage, so other threads that want to access + // it will wait + synchronized (modelsInLoadStage) { + modelsInLoadStage.add(projectName); + } + logger.debug("Unknown model, trying to load the model into memory."); + + ModelData modelData = modelDao.getLastModelForProjectIdentifier(projectName, false); + + if (modelData == null) { + logger.debug("Model doesn't exist"); + return null; + } + logger.debug("Model loaded from db."); + model = new ModelFullIndexed(modelData); + + // this is a trick to load all required subelements of the model... ;/ + // lets copy model - it will access all elements... + new CopyCommand(model).execute(); + + for (ModelData m : model.getProject().getModels()) { + new CopyCommand(m.getModel()).execute(); + } + + logger.debug("Model loaded successfullly"); + models.put(projectName, model); + } finally { + // model is not being loaded anymore + synchronized (modelsInLoadStage) { + modelsInLoadStage.remove(projectName); + } + } + } + if (userService.userHasPrivilege(token, PrivilegeType.VIEW_PROJECT, model.getProject())) { + return model; + } else if (userService.userHasPrivilege(token, PrivilegeType.ADD_MAP)) { + return model; + } else { + logger.debug(userService.getUserByToken(token).getLogin()); + throw new SecurityException("User doesn't have access to project"); + } + } + + @Override + public void cacheAllPubmedIds(Model model, IProgressUpdater updater) { + logger.debug("Caching pubmed ids..."); + if (model != null) { + Set<Integer> pubmedIds = new HashSet<>(); + + for (Element element : model.getElements()) { + for (MiriamData md : element.getMiriamData()) { + if (MiriamType.PUBMED.equals(md.getDataType())) { + try { + pubmedIds.add(Integer.parseInt(md.getResource())); + } catch (NumberFormatException e) { + logger.error("Problem with parsing: " + e.getMessage(), e); + } + } + } + } + for (Reaction reaction : model.getReactions()) { + for (MiriamData md : reaction.getMiriamData()) { + if (MiriamType.PUBMED.equals(md.getDataType())) { + try { + pubmedIds.add(Integer.parseInt(md.getResource())); + } catch (NumberFormatException e) { + logger.error("Problem with parsing: " + e.getMessage(), e); + } + } + } + } + double amount = pubmedIds.size(); + double counter = 0; + for (Integer id : pubmedIds) { + try { + Article art = backend.getPubmedArticleById(id); + if (art == null) { + logger.warn("Cannot find pubmed article. Pubmed_id = " + id); + } + } catch (PubmedSearchException e) { + logger.warn("Problem with accessing info about pubmed: " + id, e); + } + counter++; + updater.setProgress(IProgressUpdater.MAX_PROGRESS * counter / amount); + } + } + logger.debug("Caching finished"); + } + + @Override + public void updateModel(ModelData model, ProjectView selectedProject) { + + for (Model cachedModel : models.values()) { + if (cachedModel.getId().equals(selectedProject.getModelId())) { + setModel(cachedModel.getModelData(), selectedProject); + } + } + setModel(model, selectedProject); + modelDao.update(model); + } + + /** + * Sets the data from selectedProject into model. + * + * @param model + * destination of the data + * @param selectedProject + * source of the data + */ + private void setModel(ModelData model, ProjectView selectedProject) { + model.setNotes(selectedProject.getDescription()); + List<Layout> toRemove = new ArrayList<Layout>(); + for (Layout layout : model.getLayouts()) { + boolean exists = false; + for (LayoutView row : selectedProject.getLayouts()) { + if (layout.getId() == row.getIdObject()) { + exists = true; + // don't allow client side to edit layouts + // layout.setDirectory(row.getDirectory()); + layout.setTitle(row.getName()); + layout.setDescription(row.getDescription()); + } + } + if (!exists) { + toRemove.add(layout); + } + } + model.getLayouts().removeAll(toRemove); + + MiriamData disease = null; + if (selectedProject.getNewDiseaseName() != null) { + disease = new MiriamData(MiriamType.MESH_2012, selectedProject.getNewDiseaseName()); + } + + try { + if (meshParser.isValidMeshId(disease)) { + model.getProject().setDisease(disease); + } else { + model.getProject().setDisease(null); + selectedProject.setNewDiseaseName(null); + } + } catch (AnnotatorException e) { + logger.warn("Problem with accessing mesh db"); + } + + if (model.getProject() != null) { + MiriamData organism = null; + if (selectedProject.getNewOrganismName() != null && !selectedProject.getNewOrganismName().isEmpty()) { + organism = new MiriamData(MiriamType.TAXONOMY, selectedProject.getNewOrganismName()); + } + + try { + if (taxonomyBackend.getNameForTaxonomy(organism) != null) { + model.getProject().setOrganism(organism); + } else { + model.getProject().setOrganism(null); + } + } catch (TaxonomySearchException e) { + logger.error("Problem with accessing taxonomy db", e); + model.getProject().setOrganism(null); + } + } + + } + + @Override + public void removeModelFromCache(Model model) { + models.remove(model.getProject().getProjectId()); + + } + + /** + * @return the modelDao + * @see #modelDao + */ + public ModelDao getModelDao() { + return modelDao; + } + + /** + * @param modelDao + * the modelDao to set + * @see #modelDao + */ + public void setModelDao(ModelDao modelDao) { + this.modelDao = modelDao; + } + + /** + * @return the backend + * @see #backend + */ + public PubmedParser getBackend() { + return backend; + } + + /** + * @param backend + * the backend to set + * @see #backend + */ + public void setBackend(PubmedParser backend) { + this.backend = backend; + } + + @Override + public void cacheAllMiriamLinks(Model model, IProgressUpdater updater) { + logger.debug("Caching miriam ids..."); + if (model != null) { + Set<MiriamData> pubmedIds = new HashSet<MiriamData>(); + + for (Element element : model.getElements()) { + pubmedIds.addAll(element.getMiriamData()); + } + for (Reaction reaction : model.getReactions()) { + pubmedIds.addAll(reaction.getMiriamData()); + } + double amount = pubmedIds.size(); + double counter = 0; + for (MiriamData md : pubmedIds) { + miriamConnector.getUrlString(md); + counter++; + updater.setProgress(IProgressUpdater.MAX_PROGRESS * counter / amount); + } + } + logger.debug("Caching finished"); + + } + + @Override + public ModelView getModelView(Model model, User user) { + ModelView result = modelViewFactory.create(model); + if (user != null) { + result.setCustomLayouts(layoutService.getCustomLayouts(model, user, true, user)); + for (ModelView view : result.getSubmodels()) { + view.setCustomLayouts( + layoutService.getCustomLayouts(model.getSubmodelById(view.getIdObject()), user, true, user)); + } + } + return result; + } + + @Override + public void removeModelFromCache(ModelData model) { + models.remove(model.getProject().getProjectId()); + } + + /** + * @return the layoutService + * @see #layoutService + */ + public ILayoutService getLayoutService() { + return layoutService; + } + + /** + * @param layoutService + * the layoutService to set + * @see #layoutService + */ + public void setLayoutService(ILayoutService layoutService) { + this.layoutService = layoutService; + } + + @Override + public void removeModelFromCacheByProjectId(String projectId) { + models.remove(projectId); + } + + @Override + public List<LightAliasView> getLightAliasesByIds(Model model, List<Pair<Integer, Integer>> identifiers) { + LightAliasViewFactory lightAliasViewFactory = new LightAliasViewFactory(); + List<LightAliasView> result = lightAliasViewFactory.createList(getAliasesByIds(model, identifiers)); + return result; + } + + /** + * Returns list of {@link Element aliases} for given identifiers. + * + * @param model + * model where aliases are located + * @param identifiers + * list of alias identifiers in a given submodel. Every {@link Pair} + * contains information about {@link Model#getId() model identifier} + * (in {@link Pair#left}) and + * {@link lcsb.mapviewer.model.map.species.Element#getId() alias + * identifier} (in {@link Pair#right}). + * @return list of {@link Element aliases} for given identifiers + */ + private List<Element> getAliasesByIds(Model model, List<Pair<Integer, Integer>> identifiers) { + Map<Integer, Set<Integer>> identifiersForMap = new HashMap<>(); + for (Pair<Integer, Integer> pair : identifiers) { + Set<Integer> set = identifiersForMap.get(pair.getLeft()); + if (set == null) { + set = new HashSet<>(); + identifiersForMap.put(pair.getLeft(), set); + } + set.add(pair.getRight()); + } + + List<Element> result = new ArrayList<>(); + List<Model> models = new ArrayList<>(); + models.add(model); + models.addAll(model.getSubmodels()); + for (Model model2 : models) { + Set<Integer> set = identifiersForMap.get(model2.getModelData().getId()); + if (set != null) { + for (Element alias : model2.getElements()) { + if (set.contains(alias.getId())) { + result.add(alias); + set.remove(alias.getId()); + } + if (set.size() == 0) { + break; + } + } + if (set.size() > 0) { + Integer aliasId = set.iterator().next(); + throw new InvalidArgumentException( + "Cannot find alias (id: " + aliasId + ") in a model (id: " + model2.getModelData().getId() + ")"); + } + identifiersForMap.remove(model2.getModelData().getId()); + } + } + if (identifiersForMap.keySet().size() > 0) { + Integer modelId = identifiersForMap.keySet().iterator().next(); + Integer aliasId = identifiersForMap.get(modelId).iterator().next(); + throw new InvalidArgumentException( + "Cannot find alias (id: " + aliasId + ") in a model (id: " + modelId + "). Model doesn't exist."); + } + return result; + } + + @Override + public List<LightReactionView> getLightReactionsByIds(Model model, List<Pair<Integer, Integer>> identifiers) { + + Map<Integer, Set<Integer>> identifiersForMap = new HashMap<>(); + for (Pair<Integer, Integer> pair : identifiers) { + Set<Integer> set = identifiersForMap.get(pair.getLeft()); + if (set == null) { + set = new HashSet<>(); + identifiersForMap.put(pair.getLeft(), set); + } + set.add(pair.getRight()); + } + + List<LightReactionView> result = new ArrayList<>(); + List<Model> models = new ArrayList<>(); + models.add(model); + models.addAll(model.getSubmodels()); + for (Model model2 : models) { + Set<Integer> set = identifiersForMap.get(model2.getModelData().getId()); + if (set != null) { + for (Reaction reaction : model2.getReactions()) { + if (set.contains(reaction.getId())) { + result.add(new LightReactionView(reaction)); + set.remove(reaction.getId()); + } + if (set.size() == 0) { + break; + } + } + if (set.size() > 0) { + Integer aliasId = set.iterator().next(); + throw new InvalidArgumentException( + "Cannot find reaction (id: " + aliasId + ") in a model (id: " + model2.getModelData().getId() + ")"); + } + identifiersForMap.remove(model2.getModelData().getId()); + } + } + if (identifiersForMap.keySet().size() > 0) { + Integer modelId = identifiersForMap.keySet().iterator().next(); + Integer aliasId = identifiersForMap.get(modelId).iterator().next(); + throw new InvalidArgumentException( + "Cannot find reaction (id: " + aliasId + ") in a model (id: " + modelId + "). Model doesn't exist."); + } + return result; + } + + /** + * @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; + } + + /** + * @return the projectDao + * @see #projectDao + */ + public ProjectDao getProjectDao() { + return projectDao; + } + + /** + * @param projectDao + * the projectDao to set + * @see #projectDao + */ + public void setProjectDao(ProjectDao projectDao) { + this.projectDao = projectDao; + } + + @Override + public void updateModel(ModelData model, AuthenticationToken token) { + Model topCachedData = getLastModelByProjectId(model.getProject().getProjectId(), token); + Model cachedData = topCachedData.getSubmodelById(model.getId()); + cachedData.setDefaultCenterX(model.getDefaultCenterX()); + cachedData.setDefaultCenterY(model.getDefaultCenterY()); + cachedData.setDefaultZoomLevel(model.getDefaultZoomLevel()); + modelDao.update(model); + + } + +} diff --git a/service/src/main/java/lcsb/mapviewer/services/interfaces/IModelService.java b/service/src/main/java/lcsb/mapviewer/services/interfaces/IModelService.java index 43cc0ff508..53fda094d0 100644 --- a/service/src/main/java/lcsb/mapviewer/services/interfaces/IModelService.java +++ b/service/src/main/java/lcsb/mapviewer/services/interfaces/IModelService.java @@ -1,128 +1,130 @@ -package lcsb.mapviewer.services.interfaces; - -import java.util.List; - -import lcsb.mapviewer.common.IProgressUpdater; -import lcsb.mapviewer.common.Pair; -import lcsb.mapviewer.model.map.model.Model; -import lcsb.mapviewer.model.map.model.ModelData; -import lcsb.mapviewer.model.user.User; -import lcsb.mapviewer.services.search.data.LightAliasView; -import lcsb.mapviewer.services.search.data.LightReactionView; -import lcsb.mapviewer.services.view.AuthenticationToken; -import lcsb.mapviewer.services.view.ModelView; -import lcsb.mapviewer.services.view.ProjectView; - -/** - * Service that manages models (maps). - * - * @author Piotr Gawron - * - */ -public interface IModelService { - - /** - * Returns the newest model for the project identified by the - * {@link lcsb.mapviewer.model.Project#projectId project identifier}. - * - * @param projectId - * {@link lcsb.mapviewer.model.Project#projectId project identifier} - * @return the newest model for the project - */ - Model getLastModelByProjectId(String projectId, AuthenticationToken token); - - /** - * Caches information about all pubmed articles for the given model. - * - * @param model - * model in which we are caching the data - * @param updater - * callback interface that update progress information - */ - void cacheAllPubmedIds(Model model, IProgressUpdater updater); - - /** - * Updates information in the model with the one taken from selectedProject. - * - * @param model - * model to update - * @param selectedProject - * source of data that should be used in the update - */ - void updateModel(ModelData model, ProjectView selectedProject); - - /** - * Removes model from application cache. - * - * @param model - * model to remove - */ - void removeModelFromCache(Model model); - - /** - * Caches information about all miriam resources in the model. - * - * @param model - * model in which we are caching the data - * @param updater - * callback interface that update progress information - */ - void cacheAllMiriamLinks(Model model, IProgressUpdater updater); - - /** - * Returns {@link ModelView} for the given model. - * - * @param model - * model fgor which view is returned. - * @param user - * user for which custom layouts should be added - * @return {@link ModelView} for the given {@link Model} - */ - ModelView getModelView(Model model, User user); - - /** - * Removes model from application cache. - * - * @param model - * model to remove - */ - void removeModelFromCache(ModelData model); - - /** - * Removes model from application cache. - * - * @param projectId - * identifier of the project - */ - void removeModelFromCacheByProjectId(String projectId); - - /** - * Returns list of {@link LightAliasView aliases} for given identifiers. - * - * @param model - * model where aliases are located - * @param identifiers - * list of alias identifiers in a given submodel. Every {@link Pair} - * contains information about {@link Model#getId() model identifier} - * (in {@link Pair#left}) and - * {@link lcsb.mapviewer.model.map.species.Element#getId() alias - * identifier} (in {@link Pair#right}). - * @return list of {@link LightAliasView aliases} for given identifiers - */ - List<LightAliasView> getLightAliasesByIds(Model model, List<Pair<Integer, Integer>> identifiers); - - /** - * Returns list of {@link LightReactionView reactions} for given identifiers. - * - * @param model - * model where aliases are located - * @param identifiers - * list of reaction identifiers in a given submodel. Every - * {@link Pair} contains information about {@link Model#getId() model - * identifier} (in {@link Pair#left}) and - * {@link lcsb.mapviewer.model.map.reaction.Reaction#getId() reaction - * identifier} (in {@link Pair#right}). - * @return list of {@link LightReactionView reactions} for given identifiers - */ - List<LightReactionView> getLightReactionsByIds(Model model, List<Pair<Integer, Integer>> identifiers); -} +package lcsb.mapviewer.services.interfaces; + +import java.util.List; + +import lcsb.mapviewer.common.IProgressUpdater; +import lcsb.mapviewer.common.Pair; +import lcsb.mapviewer.model.map.model.Model; +import lcsb.mapviewer.model.map.model.ModelData; +import lcsb.mapviewer.model.user.User; +import lcsb.mapviewer.services.search.data.LightAliasView; +import lcsb.mapviewer.services.search.data.LightReactionView; +import lcsb.mapviewer.services.view.AuthenticationToken; +import lcsb.mapviewer.services.view.ModelView; +import lcsb.mapviewer.services.view.ProjectView; + +/** + * Service that manages models (maps). + * + * @author Piotr Gawron + * + */ +public interface IModelService { + + /** + * Returns the newest model for the project identified by the + * {@link lcsb.mapviewer.model.Project#projectId project identifier}. + * + * @param projectId + * {@link lcsb.mapviewer.model.Project#projectId project identifier} + * @return the newest model for the project + */ + Model getLastModelByProjectId(String projectId, AuthenticationToken token); + + /** + * Caches information about all pubmed articles for the given model. + * + * @param model + * model in which we are caching the data + * @param updater + * callback interface that update progress information + */ + void cacheAllPubmedIds(Model model, IProgressUpdater updater); + + /** + * Updates information in the model with the one taken from selectedProject. + * + * @param model + * model to update + * @param selectedProject + * source of data that should be used in the update + */ + void updateModel(ModelData model, ProjectView selectedProject); + + /** + * Removes model from application cache. + * + * @param model + * model to remove + */ + void removeModelFromCache(Model model); + + /** + * Caches information about all miriam resources in the model. + * + * @param model + * model in which we are caching the data + * @param updater + * callback interface that update progress information + */ + void cacheAllMiriamLinks(Model model, IProgressUpdater updater); + + /** + * Returns {@link ModelView} for the given model. + * + * @param model + * model for which view is returned. + * @param user + * user for which custom layouts should be added + * @return {@link ModelView} for the given {@link Model} + */ + ModelView getModelView(Model model, User user); + + /** + * Removes model from application cache. + * + * @param model + * model to remove + */ + void removeModelFromCache(ModelData model); + + /** + * Removes model from application cache. + * + * @param projectId + * identifier of the project + */ + void removeModelFromCacheByProjectId(String projectId); + + /** + * Returns list of {@link LightAliasView aliases} for given identifiers. + * + * @param model + * model where aliases are located + * @param identifiers + * list of alias identifiers in a given submodel. Every {@link Pair} + * contains information about {@link Model#getId() model identifier} + * (in {@link Pair#left}) and + * {@link lcsb.mapviewer.model.map.species.Element#getId() alias + * identifier} (in {@link Pair#right}). + * @return list of {@link LightAliasView aliases} for given identifiers + */ + List<LightAliasView> getLightAliasesByIds(Model model, List<Pair<Integer, Integer>> identifiers); + + /** + * Returns list of {@link LightReactionView reactions} for given identifiers. + * + * @param model + * model where aliases are located + * @param identifiers + * list of reaction identifiers in a given submodel. Every {@link Pair} + * contains information about {@link Model#getId() model identifier} + * (in {@link Pair#left}) and + * {@link lcsb.mapviewer.model.map.reaction.Reaction#getId() reaction + * identifier} (in {@link Pair#right}). + * @return list of {@link LightReactionView reactions} for given identifiers + */ + List<LightReactionView> getLightReactionsByIds(Model model, List<Pair<Integer, Integer>> identifiers); + + void updateModel(ModelData model, AuthenticationToken token); +} -- GitLab