Call for:
TypeRel cleaning and inheritance support

Description: 
========================================

Currently the typerel builder's getAllowedRelations function does not
take into account the new 'inheritance' features of MMBase. 

While exploring how difficult it would be to add support for this in
TypeRel, I ran into the other issue, being that the current typerel
implementation got a little messy. We count for example 3 seperate
caches in it, and a bunch of private methods which try to keep those
in sync with the actual situation.

Several functions of TypeRel also do database queries sometimes if
that seems to be necessary to give the right result
(getAllowedRelations, reldefCorrect), and the result is then stored in
the hashtables to avoid this next time.

My proposal it to rewrite this drasticly. Throw away all those
hashtable caches (one is lru), and replace it with one, which simply
should contain every TypeRel node. FurtherMore it should contain
'virtual' typerel nodes which reflect relations only possible because
of inheritance.

All public functions then can be rewritten, using only this
cache. This cache is implemented as a Set of MMObjectNodes (since most
functions need to return Enumerations of MMObjectNodes). To make such
an implementation efficient, to MMObjectNode and MMObjectBuilder must
be added hashCode and equals functions (e.g. a 'VirtualTypeRelNode'
must be equal to an actual typerel MMObjectNode if they reprsent the
same relation type). Checking if a relation is possible can then be
implementated completely straightforward, just by checking if the Set
contains the right node. Searching with a source can be done with a
subSet operation.

Impact
========================================

Typically this cache should not become very large. VPRO (perhaps a
good estimation of the upper-limit) has 676 typerel entries in
database now. So, after this hack there will be about that much
MMObjectNodes always in memory, which seems acceptable to me (I would
not even dare to estimate how much is in memory in the _current_
implementation)

Inheritance could make this number considerably larger though. If for
example vpro (having about 200 builders) would like to define object
-> object (related) then it would cost them 200 * 200 = 40000
MMObjectNodes in this cache. Still affordable, but I'd say they simply
should not do such a thing, and in practice it should become only
little bigger than 676.

I estimate that this implementation would perform about the same as
the original one, though it is not yet really tweaked for
performance. It is garanteed that no database actions occur (until
someone updates, deletes or adds a typerel node).


Reason
========================================

I'd like to add this code to the 1.6 branch (and also 1.7), because
inheritance is part of 1.6, so every change to make it work better is
welcome. The media-project, which heavily uses mmbase-inheritance will
profit, and can easily remain to be 1.6 compatibable.

It is little more than a bugfix so I ask for a Vote, but more
importantly, someone to test/review it.



Remarks 
========================================

- Code which looks in the typerel table itself, like the jsp-editors,
  will still fail to list all possible relations. 

- Perhaps also in BasicNodeManager.getAllowedRelations must change
  something to get it right complete there too (i see something with
  thisOType there)

- The getAllowedRelation functions of TypeRel return Enumeration. In
  1.7 it would perhaps be a nice idea to let them return SortedSets or
  Lists in stead.

- Perhaps also must be thought of a way to request the allowed
  relations with inheritance partially switch off.  if
  e.g. object->object (related) is allowed, then in editors you
  perhaps only want to see all related 'objects'. and not
  differentiated to every object type too.  It would perhaps be easy
  to implement, because the typerels in the resulting list which are
  'automaticly' added because of inheritance are instanceof
  VirtualTypeRelNode, while the 'real' ones are not. So perhaps a
  function isVirtual() on RelationManager, or something like that?

- perhaps the Set of all TypeRels can be made more efficient by adding also
  VirtualTypeRelNodes for the relation destination->source (if dir = 2)?


START OF CALL: Sunday 16 february 2003

END OF CALL: Wednesday 19 february 2003


[_] +1 (YEA)

[_] +0 (ABSTAIN )

[_] -1 (NAY), because :

[_] VETO, because:


