I've just been thinking about a way to avoid to define a Spec Transformer for 
every Entity class in the Domain.

As it's just "infrastructure software", I thought that was "just enough". 

The attached code allows to use just one Spec Transformer when the Glue is 
written like this:
-  the employee with name "PETER"
- the employee named "PETER"
- the property with reference "REF-001"
- the property referenced "REF-001"
- the product with id "PR-001" 

From there, we know that:
- The Entity singular name is "employee" (so it can be obtained from the Isis 
Object Specifications, reinforcing the Ubiquitous Language use on BDD 
specifications).
- We must search by name
- The name must be PETER



For using it, I must define a Glue like this one:

Define a Glue like this one:
@When("The company (employee with name \"[^\"]*\") has a role assigned")
public void 
the_company_employee_with_name(@Transform(GenericIsisJdoTransformer.class) 
final  Employee employee) {
   ...
}



First "proof-of-concept" code version follows this text.

It can be improved in many ways, for allowing customization through 
inheritance, avoiding JDO through the use of the Apache Isis query methods, etc.


But I would let you know, right to know if you think it can be useful. 

On this way, the only implemented classes  are the ones supporting the Glues 
(and only the Spec Transformers for more specific use cases).


HTH, 

Oscar





---------------------




package com.xms.framework.testing.integration.spectransformers;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.jdo.Query;

import org.opensaml.artifact.InvalidArgumentException;

import org.apache.isis.applib.DomainObjectContainer;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
import org.apache.isis.core.runtime.system.context.IsisContext;
import org.apache.isis.core.specsupport.scenarios.ScenarioExecution;
import 
org.apache.isis.objectstore.jdo.datanucleus.service.support.IsisJdoSupportImpl;

/**
 * Requires the Gherkin's capture in the format '([entitySingularName] [(.+?)
 * name(d)|(.+?) reference|(.+?) id] \"[entityId]\")'.
 * <p>
 * For example:
 * <ul>
 * <li>the employee with name "PETER"</li>
 * <li>the employee named "PETER"</li>
 * <li>the property with reference "REF-001"</li>
 * <li>the property referenced "REF-001"</li>
 * <li>the product with id "PR-001"</li>
 * </ul>
 * <p>
 * For matching the first one we will need the following Gherkin regular
 * expression:
 * <ul>
 * <li>
 * \\@When("The company's (employee with name \"[^\"]*\") has a role 
assigned")</li>
 * </ul>
 * <p>
 * From there, we know that:
 * <ul>
 * <li>The Entity singular name is "employee".</li>
 * <li>We must search by name</li>
 * <li>The name must be PETER</li>
 * </ul>
 * 
 */
