Changeset 6768


Ignore:
Timestamp:
Mar 10, 2015, 1:04:37 PM (7 years ago)
Author:
Nicklas Nordborg
Message:

References #1325: Lists of items

Added functionality for storing the active filter used to create a list. The functionality for re-syncing the list with the filter at a later time is still rudimentary but will be improved...

Location:
trunk
Files:
2 added
7 edited

Legend:

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

    r6756 r6768  
    3939import net.sf.basedb.core.query.SqlQuery;
    4040import net.sf.basedb.core.data.ContextData;
     41import net.sf.basedb.core.data.ItemListSyncFilterData;
    4142import net.sf.basedb.core.data.PropertyFilterData;
    4243import net.sf.basedb.core.data.UserData;
     
    176177  private int itemId;
    177178  private Set<Integer> selected;
     179  private boolean noPaging = false;
    178180  private int rowsPerPage;
    179181  private int page;
     
    236238    this.sortDirection = SortDirection.fromValue(context.getSortDirection());
    237239    this.include = Include.fromInt(context.getInclude());
    238     for (Map.Entry<String, PropertyFilterData> entry : context.getPropertyFilters().entrySet())
    239     {
    240       // The property can string can actually be <property>[filterrow]
    241       // So we need to split and parse in that case
    242       // properties without [filterrow] belong to row 0.
    243       // The largest value for filterrow becomes the 'numFilterRows' value
    244       String property = entry.getKey();
    245       int rowIndex = 0;
    246       if (property.endsWith("]"))
    247       {
    248         int split = property.lastIndexOf('[');
    249         rowIndex = Integer.parseInt(property.substring(split+1, property.length()-1));
    250         property = property.substring(0, split);
    251       }
    252       if (rowIndex >= numFilterRows) numFilterRows = rowIndex+1;
    253       setPropertyFilter(new PropertyFilter(property, rowIndex, entry.getValue()));
    254     }
     240    initPropertyFilters(context.getPropertyFilters());
    255241    if (context.getSettings().size() != 0)
    256242    {
    257243      settings = new HashMap<String, String>(context.getSettings());
    258244    }
     245  }
     246 
     247  /**
     248    Initialize the context from an item list synchronization filter.
     249    @since 3.5
     250  */
     251  ItemContext(Item itemType, ItemListSyncFilterData syncFilter)
     252  {
     253    this.itemType = itemType;
     254    this.subContext = null;
     255    this.name = null;
     256    this.include = Include.fromInt(syncFilter.getInclude());
     257    this.noPaging = true;
     258    initPropertyFilters(syncFilter.getPropertyFilters());
    259259  }
    260260 
     
    283283    if (propertyFilters != null)
    284284    {
    285       Map<String, PropertyFilterData> filterData = context.getPropertyFilters();
    286       for (PropertyFilterPair filterPair : propertyFilters.values())
    287       {
    288         PropertyFilter filter = filterPair.primary;
    289         if (filter != null && !filter.isTemporary())
    290         {
    291           // Only save primary filters
    292           // For filters on filter rows > 0 we must combine <property>[filterrow]
    293           int rowIndex = filter.getRowIndex();
    294           filterData.put(filter.getProperty() + (rowIndex > 0 ? "[" + rowIndex + "]" : ""), filter.getData());
    295         }
    296       }
     285      storePropertyFilters(context.getPropertyFilters());
    297286    }
    298287    if (settings != null)
     
    303292  }
    304293
     294  /**
     295    Create a new item list synchronization filter from this context. Basically the
     296    same as {@ink #getData(String, UserData, ClientData, boolean)} but only
     297    keep filter-related settings.
     298    @since 3.5
     299  */
     300  ItemListSyncFilterData asItemListSyncFilter()
     301  {
     302    ItemListSyncFilterData syncFilter = new ItemListSyncFilterData();
     303    if (include != null) syncFilter.setInclude(Include.toInt(include));
     304    if (propertyFilters != null)
     305    {
     306      storePropertyFilters(syncFilter.getPropertyFilters());
     307    }
     308    return syncFilter;
     309  }
     310 
     311  /**
     312    Store the current property filters and save them in the give filterData
     313    map.
     314  */
     315  private void storePropertyFilters(Map<String, PropertyFilterData> filterData)
     316  {
     317    for (PropertyFilterPair filterPair : propertyFilters.values())
     318    {
     319      PropertyFilter filter = filterPair.primary;
     320      if (filter != null && !filter.isTemporary())
     321      {
     322        // Only save primary filters
     323        // For filters on filter rows > 0 we must combine <property>[filterrow]
     324        int rowIndex = filter.getRowIndex();
     325        filterData.put(filter.getProperty() + (rowIndex > 0 ? "[" + rowIndex + "]" : ""), filter.getData());
     326      }
     327    }
     328  }
     329 
     330  /**
     331    Initialize the property filters by loading them from the given map. Eg.
     332    revese of {@link #storePropertyFilters(Map).
     333  */
     334  private void initPropertyFilters(Map<String, PropertyFilterData> filterData)
     335  {
     336    for (Map.Entry<String, PropertyFilterData> entry : filterData.entrySet())
     337    {
     338      // The property can string can actually be <property>[filterrow]
     339      // So we need to split and parse in that case
     340      // properties without [filterrow] belong to row 0.
     341      // The largest value for filterrow becomes the 'numFilterRows' value
     342      String property = entry.getKey();
     343      int rowIndex = 0;
     344      if (property.endsWith("]"))
     345      {
     346        int split = property.lastIndexOf('[');
     347        rowIndex = Integer.parseInt(property.substring(split+1, property.length()-1));
     348        property = property.substring(0, split);
     349      }
     350      if (rowIndex >= numFilterRows) numFilterRows = rowIndex+1;
     351      setPropertyFilter(new PropertyFilter(property, rowIndex, entry.getValue()));
     352    }
     353  }
     354 
    305355  /**
    306356    Get the type of item this object is storing context for.
     
    14121462  {
    14131463    // Add page information
    1414     query.setReturnTotalCount(true);
    1415     query.setFirstResult(getPage() * getRowsPerPage());
    1416     query.setMaxResults(getRowsPerPage());
     1464    if (!noPaging)
     1465    {
     1466      query.setReturnTotalCount(true);
     1467      query.setFirstResult(getPage() * getRowsPerPage());
     1468      query.setMaxResults(getRowsPerPage());
     1469    }
    14171470
    14181471    // Temporary storage for all associations that must be left joined
  • trunk/src/core/net/sf/basedb/core/ItemList.java

    r6751 r6768  
    2323
    2424import java.util.Collection;
     25import java.util.Date;
    2526import java.util.HashMap;
    2627import java.util.HashSet;
     
    3334
    3435import net.sf.basedb.core.query.Hql;
     36import net.sf.basedb.core.data.ItemListSyncFilterData;
    3537import net.sf.basedb.core.data.ItemListData;
    3638
     
    888890    return numRemoved;
    889891  }
    890 
     892 
     893  /**
     894    Is this item list backed by a synchronization filter or not?
     895  */
     896  public boolean hasSyncFilter()
     897  {
     898    return getData().getSyncFilter() != null;
     899  }
     900
     901  /**
     902    Get the last sync date+time. Note that manual changes may have been made to
     903    the list after this date.
     904  */
     905  public Date getSyncDate()
     906  {
     907    return DateUtil.copy(getData().getSyncDate());
     908  }
     909 
     910  /**
     911    Get information about the synchronization filter that is backing
     912    this list.
     913    @return A SyncFilter object or null
     914  */
     915  public SyncFilter getSyncFilter()
     916  {
     917    ItemListSyncFilterData syncFilter = getData().getSyncFilter();
     918    if (syncFilter == null) return null;
     919    return new SyncFilter(getMemberType(), syncFilter);
     920  }
     921 
     922  /**
     923    Apply a synchronization filter to this list. If this list already has
     924    synchronization filter it is replaced. The filter is stored
     925   
     926    @param dc
     927    @param syncContext An ItemContext providing the filter
     928    @param syncOptions Options for how to sync with existing members of this list
     929    @return The number of members in the list after applying the filter
     930  */
     931  public int applySyncFilter(DbControl dc, ItemContext syncContext, SynchronizeOption syncOptions)
     932  {
     933    checkPermission(Permission.WRITE);
     934    checkMemberType(syncContext.getItemType());
     935   
     936    ItemListData data = getData();
     937    org.hibernate.Session session = dc.getHibernateSession();
     938   
     939    // Switch the filter
     940    ItemListSyncFilterData oldFilter = data.getSyncFilter();
     941    if (oldFilter != null) session.delete(oldFilter);
     942    ItemListSyncFilterData newFilter = syncContext.asItemListSyncFilter();
     943    session.save(newFilter);
     944    data.setSyncFilter(newFilter);
     945   
     946    return resyncFilter(dc, syncOptions);
     947  }
     948 
     949  /**
     950    Re-sync with the current synchronization filter. If this list
     951    doesn't use a synchronization filter, no change is made.
     952    @return The number of members in the list after applying the filter
     953  */
     954  public int resyncFilter(DbControl dc, SynchronizeOption syncOptions)
     955  {
     956    checkPermission(Permission.WRITE);
     957    ItemListSyncFilterData syncFilter = getData().getSyncFilter();
     958    if (syncFilter == null) return getSize();
     959   
     960    // Create filter query
     961    ItemQuery<? extends Listable> query = getAllItems();
     962    ItemContext syncContext = new ItemContext(getMemberType(), syncFilter);
     963    syncContext.configureQuery(dc, query, true);
     964   
     965    // Load all matching items and save their id:s
     966    Set<Integer> matching = new HashSet<Integer>();
     967    Iterator<? extends Listable> it = query.iterate(dc);
     968    while (it.hasNext())
     969    {
     970      matching.add(it.next().getId());
     971    }
     972 
     973    Set<Integer> members = getData().getMembers();
     974    syncOptions.synchronize(members, matching);
     975   
     976    getData().setSyncDate(new Date());
     977    getData().setSize(members.size());
     978    return members.size();
     979  }
     980 
     981 
     982  /**
     983    Options for synchronizing an existing item list with a current settings
     984    in an {@link ItemContext}. See {@link ItemList#applyFilterContext(DbControl, ItemContext, SynchronizeOption)}.
     985  */
     986  public enum SynchronizeOption
     987  {
     988    /**
     989      Synchronize the members so that it contains all of the items matching the filter
     990      and no items not matching the filter. This options can both remove and add members.
     991    */
     992    FULL
     993    {
     994      @Override
     995      void synchronize(Set<Integer> existing, Set<Integer> matching)
     996      {
     997        existing.clear();
     998        existing.addAll(matching);
     999      }
     1000    },
     1001   
     1002    /**
     1003      Synchronize the members by adding new items matching the filter.
     1004      This option will not remove existing members that doesn't match the filter.
     1005    */
     1006    ADD_ONLY
     1007    {
     1008      @Override
     1009      void synchronize(Set<Integer> existing, Set<Integer> matching)
     1010      {
     1011        existing.addAll(matching);
     1012      }
     1013    },
     1014   
     1015    /**
     1016      Synchronize the members by removing items that doesn't match the filter.
     1017      This option will not add items that matches the filter but are not found
     1018      in the list.
     1019    */
     1020    REMOVE_ONLY
     1021    {
     1022      @Override
     1023      void synchronize(Set<Integer> existing, Set<Integer> matching)
     1024      {
     1025        existing.retainAll(matching);
     1026      }
     1027    };
     1028   
     1029    /**
     1030      Synchronize the existing members with the items matching the
     1031      current filter context. The synchonrization should modify
     1032      the 'existing' set.
     1033      @param existing A set containing the existing members of the item list
     1034      @param matching A set containing the items matching the sync filter
     1035    */
     1036    abstract void synchronize(Set<Integer> existing, Set<Integer> matching);
     1037  }
     1038 
    8911039}
  • trunk/src/core/net/sf/basedb/core/data/ItemListData.java

    r6751 r6768  
    2222package net.sf.basedb.core.data;
    2323
     24import java.util.Date;
    2425import java.util.HashSet;
    2526import java.util.Set;
     
    139140    this.members = members;
    140141  }
     142
     143  private Date syncDate;
     144  /**
     145    Get the date and time the list members was last synchronized
     146    by the filter context.
     147    @hibernate.property column="`sync_date`" type="timestamp" not-null="false"
     148  */
     149  public Date getSyncDate()
     150  {
     151    return syncDate;
     152  }
     153  public void setSyncDate(Date syncDate)
     154  {
     155    this.syncDate = syncDate;
     156  }
     157 
     158  private ItemListSyncFilterData syncFilter;
     159  /**
     160    Get the filter context that was used to create this item list. This can
     161    be used to update members in the item list.
     162    @hibernate.many-to-one column="`syncfilter_id`" not-null="false" outer-join="false"
     163  */
     164  public ItemListSyncFilterData getSyncFilter()
     165  {
     166    return syncFilter;
     167  }
     168  public void setSyncFilter(ItemListSyncFilterData syncFilter)
     169  {
     170    this.syncFilter = syncFilter;
     171  }
    141172 
    142173}
  • trunk/www/views/itemlists/edit_list.jsp

    r6753 r6768  
    230230            <input type="radio" name="source" id="sourceAll" value="all" checked
    231231              ><label for="sourceAll">All pages</label><br>
     232              <input type="checkbox" name="syncFilter" id="syncFilter" value="1" checked style="margin-left:2em;"
     233                ><label for="syncFilter">Remeber current filter for future re-sync</label>
    232234          </td>
    233235          <td></td>
  • trunk/www/views/itemlists/index.jsp

    r6753 r6768  
    189189        {
    190190          String subContext = Values.getString(request.getParameter("subContext"), "");
    191          
    192           ItemQuery<? extends Listable> query =
    193             (ItemQuery<? extends Listable>)sc.getCurrentContext(list.getMemberType(), subContext).getQuery();
     191          boolean useSyncFilter = false;
     192          ItemContext filterContext = sc.getCurrentContext(list.getMemberType(), subContext);
     193          ItemQuery<? extends Listable> query = (ItemQuery<? extends Listable>)filterContext.getQuery();
    194194          if ("all".equals(source))
    195195          {
     196            useSyncFilter = Values.getBoolean(request.getParameter("syncFilter"));
    196197            query.setFirstResult(0);
    197198            query.setMaxResults(-1);
     
    212213          // else -- no modifications to the query mean that we only get the current page
    213214         
    214           int numAdded = list.add(query.iterate(dc));
     215          int numAdded = useSyncFilter ? list.applySyncFilter(dc, filterContext, ItemList.SynchronizeOption.ADD_ONLY) : list.add(query.iterate(dc));
    215216        }
    216217       
     
    233234      cc.removeObject("item");
    234235    }
     236  }
     237  else if ("ReSyncFilter".equals(cmd))
     238  {
     239    dc = sc.newDbControl();
     240    ItemList list = ItemList.getById(dc, Values.getInt(itemId));
     241    list.resyncFilter(dc, ItemList.SynchronizeOption.FULL);
     242    dc.commit();
     243    forward = viewPage;
    235244  }
    236245  else if ("DeleteItem".equals(cmd))
  • trunk/www/views/itemlists/lists.js

    r6755 r6768  
    5959          Doc.element('lblSelected').innerHTML += ' ['+selectedItemsInParentForm.length+']';
    6060        }
     61        Events.addEventHandler('sourceSelected', 'click', lists.sourceOnChange);
     62        Events.addEventHandler('sourcePage', 'click', lists.sourceOnChange);
     63        Events.addEventHandler('sourceAll', 'click', lists.sourceOnChange);
     64        lists.sourceOnChange();
    6165      }
    6266     
     
    195199  }
    196200 
     201  lists.sourceOnChange = function()
     202  {
     203    var frm = document.forms['list'];
     204    frm.syncFilter.disabled = !Doc.element('sourceAll').checked;
     205  }
     206 
    197207  return lists;
    198208}();
  • trunk/www/views/itemlists/view_list.jsp

    r6755 r6768  
    3535  import="net.sf.basedb.core.Permission"
    3636  import="net.sf.basedb.core.ItemList"
     37  import="net.sf.basedb.core.SyncFilter"
    3738  import="net.sf.basedb.core.MultiPermissions"
    3839  import="net.sf.basedb.core.User"
     
    4950  import="net.sf.basedb.clients.web.util.HTML"
    5051  import="net.sf.basedb.util.Values"
     52  import="net.sf.basedb.util.formatter.Formatter"
     53  import="net.sf.basedb.clients.web.formatter.FormatterFactory"
    5154  import="net.sf.basedb.clients.web.extensions.ExtensionsControl"
    5255  import="net.sf.basedb.clients.web.extensions.JspContext"
     
    5760  import="java.util.Map"
    5861  import="java.util.Set"
     62  import="java.util.Date"
    5963%>
    6064<%@ taglib prefix="base" uri="/WEB-INF/base.tld" %>
     
    9195  final boolean deletePermanentlyPermission = deletePermission && !isUsed;
    9296  final boolean isOwner = list.isOwner();
     97  Formatter<Date> timeFormatter = FormatterFactory.getDateTimeFormatter(sc);
    9398  JspContext jspContext = ExtensionsControl.createContext(dc, pageContext, guiContext, list);
    9499  ExtensionsInvoker invoker = ToolbarUtil.useExtensions(jspContext);
     
    245250            %>
    246251            (<base:propertyvalue item="<%=list%>" property="itemSubtype" nulltext="<i>any</i>"/>)
    247            
    248252            <%
    249253          }
     
    263267      </tr>
    264268      <tr>
     269        <th>Sync filter</th>
     270        <td>
     271          <%
     272          if (list.hasSyncFilter())
     273          {
     274            SyncFilter syncFilter = list.getSyncFilter();
     275            StringBuilder sb = new StringBuilder();
     276            for (int filterRow = 0; filterRow < syncFilter.getFilterRows(); filterRow++)
     277            {
     278              sb.append(filterRow > 0 ? "OR " : "");
     279              sb.append(Values.getString(syncFilter.getPropertyFilters(filterRow), " AND ", true));
     280              sb.append("\n");
     281            }
     282            %>
     283            Yes (last sync: <%=timeFormatter.format(list.getSyncDate()) %>) [<a href="index.jsp?ID=<%=ID%>&item_id=<%=itemId%>&cmd=ReSyncFilter">Re-sync</a>]<br>
     284            <pre><%=sb.toString()%></pre>
     285            <%
     286          }
     287          else
     288          {
     289            %>
     290            No
     291            <%
     292          }
     293          %>
     294        </td>
     295      </tr>
     296      <tr>
    265297        <th>External ID</th>
    266298        <td><%=HTML.encodeTags(list.getExternalId())%></td>
Note: See TracChangeset for help on using the changeset viewer.