-- 
mihxil'  Michiel Meeuwissen 
Mediapark C101 Hilversum  
+31 (0)35 6772979
[]() 
Index: MMBase.java
===================================================================
RCS file: /usr/local/cvs/mmbase/module/core/MMBase.java,v
retrieving revision 1.79.2.2
diff -r1.79.2.2 MMBase.java
416a417,418
>         TypeRel.readCache();
> 
1025d1026
< 
1170c1171
<         MMObjectBuilder bul=getMMObject(builder);
---
>         MMObjectBuilder bul = getMMObject(builder);
/*

This software is OSI Certified Open Source Software.
OSI Certified is a certification mark of the Open Source Initiative.

The license (Mozilla version 1.0) can be read at the MMBase site.
See http://www.MMBase.org/license

*/
package org.mmbase.module.corebuilders;

import java.util.*;
import org.mmbase.util.*;
import org.mmbase.module.core.*;
import org.mmbase.cache.Cache;

import org.mmbase.util.logging.Logger;
import org.mmbase.util.logging.Logging;


/**
 * TypeRel defines the allowed relations between two object types.
 *
 * @author Daniel Ockeloen
 * @author Pierre van Rooden
 * @author Michiel Meeuwissen
 * @version $Id: TypeRel.java,v 1.27 2002/07/05 12:56:19 pierre Exp $
 */
public class TypeRel extends MMObjectBuilder implements MMBaseObserver {

    private static Logger log = Logging.getLoggerInstance(TypeRel.class.getName());

    /**
     * A TypeRelSet is a HashSet of typerel nodes. The TypeRel
     * builders maintains a Set of all typerel nodes for quick
     * reference. TypeRelSets are also instantiated when doing queries
     * on TypeRel like getAllowedRelations(MMObjectBuilder) etc.
     *
     * @since MMBase-1.6.2
     */

    protected class TypeRelSet extends TreeSet { 

        TypeRelSet() {
            super(new Comparator() {
                    public int compare(Object o1, Object o2) {
                        MMObjectNode n1 = (MMObjectNode) o1;
                        MMObjectNode n2 = (MMObjectNode) o2;
                        
                        int i1 = n1.getIntValue("snumber");
                        int i2 = n2.getIntValue("snumber");
                        if (i1 != i2) return i1 - i2;
                        
                        i1 = n1.getIntValue("dnumber");
                        i2 = n2.getIntValue("dnumber");
                        if (i1 != i2) return i1 - i2;
                        
                        i1 = n1.getIntValue("rnumber");
                        i2 = n2.getIntValue("rnumber");
                        if (i1 != i2) return i1 - i2;
                        
                        
                        return 0;
                    }
                });
        }
        

        // make sure only MMObjectNode's are added
        public boolean add(Object object) {
            return super.add((MMObjectNode) object);
        }

        
        // find some subsets:
        SortedSet getBySource(MMObjectBuilder source) {
            return Collections.unmodifiableSortedSet(subSet(new VirtualTypeRelNode(source.oType),
                          new VirtualTypeRelNode(source.oType +1)));
        }

        SortedSet getBySource(int sourceOType) {
            return Collections.unmodifiableSortedSet(subSet(new VirtualTypeRelNode(sourceOType),
                          new VirtualTypeRelNode(sourceOType +1)));
        }

        SortedSet getByDestination(MMObjectBuilder destination) {
            return getByDestination(destination.oType);
        }


        SortedSet getByDestination(int destinationOType) {
            SortedSet result = new TypeRelSet();
            Iterator i = iterator();
            while (i.hasNext()) {
                MMObjectNode n = (MMObjectNode) i.next();
                if (destinationOType  == n.getIntValue("dnumber")) {
                    result.add(n);
                }
            }
            return result;
            
        }
        SortedSet getBySourceDestination(int source, int destination) {
            return Collections.unmodifiableSortedSet(subSet(new VirtualTypeRelNode(source, destination),
                          new VirtualTypeRelNode(source, destination + 1)));
        }
        SortedSet getBySourceDestination(MMObjectBuilder source, MMObjectBuilder destination) {
            return Collections.unmodifiableSortedSet(subSet(new VirtualTypeRelNode(source.oType, destination.oType),
                          new VirtualTypeRelNode(source.oType, destination.oType + 1)));
        }

    }

