incubator-isis-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Mike Burton <mi...@mycosystems.co.uk>
Subject Re: More on the Oid refactor
Date Sat, 05 May 2012 15:51:51 GMT
Hi Dan,

Thanks for the update, good work, worth eating a few bits of eggshell:-)

Could you list/ clarify meanings of your canonical string for symbols #~! etc please- I'm
confused by difference between ~ and # eg
CUS:123~NME:2~CTY:LON 

Mike Burton
( iPhone)


On 5 May 2012, at 10:13, Dan Haywood <dan@haywood-associates.co.uk> wrote:

> Hello all,
> 
> First, just to say that I *know* the build is broken, and it's a pretty
> poor show on my part to let it happen, but it's a little bit of breaking
> eggs to make an omelette.
> 
> Second, wanted to update on my current thinking on OIDs ... because this is
> what I'm currently working on and what I intend to commit as soon as I can
> (hopefully this coming week).
> 
> Already committed:
> * our OIDs are now immutable
> * we have three types of OID:
>   - RootOid, which is the OID of a root entity  (implementation: is
> RootOidDefault).
>   - AggregateOid, which is an aggregated entity that is persisted within a
> root entity (eg part of a JSON doc in MongoDB).
>   - CollectionOid, which wraps a List or Set of a root entity
> - The RootOidDefault was refactored from SerialOid, but it now has an
> "objectType" (eg "CUS" for customer), so is self-describing.
> - The CollectionOid also existed previously, and wraps a List or Set for an
> entity
> - The AggregatedOid we also had a version of; it has a localId to
> distinguish it from other aggregates of its type (within its parent root)
> - Both CollectionOid and AggregatedOid have a parentOid
> 
> 
> Not yet committed:
> 
> The first thing I've added to AggregateOid is its own objectType, and so
> that has also led me to introduce a TypedOid interface: both RootOidDefault
> and AggregatedOid implement TypedOid.  And so from there we can say that
> parents (of either AggregateOids or of CollectionOids) are always TypedOids.
> 
> With this change to AggregateOid, we can now say that all OIDs contain
> enough information to fully recreate them as objects: we do know the type
> of the object to instantiate and its unique identity.  That isn't to say
> there's enough information to determine how these objects are wired back
> together again, but that's a responsibility of the object store to persist
> this additional structural information.
> 
> ~~~
> Following on from this, I've been working on a string format for each of
> these three types.  Currently we have numerous incompatible ways of
> memo-izing OIDs: the XML object store does it one way, the NOSQL object
> store another, the SQL OS object store another, the "*Mapping" classes for
> the scimpi and HTML viewers (for storing the states of transient objects in
> the Http session) in another, the Memento utility class in yet another, the
> JSON viewer (in its URLs) yet another.  It's all very complex and messy.
> 
> Having a standard canonical string form would therefore simplify things
> enormously.
> 
> I've just been writing the unit tests for this, and I've come up with the
> following:
> 
> CUS:123                       - persistent root
> !CUS:123                      - transient root
> CUS:123#items                 - collection of persistent root
> !CUS:123#items                - collection of transient root
> CUS:123#NME:2                 - aggregated object within persistent root
> !CUS:123~NME:2                - aggregated object within transient root
> CUS:123~NME:2~CTY:LON         - aggregated object within aggregated object
> within root
> CUS:123~NME:2#items           - collection of an aggregated object within
> root
> CUS:123~NME:2~CTY:LON#streets - collection of an aggregated object within
> aggregated object within root
> 
> I've pasted in the unit tests for this below, so you can see the tests in
> all its glory.
> 
> Note how this allows for aggregates to be embedded within aggregates (even
> though I'm not sure that any of the object stores could handle this yet).
> 
> OK, that's it for now.  Wanted to put this out there for reaction; as usual
> with these things, I'll assume silence = consensus.
> 
> Dan
> ~~~~~~
> 
> 
> package org.apache.isis.core.metamodel.adapter.oid;
> 
> import static org.hamcrest.CoreMatchers.equalTo;
> import static org.hamcrest.CoreMatchers.is;
> import static org.junit.Assert.assertThat;
> 
> import org.junit.Before;
> import org.junit.Test;
> 
> /**
> * <dt>CUS:123</dt>
> * <dd>persistent root</dd>
> * <dt>!CUS:123</dt>
> * <dd>transient root</dd>
> * <dt>CUS:123#items</dt>
> * <dd>collection of persistent root</dd>
> * <dt>!CUS:123#items</dt>
> * <dd>collection of transient root</dd>
> * <dt>CUS:123#NME:2</dt>
> * <dd>aggregated object within persistent root</dd>
> * <dt>!CUS:123~NME:2</dt>
> * <dd>aggregated object within transient root</dd>
> * <dt>CUS:123~NME:2~CTY:LON</dt>
> * <dd>aggregated object within aggregated object within root</dd>
> * <dt>CUS:123~NME:2#items</dt>
> * <dd>collection of an aggregated object within root</dd>
> * <dt>CUS:123~NME:2~CTY:LON#streets</dt>
> * <dd>collection of an aggregated object within aggregated object within
> root</dd>
> */
> public class OidMarshallerTest_unmarshal {
> 
>    private OidMarshaller oidMarshaller;
> 
>    @Before
>    public void setUp() throws Exception {
>        oidMarshaller = new OidMarshaller();
>    }
> 
>    @Test
>    public void persistentRoot() {
>        final String oidStr = "CUS:123";
> 
>        final RootOidDefault rootOid = oidMarshaller.unmarshal(oidStr,
> RootOidDefault.class);
>        assertThat(rootOid.isTransient(), is(false));
>        assertThat(rootOid.getObjectType(), is("CUS"));
>        assertThat(rootOid.getIdentifier(), is("123"));
> 
>        final Oid oid = oidMarshaller.unmarshal(oidStr, Oid.class);
>        assertThat(oid, equalTo((Oid)rootOid));
>    }
> 
>    @Test
>    public void transientRoot() {
>        final String oidStr = "!CUS:123";
> 
>        final RootOidDefault rootOid = oidMarshaller.unmarshal(oidStr,
> RootOidDefault.class);
>        assertThat(rootOid.isTransient(), is(true));
>        assertThat(rootOid.getObjectType(), is("CUS"));
>        assertThat(rootOid.getIdentifier(), is("123"));
> 
>        final Oid oid = oidMarshaller.unmarshal(oidStr, Oid.class);
>        assertThat(oid, equalTo((Oid)rootOid));
>    }
> 
>    @Test
>    public void collectionOfPersistentRoot() {
>        final String oidStr = "CUS:123$items";
> 
>        final CollectionOid collectionOid = oidMarshaller.unmarshal(oidStr,
> CollectionOid.class);
>        assertThat(collectionOid.isTransient(), is(false));
>        assertThat(collectionOid.getParentOid(),
> is((TypedOid)oidMarshaller.unmarshal("CUS:123", RootOidDefault.class)));
>        assertThat(collectionOid.getName(), is("items"));
> 
>        final Oid oid = oidMarshaller.unmarshal(oidStr, Oid.class);
>        assertThat(oid, equalTo((Oid)collectionOid));
>    }
> 
>    @Test
>    public void collectionOfTransientRoot() {
>        final String oidStr = "!CUS:123$items";
> 
>        final CollectionOid collectionOid = oidMarshaller.unmarshal(oidStr,
> CollectionOid.class);
>        assertThat(collectionOid.isTransient(), is(true));
>        assertThat(collectionOid.getParentOid(),
> is((TypedOid)oidMarshaller.unmarshal("!CUS:123", RootOidDefault.class)));
>        assertThat(collectionOid.getName(), is("items"));
> 
>        final Oid oid = oidMarshaller.unmarshal(oidStr, Oid.class);
>        assertThat(oid, equalTo((Oid)collectionOid));
>    }
> 
>    @Test
>    public void aggregatedWithinPersistent() {
>        final String oidStr = "CUS:123~NME:2";
> 
>        final AggregatedOid aggregatedOid = oidMarshaller.unmarshal(oidStr,
> AggregatedOid.class);
>        assertThat(aggregatedOid.isTransient(), is(false));
>        assertThat(aggregatedOid.getParentOid(),
> is((TypedOid)oidMarshaller.unmarshal("CUS:123", RootOidDefault.class)));
>        assertThat(aggregatedOid.getObjectType(), is("NME"));
>        assertThat(aggregatedOid.getLocalId(), is("2"));
> 
>        final Oid oid = oidMarshaller.unmarshal(oidStr, Oid.class);
>        assertThat(oid, equalTo((Oid)aggregatedOid));
>    }
> 
>    @Test
>    public void aggregatedWithinTransient() {
>        final String oidStr = "!CUS:123~NME:2";
> 
>        final AggregatedOid aggregatedOid = oidMarshaller.unmarshal(oidStr,
> AggregatedOid.class);
>        assertThat(aggregatedOid.isTransient(), is(true));
>        assertThat(aggregatedOid.getParentOid(),
> is((TypedOid)oidMarshaller.unmarshal("!CUS:123", RootOidDefault.class)));
>        assertThat(aggregatedOid.getObjectType(), is("NME"));
>        assertThat(aggregatedOid.getLocalId(), is("2"));
> 
>        final Oid oid = oidMarshaller.unmarshal(oidStr, Oid.class);
>        assertThat(oid, equalTo((Oid)aggregatedOid));
>    }
> 
>    @Test
>    public void aggregatedWithinAggregatedWithinRoot() {
>        final String oidStr = "CUS:123~ADR:2~CTY:LON";
> 
>        final AggregatedOid aggregatedOid = oidMarshaller.unmarshal(oidStr,
> AggregatedOid.class);
>        assertThat(aggregatedOid.isTransient(), is(false));
>        assertThat(aggregatedOid.getParentOid(),
> is((TypedOid)oidMarshaller.unmarshal("CUS:123~ADR:2",
> AggregatedOid.class)));
>        assertThat(aggregatedOid.getObjectType(), is("CTY"));
>        assertThat(aggregatedOid.getLocalId(), is("LON"));
> 
>        final Oid oid = oidMarshaller.unmarshal(oidStr, Oid.class);
>        assertThat(oid, equalTo((Oid)aggregatedOid));
>    }
> 
>    @Test
>    public void collectionOfAggregatedWithinRoot() {
>        final String oidStr = "CUS:123~NME:2$items";
> 
>        final CollectionOid collectionOid = oidMarshaller.unmarshal(oidStr,
> CollectionOid.class);
>        assertThat(collectionOid.isTransient(), is(false));
>        assertThat(collectionOid.getParentOid(),
> is((TypedOid)oidMarshaller.unmarshal("CUS:123~NME:2",
> AggregatedOid.class)));
>        assertThat(collectionOid.getName(), is("items"));
> 
>        final Oid oid = oidMarshaller.unmarshal(oidStr, Oid.class);
>        assertThat(oid, equalTo((Oid)collectionOid));
>    }
> 
>    @Test
>    public void collectionOfAggregatedWithinAggregatedWithinRoot() {
>        final String oidStr = "CUS:123~ADR:2~CTY:LON$streets";
> 
>        final CollectionOid collectionOid = oidMarshaller.unmarshal(oidStr,
> CollectionOid.class);
>        assertThat(collectionOid.isTransient(), is(false));
>        assertThat(collectionOid.getParentOid(),
> is((TypedOid)oidMarshaller.unmarshal("CUS:123~ADR:2~CTY:LON",
> AggregatedOid.class)));
>        assertThat(collectionOid.getName(), is("streets"));
> 
>        final Oid oid = oidMarshaller.unmarshal(oidStr, Oid.class);
>        assertThat(oid, equalTo((Oid)collectionOid));
>    }
> 
> 
>    @Test(expected=IllegalArgumentException.class)
>    public void root_forCollection_oidStr() {
>        oidMarshaller.unmarshal("CUS:123~NME:123$items",
> RootOidDefault.class);
>    }
> 
>    @Test(expected=IllegalArgumentException.class)
>    public void root_forAggregated_oidStr() {
>        oidMarshaller.unmarshal("CUS:123~NME:123", RootOidDefault.class);
>    }
> 
>    @Test(expected=IllegalArgumentException.class)
>    public void collection_forRoot_oidStr() {
>        oidMarshaller.unmarshal("CUS:123", CollectionOid.class);
>    }
> 
>    @Test(expected=IllegalArgumentException.class)
>    public void collection_forAggregated_oidStr() {
>        oidMarshaller.unmarshal("CUS:123~NME:123", CollectionOid.class);
>    }
> 
> 
>    @Test(expected=IllegalArgumentException.class)
>    public void aggregated_forPersistentRoot_oidStr() {
>        oidMarshaller.unmarshal("CUS:123", AggregatedOid.class);
>    }
> 
>    @Test(expected=IllegalArgumentException.class)
>    public void aggregated_forTransientRoot_oidStr() {
>        oidMarshaller.unmarshal("!CUS:123", AggregatedOid.class);
>    }
> 
>    @Test(expected=IllegalArgumentException.class)
>    public void aggregated_forCollection_oidStr() {
>        oidMarshaller.unmarshal("CUS:123~NME:123$items",
> AggregatedOid.class);
>    }
> 
>    @Test(expected=IllegalArgumentException.class)
>    public void badPattern() {
>        oidMarshaller.unmarshal("xxx", RootOidDefault.class);
>    }
> 
> 
> }
> 
> 
> 
> 
> 
> 
> package org.apache.isis.core.metamodel.adapter.oid;
> 
> import java.util.Iterator;
> import java.util.List;
> import java.util.regex.Matcher;
> import java.util.regex.Pattern;
> 
> import com.google.common.base.Splitter;
> import com.google.common.base.Strings;
> import com.google.common.collect.Lists;
> 
> import org.apache.isis.core.commons.lang.CastUtils;
> import org.apache.isis.core.metamodel.adapter.oid.Oid.State;
> 
> /**
> * Factory for subtypes of {@link Oid}, based on their oid str.
> *
> * <p>
> * Examples
> * <dl>
> * <dt>CUS:123</dt>
> * <dd>persistent root</dd>
> * <dt>T~CUS:123</dt>
> * <dd>transient root</dd>
> * <dt>CUS:123#items</dt>
> * <dd>collecion of persistent root</dd>
> * <dt>T~CUS:123#items</dt>
> * <dd>collecion of transient root</dd>
> * <dt>CUS:123#NME:2</dt>
> * <dd>aggregated object within persistent root</dd>
> * <dt>T~CUS:123#NME:2</dt>
> * <dd>aggregated object within transient root</dd>
> * <dt>CUS:123#NME:2#CTY:LON</dt>
> * <dd>aggregated object within aggregated object within root</dd>
> * <dt>CUS:123#NME:2#items</dt>
> * <dd>collection of an aggregated object within root</dd>
> * <dt>CUS:123#NME:2#CTY:LON#streets</dt>
> * <dd>collection of an aggregated object within aggregated object within
> root</dd>
> * </dl>
> *
> * <p>
> * Separators:
> * <dl>
> * <dt>!</dt>
> * <dd>precedes root object type, indicates transient</dd>
> * <dt>:</dt>
> * <dd>precedes root object identifier</dd>
> * <dt>~</dt>
> * <dd>precedes aggregate oid</dd>
> * <dt>$</dt>
> * <dd>precedes collection name</dd>
> * </dl>
> * <p>
> * Note that # and ; were not chosen as separators to minimize noise when
> URL encoding OIDs.
> */
> public class OidMarshaller {
> 
>    private static Pattern OIDSTR_PATTERN =
> 
> Pattern.compile("^((([!])?([^:@#$]+):([^:@#$]+))((~[^:@#$]+:[^:@#$]+)*))([$]([^:@#$]+))?$");
> 
>    ////////////////////////////////////////////////////////////////
>    // unmarshall
>    ////////////////////////////////////////////////////////////////
> 
>    public <T extends Oid> T unmarshal(String oidStr, Class<T>
> requestedType) {
> 
>        final Matcher matcher = OIDSTR_PATTERN.matcher(oidStr);
>        if (!matcher.matches()) {
>            throw new IllegalArgumentException("Could not parse OID '" +
> oidStr + "'; should match pattern: " + OIDSTR_PATTERN.pattern());
>        }
> 
>        final int groupCount = matcher.groupCount();
> 
>        final String isTransientStr = getGroup(matcher, 3);
>        boolean isTransient = "!".equals(isTransientStr);
> 
>        final String oidStrWithoutCollectionName = getGroup(matcher, 1);
>        final String rootOidStr = getGroup(matcher, 2);
> 
>        final String rootObjectType = getGroup(matcher, 4);
>        final String rootIdentifier = getGroup(matcher, 5);
> 
>        final String aggregateOidPart = getGroup(matcher, 6);
>        final List<AggregateOidPart> aggregateOidParts =
> Lists.newArrayList();
>        final Splitter tildaSplitter = Splitter.on("~");
>        final Splitter colonSplitter = Splitter.on(":");
>        if(aggregateOidPart != null) {
>            final Iterable<String> tildaSplitIter =
> tildaSplitter.split(aggregateOidPart);
>            for(String str: tildaSplitIter) {
>                if(Strings.isNullOrEmpty(str)) {
>                    continue; // leading "~"
>                }
>                final Iterator<String> colonSplitIter =
> colonSplitter.split(str).iterator();
>                final String objectType = colonSplitIter.next();
>                final String localId = colonSplitIter.next();
>                aggregateOidParts.add(new AggregateOidPart(objectType,
> localId));
>            }
>        }
>        final String collectionName = getGroup(matcher, groupCount); //
> last one
> 
>        if(collectionName == null) {
>            if(aggregateOidParts.isEmpty()) {
>                ensureCorrectType(oidStr, requestedType,
> RootOidDefault.class);
>                return CastUtils.cast(new RootOidDefault(rootObjectType,
> rootIdentifier, State.valueOf(isTransient)));
>            } else {
>                ensureCorrectType(oidStr, requestedType,
> AggregatedOid.class);
>                final AggregateOidPart lastPart =
> aggregateOidParts.remove(aggregateOidParts.size()-1);
>                final TypedOid parentOid = parentOidFor(rootOidStr,
> aggregateOidParts);
>                return CastUtils.cast(new
> AggregatedOid(lastPart.objectType, parentOid, lastPart.localId));
>            }
>        } else {
>            TypedOid parentOid =
> this.unmarshal(oidStrWithoutCollectionName, TypedOid.class);
>            ensureCorrectType(oidStr, requestedType, CollectionOid.class);
>            return CastUtils.cast(new CollectionOid(parentOid,
> collectionName));
>        }
>    }
> 
>    private static class AggregateOidPart {
>        AggregateOidPart(String objectType, String localId) {
>            this.objectType = objectType;
>            this.localId = localId;
>        }
>        String objectType;
>        String localId;
>        public String toString() {
>            return "~" + objectType + ":" + localId;
>        }
>    }
> 
> 
>    private TypedOid parentOidFor(final String rootOidStr, final
> List<AggregateOidPart> aggregateOidParts) {
>        final StringBuilder buf = new StringBuilder(rootOidStr);
>        for(AggregateOidPart part: aggregateOidParts) {
>            buf.append(part.toString());
>        }
>        return unmarshal(buf.toString(), TypedOid.class);
>    }
> 
>    private <T> void ensureCorrectType(String oidStr, Class<T>
> requestedType, final Class<? extends Oid> actualType) {
>        if(!requestedType.isAssignableFrom(actualType)) {
>            throw new IllegalArgumentException("OID '" + oidStr + "' does
> not represent a " +
>            actualType.getSimpleName());
>        }
>    }
> 
>    private String getGroup(final Matcher matcher, final int group) {
>        final int groupCount = matcher.groupCount();
>        if(group > groupCount) {
>            return null;
>        }
>        final String val = matcher.group(group);
>        return Strings.emptyToNull(val);
>    }
> 
> 
>    ////////////////////////////////////////////////////////////////
>    // marshall
>    ////////////////////////////////////////////////////////////////
> 
>    public String marshal(RootOidDefault rootOid) {
>        return (rootOid.isTransient()? "!" : "") + rootOid.getObjectType()
> + ":" + rootOid.getIdentifier();
>    }
> 
>    public String marshal(CollectionOid collectionOid) {
>        return collectionOid.getParentOid().enString() + "$" +
> collectionOid.getName();
>    }
> 
>    public String marshal(AggregatedOid aggregatedOid) {
>        return aggregatedOid.getParentOid().enString() + "~" +
> aggregatedOid.getObjectType() + ":" + aggregatedOid.getLocalId();
>    }
> 
> 
> }

Mime
View raw message