diff --git a/src/main/java/org/jbei/ice/lib/entry/sequence/PartSequence.java b/src/main/java/org/jbei/ice/lib/entry/sequence/PartSequence.java index 26d5c774e..cba7e1796 100644 --- a/src/main/java/org/jbei/ice/lib/entry/sequence/PartSequence.java +++ b/src/main/java/org/jbei/ice/lib/entry/sequence/PartSequence.java @@ -28,6 +28,7 @@ import org.jbei.ice.storage.DAOFactory; import org.jbei.ice.storage.hibernate.dao.FeatureDAO; import org.jbei.ice.storage.hibernate.dao.SequenceDAO; +import org.jbei.ice.storage.hibernate.dao.SequenceFeatureAttributeDAO; import org.jbei.ice.storage.hibernate.dao.SequenceFeatureDAO; import org.jbei.ice.storage.model.*; @@ -366,26 +367,83 @@ private void checkForUpdatedFeatures(Sequence existing, Sequence updated) { if (existing == null || existing.getSequenceFeatures() == null || updated.getSequenceFeatures() == null) return; - for (SequenceFeature existingFeature : existing.getSequenceFeatures()) { - // for each existing feature, check with updated for difference in name and type - for (SequenceFeature updatedSequenceFeature : updated.getSequenceFeatures()) { - Feature sequenceFeature = updatedSequenceFeature.getFeature(); - Feature existingFeatureFeature = existingFeature.getFeature(); - - if (!sequenceFeature.getHash().equals(existingFeatureFeature.getHash())) - continue; - - if (!sequenceFeature.getName().equals(existingFeatureFeature.getName()) || - !sequenceFeature.getGenbankType().equals(existingFeatureFeature.getGenbankType())) { - // update - existingFeatureFeature.setName(sequenceFeature.getName()); - existingFeatureFeature.setGenbankType(sequenceFeature.getGenbankType()); - featureDAO.update(existingFeatureFeature); + final SequenceFeatureAttributeDAO attributeDAO = DAOFactory.getSequenceFeatureAttributeDAO(); + + // for each existing feature, check with updated for difference in name and type and notes + for (SequenceFeature existingSequenceFeature : existing.getSequenceFeatures()) { + + SequenceFeature updatedSequenceFeature = getUpdatedEquivalent(existingSequenceFeature, updated); + if (updatedSequenceFeature == null) + continue; + + Feature updatedFeature = updatedSequenceFeature.getFeature(); + Feature existingFeature = existingSequenceFeature.getFeature(); + + // check if existing feature name/type needs to be updated + if (!updatedFeature.getName().equals(existingFeature.getName()) || + !updatedFeature.getGenbankType().equals(existingFeature.getGenbankType())) { + existingFeature.setName(updatedFeature.getName()); + existingFeature.setGenbankType(updatedFeature.getGenbankType()); + featureDAO.update(existingFeature); + } + + // check notes for existing feature TODO : if key same but value is different, update instead + // first build cache of updated + Map> updatedCache = new HashMap<>(); + for (SequenceFeatureAttribute updatedAttribute : updatedSequenceFeature.getSequenceFeatureAttributes()) { + String key = updatedAttribute.getKey(); + String value = updatedAttribute.getValue(); + + List values = updatedCache.computeIfAbsent(key, k -> new ArrayList<>()); + values.add(value); + } + + Iterator iterator = existingSequenceFeature.getSequenceFeatureAttributes().iterator(); + + // remove notes that are not in updated list + while (iterator.hasNext()) { + SequenceFeatureAttribute attribute = iterator.next(); + List values = updatedCache.get(attribute.getKey()); + if (values == null || !values.contains(attribute.getValue())) { + attributeDAO.delete(attribute); + iterator.remove(); + } + } + + // add notes from updated list not present in existing + for (SequenceFeatureAttribute updatedAttribute : updatedSequenceFeature.getSequenceFeatureAttributes()) { + // check if attribute is available on existing + boolean isAvailable = false; + for (SequenceFeatureAttribute existingAttribute : existingSequenceFeature.getSequenceFeatureAttributes()) { + isAvailable = updatedAttribute.getKey().equals(existingAttribute.getKey()) && + updatedAttribute.getValue().equals(existingAttribute.getValue()); + if (isAvailable) + break; + } + + // if not available add to list + if (!isAvailable) { + updatedAttribute.setSequenceFeature(existingSequenceFeature); + updatedAttribute = attributeDAO.create(updatedAttribute); + existingSequenceFeature.getSequenceFeatureAttributes().add(updatedAttribute); } } } } + // todo : can cache using hash as key (hash -> sequence) for performance improvements + private SequenceFeature getUpdatedEquivalent(SequenceFeature existing, Sequence updatedSequence) { + if (updatedSequence.getSequenceFeatures() == null) + return null; + + for (SequenceFeature updated : updatedSequence.getSequenceFeatures()) { + if (existing.getFeature().getHash().equals(updated.getFeature().getHash())) + return updated; + } + // no match found + return null; + } + private void deleteSequenceFeature(SequenceFeature sequenceFeature) { sequenceFeature.setSequence(null); sequenceFeature.setFeature(null); diff --git a/src/main/java/org/jbei/ice/storage/DAOFactory.java b/src/main/java/org/jbei/ice/storage/DAOFactory.java index 43344d7ab..7c53bdd64 100644 --- a/src/main/java/org/jbei/ice/storage/DAOFactory.java +++ b/src/main/java/org/jbei/ice/storage/DAOFactory.java @@ -41,6 +41,7 @@ public class DAOFactory { private static CustomEntryFieldDAO customEntryFieldDAO; private static CustomEntryFieldValueDAO customEntryFieldValueDAO; private static SampleCreateModelDAO sampleCreateModelDAO; + private static SequenceFeatureAttributeDAO sequenceFeatureAttributeDAO; public static AccountDAO getAccountDAO() { if (accountDAO == null) @@ -245,4 +246,10 @@ public static SampleCreateModelDAO getSampleCreateModelDAO() { sampleCreateModelDAO = new SampleCreateModelDAO(); return sampleCreateModelDAO; } + + public static SequenceFeatureAttributeDAO getSequenceFeatureAttributeDAO() { + if (sequenceFeatureAttributeDAO == null) + sequenceFeatureAttributeDAO = new SequenceFeatureAttributeDAO(); + return sequenceFeatureAttributeDAO; + } } diff --git a/src/main/java/org/jbei/ice/storage/hibernate/dao/SequenceFeatureAttributeDAO.java b/src/main/java/org/jbei/ice/storage/hibernate/dao/SequenceFeatureAttributeDAO.java new file mode 100644 index 000000000..0ecd833f3 --- /dev/null +++ b/src/main/java/org/jbei/ice/storage/hibernate/dao/SequenceFeatureAttributeDAO.java @@ -0,0 +1,12 @@ +package org.jbei.ice.storage.hibernate.dao; + +import org.jbei.ice.storage.hibernate.HibernateRepository; +import org.jbei.ice.storage.model.SequenceFeatureAttribute; + +public class SequenceFeatureAttributeDAO extends HibernateRepository { + + @Override + public SequenceFeatureAttribute get(long id) { + return super.get(SequenceFeatureAttribute.class, id); + } +} diff --git a/src/main/java/org/jbei/ice/storage/model/SequenceFeatureAttribute.java b/src/main/java/org/jbei/ice/storage/model/SequenceFeatureAttribute.java index cade50a1e..129c153c5 100755 --- a/src/main/java/org/jbei/ice/storage/model/SequenceFeatureAttribute.java +++ b/src/main/java/org/jbei/ice/storage/model/SequenceFeatureAttribute.java @@ -1,7 +1,7 @@ package org.jbei.ice.storage.model; +import org.jbei.ice.lib.dto.DNAFeatureNote; import org.jbei.ice.storage.DataModel; -import org.jbei.ice.storage.IDataTransferModel; import javax.persistence.*; @@ -77,7 +77,12 @@ public void setSequenceFeature(SequenceFeature sequenceFeature) { } @Override - public IDataTransferModel toDataTransferObject() { - return null; //To change body of implemented methods use File | Settings | File Templates. + public String toString() { + return "(" + this.key + ", " + this.value + ")"; + } + + @Override + public DNAFeatureNote toDataTransferObject() { + return new DNAFeatureNote(this.key, this.value); } } diff --git a/src/main/webapp/scripts/entry/entryController.js b/src/main/webapp/scripts/entry/entryController.js index 9e21a524b..998ba6753 100644 --- a/src/main/webapp/scripts/entry/entryController.js +++ b/src/main/webapp/scripts/entry/entryController.js @@ -549,10 +549,13 @@ angular.module('ice.entry.controller', []) forward: feature.strand === 1, type: feature.type, name: feature.name, - notes: notes, + notes: [], annotationType: feature.type }; + // convert notes + console.log("notes", notes); + features.push(featureObject); } } @@ -569,7 +572,9 @@ angular.module('ice.entry.controller', []) isFullscreen: true, shouldAutosave: true, disableSetReadOnly: true, - handleFullscreenClose: function () { // this will make the editor fullscreen by default, and will allow you to handle the close request + showMenuBar: true, + handleFullscreenClose: function () { + // this will make the editor fullscreen by default, and will allow you to handle the close request $scope.vEeditor.close(); // handle vector editor root removal and clean up }, @@ -578,6 +583,16 @@ angular.module('ice.entry.controller', []) // teselagenSequenceData }, + beforeAnnotationCreate: ({ + annotationTypePlural, //one of "features"/"parts"/"primers" + annotation, //annotation info + props //general props to the dialog + }) => { + console.log("features", annotationTypePlural); + console.log("info", annotation); + console.log("general props", props); + }, + // getVersionList: function () { // Util.get('rest/sequences/' + openVEData.registryData.identifier + '/history', function (result) { // return [ @@ -660,7 +675,10 @@ angular.module('ice.entry.controller', []) if (!feature.notes.hasOwnProperty(prop)) continue; - featureMap[feature.id].notes.push({name: "note", value: prop}) + const values = feature.notes[prop]; + for (let i = 0; i < values.length; i += 1) { + featureMap[feature.id].notes.push({name: prop, value: values[i]}); + } } } } @@ -673,11 +691,8 @@ angular.module('ice.entry.controller', []) sequence.features.push(featureMap[property]); } - console.log(sequence.features); - Util.update("rest/parts/" + entry.id + "/sequence", sequence, {}, function (result) { - console.log("save completed for", entry.id); $rootScope.$emit("ReloadVectorViewData", result); const sequenceModel = { sequenceData: { @@ -691,7 +706,8 @@ angular.module('ice.entry.controller', []) $scope.updatedSequence = result; onSuccessCallback(); }) - }, + } + , onCopy: function (event, copiedSequenceData, editorState) { let clipboardData = event.clipboardData || window.clipboardData || event.originalEvent.clipboardData; @@ -700,7 +716,8 @@ angular.module('ice.entry.controller', []) openVEData.openVECopied = copiedSequenceData; clipboardData.setData('application/json', JSON.stringify(openVEData)); event.preventDefault(); - }, + } + , onPaste: function (event, editorState) { let clipboardData = event.clipboardData || window.clipboardData || event.originalEvent.clipboardData; @@ -710,7 +727,8 @@ angular.module('ice.entry.controller', []) jsonData = jsonData.openVECopied; } return jsonData || {sequence: clipboardData.getData("text/plain")} - }, + } + , PropertiesProps: { propertiesList: [ @@ -719,7 +737,8 @@ angular.module('ice.entry.controller', []) "cutsites", "orfs" ] - }, + } + , ToolBarProps: { //name the tools you want to see in the toolbar in the order you want to see them toolList: [ diff --git a/src/main/webapp/scripts/entry/entryDirectives.js b/src/main/webapp/scripts/entry/entryDirectives.js index d7462e1da..95fc2afdf 100644 --- a/src/main/webapp/scripts/entry/entryDirectives.js +++ b/src/main/webapp/scripts/entry/entryDirectives.js @@ -91,7 +91,6 @@ angular.module('ice.entry.directives', []) if (!data) return; - console.log("refreshing vector editor"); $scope.loadVectorEditor(convertToVEModel(data)); }); @@ -178,6 +177,18 @@ angular.module('ice.entry.directives', []) return l1.genbankStart - l2.genbankStart; } + let convertNotes = function (feature, featureObject) { + if (feature.notes.length) { + for (let k = 0; k < feature.notes.length; k += 1) { + const note = feature.notes[k]; + if (!featureObject.notes[note.name]) + featureObject.notes[note.name] = []; + featureObject.notes[note.name].push(note.value); + } + } + return featureObject; + } + // converts the features array (which is what is returned by ICE) to a class (which is what // openVE uses) const convertFeaturesToVEModel = function (features, openVE = null) { @@ -202,8 +213,6 @@ angular.module('ice.entry.directives', []) feature.locations.sort(compareLocations); // todo: there is a bug here if spanning origin // deal with locations - let notes = feature.notes.length ? feature.notes[0].value : ""; - for (let j = 0; j < feature.locations.length; j += 1) { let location = feature.locations[j]; let featureObject = openVE[feature.id]; @@ -217,6 +226,9 @@ angular.module('ice.entry.directives', []) locations.push({start: location.genbankStart - 1, end: location.end - 1}); featureObject.end = location.end - 1; featureObject.locations = locations; + + // deal with feature notes + featureObject = convertNotes(feature, featureObject); } else { featureObject = { start: location.genbankStart - 1, @@ -225,9 +237,12 @@ angular.module('ice.entry.directives', []) forward: feature.strand === 1, type: feature.type, name: feature.name, - notes: notes, + notes: {}, annotationType: feature.type }; + + // deal with feature notes + featureObject = convertNotes(feature, featureObject); } openVE[featureObject.fid] = featureObject; @@ -285,7 +300,6 @@ angular.module('ice.entry.directives', []) } else { $scope.fetchingAnnotations = false; $scope.loaded(seqData); - console.log("annotations loaded", seqData); $rootScope.$emit("VectorEditorSequenceModel", {sequenceData: seqData}); } }, {start: start, limit: 10}, function (error) {