    /**
     * A VirtualTypeRelNode is a MMObjectNode which is added to the
     * typerelset with extensions of the actual builders specified. So
     * these entries are not in the database.
     * 
     * @since MMBase-1.6.2
     */

    protected class VirtualTypeRelNode extends MMObjectNode {
        VirtualTypeRelNode(int snumber, int dnumber) { // only for use in lookups
            this(snumber, dnumber, -1);
        }
        VirtualTypeRelNode(int snumber) {             // only for use in lookups
            this(snumber, -1, -1);
        }
        VirtualTypeRelNode(int snumber, int dnumber, int rnumber) {
            super(TypeRel.this);
            setValue("snumber", snumber);
            setValue("dnumber", dnumber);
            setValue("rnumber", rnumber);
        }
    }

    /**
     * TypeRel should contain only a limited amount of nodes, so we
     * can simply cache them all, and avoid all further querying.
     */

    private TypeRelSet typeRelNodes;


    public boolean init() {
        if (oType != -1) return true;
        super.init();
        // during init not yet all builder are available so inhertiance is not yet possible
        // This means that calls to getAllowedRelations do not consider inheritance during initializion of MMBase.
        // This occurs e.g. in one of the Community-builders.
        readCache(false); 
        return true;
    }


    /**
     * The TypeRel cache contains all TypeRels MMObjectNodes.
     * Called after init by MMBase, and when something changes.
     * @since MMBase-1.6.2
     */
    
    public void readCache() {
        readCache(true);
    }

    /**
     * @since MMBase-1.6.2
     */

    private void readCache(boolean inheritance) {
        log.debug("Reading in typerels");
        typeRelNodes = new TypeRelSet();

        TypeDef typeDef = mmb.getTypeDef();
        typeDef.init();
        // Find all typerel nodes
        Enumeration alltypes = search("");
        while(alltypes.hasMoreElements()) {
            // For every reltype node :
            MMObjectNode typerel = (MMObjectNode) alltypes.nextElement();


            if (inheritance) { // handle inheritance 
                int rnumber = typerel.getIntValue("rnumber");
                MMObjectBuilder sourceBuilder      = typeDef.getBuilder(getNode(typerel.getIntValue("snumber")));
                if (sourceBuilder == null) {
                    log.warn("Found a strange snumber in typerel table: " + typerel);
                    continue;
                }

                MMObjectBuilder destinationBuilder = typeDef.getBuilder(getNode(typerel.getIntValue("dnumber")));
                if (destinationBuilder == null) {
                    log.warn("Found a strange dnumber in typerel table: " + typerel);
                    continue;
                }

                
                List sources = sourceBuilder.getDescendants();
                sources.add(sourceBuilder);
                
                List destinations = destinationBuilder.getDescendants();
                destinations.add(destinationBuilder);
                
                
                Iterator i = sources.iterator();
                while (i.hasNext()) {
                    MMObjectBuilder s = (MMObjectBuilder) i.next();
                    Iterator j = destinations.iterator();
                    while (j.hasNext()) {
                        MMObjectBuilder d = (MMObjectBuilder) j.next();
                        typeRelNodes.add(new VirtualTypeRelNode(s.oType, d.oType, rnumber));
                    }
                }                             
            }

            typeRelNodes.add(typerel); // replaces the ones added in the 'inheritance' loop (so now not any more Virtual)

        }
        log.service("Done reading typerel cache " + (inheritance ? "(considered inheritance)" : "") + ": " + typeRelNodes );
    }


    /**
     * Insert a new object (content provided) in the cloud, including an entry for the object alias (if provided).
     * This method indirectly calls {@link #preCommit}.
     * @param owner The administrator creating the node
     * @param node The object to insert. The object need be of the same type as the current builder.
     * @return An <code>int</code> value which is the new object's unique number, -1 if the insert failed.
     */
    public int insert(String owner, MMObjectNode node) {
        int res = super.insert(owner,node);
        readCache(); // could perhasp be implemented a bit less dumb
        return res;
    }

