2a070e8a896e295089ac2110a33130f161925d14
[jalview.git] / src / jalview / io / vamsas / DatastoreItem.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.io.vamsas;
22
23 import jalview.bin.Cache;
24 import jalview.io.VamsasAppDatastore;
25 import jalview.util.MessageManager;
26
27 import java.util.Enumeration;
28 import java.util.Hashtable;
29 import java.util.IdentityHashMap;
30 import java.util.Vector;
31
32 import uk.ac.vamsas.client.IClientDocument;
33 import uk.ac.vamsas.client.Vobject;
34 import uk.ac.vamsas.client.VorbaId;
35 import uk.ac.vamsas.objects.core.Entry;
36 import uk.ac.vamsas.objects.core.Provenance;
37 import uk.ac.vamsas.objects.core.Seg;
38
39 /**
40  * Holds all the common machinery for binding objects to vamsas objects
41  * 
42  * @author JimP
43  * 
44  */
45 public abstract class DatastoreItem
46 {
47   /**
48    * 
49    */
50   Entry provEntry = null;
51
52   IClientDocument cdoc;
53
54   Hashtable vobj2jv;
55
56   IdentityHashMap jv2vobj;
57
58   boolean tojalview = false;
59
60   /**
61    * shared log instance
62    */
63   protected static org.apache.log4j.Logger log = org.apache.log4j.Logger
64           .getLogger(DatastoreItem.class);
65
66   /**
67    * note: this is taken verbatim from jalview.io.VamsasAppDatastore
68    * 
69    * @return the Vobject bound to Jalview datamodel object
70    */
71   protected Vobject getjv2vObj(Object jvobj)
72   {
73     if (jv2vobj.containsKey(jvobj))
74     {
75       return cdoc.getObject((VorbaId) jv2vobj.get(jvobj));
76     }
77     if (Cache.log.isDebugEnabled())
78     {
79       Cache.log.debug("Returning null VorbaID binding for jalview object "
80               + jvobj);
81     }
82     return null;
83   }
84
85   /**
86    * 
87    * @param vobj
88    * @return Jalview datamodel object bound to the vamsas document object
89    */
90   protected Object getvObj2jv(uk.ac.vamsas.client.Vobject vobj)
91   {
92     if (vobj2jv == null)
93       return null;
94     VorbaId id = vobj.getVorbaId();
95     if (id == null)
96     {
97       id = cdoc.registerObject(vobj);
98       Cache.log
99               .debug("Registering new object and returning null for getvObj2jv");
100       return null;
101     }
102     if (vobj2jv.containsKey(vobj.getVorbaId()))
103     {
104       return vobj2jv.get(vobj.getVorbaId());
105     }
106     return null;
107   }
108
109   /**
110    * note: this is taken verbatim from jalview.io.VamsasAppDatastore with added
111    * call to updateRegistryEntry
112    * 
113    * @param jvobj
114    * @param vobj
115    */
116   protected void bindjvvobj(Object jvobj, uk.ac.vamsas.client.Vobject vobj)
117   {
118     VorbaId id = vobj.getVorbaId();
119     if (id == null)
120     {
121       id = cdoc.registerObject(vobj);
122       if (id == null || vobj.getVorbaId() == null
123               || cdoc.getObject(id) != vobj)
124       {
125         Cache.log.error("Failed to get id for "
126                 + (vobj.isRegisterable() ? "registerable"
127                         : "unregisterable") + " object " + vobj);
128       }
129     }
130     if (vobj2jv.containsKey(vobj.getVorbaId())
131             && !(vobj2jv.get(vobj.getVorbaId())).equals(jvobj))
132     {
133       Cache.log.debug(
134               "Warning? Overwriting existing vamsas id binding for "
135                       + vobj.getVorbaId(), new Exception(MessageManager.getString("exception.overwriting_vamsas_id_binding")));
136     }
137     else if (jv2vobj.containsKey(jvobj)
138             && !((VorbaId) jv2vobj.get(jvobj)).equals(vobj.getVorbaId()))
139     {
140       Cache.log.debug(
141               "Warning? Overwriting existing jalview object binding for "
142                       + jvobj, new Exception(MessageManager.getString("exception.overwriting_jalview_id_binding")));
143     }
144     /*
145      * Cache.log.error("Attempt to make conflicting object binding! "+vobj+" id "
146      * +vobj.getVorbaId()+" already bound to "+getvObj2jv(vobj)+" and "+jvobj+"
147      * already bound to "+getjv2vObj(jvobj),new Exception("Excessive call to
148      * bindjvvobj")); }
149      */
150     // we just update the hash's regardless!
151     Cache.log.debug("Binding " + vobj.getVorbaId() + " to " + jvobj);
152     vobj2jv.put(vobj.getVorbaId(), jvobj);
153     // JBPNote - better implementing a hybrid invertible hash.
154     jv2vobj.put(jvobj, vobj.getVorbaId());
155     if (jvobj == this.jvobj || vobj == this.vobj)
156     {
157       updateRegistryEntry(jvobj, vobj);
158     }
159   }
160
161   /**
162    * update the vobj and jvobj references and the registry entry for this
163    * datastore object called by bindjvvobj and replacejvobjmapping
164    */
165   private void updateRegistryEntry(Object jvobj, Vobject vobj)
166   {
167     if (this.jvobj != null && this.vobj != null)
168     {
169       Cache.log.debug("updating dsobj registry. ("
170               + this.getClass().getName() + ")");
171     }
172     this.jvobj = jvobj;
173     this.vobj = vobj;
174     dsReg.registerDsObj(this);
175   }
176
177   /**
178    * replaces oldjvobject with newjvobject in the Jalview Object <> VorbaID
179    * binding tables note: originally taken verbatim from
180    * jalview.io.VamsasAppDatastore with added call to updateRegistryEntry
181    * 
182    * @param oldjvobject
183    * @param newjvobject
184    *          (may be null to forget the oldjvobject's document mapping)
185    * 
186    */
187   protected void replaceJvObjMapping(Object oldjvobject, Object newjvobject)
188   {
189     Object vobject = jv2vobj.remove(oldjvobject);
190     if (vobject == null)
191     {
192       throw new Error(MessageManager.formatMessage("error.implementation_error_old_jalview_object_not_bound", new String[]{oldjvobject.toString()}));
193     }
194     if (newjvobject != null)
195     {
196       jv2vobj.put(newjvobject, vobject);
197       vobj2jv.put(vobject, newjvobject);
198       updateRegistryEntry(newjvobject, vobj);
199     }
200   }
201
202   public DatastoreItem()
203   {
204     super();
205   }
206
207   public DatastoreItem(VamsasAppDatastore datastore)
208   {
209     this();
210     initDatastoreItem(datastore);
211     // TODO Auto-generated constructor stub
212   }
213
214   /**
215    * construct and initialise datastore object and retrieve object bound to
216    * vobj2 and validate it against boundType
217    * 
218    * @param datastore2
219    * @param vobj2
220    * @param boundType
221    */
222   public DatastoreItem(VamsasAppDatastore datastore2, Vobject vobj2,
223           Class boundType)
224   {
225     this(datastore2);
226     vobj = vobj2;
227     jvobj = getvObj2jv(vobj2);
228     tojalview = true;
229     if (jvobj != null && !(boundType.isAssignableFrom(jvobj.getClass())))
230     {
231       throw new Error(MessageManager.formatMessage("error.implementation_error_vamsas_doc_class_should_bind_to_type"
232                         , new String[]{vobj.getClass().toString(),boundType.toString(),jvobj.getClass().toString()}));
233     }
234     dsReg.registerDsObj(this);
235   }
236
237   /**
238    * construct and initialise datastore object and retrieve document object
239    * bound to Jalview object jvobj2 and validate it against boundType
240    * 
241    * @param datastore2
242    *          the datastore
243    * @param jvobj2
244    *          the jalview object
245    * @param boundToType
246    *          - the document object class that the bound object should be
247    *          assignable from
248    */
249   public DatastoreItem(VamsasAppDatastore datastore2, Object jvobj2,
250           Class boundToType)
251   {
252     this(datastore2);
253     jvobj = jvobj2;
254     tojalview = false;
255     vobj = getjv2vObj(jvobj);
256     if (vobj != null && !(boundToType.isAssignableFrom(vobj.getClass())))
257     {
258         throw new Error(MessageManager.formatMessage("error.implementation_error_vamsas_doc_class_should_bind_to_type"
259                         , new String[]{jvobj2.getClass().toString(),boundToType.toString(),vobj.getClass().toString()}));
260     }
261     dsReg.registerDsObj(this);
262   }
263
264   /**
265    * create a new vobj to be added to the document for the jalview object jvobj
266    * (jvobj!=null, vobj==null)
267    */
268   public abstract void addToDocument();
269
270   /**
271    * handle a conflict where both an existing vobj has been updated and a local
272    * jalview object has been updated. This method is only called from doSync,
273    * when an incoming update from the vamsas session conflicts with local
274    * modifications made by the Jalview user. (jvobj!=null, vobj!=null)
275    */
276   public abstract void conflict();
277
278   /**
279    * update an existing vobj in the document with the data and settings from
280    * jvobj (jvobj!=null, vobj!=null)
281    */
282   public abstract void updateToDoc();
283
284   /**
285    * update the local jalview object with the data from an existing vobj in the
286    * document (jvobj!=null, vobj!=null)
287    */
288   public abstract void updateFromDoc();
289
290   /**
291    * create a new local jvobj bound to the vobj in the document. (jvobj==null,
292    * vobj!=null)
293    */
294   public abstract void addFromDocument();
295
296   boolean addtodoc = false, conflicted = false, updated = false,
297           addfromdoc = false, success = false;
298
299   private boolean updatedtodoc;
300
301   private boolean updatedfromdoc;
302
303   /**
304    * Sync jalview to document. Enact addToDocument, conflict or update dependent
305    * on existence of a vobj bound to the local jvobj.
306    */
307   protected void doSync()
308   {
309     dsReg.registerDsObj(this);
310     if (vobj == null)
311     {
312       log.debug("adding new vobject to document.");
313       addtodoc = true;
314       addToDocument();
315     }
316     else
317     {
318       if (vobj.isUpdated())
319       {
320         log.debug("Handling update conflict for existing bound vobject.");
321         conflicted = true;
322         conflict();
323       }
324       else
325       {
326         log.debug("updating existing vobject in document.");
327         updatedtodoc = true;
328         updateToDoc();
329       }
330     }
331     // no exceptions were encountered...
332     success = true;
333   }
334
335   /**
336    * Update jalview from document. enact addFromDocument if no local jvobj
337    * exists, or update iff jvobj exists and the vobj.isUpdated() flag is set.
338    */
339   protected void doJvUpdate()
340   {
341     dsReg.registerDsObj(this);
342     if (jvobj == null)
343     {
344       log.debug("adding new vobject to Jalview from Document");
345       addfromdoc = true;
346       addFromDocument();
347     }
348     else
349     {
350       if (vobj.isUpdated())
351       {
352         log.debug("updating Jalview from existing bound vObject");
353         updatedfromdoc = true;
354         updateFromDoc();
355       }
356     }
357   }
358
359   VamsasAppDatastore datastore = null;
360
361   /**
362    * object in vamsas document
363    */
364   protected Vobject vobj = null;
365
366   /**
367    * local jalview object
368    */
369   protected Object jvobj = null;
370
371   protected DatastoreRegistry dsReg;
372
373   public void initDatastoreItem(VamsasAppDatastore ds)
374   {
375     datastore = ds;
376     dsReg = ds.getDatastoreRegisty();
377     initDatastoreItem(ds.getProvEntry(), ds.getClientDocument(),
378             ds.getVamsasObjectBinding(), ds.getJvObjectBinding());
379   }
380
381   private void initDatastoreItem(Entry provEntry, IClientDocument cdoc,
382           Hashtable vobj2jv, IdentityHashMap jv2vobj)
383   {
384     this.provEntry = provEntry;
385     this.cdoc = cdoc;
386     this.vobj2jv = vobj2jv;
387     this.jv2vobj = jv2vobj;
388   }
389
390   protected boolean isModifiable(String modifiable)
391   {
392     return modifiable == null; // TODO: USE VAMSAS LIBRARY OBJECT LOCK METHODS)
393   }
394
395   protected Vector getjv2vObjs(Vector alsq)
396   {
397     Vector vObjs = new Vector();
398     Enumeration elm = alsq.elements();
399     while (elm.hasMoreElements())
400     {
401       vObjs.addElement(getjv2vObj(elm.nextElement()));
402     }
403     return vObjs;
404   }
405
406   // utility functions
407   /**
408    * get start<end range of segment, adjusting for inclusivity flag and
409    * polarity.
410    * 
411    * @param visSeg
412    * @param ensureDirection
413    *          when true - always ensure start is less than end.
414    * @return int[] { start, end, direction} where direction==1 for range running
415    *         from end to start.
416    */
417   public int[] getSegRange(Seg visSeg, boolean ensureDirection)
418   {
419     boolean incl = visSeg.getInclusive();
420     // adjust for inclusive flag.
421     int pol = (visSeg.getStart() <= visSeg.getEnd()) ? 1 : -1; // polarity of
422     // region.
423     int start = visSeg.getStart() + (incl ? 0 : pol);
424     int end = visSeg.getEnd() + (incl ? 0 : -pol);
425     if (ensureDirection && pol == -1)
426     {
427       // jalview doesn't deal with inverted ranges, yet.
428       int t = end;
429       end = start;
430       start = t;
431     }
432     return new int[]
433     { start, end, pol < 0 ? 1 : 0 };
434   }
435
436   /**
437    * provenance bits
438    */
439   protected jalview.datamodel.Provenance getJalviewProvenance(
440           Provenance prov)
441   {
442     // TODO: fix App and Action entries and check use of provenance in jalview.
443     jalview.datamodel.Provenance jprov = new jalview.datamodel.Provenance();
444     for (int i = 0; i < prov.getEntryCount(); i++)
445     {
446       jprov.addEntry(prov.getEntry(i).getUser(), prov.getEntry(i)
447               .getAction(), prov.getEntry(i).getDate(), prov.getEntry(i)
448               .getId());
449     }
450
451     return jprov;
452   }
453
454   /**
455    * 
456    * @return default initial provenance list for a Jalview created vamsas
457    *         object.
458    */
459   Provenance dummyProvenance()
460   {
461     return dummyProvenance(null);
462   }
463
464   protected Entry dummyPEntry(String action)
465   {
466     Entry entry = new Entry();
467     entry.setApp(this.provEntry.getApp());
468     if (action != null)
469     {
470       entry.setAction(action);
471     }
472     else
473     {
474       entry.setAction("created.");
475     }
476     entry.setDate(new java.util.Date());
477     entry.setUser(this.provEntry.getUser());
478     return entry;
479   }
480
481   protected Provenance dummyProvenance(String action)
482   {
483     Provenance prov = new Provenance();
484     prov.addEntry(dummyPEntry(action));
485     return prov;
486   }
487
488   protected void addProvenance(Provenance p, String action)
489   {
490     p.addEntry(dummyPEntry(action));
491   }
492
493   /**
494    * @return true if jalview was being updated from the vamsas document
495    */
496   public boolean isTojalview()
497   {
498     return tojalview;
499   }
500
501   /**
502    * @return true if addToDocument() was called.
503    */
504   public boolean isAddtodoc()
505   {
506     return addtodoc;
507   }
508
509   /**
510    * @return true if conflict() was called
511    */
512   public boolean isConflicted()
513   {
514     return conflicted;
515   }
516
517   /**
518    * @return true if updateFromDoc() was called
519    */
520   public boolean isUpdatedFromDoc()
521   {
522     return updatedfromdoc;
523   }
524
525   /**
526    * @return true if updateToDoc() was called
527    */
528   public boolean isUpdatedToDoc()
529   {
530     return updatedtodoc;
531   }
532
533   /**
534    * @return true if addFromDocument() was called.
535    */
536   public boolean isAddfromdoc()
537   {
538     return addfromdoc;
539   }
540
541   /**
542    * @return true if object sync logic completed normally.
543    */
544   public boolean isSuccess()
545   {
546     return success;
547   }
548
549   /**
550    * @return the vobj
551    */
552   public Vobject getVobj()
553   {
554     return vobj;
555   }
556
557   /**
558    * @return the jvobj
559    */
560   public Object getJvobj()
561   {
562     return jvobj;
563   }
564
565   public boolean docWasUpdated()
566   {
567     return (this.addtodoc || this.updated) && this.success;
568   }
569
570   public boolean jvWasUpdated()
571   {
572     return (success); // TODO : Implement this properly!
573   }
574
575 }