public class GenericIsisJdoTransformer extends 
NullRecognizingTransformer<Object> {

    private static Map<String, ObjectSpecification> 
specificationsBySingularName;

    /**
     * Tries to obtain the Entity class name, id and search field from the
     * Cucumber capture, and find it on the JDO Object Store.
     * 
     * @see com.xms.framework.testing.integration.spectransformers.
     *      NullRecognizingTransformer#transformNonNull(java.lang.String)
     */
    @Override
    protected Object transformNonNull(final String capture) {

        return this.findFromCapture(capture);

    }

    /**
     * @param entityName
     *            Name of the Entity specified on the Gherkin's capture.
     * @return
     */
    private ObjectSpecification specificationOf(final String entityName) {

        if (IsisJdoTransformer.specificationsBySingularName == null) {
            IsisJdoTransformer.specificationsBySingularName = new 
HashMap<String, ObjectSpecification>();

            for (final ObjectSpecification current : 
IsisContext.getSpecificationLoader().allSpecifications()) {

                
IsisJdoTransformer.specificationsBySingularName.put(current.getSingularName().toLowerCase(),
 current);
            }

        }
        return 
IsisJdoTransformer.specificationsBySingularName.get(entityName.toLowerCase());

    }

    private final Class<?> entityClassFrom(final String capture) {

        // The Entity Id will be between "".
        String entityName = "";

        final String[] recognizedPatterns = { "( with name )", "( named )", "( 
with reference )", "( referenced )", "( with id )" };

        for (final String currentPattern : recognizedPatterns) {
            final Pattern p = Pattern.compile(currentPattern);
            final Matcher m = p.matcher(capture);
            if (m.find()) {
                entityName = capture.substring(0, m.start());
                break;
            }
        }

        if (entityName == "") {
            throw new InvalidArgumentException(String.format("Cannot find the 
entity's name on the capture %s. The format must be '([entitySingularName] 
[with name|with reference|named|referenced] \"[entityId]\")'", capture));
        }

        final ObjectSpecification specification = 
this.specificationOf(entityName);
        if (specification == null) {
            throw new InvalidArgumentException(String.format("There is no 
Entity registered in Isis with '%s' as it's singular name", entityName));
        }
        return specification.getCorrespondingClass();

    }

    /**
     * @param capture
     *            The Gherkin's capture
     * @return
     */
    private final String filterFrom(final String capture) {
        // Find by "name".
        if (capture.matches("(.+?)name(.+?)")) {
            return String.format("name==\"%s\"", this.entityIdFrom(capture));
        }

        // Find by "reference".
        if (capture.matches("(.+?)reference(.+?)")) {
            return String.format("reference==\"%s\"", 
this.entityIdFrom(capture));
        }

        // Find by "id".
        if (capture.matches("(.+?)id(.+?)")) {
            return String.format("id==\"%s\"", this.entityIdFrom(capture));
        }

        throw new InvalidArgumentException(String.format("The entity id has not 
been found on the capture '%s'. It must be between two \" characters.", 
capture));
    }

    private final Object entityIdFrom(final String capture) {
        // The Entity Id will be between "".
        final Pattern p = Pattern.compile("\"(.+?)\"");
        final Matcher m = p.matcher(capture);
        if (m.find()) {
            return m.group().replace("\"", "");
        } else {
            throw new InvalidArgumentException(String.format("The entity id has 
not been found on the capture '%s'. It must be between two \" characters.", 
capture));
        }
    }

    private final Object findFromCapture(final String capture) {

        // Need to flush().
        // The Entity can be created on the same transaction.
        // If we don't flush() the changes, perhaps the entity have not been
        // saved to the object store yet, and will not be found.
        
ScenarioExecution.current().service(DomainObjectContainer.class).flush();

        final Query query = 
ScenarioExecution.current().service(IsisJdoSupportImpl.class).getJdoPersistenceManager().newQuery();
        query.setClass(this.entityClassFrom(capture));
        query.setFilter(this.filterFrom(capture));
        query.setUnique(true);

        final Object result = query.execute();

        return result;

    }
}




---------------------





El 27/08/2013, a las 20:24, Kevin Meyer - KMZ <ke...@kmz.co.za> escribió:

> Hi Jeremy,
> 
> If I remember correctly, Dan has examples for overriding the "Finder" 
> methods in the ToDo demo / artefact that use JDO search classes to 
> build queries.
> 
> In fact:
> 
> In the  "ToDoItemsJdo" class (which extends an 
> AbstractFactoryAndRepository):
>    // {{ notYetComplete (action)
>    @Override
>    protected List<ToDoItem> doNotYetComplete() {
>        return allMatches(
>                new QueryDefault<ToDoItem>(ToDoItem.class, 
>                        "todo_notYetComplete", 
>                        "ownedBy", currentUserName()));
>    }
>    // }}
> 
> This appears in an old copy of the repo I have in the wicket JDO 
> quickstart project.
> 
> So while you can't use "FindByPattern", JDO has implemented a 
> useful alternative.
> 
> Regards,
> Kevin
> 
> 
> On 27 Aug 2013 at 14:37, Jeremy Gurr wrote:
> 
>> I'm playing around with the cucumber support tools in isis (a testing 
>> framework for behavior driven development, for those who don't know), and 
>> have created a test that basically looks like this:
>> 
>> <snip>
>> 
>> It's very convenient that cucumber instantiates my ServiceClass model object 
>> and automatically plugs in fields according to the feature spec column 
>> header. This enables me to add new fields simply by adding a column in the 
>> spec, and adding the corresponding column in my model object. It skips the 
>> extra hassle of having to update the glue code as well as service methods to 
>> construct the object.
>> 
>> The "exists" method contains this code:
>> 
>> public boolean exists(ServiceClass serviceClass) {
>> final QueryFindByPattern<ServiceClass> query = new 
>> QueryFindByPattern<ServiceClass>(ServiceClass.class, serviceClass);
>> final ServiceClass firstMatch = firstMatch(query);
>> 
>> return firstMatch != null;
>> }
>> 
>> I'm just trying to verify that an object exists with fields matching the 
>> incoming object, some fields of which may be null, meaning that they can be 
>> any value. The QueryFindByPattern class seemed to be a perfect fit since I'm 
>> passing around ServiceClass instances anyway. However, running it executes 
>> code that is not yet implemented:
>> 
>> <snip>

Reply via email to