    /**
     * Remove a node from the cloud.
     * @param node The node to remove.
     */
    public void removeNode(MMObjectNode node) {
        super.removeNode(node);
        readCache(); // could perhasp be implemented a bit less dumb
    }

    /**
     *  Retrieves all relations which are 'allowed' for a specified node, that is,
     *  where the node is either allowed to be the source, or to be the destination (but where the
     *  corresponing relation definition is bidirectional). The allowed relations are determined by
     *  the type of the node
     *  @param mmnode The node to retrieve the allowed relations of.
     *  @return An <code>Enumeration</code> of nodes containing the typerel relation data
     */
    public Enumeration getAllowedRelations(MMObjectNode mmnode) {
        // wrap into a list, because result of getBySource is unmodifiable
        List res = new ArrayList(typeRelNodes.getBySource(mmnode.getBuilder()));
        res.addAll(typeRelNodes.getByDestination(mmnode.getBuilder()));
        return Collections.enumeration(res);
    }

    public Enumeration getAllowedRelations(int otype) {
        List res = new ArrayList(typeRelNodes.getBySource(otype));
        res.addAll(typeRelNodes.getByDestination(otype));
        return Collections.enumeration(res);
    }

    /**
     *  Retrieves all relations which are 'allowed' between two specified nodes.
     *  @param n1 The first objectnode (the source)
     *  @param n2 The second objectnode (the destination)
     *  @return An <code>Enumeration</code> of nodes containing the typerel relation data
     */
    public Enumeration getAllowedRelations(MMObjectNode n1,MMObjectNode n2) {
        return Collections.enumeration(typeRelNodes.getBySourceDestination(n1.getBuilder(), n2.getBuilder()));
    }

    public Enumeration getAllowedRelations(int builder1, int builder2) {
        return Collections.enumeration(typeRelNodes.getBySourceDestination(builder1, builder2));
    }

    /**
     *  Retrieves all relations which are 'allowed' between two specified nodes.
     *  @param snum The first objectnode type (the source)
     *  @param dnum The second objectnode type (the destination)
     *  @return An <code>Enumeration</code> of nodes containing the reldef (not typerel!) sname field
     */
    protected Vector getAllowedRelationsNames(int number1 ,int number2) {
        Vector results=new Vector();
        for(Enumeration e=getAllowedRelations(number1, number2); e.hasMoreElements();) {
            MMObjectNode node=(MMObjectNode)e.nextElement();
            int rnumber=node.getIntValue("rnumber");
            MMObjectNode snode=mmb.getRelDef().getNode(rnumber);
            results.addElement(snode.getStringValue("sname"));
        }
        return results;
    }
    /**
     *  Retrieves the identifying number of the relation definition that is 'allowed' between two specified node types.
     *  The results are dependent on there being only one type of relation between two node types (not enforced, thus unpredictable).
     *  Makes use of a typeRelNodes.
     *  @param snum The first objectnode type (the source)
     *  @param dnum The second objectnode type (the destination)
     *  @return the number of the found relation, or -1 if either no relation was found, or more than one was found.
     */
    public int getAllowedRelationType(int snum,int dnum) {
        SortedSet set = typeRelNodes.getBySourceDestination(snum, dnum);
        if (set.size() != 1) { 
            return -1;
        } else {
            MMObjectNode n =  (MMObjectNode)set.iterator().next();
            return n.getNumber();
        }        
    }



    /**
     *  Returns the display string for this node
     *  It returns a commbination of objecttypes and rolename : "source->destination (role)".
     *  @param node Node from which to retrieve the data
     *  @return A <code>String</code> describing the content of the node
     */
    public String getGUIIndicator(MMObjectNode node) {
        try {
            return mmb.getTypeDef().getValue(node.getIntValue("snumber"))+
                   "->"+mmb.getTypeDef().getValue(node.getIntValue("dnumber"))+
                   " ("+mmb.getRelDef().getNode(node.getIntValue("rnumber")).getGUIIndicator()+")";
        } catch (Exception e) {}
        return null;
    }

