Author: ernst
Date: 2009-05-28 10:32:26 +0200 (Thu, 28 May 2009)
New Revision: 35463

Added:
   speeltuin/ernst/mmbase-vob/pom.xml
   speeltuin/ernst/mmbase-vob/src/
   speeltuin/ernst/mmbase-vob/src/main/
   speeltuin/ernst/mmbase-vob/src/main/java/
   speeltuin/ernst/mmbase-vob/src/main/java/nl/
   speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/
   speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/
   speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/
   
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/DefaultQueryHelper.java
   speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/Direction.java
   speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/EntityFilter.java
   speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/Populator.java
   
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/QueryDirection.java
   speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/QueryHelper.java
   speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/
   
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Embedded.java
   
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Entity.java
   
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Field.java
   
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/PosRel.java
   
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Rel.java
   speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/converters/
   
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/converters/EpochDateConverter.java
   
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/converters/FieldConverter.java
   
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/converters/PassThroughFieldConverter.java
   speeltuin/ernst/mmbase-vob/src/main/resources/
   speeltuin/ernst/mmbase-vob/src/main/resources/org/
   speeltuin/ernst/mmbase-vob/src/main/resources/org/mmbase/
   speeltuin/ernst/mmbase-vob/src/main/resources/org/mmbase/config/
   speeltuin/ernst/mmbase-vob/src/test/
   speeltuin/ernst/mmbase-vob/src/test/java/
   speeltuin/ernst/mmbase-vob/src/test/java/nl/
   speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/
   speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/
   speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/Image.java
   speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/NewsItem.java
   speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/Tag.java
   speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/converters/
   
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/converters/ToLowercaseConverter.java
   speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/vob/
   
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/vob/NoResultsQueryHelper.java
   
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/vob/PathFragmentToNodenrQueryHelper.java
   
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/vob/PopulatorTest.java
   speeltuin/ernst/mmbase-vob/src/test/resources/
   speeltuin/ernst/mmbase-vob/src/test/resources/log4j.properties
Modified:
   speeltuin/ernst/mmbase-vob/
Log:
mmbase-vob in de speeltuin


Property changes on: speeltuin/ernst/mmbase-vob
___________________________________________________________________
Name: svn:ignore
   + .classpath

.project

.settings

target


Added: speeltuin/ernst/mmbase-vob/pom.xml
===================================================================
--- speeltuin/ernst/mmbase-vob/pom.xml                          (rev 0)
+++ speeltuin/ernst/mmbase-vob/pom.xml  2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+       <modelVersion>4.0.0</modelVersion>
+
+       <parent>
+               <groupId>nl.vpro</groupId>
+               <artifactId>vpro-parent</artifactId>
+               <version>R20090226</version>
+       </parent>
+
+       <groupId>nl.vpro</groupId>
+       <artifactId>mmbase-vob</artifactId>
+       <version>1.0-SNAPSHOT</version>
+       <packaging>jar</packaging>
+
+       <name>mmbase-vob</name>
+
+       <dependencies>
+               <dependency>
+                       <groupId>nl.vpro.mmbase</groupId>
+                       <artifactId>mmvpro</artifactId>
+                       <version>1.8.7-SNAPSHOT</version>
+                       <scope>provided</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework</groupId>
+                       <artifactId>spring-context-support</artifactId>
+                       <version>2.5.6</version>
+                       <scope>compile</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework</groupId>
+                       <artifactId>spring-jdbc</artifactId>
+                       <version>2.5.6</version>
+                       <scope>compile</scope>
+               </dependency>
+               <dependency>
+                       <groupId>commons-beanutils</groupId>
+                       <artifactId>commons-beanutils</artifactId>
+                       <version>1.8.0</version>
+               </dependency>
+               <dependency>
+                       <groupId>junit</groupId>
+                       <artifactId>junit</artifactId>
+                       <version>4.5</version>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.easymock</groupId>
+                       <artifactId>easymockclassextension</artifactId>
+                       <version>2.4</version>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>commons-lang</groupId>
+                       <artifactId>commons-lang</artifactId>
+                       <version>2.4</version>
+               </dependency>
+       </dependencies>
+       <build>
+               <defaultGoal>install</defaultGoal>
+       </build>
+</project>


Property changes on: speeltuin/ernst/mmbase-vob/pom.xml
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/DefaultQueryHelper.java
===================================================================
--- 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/DefaultQueryHelper.java
                         (rev 0)
+++ 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/DefaultQueryHelper.java
 2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,58 @@
