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>&nbsp;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>&nbsp;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>&nbsp;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>&nbsp;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