    /**
     *  Returns the display string for a specified field.
     *  Returns, for snumber and dnumber, the name of the objecttype they represent, and for
     *  rnumber the display (GUI) string for the indicated relation definition.
     *  @param field The name of the field to retrieve
     *  @param node Node from which to retrieve the data
     *  @return A <code>String</code> describing the content of the field
     */
    public String getGUIIndicator(String field, MMObjectNode node) {
        try {
            if (field.equals("snumber")) {
                return mmb.getTypeDef().getValue(node.getIntValue("snumber"));
            } else if (field.equals("dnumber")) {
                return mmb.getTypeDef().getValue(node.getIntValue("dnumber"));
            } else if (field.equals("rnumber")) {
                MMObjectNode node2=mmb.getRelDef().getNode(node.getIntValue("rnumber"));
                return node2.getGUIIndicator();
            }
        } catch (Exception e) {}
        return null;
    }

    /**
     * Processes the BUILDER-typerel-ALLOWEDRELATIONSNAMES in the LIST command, and (possibly) returns a Vector containing
     * requested data (based on the content of TYPE and NODE, which can be retrieved through tagger).
     * @javadoc parameters
     */
    public Vector getList(scanpage sp, StringTagger tagger, StringTokenizer tok) {
        if (tok.hasMoreTokens()) {
            String cmd=tok.nextToken();    //Retrieving command.
            if (cmd.equals("ALLOWEDRELATIONSNAMES")) {
                try {
                    String tmp=tagger.Value("TYPE");
                    int number1=mmb.getTypeDef().getIntValue(tmp);
                    tmp=tagger.Value("NODE");
                    int number2=Integer.parseInt(tmp);
                    MMObjectNode node=getNode(number2);
                    return getAllowedRelationsNames(number1,node.getOType());
                } catch(Exception e) {
                    log.error(Logging.stackTrace(e));
                }
            }
        }
        return null;
    }

    /**
     * Checks whether a specific relation exists.
     *
     * Note that this routine returns false both when a snumber/dnumber are swapped, and when a typecombo
     * does not exist -  it is not possible to derive whether one or the other has occurred.
     *
     * @param n1 Number of the source builder
     * @param n2 Number of the destination builder
     * @param r  Number of the relation definition
     * @return A <code>boolean</code> indicating success when the relation exists, failure if it does not.
     */
    public boolean reldefCorrect(int n1,int n2, int r) {
        return typeRelNodes.contains(new VirtualTypeRelNode(n1, n2, r));
    }

    public boolean nodeRemoteChanged(String machine,String number,String builder,String ctype) {
        super.nodeRemoteChanged(machine,number,builder,ctype);
        return nodeChanged(machine,number,builder,ctype);
    }
 
    public boolean nodeLocalChanged(String machine,String number,String builder,String ctype) {
        super.nodeLocalChanged(machine,number,builder,ctype);
        return nodeChanged(machine,number,builder,ctype);
    }

    /**
     * Watch for changes on relation types and adjust our memory table accordingly
     * @todo Should update artCache en relDefCorrectCache as wel
     */
    public boolean nodeChanged(String machine,String number,String builder,String ctype) {
        if (log.isDebugEnabled()) log.debug("Seeing change on "+number+" : "+ctype);
        if (builder.equals(getTableName())) {
            // something changed in typerel node? reread the typeRelNodes.
            readCache();
        }
        return true;
    }


    /**
     * Implements equals MMObjectNode
     * @since MMBase-1.6.2
     */