+package nl.vpro.mmbase.vob;
+
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.List;
+
+import org.mmbase.bridge.Cloud;
+import org.mmbase.bridge.Node;
+import org.mmbase.bridge.NodeIterator;
+import org.mmbase.bridge.NodeList;
+import org.mmbase.bridge.Query;
+import org.mmbase.bridge.util.Queries;
+
+/**
+ * Default implementation, almost one-to-one patch trough to mmbase with some
+ * minor enhancements, like explicit typing for sorting and query directions.
+ * 
+ * @author [email protected]
+ */
+public class DefaultQueryHelper implements QueryHelper {
+  private static final EnumMap<Direction, String> remapSortDirection = new 
EnumMap<Direction, String>(Direction.class);
+  static {
+    remapSortDirection.put(Direction.ASC, "up");
+    remapSortDirection.put(Direction.DESC, "down");
+  }
+
+  /* (non-Javadoc)
+   * @see nl.vpro.mmbase.vob.QueryHelper#query(org.mmbase.bridge.Cloud, int, 
java.lang.String, java.lang.String, java.lang.String, 
nl.vpro.mmbase.vob.Direction, nl.vpro.mmbase.vob.QueryDirection)
+   */
+  public List<Node> query(Cloud cloud, int startNumber, String path, String 
fields, String sortField, Direction dir, QueryDirection queryDir) {
+    String queryDirectionAsString = queryDir.toString().toLowerCase();
+    Query query = Queries.createQuery(cloud, String.valueOf(startNumber), 
path, fields, null, sortField, remapSortDirection.get(dir), 
queryDirectionAsString, true);
+
+    return executeQuery(cloud, query);
+  }
+
+  /* (non-Javadoc)
+   * @see nl.vpro.mmbase.vob.QueryHelper#query(org.mmbase.bridge.Cloud, int, 
java.lang.String, java.lang.String, java.lang.String, 
nl.vpro.mmbase.vob.Direction, nl.vpro.mmbase.vob.QueryDirection, int)
+   */
+  public List<Node> query(Cloud cloud, int startNumber, String path, String 
fields, String sortField, Direction dir, QueryDirection queryDir, int max) {
+    String queryDirectionAsString = queryDir.toString().toLowerCase();
+
+    Query query = Queries.createQuery(cloud, String.valueOf(startNumber), 
path, fields, null, sortField, remapSortDirection.get(dir), 
queryDirectionAsString, true);
+    query.setMaxNumber(max);
+
+    return executeQuery(cloud, query);
+  }
+
+  private List<Node> executeQuery(Cloud cloud, Query query) {
+    List<Node> nodes = new ArrayList<Node>();
+    NodeList list = cloud.getList(query);
+    NodeIterator nodeIterator = list.nodeIterator();
+    while (nodeIterator.hasNext()) {
+      nodes.add(nodeIterator.nextNode());
+    }
+    return nodes;
+  }
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/DefaultQueryHelper.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/Direction.java
===================================================================
--- speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/Direction.java  
                        (rev 0)
+++ speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/Direction.java  
2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,10 @@
+package nl.vpro.mmbase.vob;
+
+/**
+ * Defines possible directions to sort.
+ * 
+ * @author [email protected]
+ */
+public enum Direction {
+       ASC, DESC
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/Direction.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/EntityFilter.java
===================================================================
--- 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/EntityFilter.java   
                            (rev 0)
+++ 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/EntityFilter.java   
    2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,48 @@
+package nl.vpro.mmbase.vob;
+
+import java.io.IOException;
+import java.util.concurrent.ConcurrentMap;
+
+import nl.vpro.mmbase.vob.annotations.Entity;
+
+import org.apache.log4j.Logger;
+import org.springframework.core.type.ClassMetadata;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.core.type.filter.TypeFilter;
+
+/**
+ * Classpath scanner, retrieves entities from the classpath. Uses Springs'
+ * annotationscanner to do so.
+ * 
+ * @author [email protected]
+ */
+public final class EntityFilter implements TypeFilter {
+  private static final Logger log = Logger.getLogger(EntityFilter.class);
+  private final ConcurrentMap<String, Class<?>> entityRegistry;
+
+  public EntityFilter(final ConcurrentMap<String, Class<?>> entityRegistry) {
+    this.entityRegistry = entityRegistry;
+  }
+
+  public boolean match(final MetadataReader metadataReader, final 
MetadataReaderFactory metadataReaderFactory) throws IOException {
+    final boolean matches = isEntity(metadataReader);
+    if (matches) {
+      final ClassMetadata classMetadata = metadataReader.getClassMetadata();
+      if (classMetadata.isConcrete()) {
+        try {
+          Class<?> clazz = 
getClass().getClassLoader().loadClass(classMetadata.getClassName());
+          final String builder = Populator.determineBuilder(clazz);
+          entityRegistry.putIfAbsent(builder, clazz);
+        } catch (final ClassNotFoundException e) {
+          log.warn("unable to load class: " + classMetadata.getClassName(), e);
+        }
+      }
+    }
+    return matches;
+  }
+
+  private boolean isEntity(final MetadataReader metadataReader) {
+    return 
metadataReader.getAnnotationMetadata().hasAnnotation(Entity.class.getName());
+  }
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/EntityFilter.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/Populator.java
===================================================================
--- speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/Populator.java  
                        (rev 0)
+++ speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/Populator.java  
2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,363 @@
+package nl.vpro.mmbase.vob;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import nl.vpro.mmbase.vob.annotations.Embedded;
+import nl.vpro.mmbase.vob.annotations.Entity;
+import nl.vpro.mmbase.vob.annotations.PosRel;
+import nl.vpro.mmbase.vob.annotations.Rel;
+import nl.vpro.mmbase.vob.converters.FieldConverter;
+
+import org.apache.commons.lang.StringUtils;
+import org.mmbase.bridge.Cloud;
+import org.mmbase.bridge.Node;
+import org.mmbase.bridge.util.NodeMap;
+import org.mmbase.util.logging.Logger;
+import org.mmbase.util.logging.Logging;
+import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
+import org.springframework.context.support.GenericApplicationContext;
+
+/**
+ * Entry class for converting mmbase nodes to annotated entity objects scanned
+ * from the classpath.
+ * 
+ * NOTE: Apart from the entity registry all methods in this class are stateless
+ * since graphs are populated in a recursive manner.
+ * 
+ * @author [email protected]
+ */
+public class Populator {
+    private static Logger log = 
Logging.getLoggerInstance(Populator.class.getName());
+
+    private static ConcurrentMap<String, Class<?>> entityRegistry = null;
+    private final QueryHelper queryHelper;
+
+    /**
+     * Instantiates a DefaultQueryHelper to retrieve associations.
+     * 
+     * @param pathToScan The base path the scan for entities
+     */
+    public Populator(final String pathToScan) {
+        this(new DefaultQueryHelper(), pathToScan);
+    }
+
+    /**
+     * @param queryHelper The query helper to use when executing subqueries.
+     * @param pathToScan The base path the scan for entities
+     */
+    public Populator(final QueryHelper queryHelper, final String pathToScan) {
+        this.queryHelper = queryHelper;
+        scanClasspathForEntities(pathToScan);
+    }
+
+    /**
+     * Searches the given path for object annotated with the @entity 
annotation.
+     * 
+     * When found, entities are added to a registry to be used for quick 
lookups
+     * will converting.
+     * 
+     * @param pathToScan The base path the scan for entities
+     */
+    private void scanClasspathForEntities(final String pathToScan) {
+        if (entityRegistry == null) {
+            entityRegistry = new ConcurrentHashMap<String, Class<?>>();
+            final GenericApplicationContext context = new 
GenericApplicationContext();
+            final ClassPathBeanDefinitionScanner scanner = new 
ClassPathBeanDefinitionScanner(context);
+            scanner.resetFilters(false);
+            scanner.addIncludeFilter(new EntityFilter(entityRegistry));
+            scanner.scan(pathToScan);
+            log.service(entityRegistry.size() + " Entity mappings scanned on 
classpath " + pathToScan);
+        }
+    }
+
+    /**
+     * Call this method to convert a node to a graph of corresponding objects,
+     * including all annotated associations etc. This method will be called
+     * recursively and is stateless, as are the methods called from here.
+     * 
+     * 
+     * TODO: do we really need to provide the builderName ourselves? currently
+     * in here to simplify testing. Fix this, before it becomes a feature!!
+     * 
+     * @param node The concrete node which should be unmarshalled
+     * @param builderName The name of the builder
+     * @return The populated object graph
+     */
+    public Object unmarshallNode(final Node node, final String builderName) {
+        Object target = null;
+        if (hasEntityFor(builderName)) {
+            try {
+                target = entityRegistry.get(builderName).newInstance();
+                copyBasicProperties(target, node);
+                copyEmbeddeds(target, node);
+                copyAssociations(target, node);
+
+            } catch (final Exception e) {
+                log.error("unable to unmarshall node " + node);
+            }
+        } else {
+            log.debug("no entity mapping for builder " + builderName);
+        }
+        return target;
+    }
+
+    private void copyBasicProperties(final Object target, final Node node) {
+        final NodeMap nodeMap = new NodeMap(node);
+        final Set<Map.Entry<String, Object>> fieldNameSet = 
uncheckedCast(nodeMap.entrySet());
+        for (final Map.Entry<String, Object> entry : fieldNameSet) {
+            final String fieldName = entry.getKey();
+            // final Object fieldValue = entry.getValue();
+            populateField(target, fieldName, node);
+        }
+    }
+
+    private void copyEmbeddeds(final Object target, final Node node) {
+        for (final Field field : target.getClass().getDeclaredFields()) {
+            field.setAccessible(true);
+            final Embedded embeddedAnnotation = 
field.getAnnotation(Embedded.class);
+            if (embeddedAnnotation != null) {
+                final String srcBuilder = determineBuilder(target.getClass());
+
+                try {
+                    final Object result = retrieveEmbedded(node, srcBuilder, 
embeddedAnnotation);
+                    field.set(target, result);
+                } catch (final Exception e) {
+                    log.warn("unable to populate embedded field: 
"+e.getMessage());
+                }
+            }
+        }
+    }
+
+    private Object retrieveEmbedded(final Node node, final String srcBuilder, 
final Embedded embeddedAnnotation) {
+        final Cloud cloud = node.getCloud();
+        final String targetBuilder = embeddedAnnotation.builder();
+        final String relationRole = embeddedAnnotation.relationRole();
+        final String path = String.format("%s,%s,%s", srcBuilder, 
relationRole, targetBuilder);
+
+        final String targetField = String.format("%s.%s", targetBuilder, 
embeddedAnnotation.field());
+        //TODO: parhaps the order field is a relation field?
+        final String orderField = String.format("%s.%s", targetBuilder, 
embeddedAnnotation.orderField());
+
+        final List<Node> virtualNodes = queryHelper.query(cloud, 
node.getNumber(), path, targetField, orderField,
+                embeddedAnnotation.orderDirection(), 
embeddedAnnotation.queryDirection(), 1);
+
+        if (virtualNodes.size() > 0) {
+            try {
+                final FieldConverter fieldConvertor = 
embeddedAnnotation.convertor().newInstance();
+                return fieldConvertor.convert(virtualNodes.get(0), 
targetField);
+            } catch (final Exception e) {
+                log.warn("unable to convert returntype");
+            }
+        }
+
+        return null;
+    }
+
+    private void copyAssociations(final Object target, final Node node) {
+        for (final Field field : target.getClass().getDeclaredFields()) {
+            field.setAccessible(true);
+            final Rel relAnnotation = field.getAnnotation(Rel.class);
+            final PosRel posrelAnnotation = field.getAnnotation(PosRel.class);
+
+            if (posrelAnnotation != null) {
+                final Class<?> relatedType = retrieveActualType(field);
+                final String srcBuilder = determineBuilder(target.getClass());
+                final String targetBuilder = determineBuilder(relatedType);
+                final List<?> retrievedPosRel = retrievePosRel(node, 
relatedType, srcBuilder, targetBuilder,
+                        posrelAnnotation);
+                safeSet(target, field, retrievedPosRel);
+            } else if (relAnnotation != null) {
+                final Class<?> relatedType = retrieveActualType(field);
+                final String srcBuilder = determineBuilder(target.getClass());
+                final String targetBuilder = determineBuilder(relatedType);
+                final List<?> retrievedRel = retrieveRel(node, relatedType, 
srcBuilder, targetBuilder, relAnnotation);
+                safeSet(target, field, retrievedRel);
+            }
+        }
+    }
+
+    private static void safeSet(final Object target, final Field field, final 
Object retrievedPosRel) {
+        field.setAccessible(true);
+        try {
+            field.set(target, retrievedPosRel);
+        } catch (final Exception e) {
+            log.error("unable to set target field " + field.getName() + " on 
object " + target);
+        }
+    }
+
+    private <T> List<T> retrievePosRel(final Node node, final Class<T> 
relatedType, final String srcBuilder,
+            final String targetBuilder, final PosRel posRel) {
+        final Cloud cloud = node.getCloud();
+
+        final String path = String.format("%s,posrel,%s", srcBuilder, 
targetBuilder);
+        final String targetField = String.format("%s.number", targetBuilder);
+        final String orderField = "posrel.pos";
+
+        final List<Node> virtualNodes = queryHelper.query(cloud, 
node.getNumber(), path, targetField, orderField,
+                posRel.orderDirection(), posRel.queryDirection());
+        final List<Node> nodes = loadNodes(cloud, virtualNodes, targetField);
+        return unmarshallList(relatedType, nodes);
+    }
+
+    private <T> List<T> retrieveRel(final Node node, final Class<T> 
relatedType, final String srcBuilder,
+            final String targetBuilder, final Rel rel) {
+        final Cloud cloud = node.getCloud();
+
+        final String path = String.format("%s,%s", srcBuilder, targetBuilder);
+        final String targetField = String.format("%s.number", targetBuilder);
+        final String orderField = String.format("%s.%s", targetBuilder, 
rel.orderField());
+
+        final List<Node> virtualNodes = queryHelper.query(cloud, 
node.getNumber(), path, targetField, orderField, rel
+                .orderDirection(), rel.queryDirection());
+        final List<Node> nodes = loadNodes(cloud, virtualNodes, targetField);
+        return unmarshallList(relatedType, nodes);
+    }
+
+    private List<Node> loadNodes(final Cloud cloud, final List<Node> virtual, 
final String numberField) {
+        final List<Node> results = new ArrayList<Node>(virtual.size());
+        for (final Node virtualNode : virtual) {
+            final Node realNode = 
cloud.getNode(virtualNode.getIntValue(numberField));
+            results.add(realNode);
+        }
+
+        return results;
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T> List<T> unmarshallList(final Class<T> relatedType, final 
List<Node> nodes) {
+        final List<T> results = new ArrayList<T>(nodes.size());
+        for (final Node n : nodes) {
+            try {
+                final T relatedObject = (T) unmarshallNode(n, 
determineBuilder(relatedType));
+                results.add(relatedObject);
+            } catch (final Exception e) {
+                log.warn("unable to instantiate related object of type " + 
relatedType
+                        + " , no accessible contructor present?");
+            }
+        }
+        return results;
+    }
+
+    private static Class<?> retrieveActualType(final Field field) {
+        return (Class<?>) ((ParameterizedType) 
field.getGenericType()).getActualTypeArguments()[0];
+    }
+
+    public static Class<?> determineParametrizedType(final Type type) {
+        if (type instanceof ParameterizedType) {
+            final ParameterizedType paramType = (ParameterizedType) type;
+            final Type[] typeArguments = paramType.getActualTypeArguments();
+            if (typeArguments.length > 0) {
+                return (Class<?>) typeArguments[0];
+            }
+        }
+        return null;
+    }
+
+    public static String determineBuilder(final Class<?> clazz) {
+        return clazz.getAnnotation(Entity.class) != null ? 
clazz.getAnnotation(Entity.class).builder() : null;
+    }
+
+    /**
+     * Set the value of an Entity property based on a node field value. this is
+     * done when a property of matching name is found in the Entity object, or
+     * when there is an entity property with a Field annotation that maps that
+     * property to this node field. If a Convertor is set through a Field
+     * annotation, it is applied before setting the property.
+     * 
+     * @param target is the Entity object the field value is set on.
+     * @param fieldName name of node field that is being set.
+     * @param node that the value is copied from
+     */
+    static void populateField(final Object target, final String fieldName, 
final Node node) {
+        final Field[] fields = target.getClass().getDeclaredFields();
+        try {
+            for (final Field field : fields) {
+                final nl.vpro.mmbase.vob.annotations.Field fieldAnnotation = 
field
+                        
.getAnnotation(nl.vpro.mmbase.vob.annotations.Field.class);
+                if (fieldMatchesWithoutAnnotatedFieldMapping(fieldName, field, 
fieldAnnotation)
+                        || AnnotatedFieldMappingMatchesNodeField(fieldName, 
fieldAnnotation)) {
+                    setFieldValue(target, fieldName, node, field, 
fieldAnnotation);
+                    return;
+                }
+            }
+        } catch (final Exception e) {
+            log.warn(String.format("unable to set property %s on object %s", 
fieldName, target));
+        }
+    }
+
+    private static boolean AnnotatedFieldMappingMatchesNodeField(final String 
fieldName,
+            final nl.vpro.mmbase.vob.annotations.Field fieldAnnotation) {
+        return fieldAnnotation != null && 
fieldAnnotation.nodeField().equalsIgnoreCase(fieldName);
+    }
+
+    private static boolean fieldMatchesWithoutAnnotatedFieldMapping(final 
String fieldName, final Field field,
+            final nl.vpro.mmbase.vob.annotations.Field fieldAnnotation) {
+        return field.getName().equalsIgnoreCase(fieldName)
+                && (fieldAnnotation == null || 
"".equals(fieldAnnotation.nodeField()));
+    }
+
+    private static void setFieldValue(final Object target, final String 
fieldName, final Node node, final Field field,
+            final nl.vpro.mmbase.vob.annotations.Field fieldAnnotation) throws 
IllegalAccessException,
+            InstantiationException {
+        field.setAccessible(true);
+        if (fieldAnnotation != null) {
+            field.set(target, 
fieldAnnotation.convertor().newInstance().convert(node, fieldName));
+        } else {
+            field.set(target, node.getObjectValue(fieldName));
+        }
+    }
+
+    /**
+     * @param builderName
+     * @return true if There is an entity mapping for given node type.
+     */
+    public boolean hasEntityFor(final String builderName) {
+        return entityRegistry.containsKey(builderName);
+    }
+
+    /**
+     * @param builderName
+     * @return an entity class mapped to this builder
+     */
+    public Class<?> getEntityFor(final String builderName) {
+        return entityRegistry.get(builderName);
+    }
+
+    public Class<?> findEntityThatEmbeds(String relationRole, String builder) {
+        for (Class<?> entityClass : entityRegistry.values()) {
+            for (Field field : entityClass.getDeclaredFields()) {
+                Embedded embedded = field.getAnnotation(Embedded.class);
+                if (embedded != null) {
+                    if (equalsNotBlank(relationRole, embedded.relationRole())
+                            && equalsNotBlank(builder, embedded.builder())) {
+                        return entityClass;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+    
+    public static boolean entityIsRoot(Object o){
+        Class<? extends Object> c = o.getClass();
+        Entity e = (Entity) c.getAnnotation(Entity.class);
+        return e != null && e.root() == true;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> T uncheckedCast(final Object obj) {
+        return (T) obj;
+    }
+
+    private static boolean equalsNotBlank(String s1, String s2) {
+        return !StringUtils.isBlank(s1) && s1.equals(s2);
+    }
+}
\ No newline at end of file


Property changes on: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/Populator.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/QueryDirection.java
===================================================================
--- 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/QueryDirection.java 
                            (rev 0)
+++ 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/QueryDirection.java 
    2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,10 @@
+package nl.vpro.mmbase.vob;
+
+/**
+ * Specify the directionality of the query.
+ * 
+ * @author [email protected]
+ */
+public enum QueryDirection {
+       SOURCE, DESTINATION, BOTH
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/QueryDirection.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/QueryHelper.java
===================================================================
--- 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/QueryHelper.java    
                            (rev 0)
+++ 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/QueryHelper.java    
    2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,54 @@
+package nl.vpro.mmbase.vob;
+
+import java.util.List;
+
+import org.mmbase.bridge.Cloud;
+import org.mmbase.bridge.Node;
+
+/**
+ * Wrapper interface for the Query utility in mmbase, to allow for mocking and
+ * alternative implementations.
+ * 
+ * @author [email protected]
+ */
+interface QueryHelper {
+  /**
+   * @param cloud
+   *          The cloud to query
+   * @param startNumber
+   *          The startnode of the path
+   * @param path
+   *          The actual path
+   * @param fields
+   *          The fields to retrieve
+   * @param sortField
+   *          The field to sort by
+   * @param dir
+   *          The direction to sort in
+   * @param queryDir
+   *          The direction to query in
+   * @return A list of resulting nodes
+   */
+  List<Node> query(Cloud cloud, int startNumber, String path, String fields, 
String sortField, Direction dir, QueryDirection queryDir);
+
+  /**
+   * @param cloud
+   *          The cloud to query
+   * @param startNumber
+   *          The startnode of the path
+   * @param path
+   *          The actual path
+   * @param fields
+   *          The fields to retrieve
+   * @param sortField
+   *          The field to sort by
+   * @param dir
+   *          The direction to sort in
+   * @param queryDir
+   *          The direction to query in
+   * @param max
+   *          The maximum number of nodes to retrieve
+   * @return A list of resulting nodes
+   */
+  List<Node> query(Cloud cloud, int startNumber, String path, String fields, 
String sortField, Direction dir, QueryDirection queryDir, int max);
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/QueryHelper.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Embedded.java
===================================================================
--- 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Embedded.java
                               (rev 0)
+++ 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Embedded.java
       2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,24 @@
+package nl.vpro.mmbase.vob.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import nl.vpro.mmbase.vob.Direction;
+import nl.vpro.mmbase.vob.QueryDirection;
+import nl.vpro.mmbase.vob.converters.FieldConverter;
+import nl.vpro.mmbase.vob.converters.PassThroughFieldConverter;
+
+
+...@retention(RetentionPolicy.RUNTIME)
+...@target(ElementType.FIELD)
+public @interface Embedded {
+       String builder();
+       String field();
+       String relationRole() default "related";
+       String orderField() default "number";
+       Direction orderDirection() default Direction.ASC;
+       QueryDirection queryDirection() default QueryDirection.DESTINATION;
+       Class<? extends FieldConverter> convertor() default 
PassThroughFieldConverter.class;
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Embedded.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Entity.java
===================================================================
--- 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Entity.java
                         (rev 0)
+++ 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Entity.java
 2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,13 @@
+package nl.vpro.mmbase.vob.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+...@retention(RetentionPolicy.RUNTIME)
+...@target(ElementType.TYPE)
+public @interface Entity {
+       String builder();
+       boolean root() default false;
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Entity.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Field.java
===================================================================
--- 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Field.java
                          (rev 0)
+++ 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Field.java
  2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,16 @@
+package nl.vpro.mmbase.vob.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import nl.vpro.mmbase.vob.converters.FieldConverter;
+import nl.vpro.mmbase.vob.converters.PassThroughFieldConverter;
+
+...@retention(RetentionPolicy.RUNTIME)
+...@target(ElementType.FIELD)
+public @interface Field {
+       String nodeField() default "";
+       Class<? extends FieldConverter> convertor() default 
PassThroughFieldConverter.class;
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Field.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/PosRel.java
===================================================================
--- 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/PosRel.java
                         (rev 0)
+++ 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/PosRel.java
 2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,19 @@
+package nl.vpro.mmbase.vob.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import nl.vpro.mmbase.vob.Direction;
+import nl.vpro.mmbase.vob.QueryDirection;
+
+
+...@retention(RetentionPolicy.RUNTIME)
+...@target(ElementType.FIELD)
+...@rel
+public @interface PosRel  {
+       Direction orderDirection() default Direction.ASC;
+
+       QueryDirection queryDirection() default QueryDirection.DESTINATION;
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/PosRel.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Rel.java
===================================================================
--- 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Rel.java
                            (rev 0)
+++ 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Rel.java
    2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,20 @@
+package nl.vpro.mmbase.vob.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import nl.vpro.mmbase.vob.Direction;
+import nl.vpro.mmbase.vob.QueryDirection;
+
+
+...@retention(RetentionPolicy.RUNTIME)
+...@target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
+public @interface Rel {
+       String orderField() default "number";
+
+       Direction orderDirection() default Direction.ASC;
+
+       QueryDirection queryDirection() default QueryDirection.DESTINATION;
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/annotations/Rel.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/converters/EpochDateConverter.java
===================================================================
--- 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/converters/EpochDateConverter.java
                              (rev 0)
+++ 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/converters/EpochDateConverter.java
      2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,23 @@
+package nl.vpro.mmbase.vob.converters;
+
+import java.util.Date;
+
+import nl.vpro.mmbase.vob.converters.FieldConverter;
+
+import org.mmbase.bridge.Node;
+
+/**
+ * Converts 'seconds from the epoch' to a real date object.
+ * 
+ * @author [email protected]
+ */
+public class EpochDateConverter implements FieldConverter {
+
+       /* (non-Javadoc)
+        * @see 
nl.vpro.mmbase.vob.convertors.FieldConvertor#convert(org.mmbase.bridge.Node, 
java.lang.String)
+        */
+       public Object convert(Node node, String field) {
+               return new Date(node.getLongValue(field) * 1000L);
+       }
+
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/converters/EpochDateConverter.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/converters/FieldConverter.java
===================================================================
--- 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/converters/FieldConverter.java
                          (rev 0)
+++ 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/converters/FieldConverter.java
  2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,15 @@
+package nl.vpro.mmbase.vob.converters;
+
+/**
+ * Convertors are used to transform the mmbase fieldvalue to the wanted type.
+ * 
+ * @author [email protected]
+ */
+public interface FieldConverter {
+       /**
+        * @param node The node containing the field
+        * @param field The name of the field to convert
+        * @return the resulting value
+        */
+       Object convert(org.mmbase.bridge.Node node, String field);
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/converters/FieldConverter.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/converters/PassThroughFieldConverter.java
===================================================================
--- 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/converters/PassThroughFieldConverter.java
                               (rev 0)
+++ 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/converters/PassThroughFieldConverter.java
       2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,11 @@
+package nl.vpro.mmbase.vob.converters;
+
+import org.mmbase.bridge.Node;
+
+public class PassThroughFieldConverter implements FieldConverter {
+
+       public Object convert(Node node, String field) {
+               return node.getObjectValue(field);
+       }
+
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/main/java/nl/vpro/mmbase/vob/converters/PassThroughFieldConverter.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/Image.java
===================================================================
--- speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/Image.java          
                (rev 0)
+++ speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/Image.java  
2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,34 @@
+package nl.vpro.mmbase;
+
+import nl.vpro.mmbase.vob.annotations.Entity;
+
+...@entity(builder = "images")
+public class Image {
+       private Long number;
+       private String title;
+
+       public Long getNumber() {
+               return number;
+       }
+
+       public void setNumber(Long number) {
+               this.number = number;
+       }
+
+       public String getTitle() {
+               return title;
+       }
+
+       public void setTitle(String title) {
+               this.title = title;
+       }
+       
+       public String getUrl() {
+               return String.format("http://images.vpro.nl/%d";, number);
+       }
+       
+       @Override
+       public String toString() {
+               return String.format("Image[%d, %s]", number, title);
+       }
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/Image.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/NewsItem.java
===================================================================
--- speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/NewsItem.java       
                        (rev 0)
+++ speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/NewsItem.java       
2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,117 @@
+package nl.vpro.mmbase;
+
+import java.util.Date;
+import java.util.List;
+
+import nl.vpro.mmbase.converters.ToLowercaseConverter;
+import nl.vpro.mmbase.vob.Direction;
+import nl.vpro.mmbase.vob.QueryDirection;
+import nl.vpro.mmbase.vob.annotations.Embedded;
+import nl.vpro.mmbase.vob.annotations.Entity;
+import nl.vpro.mmbase.vob.annotations.Field;
+import nl.vpro.mmbase.vob.annotations.PosRel;
+import nl.vpro.mmbase.vob.annotations.Rel;
+import nl.vpro.mmbase.vob.converters.EpochDateConverter;
+
+...@entity(builder = "news", root=true)
+public class NewsItem {
+
+       private Long number;
+       private String title;
+       
+       @Field(convertor = ToLowercaseConverter.class)
+       private String subtitle;
+       
+       private String credits;
+
+       @Field(nodeField = "intro")
+       private String description;
+
+       private String body;
+
+       @Embedded(builder = "mmevents", relationRole="posrel", queryDirection = 
QueryDirection.BOTH, field = "start", convertor = EpochDateConverter.class)
+       private Date created;
+
+       @PosRel(orderDirection = Direction.DESC, queryDirection = 
QueryDirection.SOURCE)
+       private List<Image> images;
+
+       @Rel(orderDirection = Direction.DESC, orderField = "value", 
queryDirection = QueryDirection.SOURCE)
+       private List<Tag> tags;
+
+       public String getTitle() {
+               return title;
+       }
+
+       public void setTitle(String title) {
+               this.title = title;
+       }
+
+       public String getDescription() {
+               return description;
+       }
+
+       public void setDescription(String description) {
+               this.description = description;
+       }
+
+       public Long getNumber() {
+               return number;
+       }
+
+       public void setNumber(Long number) {
+               this.number = number;
+       }
+
+       public String getSubtitle() {
+               return subtitle;
+       }
+
+       public void setSubtitle(String subtitle) {
+               this.subtitle = subtitle;
+       }
+
+       public String getBody() {
+               return body;
+       }
+
+       public void setBody(String body) {
+               this.body = body;
+       }
+
+       public String getCredits() {
+               return credits;
+       }
+
+       public void setCredits(String credits) {
+               this.credits = credits;
+       }
+
+       public Date getCreated() {
+               return created;
+       }
+
+       public void setCreated(Date created) {
+               this.created = created;
+       }
+
+       public List<Image> getImages() {
+               return images;
+       }
+
+       public void setImages(List<Image> images) {
+               this.images = images;
+       }
+
+       public List<Tag> getTags() {
+               return tags;
+       }
+
+       public void setTags(List<Tag> tags) {
+               this.tags = tags;
+       }
+
+       @Override
+       public String toString() {
+               return String.format("News[%d, %s]", number, title);
+       }
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/NewsItem.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/Tag.java
===================================================================
--- speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/Tag.java            
                (rev 0)
+++ speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/Tag.java    
2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,30 @@
+package nl.vpro.mmbase;
+
+import nl.vpro.mmbase.vob.annotations.Entity;
+
+...@entity(builder = "tags")
+public class Tag {
+       private Long number;
+       private String value;
+
+       public String getValue() {
+               return value;
+       }
+
+       public void setValue(String value) {
+               this.value = value;
+       }
+
+       public Long getNumber() {
+               return number;
+       }
+
+       public void setNumber(Long number) {
+               this.number = number;
+       }
+
+       @Override
+       public String toString() {
+               return String.format("Tag[%d, %s]", number, value);
+       }
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/Tag.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: 
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/converters/ToLowercaseConverter.java
===================================================================
--- 
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/converters/ToLowercaseConverter.java
                                (rev 0)
+++ 
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/converters/ToLowercaseConverter.java
        2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,13 @@
+package nl.vpro.mmbase.converters;
+
+import org.mmbase.bridge.Node;
+
+import nl.vpro.mmbase.vob.converters.FieldConverter;
+
+public class ToLowercaseConverter implements FieldConverter {
+
+       public Object convert(Node node, String field) {
+               return node.getStringValue(field).toLowerCase();
+       }
+
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/converters/ToLowercaseConverter.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: 
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/vob/NoResultsQueryHelper.java
===================================================================
--- 
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/vob/NoResultsQueryHelper.java
                               (rev 0)
+++ 
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/vob/NoResultsQueryHelper.java
       2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,21 @@
+package nl.vpro.mmbase.vob;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.mmbase.bridge.Cloud;
+import org.mmbase.bridge.Node;
+
+public class NoResultsQueryHelper implements QueryHelper {
+
+    public List<Node> query(Cloud cloud, int startNumber, String path, String 
fields, String sortField,
+            Direction dir, QueryDirection queryDir) {
+        return Collections.emptyList();
+    }
+
+    public List<Node> query(Cloud cloud, int startNumber, String path, String 
fields, String sortField,
+            Direction dir, QueryDirection queryDir, int max) {
+        return Collections.emptyList();
+    }
+
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/vob/NoResultsQueryHelper.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: 
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/vob/PathFragmentToNodenrQueryHelper.java
===================================================================
--- 
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/vob/PathFragmentToNodenrQueryHelper.java
                            (rev 0)
+++ 
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/vob/PathFragmentToNodenrQueryHelper.java
    2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,43 @@
+package nl.vpro.mmbase.vob;
+
+import java.util.*;
+
+import org.mmbase.bridge.Cloud;
+import org.mmbase.bridge.Node;
+import org.mmbase.bridge.util.MapNode;
+
+public class PathFragmentToNodenrQueryHelper extends  NoResultsQueryHelper{
+    private String path;
+    Map<String, ? extends Object> map;
+
+    public PathFragmentToNodenrQueryHelper(String nodeType, int 
nodenrToReturn) {
+        path = nodeType;
+        this.map = Collections.singletonMap(nodeType + ".number", new 
Integer(nodenrToReturn));
+    }
+
+    public PathFragmentToNodenrQueryHelper(String nodeType, int 
nodenrToReturn, String relationRole) {
+        path = relationRole + "," + nodeType;
+        this.map = Collections.singletonMap(nodeType + ".number", new 
Integer(nodenrToReturn));
+    }
+    
+    public PathFragmentToNodenrQueryHelper(String nodeType, String 
relationRole, Map<String, ? extends Object> returnValuesMap) {
+        this.path = relationRole + "," + nodeType;
+        this.map = returnValuesMap;
+    }
+
+    public List<Node> query(Cloud cloud, int startNumber, String path, String 
fields, String sortField, Direction dir,
+            QueryDirection queryDir) {
+
+        if (path.contains(this.path)) {
+            MapNode dummyNode = new MapNode(map, cloud);
+            return Collections.singletonList((Node) dummyNode);
+        } else {
+            return Collections.emptyList();
+        }
+    }
+
+    public List<Node> query(Cloud cloud, int startNumber, String path, String 
fields, String sortField, Direction dir,
+            QueryDirection queryDir, int max) {
+        return query(cloud, startNumber, path, fields, sortField, dir, 
queryDir).subList(0, max);
+    }
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/vob/PathFragmentToNodenrQueryHelper.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: 
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/vob/PopulatorTest.java
===================================================================
--- 
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/vob/PopulatorTest.java  
                            (rev 0)
+++ 
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/vob/PopulatorTest.java  
    2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,170 @@
+package nl.vpro.mmbase.vob;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+import java.beans.FeatureDescriptor;
+import java.util.*;
+
+import nl.vpro.mmbase.Image;
+import nl.vpro.mmbase.NewsItem;
+
+import org.junit.Test;
+import org.mmbase.bridge.Cloud;
+import org.mmbase.bridge.Node;
+import org.mmbase.bridge.util.MapNode;
+
+public class PopulatorTest {
+
+    @Test
+    public void testUnmarshallNode() throws IllegalArgumentException, 
IllegalAccessException, SecurityException,
+            NoSuchFieldException {
+        Populator populator = new Populator(new NoResultsQueryHelper(), 
"nl.vpro");
+
+//        Map<String, Object> map = createDefaultNodeForNewsitem((Cloud) null);
+        Node newsItemNode = createDefaultNodeForNewsitem((Cloud) null);
+
+        NewsItem item = (NewsItem) populator.unmarshallNode(newsItemNode, 
"news");
+
+        assertEquals(Long.valueOf(1234), item.getNumber());
+        assertEquals("title", item.getTitle());
+    }
+
+    /**
+     * The field annotation can be used with just a converter
+     */
+    @Test
+    public void testFieldConverterOnFieldAnnotation() throws 
IllegalArgumentException, IllegalAccessException,
+            SecurityException, NoSuchFieldException {
+        Populator populator = new Populator(new NoResultsQueryHelper(), 
"nl.vpro");
+
+        Map<String, Object> map = new Mapper().put("number", 
Long.valueOf(1234)).put("subtitle", "Subtitle").getMap();
+        MapNode newsItemNode = new MapNode(map, (Cloud) null);
+
+        NewsItem item = (NewsItem) populator.unmarshallNode(newsItemNode, 
"news");
+
+        assertEquals(Long.valueOf(1234), item.getNumber());
+        assertEquals("subtitle", item.getSubtitle());
+    }
+
+    @Test
+    public void testUnmarshallNodeWithAssociations() throws 
IllegalArgumentException, IllegalAccessException,
+            SecurityException, NoSuchFieldException {
+        Cloud cloud = createMock(Cloud.class);
+        Map<String, Object> map = new Mapper().put("number", 
Long.valueOf(12345)).put("title", "image title").getMap();
+        MapNode imageNode = new MapNode(map, (Cloud) null);
+
+        expect(cloud.getNode(12345)).andReturn((Node) imageNode);
+        replay(cloud);
+
+        Populator populator = new Populator(new 
PathFragmentToNodenrQueryHelper("images", 12345), "nl.vpro");
+        map = new Mapper()  
+            .put("number", Long.valueOf(1234))
+            .put("title", "title").getMap();
+        MapNode newsItemNode = new MapNode(map, cloud);
+
+        NewsItem item = (NewsItem) populator.unmarshallNode(newsItemNode, 
"news");
+
+        assertEquals(Long.valueOf(1234), item.getNumber());
+        assertEquals("title", item.getTitle());
+        assertEquals(1, item.getImages().size());
+        Image image = item.getImages().get(0);
+        assertEquals(new Long(12345), image.getNumber());
+        assertEquals("image title", image.getTitle());
+        verify(cloud);
+    }
+    
+    @Test
+    public void testEntityIsRoot(){
+        Populator populator = new Populator(null, "nl.vpro");
+        Node node = new MapNode(new Mapper()
+            .put("number", Long.valueOf(1234))
+            .getMap(), (Cloud) null);
+        
+        assertTrue(Populator.entityIsRoot(populator.unmarshallNode(node, 
"news")));
+        assertFalse(Populator.entityIsRoot(populator.unmarshallNode(node, 
"images")));
+    }
+
+    @Test
+    public void testUnmashallNodeWithEmbedded() {
+        
+        Map<String, Object> returnValuesMap = new Mapper()
+        .put("mmevents.number", Long.valueOf(12345))
+        .put("mmevents.start", 
Long.valueOf(createMyBirthday().getTimeInMillis() / 1000L))
+        .getMap();
+        PathFragmentToNodenrQueryHelper queryHelper = new 
PathFragmentToNodenrQueryHelper("mmevents", "posrel", returnValuesMap);
+        Populator populator = new Populator(queryHelper, "nl.vpro");
+
+        Node createDefaultNodeForNewsitem = createDefaultNodeForNewsitem(null);
+        NewsItem item = (NewsItem) 
populator.unmarshallNode(createDefaultNodeForNewsitem, "news");
+        
+        assertNotNull(item.getCreated());
+        Calendar c = new GregorianCalendar();
+        c.setTime(item.getCreated());
+        assertEquals(1970, c.get(Calendar.YEAR));
+        assertEquals(Calendar.FEBRUARY, c.get(Calendar.MONTH));
+        assertEquals(19, c.get(Calendar.DAY_OF_MONTH));
+    }
+    
+    
+
+    /**
+     * 
+     * @param cloud use mock cloud if this node is triggering a query
+     * @return
+     */
+    private Node createDefaultNodeForNewsitem(Cloud cloud) {
+        Map<String,Object> map = new Mapper()  
+        .put("number", Long.valueOf(1234))
+        .put("title", "title").getMap();
+        MapNode newsItemNode = new MapNode(map, cloud);
+        return newsItemNode;
+    }
+    
+    private Cloud createMockCloud(Map<String,Object> node, int nodenr){
+        Cloud cloud = createMock(Cloud.class);
+        if(node != null){
+            MapNode mmeventsNode = new MapNode(node, (Cloud) null);
+            expect(cloud.getNode(nodenr)).andReturn((Node) mmeventsNode);
+        }
+        replay(cloud);
+        return cloud;
+    }
+
+    private Calendar createMyBirthday() {
+        Calendar c = new GregorianCalendar();
+        c.set(1970, Calendar.FEBRUARY, 19);
+        return c;
+    }
+
+    //@Test
+    public void testFindEntityThatEmbeds() {
+        Populator populator = new Populator(new 
PathFragmentToNodenrQueryHelper("mmevents", 12345, "posrel"), "nl.vpro");
+        Class<?> c = populator.findEntityThatEmbeds("posrel", "mmevents");
+        assertEquals(NewsItem.class, c);
+
+        c = populator.findEntityThatEmbeds("related", "mmevents");
+        assertNull(c);
+    }
+
+    // @Test
+    public void thePopulatorCanDetermineBuildersFromEntities() {
+        assertEquals("images", Populator.determineBuilder(Image.class));
+        assertEquals("news", Populator.determineBuilder(NewsItem.class));
+    }
+
+
+
+    public static class Mapper {
+        private Map<String, Object> wrapped = new HashMap<String, Object>();
+
+        public Mapper put(String key, Object value) {
+            wrapped.put(key, value);
+            return this;
+        }
+
+        public Map<String, Object> getMap() {
+            return wrapped;
+        }
+    }
+}


Property changes on: 
speeltuin/ernst/mmbase-vob/src/test/java/nl/vpro/mmbase/vob/PopulatorTest.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: speeltuin/ernst/mmbase-vob/src/test/resources/log4j.properties
===================================================================
--- speeltuin/ernst/mmbase-vob/src/test/resources/log4j.properties              
                (rev 0)
+++ speeltuin/ernst/mmbase-vob/src/test/resources/log4j.properties      
2009-05-28 08:32:26 UTC (rev 35463)
@@ -0,0 +1,12 @@
+//Example log4j Configuration File 
+/*
+#set the level of the root logger to DEBUG and set its appender
+as an appender named X
+log4j.rootLogger = INFO, X
+
+#set the appender named X to be a console appender
+log4j.appender.X=org.apache.log4j.ConsoleAppender
+
+#set the layout for the appender X
+log4j.appender.X.layout=org.apache.log4j.PatternLayout
+log4j.appender.X.layout.conversionPattern=%m%n


Property changes on: 
speeltuin/ernst/mmbase-vob/src/test/resources/log4j.properties
___________________________________________________________________
Name: svn:mime-type
   + text/plain

_______________________________________________
Cvs mailing list
[email protected]
http://lists.mmbase.org/mailman/listinfo/cvs

Reply via email to