Changeset 6085


Ignore:
Timestamp:
Aug 17, 2012, 2:04:45 PM (11 years ago)
Author:
Nicklas Nordborg
Message:

References #1707: Make it possible for a derived bioassay to have multiple physical bioassays as parents

The core should now make sure that parent physical bioassays are synchronized on child items if parents are removed or added to a derived bioassay. There might be some optimizations to be done when multiple items are updated in the same transaction, but I'll have to fix the batch importer before this can be tested.

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/core/net/sf/basedb/core/DerivedBioAssay.java

    r6082 r6085  
    2424import java.util.ArrayList;
    2525import java.util.Collection;
     26import java.util.Collections;
    2627import java.util.Date;
    2728import java.util.HashSet;
     29import java.util.IdentityHashMap;
     30import java.util.Iterator;
     31import java.util.LinkedList;
     32import java.util.List;
     33import java.util.Map;
    2834import java.util.Set;
    2935
     
    437443  }
    438444 
     445  public void testFixChildren()
     446  {
     447    FixDerivedBioAssayParentsRecursivelyAction.get(getDbControl()).add(getData());
     448  }
     449 
    439450  /**
    440451    Add a physical bioassay as a parent to this derived bioassay.
     
    452463    if (bioAssay == null) throw new InvalidUseOfNullException("bioAssay");
    453464    bioAssay.checkPermission(Permission.USE);
    454     getData().getPhysicalBioAssays().add(bioAssay.getData());
     465    boolean wasAdded = getData().getPhysicalBioAssays().add(bioAssay.getData());
     466    if (wasAdded)
     467    {
     468      FixDerivedBioAssayParentsRecursivelyAction.get(getDbControl()).add(getData());
     469    }
    455470  }
    456471 
     
    470485    if (!isRoot()) throw new PermissionDeniedException("Can't remove physical bioassay from non-root derived bioassay");
    471486    if (bioAssay == null) throw new InvalidUseOfNullException("bioAssay");
    472     getData().getPhysicalBioAssays().remove(bioAssay.getData());
     487    boolean wasRemoved = getData().getPhysicalBioAssays().remove(bioAssay.getData());
     488    if (wasRemoved)
     489    {
     490      FixDerivedBioAssayParentsRecursivelyAction.get(getDbControl()).add(getData());
     491    }
    473492  }
    474493 
     
    521540    if (this.equals(bioAssay)) throw new InvalidDataException("Can't add self as parent: " + this);
    522541    bioAssay.checkPermission(Permission.USE);
    523     getData().getParents().add(bioAssay.getData());
    524     getData().getPhysicalBioAssays().addAll(bioAssay.getData().getPhysicalBioAssays());
     542    boolean wasAdded = getData().getParents().add(bioAssay.getData());
     543    if (wasAdded)
     544    {
     545      FixDerivedBioAssayParentsRecursivelyAction.get(getDbControl()).add(getData());
     546    }
    525547  }
    526548
     
    539561    if (isRoot()) throw new PermissionDeniedException("Root derived bioassays can't have parent derived bioassays");
    540562    if (bioAssay == null) throw new InvalidUseOfNullException("bioAssay");
    541     getData().getParents().remove(bioAssay.getData());
     563    boolean wasRemoved = getData().getParents().remove(bioAssay.getData());
     564    if (wasRemoved)
     565    {
     566      FixDerivedBioAssayParentsRecursivelyAction.get(getDbControl()).add(getData());
     567    }
    542568  }
    543569
     
    773799  }
    774800
     801  /**
     802    Whenever we modify the parent items to a derived bioassay,
     803    we must make sure that the physical bioassays for all
     804    child items are synchronized with the same changes.
     805    This is a bit complicated due to the fact that a single transaction
     806    may affect multiple derived bioassays at different levels in
     807    the parent-child tree. The fix must be made top-down and
     808    must handle accidentally created circular references.
     809   
     810    Whenever a change is deteced a single instance of this
     811    class is associated with the DbControl. All changed
     812    derived bioassays are collected with the {@link #add(DerivedBioAssayData)}
     813    method. The actual fix is done as a last step before committing
     814    the transaction {@link #onBeforeCommit()}.
     815  */
     816  static class FixDerivedBioAssayParentsRecursivelyAction
     817    implements TransactionalAction
     818  {
     819
     820    private static Map<DbControl, FixDerivedBioAssayParentsRecursivelyAction> cache =
     821      Collections.synchronizedMap(new IdentityHashMap<DbControl, DerivedBioAssay.FixDerivedBioAssayParentsRecursivelyAction>());
     822   
     823    /**
     824      Get an instance tht handles the fixes for the given DbControl.
     825      If no instance exists yet, a new one is created.
     826    */
     827    static FixDerivedBioAssayParentsRecursivelyAction get(DbControl dc)
     828    {
     829      FixDerivedBioAssayParentsRecursivelyAction dbt = cache.get(dc);
     830      if (dbt == null)
     831      {
     832        dbt = new FixDerivedBioAssayParentsRecursivelyAction(dc);
     833        cache.put(dc, dbt);
     834      }
     835      return dbt;
     836    }
     837   
     838    private final DbControl dc;
     839    private final Set<DerivedBioAssayData> allModified;
     840   
     841    private FixDerivedBioAssayParentsRecursivelyAction(DbControl dc)
     842    {
     843      this.dc = dc;
     844      this.allModified = new HashSet<DerivedBioAssayData>();
     845      dc.addTransactionalAction(this);
     846    }
     847   
     848    /**
     849      Register the given derived bioassay as one that has had it's
     850      parent items changed.
     851    */
     852    void add(DerivedBioAssayData dba)
     853    {
     854      allModified.add(dba);
     855    }
     856   
     857    /*
     858      From the TransactionalAction interface
     859      --------------------------------------
     860    */
     861    @Override
     862    public void onBeforeCommit()
     863    {
     864     
     865      Set<DerivedBioAssayData> allToFix = new HashSet<DerivedBioAssayData>();
     866      List<DerivedBioAssayData> moreToFix = new LinkedList<DerivedBioAssayData>();
     867     
     868      // First step is to find all child items recursively
     869      // All non-root bioassays need to be fixed and all children may also have to be fixed
     870      for (DerivedBioAssayData dba : allModified)
     871      {
     872        if (!dba.isRoot()) allToFix.add(dba);
     873        if (dba.getChildren() != null)
     874        {
     875          moreToFix.addAll(dba.getChildren());
     876        }
     877      }
     878     
     879      // Check each child item and then also grandchildren
     880      while (moreToFix.size() > 0)
     881      {
     882        DerivedBioAssayData dba = moreToFix.remove(0);
     883        if (allToFix.add(dba) && dba.getChildren() != null)
     884        {
     885          moreToFix.addAll(dba.getChildren());
     886        }
     887      }
     888      // We now have all items that must be fixed...
     889
     890      //System.out.println("allToFix: " + allToFix);
     891
     892      // We don't know the actual order that items can be fixed, so
     893      // we have to keep trying until the set is empty
     894      while (allToFix.size() > 0)
     895      {
     896        int numFixed = 0;
     897        Iterator<DerivedBioAssayData> it = allToFix.iterator();
     898        while (it.hasNext())
     899        {
     900          DerivedBioAssayData dba = it.next();
     901          // Collect all physical bioassays from all parents in this set
     902          Set<PhysicalBioAssayData> allPhysicalBioAssays = new HashSet<PhysicalBioAssayData>();
     903          boolean canFix = true;
     904         
     905          // For each item that should be fixed, load all parent items...
     906          for (DerivedBioAssayData parent : dba.getParents())
     907          {
     908            if (allToFix.contains(parent))
     909            {
     910              // If at least one parent has not yet been fixed, we can't fix this item now
     911              canFix = false;
     912              break;
     913            }
     914            allPhysicalBioAssays.addAll(parent.getPhysicalBioAssays());
     915          }
     916         
     917          if (canFix)
     918          {
     919            //System.out.println("Fixing: " + dba.getName() + "; " + allPhysicalBioAssays);
     920            it.remove();
     921            dba.getPhysicalBioAssays().clear();
     922            dba.getPhysicalBioAssays().addAll(allPhysicalBioAssays);
     923            numFixed++;
     924          }
     925          else
     926          {
     927            //System.out.println("Can't fix " + dba.getName() + " yet, since it has unfixed parents");
     928          }
     929        }
     930        // If we couldn't fix any item in the last iteration, but there are more
     931        // items to fix, throw an exception since this is an indication of circular
     932        // reference
     933        if (numFixed == 0 && allToFix.size() > 0)
     934        {
     935          // Hmm... a circular reference prevents us from fixing
     936          throw new InvalidDataException("Circular reference between derived bioassays: " + allToFix);
     937        }
     938      }
     939     
     940    }
     941
     942    @Override
     943    public void onAfterCommit()
     944    {
     945      cache.remove(dc);
     946    }
     947
     948    @Override
     949    public void onRollback()
     950    {
     951      cache.remove(dc);
     952    }
     953    // ---------------------------------------
     954   
     955  }
    775956}
  • trunk/src/core/net/sf/basedb/core/data/DerivedBioAssayData.java

    r6082 r6085  
    157157    @hibernate.collection-many-to-many column="`derivedbioassay_id`" class="net.sf.basedb.core.data.DerivedBioAssayData"
    158158  */
    159   Set<DerivedBioAssayData> getChildren()
     159  public Set<DerivedBioAssayData> getChildren()
    160160  {
    161161    return children;
  • trunk/www/views/derivedbioassays/edit_bioassay.jsp

    r6082 r6085  
    403403    }
    404404   
    405     function selectPhysicalBioAssayOnClick()
    406     {
    407       var frm = document.forms['bioAssay'];
    408       var url = '../physicalbioassays/index.jsp?ID=<%=ID%>&cmd=UpdateContext&mode=selectone';
    409       url += '&callback=setPhysicalBioAssayCallback&resetTemporary=1';
    410       url += ItemSubtype.createRelatedFilter('bioAssay', 'PHYSICALBIOASSAY');
    411       if (frm.physicalbioassay_id.selectedIndex >= 0)
    412       {
    413         var id = Math.abs(parseInt(frm.physicalbioassay_id[frm.physicalbioassay_id.selectedIndex].value));       
    414         url += '&item_id='+id;
    415       }
    416       Main.openPopup(url, 'SelectPhysicalBioAssay', 1050, 700);
    417     }
    418     function setPhysicalBioAssayCallback(id, name)
    419     {
    420       var frm = document.forms['bioAssay'];
    421       var list = frm.physicalbioassay_id;
    422       if (list.length < 1 || list[0].value == '0') // >
    423       {
    424         Forms.addListOption(list, 0, new Option());
    425       }
    426       list[0].value = id;
    427       list[0].text = name;
    428       list.selectedIndex = 0;
    429       physicalBioAssayOnChange();
    430     }
    431405    function physicalBioAssayOnChange()
    432406    {
     
    435409    }
    436410   
    437     function selectParentBioAssayOnClick()
    438     {
    439       var frm = document.forms['bioAssay'];
    440       var url = 'index.jsp?ID=<%=ID%>&cmd=UpdateContext&mode=selectone';
    441       url += '&callback=setParentBioAssayCallback&resetTemporary=1';
    442       url += ItemSubtype.createRelatedFilter('bioAssay', 'DERIVEDBIOASSAY');
    443       if (frm.parents.selectedIndex >= 0)
    444       {
    445         var id = Math.abs(parseInt(frm.parents[frm.parents.selectedIndex].value));       
    446         url += '&item_id='+id;
    447       }
    448       Main.openPopup(url, 'SelectParentBioAssay', 1050, 700);
    449     }
    450     function setParentBioAssayCallback(id, name)
    451     {
    452       var frm = document.forms['bioAssay'];
    453       var list = frm.parents;
    454       if (list.length < 1 || list[0].value == '0') // >
    455       {
    456         Forms.addListOption(list, 0, new Option());
    457       }
    458       list[0].value = id;
    459       list[0].text = name;
    460       list.selectedIndex = 0;
    461       parentBioAssayOnChange();
    462     }
    463411    function parentBioAssayOnChange()
    464412    {
     
    536484    }
    537485
     486    function removePhysicalBioAssayOnClick()
     487    {
     488      Link.removeSelected(document.forms['bioAssay'].physicalBioAssays);
     489      parentsChanged = true;
     490    }
     491
     492   
    538493    function addParentOnClick()
    539494    {
     
    553508      parentsChanged = true;
    554509    }
     510
     511    function removeParentOnClick()
     512    {
     513      Link.removeSelected(document.forms['bioAssay'].parents);
     514      parentsChanged = true;
     515    }
     516
    555517   
    556518    function init()
Note: See TracChangeset for help on using the changeset viewer.