    public boolean equals(MMObjectNode o1, MMObjectNode o2) {
        if (o2.parent instanceof TypeRel) {            
            return o1.getIntValue("snumber") == o2.getIntValue("snumber") &&
                   o1.getIntValue("dnumber") == o2.getIntValue("dnumber") &&
                   o1.getIntValue("rnumber") == o2.getIntValue("rnumber");
        }
        return false;
    }
    /**
     * Implements for MMObjectNode
     * @since MMBase-1.6.2
     */

    public int hashCode(MMObjectNode o) {
        return 127 * o.getIntValue("snumber");
    }

    public String toString(MMObjectNode n) {
        return getGUIIndicator(n);
    }
}
Index: MMObjectNode.java
===================================================================
RCS file: /usr/local/cvs/mmbase/module/core/MMObjectNode.java,v
retrieving revision 1.86.2.11
diff -r1.86.2.11 MMObjectNode.java
214c214,225
<       String result="";
---
>         if (parent != null) {
>             return parent.toString(this);
>         } else {
>             return defaultToString();
>         }
>     }
> 
>     /**
>      * @since MMBase-1.6.2
>      */
>     String defaultToString() {        
>       StringBuffer result=new StringBuffer("prefix='"+prefix+"'");
216d226
<           result="prefix='"+prefix+"'";
223c233
<                   result=key+"="+dbtype+":'"+value+"'";
---
>                   result = new StringBuffer(key+"="+dbtype+":'"+value+"'"); // can 
>this occur?
225c235
<                   result+=","+key+"="+dbtype+":'"+value+"'";
---
>                   result.append(","+key+"="+dbtype+":'"+value+"'");
229c239
<       return result;
---
>       return result.toString();
1379a1390,1416
>     }
> 
> 
>     /**
>      * @since MMBase-1.6.2
>      */
>     public int hashCode() {
>         if (parent != null) {
>             return parent.hashCode(this);
>         } else {
>             return super.hashCode();
>         }
>     }
> 
>     /**
>      * @since MMBase-1.6.2
>      */
>     public boolean equals(Object o) {
>         if (o instanceof MMObjectNode) {            
>             MMObjectNode n = (MMObjectNode) o;
>             if (parent != null) {
>                 return parent.equals(this, n);
>             } else {
>                 return n.getNumber() == getNumber();
>             }
>         }
>         return false;
Index: MMObjectBuilder.java
===================================================================
RCS file: /usr/local/cvs/mmbase/module/core/MMObjectBuilder.java,v
retrieving revision 1.181.2.13
diff -r1.181.2.13 MMObjectBuilder.java
55c55
< public class MMObjectBuilder extends MMTable {
---
> public class MMObjectBuilder extends MMTable { //  implements 
>org.mmbase.util.SizeMeasurable {{
551c551,569
<     
---
> 
>     /**
>      * Creates list of descendant-builders. 
>      *
>      * @since MMBase-1.6.2     
>      */
>     public List getDescendants() {
>         ArrayList result = new ArrayList();
>         Enumeration e = mmb.getMMObjects();
>         while(e.hasMoreElements()) {
>             MMObjectBuilder builder = (MMObjectBuilder) e.nextElement();
>             if (builder.isExtensionOf(this)) {
>                 result.add(builder);
>             }
>         }
>         return result;
>     }
> 
> 
3477a3496,3504
> 
>     /**
>      * Implements for MMObjectNode
>      * @since MMBase-1.6.2
>      */
> 
>     public String toString(MMObjectNode n) {
>         return n.defaultToString();
>     }
3489a3517,3536
> 
>     /**
>      * Implements equals for nodes (this is in MMObjectBuilder because you cannot 
>override MMObjectNode)
>      *
>      * @since MMBase-1.6.2
>      */
> 
>     public boolean equals(MMObjectNode o1, MMObjectNode o2) {
>         return o1.getNumber() == o2.getNumber();
>     }
> 
>     /**
>      * Implements for MMObjectNode
>      * @since MMBase-1.6.2
>      */
> 
>     public int hashCode(MMObjectNode o) {
>         return 127 * o.getNumber();
>     }
> 

Reply via email to