You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2013/02/08 19:29:52 UTC

[13/32] ISIS-323: lots more refactoring of RO

http://git-wip-us.apache.org/repos/asf/isis/blob/bb79d33e/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/DomainObjectReprRenderer.java
----------------------------------------------------------------------
diff --git a/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/DomainObjectReprRenderer.java b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/DomainObjectReprRenderer.java
new file mode 100644
index 0000000..192774e
--- /dev/null
+++ b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/DomainObjectReprRenderer.java
@@ -0,0 +1,313 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
+
+import java.util.List;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.oid.OidMarshaller;
+import org.apache.isis.core.metamodel.adapter.oid.RootOid;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.facets.object.title.TitleFacet;
+import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
+import org.apache.isis.core.metamodel.services.ServiceUtil;
+import org.apache.isis.core.metamodel.spec.ObjectActionSet;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.metamodel.spec.feature.ObjectActionContainer.Contributed;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
+import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
+import org.apache.isis.viewer.restfulobjects.applib.RoHttpMethod;
+import org.apache.isis.viewer.restfulobjects.applib.links.Rel;
+import org.apache.isis.viewer.restfulobjects.rendering.LinkBuilder;
+import org.apache.isis.viewer.restfulobjects.rendering.LinkFollower;
+import org.apache.isis.viewer.restfulobjects.rendering.RendererContext;
+import org.apache.isis.viewer.restfulobjects.rendering.RendererFactory;
+import org.apache.isis.viewer.restfulobjects.rendering.RendererFactoryRegistry;
+import org.apache.isis.viewer.restfulobjects.rendering.ReprRenderer;
+import org.apache.isis.viewer.restfulobjects.rendering.ReprRendererAbstract;
+import org.apache.isis.viewer.restfulobjects.rendering.ReprRendererFactoryAbstract;
+import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.DomainTypeReprRenderer;
+import org.apache.isis.viewer.restfulobjects.rendering.util.OidUtils;
+
+public class DomainObjectReprRenderer extends ReprRendererAbstract<DomainObjectReprRenderer, ObjectAdapter> {
+
+    public static class Factory extends ReprRendererFactoryAbstract {
+
+        public Factory() {
+            super(RepresentationType.DOMAIN_OBJECT);
+        }
+
+        @Override
+        public ReprRenderer<?, ?> newRenderer(final RendererContext resourceContext, final LinkFollower linkFollower, final JsonRepresentation representation) {
+            return new DomainObjectReprRenderer(resourceContext, linkFollower, getRepresentationType(), representation);
+        }
+    }
+
+    public static LinkBuilder newLinkToBuilder(final RendererContext resourceContext, final Rel rel, final ObjectAdapter elementAdapter) {
+        final String oidStr = ((RootOid) elementAdapter.getOid()).enString(getOidMarshaller());
+        final String url = "objects/" + oidStr;
+        final LinkBuilder builder = LinkBuilder.newBuilder(resourceContext, rel, RepresentationType.DOMAIN_OBJECT, url).withTitle(elementAdapter.titleString());
+        return builder;
+    }
+
+    private static enum Mode {
+        REGULAR(false, true), PERSIST_LINK_ARGUMENTS(true, true), MODIFY_PROPERTIES_LINK_ARGUMENTS(true, false);
+
+        private final boolean cutDown;
+        private final boolean describedBy;
+
+        private Mode(final boolean cutDown, final boolean describedBy) {
+            this.cutDown = cutDown;
+            this.describedBy = describedBy;
+        }
+
+        public boolean isCutDown() {
+            return cutDown;
+        }
+
+        public boolean includesDescribedBy() {
+            return describedBy;
+        }
+    }
+
+    private ObjectAdapterLinkTo linkToBuilder;
+    private ObjectAdapter objectAdapter;
+    private Mode mode = Mode.REGULAR;
+
+    private DomainObjectReprRenderer(final RendererContext resourceContext, final LinkFollower linkFollower, final RepresentationType representationType, final JsonRepresentation representation) {
+        super(resourceContext, linkFollower, representationType, representation);
+        usingLinkToBuilder(new DomainObjectLinkTo());
+    }
+
+    /**
+     * Override the default {@link ObjectAdapterLinkTo} (that is used for
+     * generating links in {@link #linkTo(ObjectAdapter)}).
+     */
+    public DomainObjectReprRenderer usingLinkToBuilder(final ObjectAdapterLinkTo objectAdapterLinkToBuilder) {
+        this.linkToBuilder = objectAdapterLinkToBuilder.usingUrlBase(resourceContext);
+        return this;
+    }
+
+    @Override
+    public DomainObjectReprRenderer with(final ObjectAdapter objectAdapter) {
+        this.objectAdapter = objectAdapter;
+        return this;
+    }
+
+    @Override
+    public JsonRepresentation render() {
+
+        // self, oid
+        if (!mode.isCutDown()) {
+            if (objectAdapter.representsPersistent()) {
+                if (includesSelf) {
+                    final JsonRepresentation self = linkToBuilder.with(objectAdapter).builder(Rel.SELF).build();
+                    getLinks().arrayAdd(self);
+                }
+                representation.mapPut("oid", getOidStr());
+            }
+        }
+
+        // title
+        if (!mode.isCutDown()) {
+            final String title = objectAdapter.titleString();
+            representation.mapPut("title", title);
+
+        }
+
+        // serviceId
+        if (!mode.isCutDown()) {
+            final boolean isService = objectAdapter.getSpecification().isService();
+            if (isService) {
+                representation.mapPut("serviceId", ServiceUtil.id(objectAdapter.getObject()));
+            }
+        }
+
+        // members
+        withMembers(objectAdapter);
+
+        // described by
+        if (mode.includesDescribedBy()) {
+            getLinks().arrayAdd(DomainTypeReprRenderer.newLinkToBuilder(getResourceContext(), Rel.DESCRIBEDBY, objectAdapter.getSpecification()).build());
+        }
+
+        if (!mode.isCutDown()) {
+            // update/persist
+            addPersistLinkIfTransient();
+            addUpdatePropertiesLinkIfPersistent();
+
+            // extensions
+            final boolean isService = objectAdapter.getSpecification().isService();
+            getExtensions().mapPut("isService", isService);
+            getExtensions().mapPut("isPersistent", objectAdapter.representsPersistent());
+        }
+
+        return representation;
+    }
+
+    private String getOidStr() {
+        return OidUtils.getOidStr(resourceContext, objectAdapter);
+    }
+
+    private DomainObjectReprRenderer withMembers(final ObjectAdapter objectAdapter) {
+        final JsonRepresentation members = JsonRepresentation.newArray();
+        final List<ObjectAssociation> associations = objectAdapter.getSpecification().getAssociations();
+        addAssociations(objectAdapter, members, associations);
+
+        if (!mode.isCutDown()) {
+            final List<ObjectAction> actions = objectAdapter.getSpecification().getObjectActions(Contributed.INCLUDED);
+            addActions(objectAdapter, actions, members);
+        }
+        representation.mapPut("members", members);
+        return this;
+    }
+
+    private void addAssociations(final ObjectAdapter objectAdapter, final JsonRepresentation members, final List<ObjectAssociation> associations) {
+        final LinkFollower linkFollower = getLinkFollower().follow("members");
+        for (final ObjectAssociation assoc : associations) {
+
+            if (!mode.isCutDown()) {
+                final Consent visibility = assoc.isVisible(getAuthenticationSession(), objectAdapter, resourceContext.getWhere());
+                if (!visibility.isAllowed()) {
+                    continue;
+                }
+            }
+            if (assoc instanceof OneToOneAssociation) {
+                final OneToOneAssociation property = (OneToOneAssociation) assoc;
+
+                final RendererFactory factory = getRendererFactoryRegistry().find(RepresentationType.OBJECT_PROPERTY);
+                final ObjectPropertyReprRenderer renderer = (ObjectPropertyReprRenderer) factory.newRenderer(getResourceContext(), linkFollower, JsonRepresentation.newMap());
+
+                renderer.with(new ObjectAndProperty(objectAdapter, property)).usingLinkTo(linkToBuilder);
+
+                if (mode.isCutDown()) {
+                    renderer.asArguments();
+                }
+
+                members.arrayAdd(renderer.render());
+            }
+
+            if (mode.isCutDown()) {
+                // don't include collections
+                continue;
+            }
+            if (assoc instanceof OneToManyAssociation) {
+                final OneToManyAssociation collection = (OneToManyAssociation) assoc;
+
+                final RendererFactory factory = getRendererFactoryRegistry().find(RepresentationType.OBJECT_COLLECTION);
+                final ObjectCollectionReprRenderer renderer = (ObjectCollectionReprRenderer) factory.newRenderer(getResourceContext(), linkFollower, JsonRepresentation.newMap());
+
+                renderer.with(new ObjectAndCollection(objectAdapter, collection)).usingLinkTo(linkToBuilder);
+
+                members.arrayAdd(renderer.render());
+            }
+        }
+    }
+
+    private void addActions(final ObjectAdapter objectAdapter, final List<ObjectAction> actions, final JsonRepresentation members) {
+        final LinkFollower linkFollower = getLinkFollower().follow("members");
+        for (final ObjectAction action : actions) {
+            final Consent visibility = action.isVisible(getAuthenticationSession(), objectAdapter, resourceContext.getWhere());
+            if (!visibility.isAllowed()) {
+                continue;
+            }
+            if (action.getType().isSet()) {
+                final ObjectActionSet objectActionSet = (ObjectActionSet) action;
+                final List<ObjectAction> subactions = objectActionSet.getActions();
+                addActions(objectAdapter, subactions, members);
+
+            } else {
+
+                final RendererFactory factory = getRendererFactoryRegistry().find(RepresentationType.OBJECT_ACTION);
+                final ObjectActionReprRenderer renderer = (ObjectActionReprRenderer) factory.newRenderer(getResourceContext(), linkFollower, JsonRepresentation.newMap());
+
+                renderer.with(new ObjectAndAction(objectAdapter, action)).usingLinkTo(linkToBuilder);
+
+                members.arrayAdd(renderer.render());
+            }
+        }
+    }
+
+    private void addPersistLinkIfTransient() {
+        if (objectAdapter.representsPersistent()) {
+            return;
+        }
+        final RendererFactory rendererFactory = getRendererFactoryRegistry().find(RepresentationType.DOMAIN_OBJECT);
+        final DomainObjectReprRenderer renderer = (DomainObjectReprRenderer) rendererFactory.newRenderer(getResourceContext(), null, JsonRepresentation.newMap());
+        final JsonRepresentation domainObjectRepr = renderer.with(objectAdapter).asPersistLinkArguments().render();
+
+        final LinkBuilder persistLinkBuilder = LinkBuilder.newBuilder(getResourceContext(), Rel.PERSIST, RepresentationType.DOMAIN_OBJECT, "objects/").withHttpMethod(RoHttpMethod.POST).withArguments(domainObjectRepr);
+        getLinks().arrayAdd(persistLinkBuilder.build());
+    }
+
+    private DomainObjectReprRenderer asPersistLinkArguments() {
+        this.mode = Mode.PERSIST_LINK_ARGUMENTS;
+        return this;
+    }
+
+    private DomainObjectReprRenderer asModifyPropertiesLinkArguments() {
+        this.mode = Mode.MODIFY_PROPERTIES_LINK_ARGUMENTS;
+        return this;
+    }
+
+    private void addUpdatePropertiesLinkIfPersistent() {
+        if (!objectAdapter.representsPersistent()) {
+            return;
+        }
+
+        final RendererFactory rendererFactory = getRendererFactoryRegistry().find(RepresentationType.DOMAIN_OBJECT);
+        final DomainObjectReprRenderer renderer = (DomainObjectReprRenderer) rendererFactory.newRenderer(getResourceContext(), null, JsonRepresentation.newMap());
+        final JsonRepresentation domainObjectRepr = renderer.with(objectAdapter).asModifyPropertiesLinkArguments().render();
+
+        final LinkBuilder persistLinkBuilder = LinkBuilder.newBuilder(getResourceContext(), Rel.MODIFY, RepresentationType.DOMAIN_OBJECT, "objects/%s", getOidStr()).withHttpMethod(RoHttpMethod.PUT).withArguments(domainObjectRepr);
+        getLinks().arrayAdd(persistLinkBuilder.build());
+    }
+
+    protected RendererFactoryRegistry getRendererFactoryRegistry() {
+        return RendererFactoryRegistry.instance;
+    }
+
+    // ///////////////////////////////////////////////////////////////////
+    //
+    // ///////////////////////////////////////////////////////////////////
+
+    public static Object valueOrRef(final RendererContext resourceContext, final ObjectAdapter objectAdapter, final ObjectSpecification objectSpec) {
+        final ValueFacet valueFacet = objectSpec.getFacet(ValueFacet.class);
+        if (valueFacet != null) {
+            return new JsonValueEncoder().asObject(objectAdapter);
+        }
+        final TitleFacet titleFacet = objectSpec.getFacet(TitleFacet.class);
+        final String title = titleFacet.title(objectAdapter, resourceContext.getLocalization());
+        return DomainObjectReprRenderer.newLinkToBuilder(resourceContext, Rel.OBJECT, objectAdapter).withTitle(title).build();
+    }
+
+ 
+    
+    // ///////////////////////////////////////////////////////////////////
+    // dependencies (from context)
+    // ///////////////////////////////////////////////////////////////////
+
+    protected static OidMarshaller getOidMarshaller() {
+		return IsisContext.getOidMarshaller();
+	}
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/bb79d33e/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/DomainServiceLinkTo.java
----------------------------------------------------------------------
diff --git a/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/DomainServiceLinkTo.java b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/DomainServiceLinkTo.java
new file mode 100644
index 0000000..f3fce41
--- /dev/null
+++ b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/DomainServiceLinkTo.java
@@ -0,0 +1,50 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.services.ServiceUtil;
+import org.apache.isis.viewer.restfulobjects.applib.links.Rel;
+import org.apache.isis.viewer.restfulobjects.rendering.LinkBuilder;
+
+public class DomainServiceLinkTo extends DomainObjectLinkTo {
+    private String serviceId;
+
+    @Override
+    public ObjectAdapterLinkTo with(final ObjectAdapter objectAdapter) {
+        serviceId = ServiceUtil.id(objectAdapter.getObject());
+        return super.with(objectAdapter);
+    }
+
+    @Override
+    protected String linkRef() {
+        final StringBuilder buf = new StringBuilder("services/");
+        buf.append(serviceId);
+        return buf.toString();
+    }
+
+    @Override
+    protected Rel defaultRel() {
+        return Rel.SERVICE;
+    }
+
+    @Override
+    public LinkBuilder builder(final Rel rel) {
+        return super.builder(rel).withId(serviceId);
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/bb79d33e/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/InvokeKeys.java
----------------------------------------------------------------------
diff --git a/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/InvokeKeys.java b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/InvokeKeys.java
new file mode 100644
index 0000000..bf116e9
--- /dev/null
+++ b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/InvokeKeys.java
@@ -0,0 +1,40 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
+
+import java.util.EnumMap;
+
+import org.apache.isis.applib.annotation.ActionSemantics;
+
+public final class InvokeKeys {
+    
+    private final static EnumMap<ActionSemantics.Of, String> map = new EnumMap<ActionSemantics.Of, String>(ActionSemantics.Of.class);
+    static {
+        map.put(ActionSemantics.Of.SAFE, "invokeQueryOnly");
+        map.put(ActionSemantics.Of.IDEMPOTENT, "invokeIdempotent");
+        map.put(ActionSemantics.Of.NON_IDEMPOTENT, "invoke");
+    }
+    
+    private InvokeKeys() {
+    }
+    
+    public static String getKeyFor(ActionSemantics.Of actionSemantics) {
+        return map.get(actionSemantics);
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/bb79d33e/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueEncoder.java
----------------------------------------------------------------------
diff --git a/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueEncoder.java b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueEncoder.java
new file mode 100644
index 0000000..957aa67
--- /dev/null
+++ b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueEncoder.java
@@ -0,0 +1,224 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
+
+/**
+ * Similar to Isis' value encoding, but with additional support for JSON
+ * primitives.
+ */
+public final class JsonValueEncoder {
+
+    public static class ExpectedStringRepresentingValueException extends IllegalArgumentException {
+        private static final long serialVersionUID = 1L;
+    }
+
+    public ObjectAdapter asAdapter(final ObjectSpecification objectSpec, final JsonRepresentation representation) {
+        if (objectSpec == null) {
+            throw new IllegalArgumentException("objectSpec cannot be null");
+        }
+        final EncodableFacet encodableFacet = objectSpec.getFacet(EncodableFacet.class);
+        if (encodableFacet == null) {
+            throw new IllegalArgumentException("objectSpec expected to have EncodableFacet");
+        }
+        if (representation == null) {
+            throw new IllegalArgumentException("representation cannot be null");
+        }
+        if (!representation.isValue()) {
+            throw new IllegalArgumentException("representation must be of a value");
+        }
+
+        // special case handling for JSON built-ins
+        if (isBoolean(objectSpec)) {
+            if (!representation.isBoolean()) {
+                throwIncompatibleException(objectSpec, representation);
+            }
+            final String argStr = "" + representation.asBoolean();
+            return encodableFacet.fromEncodedString(argStr);
+        }
+
+        if (isInteger(objectSpec)) {
+            if (representation.isInt()) {
+                final String argStr = "" + representation.asInt();
+                return encodableFacet.fromEncodedString(argStr);
+            }
+            // best effort
+            if (representation.isString()) {
+                final String argStr = representation.asString();
+                return encodableFacet.fromEncodedString(argStr);
+            }
+            // give up
+            throwIncompatibleException(objectSpec, representation);
+        }
+
+        if (isLong(objectSpec)) {
+            if (!representation.isLong()) {
+                throwIncompatibleException(objectSpec, representation);
+            }
+            final String argStr = "" + representation.asLong();
+            return encodableFacet.fromEncodedString(argStr);
+        }
+
+        if (isBigInteger(objectSpec)) {
+            if (representation.isBigInteger()) {
+                final String argStr = "" + representation.asBigInteger();
+                return encodableFacet.fromEncodedString(argStr);
+            }
+            // best effort
+            if (representation.isLong()) {
+                final String argStr = "" + representation.asLong();
+                return encodableFacet.fromEncodedString(argStr);
+            }
+            if (representation.isInt()) {
+                final String argStr = "" + representation.asInt();
+                return encodableFacet.fromEncodedString(argStr);
+            }
+            if (representation.isString()) {
+                final String argStr = representation.asString();
+                return encodableFacet.fromEncodedString(argStr);
+            }
+            // give up
+            throwIncompatibleException(objectSpec, representation);
+        }
+
+        if (isBigDecimal(objectSpec)) {
+            if (representation.isBigDecimal()) {
+                final String argStr = "" + representation.asBigDecimal();
+                return encodableFacet.fromEncodedString(argStr);
+            }
+            // best effort
+            if (representation.isBigInteger()) {
+                final String argStr = "" + representation.asBigInteger();
+                return encodableFacet.fromEncodedString(argStr);
+            }
+            if (representation.isDouble()) {
+                final String argStr = "" + representation.asDouble();
+                return encodableFacet.fromEncodedString(argStr);
+            }
+            if (representation.isLong()) {
+                final String argStr = "" + representation.asLong();
+                return encodableFacet.fromEncodedString(argStr);
+            }
+            if (representation.isInt()) {
+                final String argStr = "" + representation.asInt();
+                return encodableFacet.fromEncodedString(argStr);
+            }
+            if (representation.isString()) {
+                final String argStr = representation.asString();
+                return encodableFacet.fromEncodedString(argStr);
+            }
+            // give up
+            throwIncompatibleException(objectSpec, representation);
+        }
+
+        if (isDouble(objectSpec)) {
+            if (representation.isDouble()) {
+                final String argStr = "" + representation.asDouble();
+                return encodableFacet.fromEncodedString(argStr);
+            }
+            // best effort
+            if (representation.isLong()) {
+                final String argStr = "" + representation.asLong();
+                return encodableFacet.fromEncodedString(argStr);
+            }
+            if (representation.isInt()) {
+                final String argStr = "" + representation.asInt();
+                return encodableFacet.fromEncodedString(argStr);
+            }
+            if (representation.isString()) {
+                final String argStr = representation.asString();
+                return encodableFacet.fromEncodedString(argStr);
+            }
+            // give up
+            throwIncompatibleException(objectSpec, representation);
+        }
+
+        if (!representation.isString()) {
+            throw new ExpectedStringRepresentingValueException();
+        }
+        final String argStr = representation.asString();
+        return encodableFacet.fromEncodedString(argStr);
+    }
+
+    public Object asObject(final ObjectAdapter objectAdapter) {
+        if (objectAdapter == null) {
+            throw new IllegalArgumentException("objectAdapter cannot be null");
+        }
+        final ObjectSpecification objectSpec = objectAdapter.getSpecification();
+
+        // special case handling for JSON built-ins (at least so far as json.org
+        // defines them).
+        if (isBoolean(objectSpec) || isInteger(objectSpec) || isLong(objectSpec) || isBigInteger(objectSpec) || isDouble(objectSpec) || isBigDecimal(objectSpec)) {
+            // simply return
+            return objectAdapter.getObject();
+        }
+
+        final EncodableFacet encodableFacet = objectSpec.getFacet(EncodableFacet.class);
+        if (encodableFacet == null) {
+            throw new IllegalArgumentException("objectSpec expected to have EncodableFacet");
+        }
+        return encodableFacet.toEncodedString(objectAdapter);
+    }
+
+    private boolean isBoolean(final ObjectSpecification objectSpec) {
+        return hasCorrespondingClass(objectSpec, boolean.class, Boolean.class);
+    }
+
+    private boolean isInteger(final ObjectSpecification objectSpec) {
+        return hasCorrespondingClass(objectSpec, int.class, Integer.class);
+    }
+
+    private boolean isLong(final ObjectSpecification objectSpec) {
+        return hasCorrespondingClass(objectSpec, long.class, Long.class);
+    }
+
+    private boolean isBigInteger(final ObjectSpecification objectSpec) {
+        return hasCorrespondingClass(objectSpec, BigInteger.class);
+    }
+
+    private boolean isDouble(final ObjectSpecification objectSpec) {
+        return hasCorrespondingClass(objectSpec, double.class, Double.class);
+    }
+
+    private boolean isBigDecimal(final ObjectSpecification objectSpec) {
+        return hasCorrespondingClass(objectSpec, BigDecimal.class);
+    }
+
+    private boolean hasCorrespondingClass(final ObjectSpecification objectSpec, final Class<?>... candidates) {
+        final Class<?> specClass = objectSpec.getCorrespondingClass();
+        for (final Class<?> candidate : candidates) {
+            if (specClass == candidate) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void throwIncompatibleException(final ObjectSpecification objectSpec, final JsonRepresentation representation) {
+        throw new IllegalArgumentException(String.format("representation '%s' incompatible with objectSpec '%s'", representation.toString(), objectSpec.getCorrespondingClass().getName()));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/bb79d33e/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ListReprRenderer.java
----------------------------------------------------------------------
diff --git a/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ListReprRenderer.java b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ListReprRenderer.java
new file mode 100644
index 0000000..32269f4
--- /dev/null
+++ b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ListReprRenderer.java
@@ -0,0 +1,115 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
+
+import java.util.Collection;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
+import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
+import org.apache.isis.viewer.restfulobjects.applib.links.Rel;
+import org.apache.isis.viewer.restfulobjects.rendering.LinkFollower;
+import org.apache.isis.viewer.restfulobjects.rendering.RendererContext;
+import org.apache.isis.viewer.restfulobjects.rendering.RendererFactory;
+import org.apache.isis.viewer.restfulobjects.rendering.RendererFactoryRegistry;
+import org.apache.isis.viewer.restfulobjects.rendering.ReprRenderer;
+import org.apache.isis.viewer.restfulobjects.rendering.ReprRendererAbstract;
+import org.apache.isis.viewer.restfulobjects.rendering.ReprRendererFactoryAbstract;
+
+public class ListReprRenderer extends ReprRendererAbstract<ListReprRenderer, Collection<ObjectAdapter>> {
+
+    public static class Factory extends ReprRendererFactoryAbstract {
+        public Factory() {
+            super(RepresentationType.LIST);
+        }
+
+        @Override
+        public ReprRenderer<?, ?> newRenderer(final RendererContext resourceContext, final LinkFollower linkFollower, final JsonRepresentation representation) {
+            return new ListReprRenderer(resourceContext, linkFollower, getRepresentationType(), representation);
+        }
+    }
+
+    private ObjectAdapterLinkTo linkTo;
+    private Collection<ObjectAdapter> objectAdapters;
+    private ObjectSpecification elementType;
+    private ObjectSpecification returnType;
+
+    private ListReprRenderer(final RendererContext resourceContext, final LinkFollower linkFollower, final RepresentationType representationType, final JsonRepresentation representation) {
+        super(resourceContext, linkFollower, representationType, representation);
+        usingLinkToBuilder(new DomainObjectLinkTo());
+    }
+
+    public ListReprRenderer usingLinkToBuilder(final ObjectAdapterLinkTo objectAdapterLinkToBuilder) {
+        this.linkTo = objectAdapterLinkToBuilder.usingUrlBase(resourceContext);
+        return this;
+    }
+
+    @Override
+    public ListReprRenderer with(final Collection<ObjectAdapter> objectAdapters) {
+        this.objectAdapters = objectAdapters;
+        return this;
+    }
+
+    public ListReprRenderer withReturnType(final ObjectSpecification returnType) {
+        this.returnType = returnType;
+        return this;
+    }
+
+    public ListReprRenderer withElementType(final ObjectSpecification elementType) {
+        this.elementType = elementType;
+        return this;
+    }
+
+    @Override
+    public JsonRepresentation render() {
+        addValue();
+
+        addLink(Rel.RETURN_TYPE, returnType);
+        addLink(Rel.ELEMENT_TYPE, elementType);
+
+        getExtensions();
+
+        return representation;
+    }
+
+    private void addValue() {
+        if (objectAdapters == null) {
+            return;
+        }
+
+        final JsonRepresentation values = JsonRepresentation.newArray();
+        final LinkFollower linkFollower = getLinkFollower().follow("value");
+
+        for (final ObjectAdapter adapter : objectAdapters) {
+            if (adapter.getSpecification().isHidden()) {
+                continue;
+            }
+            final JsonRepresentation linkToObject = linkTo.with(adapter).builder().build();
+            values.arrayAdd(linkToObject);
+
+            if (linkFollower.matches(linkToObject)) {
+                final RendererFactory factory = RendererFactoryRegistry.instance.find(RepresentationType.DOMAIN_OBJECT);
+                final DomainObjectReprRenderer renderer = (DomainObjectReprRenderer) factory.newRenderer(getResourceContext(), linkFollower, JsonRepresentation.newMap());
+                final JsonRepresentation domainObject = renderer.with(adapter).render();
+                linkToObject.mapPut("value", domainObject);
+            }
+        }
+        representation.mapPut("value", values);
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/bb79d33e/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/MemberType.java
----------------------------------------------------------------------
diff --git a/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/MemberType.java b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/MemberType.java
new file mode 100644
index 0000000..d4005a0
--- /dev/null
+++ b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/MemberType.java
@@ -0,0 +1,152 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
+
+import java.util.Map;
+
+import org.apache.isis.applib.annotation.ActionSemantics;
+import org.apache.isis.applib.util.Enums;
+import org.apache.isis.core.metamodel.facets.actions.invoke.ActionInvocationFacet;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionAddToFacet;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionRemoveFromFacet;
+import org.apache.isis.core.metamodel.facets.properties.modify.PropertyClearFacet;
+import org.apache.isis.core.metamodel.facets.properties.modify.PropertySetterFacet;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.metamodel.spec.feature.ObjectFeature;
+import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.isis.core.progmodel.facets.actions.validate.ActionValidationFacet;
+import org.apache.isis.core.progmodel.facets.collections.validate.CollectionValidateAddToFacet;
+import org.apache.isis.core.progmodel.facets.collections.validate.CollectionValidateRemoveFromFacet;
+import org.apache.isis.core.progmodel.facets.properties.validate.PropertyValidateFacet;
+import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
+import org.apache.isis.viewer.restfulobjects.applib.RoHttpMethod;
+import org.apache.isis.viewer.restfulobjects.applib.links.Rel;
+
+import com.google.common.collect.ImmutableMap;
+
+public enum MemberType {
+
+    PROPERTY("properties/", "id", RepresentationType.OBJECT_PROPERTY, ImmutableMap.of("modify", MutatorSpec.of(Rel.MODIFY, PropertyValidateFacet.class, PropertySetterFacet.class, RoHttpMethod.PUT, BodyArgs.ONE), "clear",
+            MutatorSpec.of(Rel.CLEAR, PropertyValidateFacet.class, PropertyClearFacet.class, RoHttpMethod.DELETE, BodyArgs.NONE))) {
+        @Override
+        public ObjectSpecification specFor(final ObjectMember objectMember) {
+            return objectMember.getSpecification();
+        }
+    },
+    /**
+     * {@link #getMutators()} are keyed by
+     * {@link CollectionSemantics#getAddToKey()}
+     */
+    COLLECTION("collections/", "id", RepresentationType.OBJECT_COLLECTION, ImmutableMap.of("addToSet", MutatorSpec.of(Rel.ADD_TO, CollectionValidateAddToFacet.class, CollectionAddToFacet.class, RoHttpMethod.PUT, BodyArgs.ONE), "addToList",
+            MutatorSpec.of(Rel.ADD_TO, CollectionValidateAddToFacet.class, CollectionAddToFacet.class, RoHttpMethod.POST, BodyArgs.ONE), "removeFrom", MutatorSpec.of(Rel.REMOVE_FROM, CollectionValidateRemoveFromFacet.class, CollectionRemoveFromFacet.class, RoHttpMethod.DELETE, BodyArgs.ONE))) {
+        @Override
+        public ObjectSpecification specFor(final ObjectMember objectMember) {
+            return objectMember.getSpecification();
+        }
+    },
+    /**
+     * {@link #getMutators()} are keyed by
+     * {@link ActionSemantics#getInvokeKey()}
+     */
+    ACTION("actions/", "id", RepresentationType.ACTION_RESULT, ImmutableMap.of("invokeQueryOnly", MutatorSpec.of(Rel.INVOKE, ActionValidationFacet.class, ActionInvocationFacet.class, RoHttpMethod.GET, BodyArgs.MANY, "invoke"), "invokeIdempotent",
+            MutatorSpec.of(Rel.INVOKE, ActionValidationFacet.class, ActionInvocationFacet.class, RoHttpMethod.PUT, BodyArgs.MANY, "invoke"), "invoke", MutatorSpec.of(Rel.INVOKE, ActionValidationFacet.class, ActionInvocationFacet.class, RoHttpMethod.POST, BodyArgs.MANY, "invoke"))) {
+        @Override
+        public ObjectSpecification specFor(final ObjectMember objectMember) {
+            final ObjectAction objectAction = (ObjectAction) objectMember;
+            return objectAction.getReturnType();
+        }
+    };
+
+    private final String urlPart;
+    private final String jsProp;
+    private final String name;
+    private final RepresentationType representationType;
+
+    private final Map<String, MutatorSpec> mutators;
+
+    private MemberType(final String urlPart, final String jsProp, final RepresentationType representationType, final Map<String, MutatorSpec> mutators) {
+        this.urlPart = urlPart;
+        this.jsProp = jsProp;
+        this.representationType = representationType;
+        this.mutators = mutators;
+        name = Enums.enumToCamelCase(this);
+    }
+
+    public String getJsProp() {
+        return jsProp;
+    }
+
+    public String getUrlPart() {
+        return urlPart;
+    }
+
+    public Map<String, MutatorSpec> getMutators() {
+        return mutators;
+    }
+
+    public abstract ObjectSpecification specFor(ObjectMember objectMember);
+
+    public boolean isProperty() {
+        return this == MemberType.PROPERTY;
+    }
+
+    public boolean isCollection() {
+        return this == MemberType.COLLECTION;
+    }
+
+    public boolean isAction() {
+        return this == MemberType.ACTION;
+    }
+
+    public static MemberType lookup(final String memberTypeName) {
+        for (final MemberType memberType : values()) {
+            if (memberType.getName().equals(memberTypeName)) {
+                return memberType;
+            }
+        }
+        return null;
+    }
+
+    public static MemberType of(final ObjectMember objectMember) {
+        return objectMember.isAction() ? ACTION : objectMember.isOneToOneAssociation() ? PROPERTY : COLLECTION;
+    }
+
+    public RepresentationType getRepresentationType() {
+        return representationType;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public static MemberType determineFrom(final ObjectFeature objectFeature) {
+        if (objectFeature instanceof ObjectAction) {
+            return MemberType.ACTION;
+        }
+        if (objectFeature instanceof OneToOneAssociation) {
+            return MemberType.PROPERTY;
+        }
+        if (objectFeature instanceof OneToManyAssociation) {
+            return MemberType.COLLECTION;
+        }
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/bb79d33e/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/MutatorSpec.java
----------------------------------------------------------------------
diff --git a/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/MutatorSpec.java b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/MutatorSpec.java
new file mode 100644
index 0000000..f44f3ea
--- /dev/null
+++ b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/MutatorSpec.java
@@ -0,0 +1,49 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
+
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.viewer.restfulobjects.applib.RoHttpMethod;
+import org.apache.isis.viewer.restfulobjects.applib.links.Rel;
+
+public class MutatorSpec {
+
+    public static MutatorSpec of(final Rel rel, final Class<? extends Facet> validationFacetType, final Class<? extends Facet> mutatorFacetType, final RoHttpMethod httpMethod, final BodyArgs argSpec) {
+        return of(rel, validationFacetType, mutatorFacetType, httpMethod, argSpec, null);
+    }
+
+    public static MutatorSpec of(final Rel rel, final Class<? extends Facet> validationFacetType, final Class<? extends Facet> mutatorFacetType, final RoHttpMethod httpMethod, final BodyArgs argSpec, final String suffix) {
+        return new MutatorSpec(rel, validationFacetType, mutatorFacetType, httpMethod, argSpec, suffix);
+    }
+
+    public final Rel rel;
+    public final Class<? extends Facet> validationFacetType;
+    public final Class<? extends Facet> mutatorFacetType;
+    public final RoHttpMethod httpMethod;
+    public final String suffix;
+    public final BodyArgs arguments;
+
+    private MutatorSpec(final Rel rel, final Class<? extends Facet> validationFacetType, final Class<? extends Facet> mutatorFacetType, final RoHttpMethod httpMethod, final BodyArgs bodyArgs, final String suffix) {
+        this.rel = rel;
+        this.validationFacetType = validationFacetType;
+        this.mutatorFacetType = mutatorFacetType;
+        this.httpMethod = httpMethod;
+        this.arguments = bodyArgs;
+        this.suffix = suffix;
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/bb79d33e/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectActionReprRenderer.java
----------------------------------------------------------------------
diff --git a/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectActionReprRenderer.java b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectActionReprRenderer.java
new file mode 100644
index 0000000..c180f3f
--- /dev/null
+++ b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectActionReprRenderer.java
@@ -0,0 +1,228 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.isis.applib.annotation.ActionSemantics;
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter;
+import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
+import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
+import org.apache.isis.viewer.restfulobjects.applib.links.Rel;
+import org.apache.isis.viewer.restfulobjects.rendering.LinkFollower;
+import org.apache.isis.viewer.restfulobjects.rendering.RendererContext;
+import org.apache.isis.viewer.restfulobjects.rendering.RendererFactory;
+import org.apache.isis.viewer.restfulobjects.rendering.RendererFactoryRegistry;
+import org.apache.isis.viewer.restfulobjects.rendering.ReprRenderer;
+import org.apache.isis.viewer.restfulobjects.rendering.ReprRendererFactoryAbstract;
+import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.ActionDescriptionReprRenderer;
+import org.codehaus.jackson.node.NullNode;
+
+import com.google.common.collect.Lists;
+
+public class ObjectActionReprRenderer extends AbstractObjectMemberReprRenderer<ObjectActionReprRenderer, ObjectAction> {
+
+    public static class Factory extends ReprRendererFactoryAbstract {
+
+        public Factory() {
+            super(RepresentationType.OBJECT_ACTION);
+        }
+
+        @Override
+        public ReprRenderer<?, ?> newRenderer(final RendererContext resourceContext, final LinkFollower linkFollower, final JsonRepresentation representation) {
+            return new ObjectActionReprRenderer(resourceContext, linkFollower, getRepresentationType(), representation);
+        }
+    }
+
+    private ObjectActionReprRenderer(final RendererContext resourceContext, final LinkFollower linkFollower, final RepresentationType representationType, final JsonRepresentation representation) {
+        super(resourceContext, linkFollower, representationType, representation, Where.OBJECT_FORMS);
+    }
+
+    @Override
+    public JsonRepresentation render() {
+        // id and memberType are rendered eagerly
+
+        renderMemberContent();
+        putDisabledReasonIfDisabled();
+
+        if (mode.isStandalone() || mode.isMutated()) {
+            addParameterDetails();
+        }
+
+        return representation;
+    }
+
+    // ///////////////////////////////////////////////////
+    // details link
+    // ///////////////////////////////////////////////////
+
+    /**
+     * Mandatory hook method to support x-ro-follow-links
+     */
+    @Override
+    protected void followDetailsLink(final JsonRepresentation detailsLink) {
+        final RendererFactory factory = RendererFactoryRegistry.instance.find(RepresentationType.OBJECT_ACTION);
+        final ObjectActionReprRenderer renderer = (ObjectActionReprRenderer) factory.newRenderer(getResourceContext(), getLinkFollower(), JsonRepresentation.newMap());
+        renderer.with(new ObjectAndAction(objectAdapter, objectMember)).usingLinkTo(linkTo).asFollowed();
+        detailsLink.mapPut("value", renderer.render());
+    }
+
+    // ///////////////////////////////////////////////////
+    // mutators
+    // ///////////////////////////////////////////////////
+
+    @Override
+    protected void addMutatorsIfEnabled() {
+        if (usability().isVetoed()) {
+            return;
+        }
+        final Map<String, MutatorSpec> mutators = memberType.getMutators();
+        
+        final ActionSemantics.Of actionSemantics = objectMember.getSemantics();
+        final String mutator = InvokeKeys.getKeyFor(actionSemantics);
+        final MutatorSpec mutatorSpec = mutators.get(mutator);
+
+        addLinkFor(mutatorSpec);
+    }
+
+    @Override
+    protected ObjectAdapterLinkTo linkToForMutatorInvoke() {
+        if (!objectMember.isContributed()) {
+            return super.linkToForMutatorInvoke();
+        }
+        final DomainServiceLinkTo linkTo = new DomainServiceLinkTo();
+        return linkTo.usingUrlBase(getResourceContext()).with(contributingServiceAdapter());
+    }
+
+    private ObjectAdapter contributingServiceAdapter() {
+        final ObjectSpecification serviceType = objectMember.getOnType();
+        final List<ObjectAdapter> serviceAdapters = getServiceAdapters();
+        for (final ObjectAdapter serviceAdapter : serviceAdapters) {
+            if (serviceAdapter.getSpecification() == serviceType) {
+                return serviceAdapter;
+            }
+        }
+        // fail fast
+        throw new IllegalStateException("Unable to locate contributing service");
+    }
+
+    @Override
+    protected JsonRepresentation mutatorArgs(final MutatorSpec mutatorSpec) {
+        final JsonRepresentation argMap = JsonRepresentation.newMap();
+        final List<ObjectActionParameter> parameters = objectMember.getParameters();
+        for (int i = 0; i < objectMember.getParameterCount(); i++) {
+            argMap.mapPut(parameters.get(i).getId(), argValueFor(i));
+        }
+        return argMap;
+    }
+
+    private Object argValueFor(final int i) {
+        if (objectMember.isContributed()) {
+            final ObjectActionParameter actionParameter = objectMember.getParameters().get(i);
+            if (actionParameter.getSpecification().isOfType(objectAdapter.getSpecification())) {
+                return DomainObjectReprRenderer.newLinkToBuilder(resourceContext, Rel.OBJECT, objectAdapter).build();
+            }
+        }
+        // force a null into the map
+        return NullNode.getInstance();
+    }
+
+    // ///////////////////////////////////////////////////
+    // parameter details
+    // ///////////////////////////////////////////////////
+
+    private ObjectActionReprRenderer addParameterDetails() {
+        final List<Object> parameters = Lists.newArrayList();
+        for (int i = 0; i < objectMember.getParameterCount(); i++) {
+            final ObjectActionParameter param = objectMember.getParameters().get(i);
+            parameters.add(paramDetails(param));
+        }
+        representation.mapPut("parameters", parameters);
+        return this;
+    }
+
+    private Object paramDetails(final ObjectActionParameter param) {
+        final JsonRepresentation paramRep = JsonRepresentation.newMap();
+        paramRep.mapPut("num", param.getNumber());
+        paramRep.mapPut("id", param.getId());
+        paramRep.mapPut("name", param.getName());
+        paramRep.mapPut("description", param.getDescription());
+        final Object paramChoices = choicesFor(param);
+        if (paramChoices != null) {
+            paramRep.mapPut("choices", paramChoices);
+        }
+        final Object paramDefault = defaultFor(param);
+        if (paramDefault != null) {
+            paramRep.mapPut("default", paramDefault);
+        }
+        return paramRep;
+    }
+
+    private Object choicesFor(final ObjectActionParameter param) {
+        final ObjectAdapter[] choiceAdapters = param.getChoices(objectAdapter);
+        if (choiceAdapters == null || choiceAdapters.length == 0) {
+            return null;
+        }
+        final List<Object> list = Lists.newArrayList();
+        for (final ObjectAdapter choiceAdapter : choiceAdapters) {
+            final ObjectSpecification objectSpec = param.getSpecification();
+            list.add(DomainObjectReprRenderer.valueOrRef(resourceContext, choiceAdapter, objectSpec));
+        }
+        return list;
+    }
+
+    private Object defaultFor(final ObjectActionParameter param) {
+        final ObjectAdapter defaultAdapter = param.getDefault(objectAdapter);
+        if (defaultAdapter == null) {
+            return null;
+        }
+        final ObjectSpecification objectSpec = param.getSpecification();
+        return DomainObjectReprRenderer.valueOrRef(resourceContext, defaultAdapter, objectSpec);
+    }
+
+    // ///////////////////////////////////////////////////
+    // extensions and links
+    // ///////////////////////////////////////////////////
+
+    @Override
+    protected void addLinksToFormalDomainModel() {
+        getLinks().arrayAdd(ActionDescriptionReprRenderer.newLinkToBuilder(resourceContext, Rel.DESCRIBEDBY, objectAdapter.getSpecification(), objectMember).build());
+    }
+
+    @Override
+    protected void addLinksIsisProprietary() {
+        if (objectMember.isContributed()) {
+            final ObjectAdapter serviceAdapter = contributingServiceAdapter();
+            final JsonRepresentation contributedByLink = DomainObjectReprRenderer.newLinkToBuilder(resourceContext, Rel.CONTRIBUTED_BY, serviceAdapter).build();
+            getLinks().arrayAdd(contributedByLink);
+        }
+    }
+
+    @Override
+    protected void putExtensionsIsisProprietary() {
+        getExtensions().mapPut("actionType", objectMember.getType().name().toLowerCase());
+
+        final ActionSemantics.Of semantics = objectMember.getSemantics();
+        getExtensions().mapPut("actionSemantics", semantics.getCamelCaseName());
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/bb79d33e/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAdapterLinkTo.java
----------------------------------------------------------------------
diff --git a/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAdapterLinkTo.java b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAdapterLinkTo.java
new file mode 100644
index 0000000..afeda55
--- /dev/null
+++ b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAdapterLinkTo.java
@@ -0,0 +1,42 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
+import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
+import org.apache.isis.viewer.restfulobjects.applib.links.Rel;
+import org.apache.isis.viewer.restfulobjects.rendering.LinkBuilder;
+import org.apache.isis.viewer.restfulobjects.rendering.RendererContext;
+
+public interface ObjectAdapterLinkTo {
+
+    ObjectAdapterLinkTo usingUrlBase(RendererContext resourceContext);
+
+    ObjectAdapterLinkTo with(ObjectAdapter objectAdapter);
+
+    LinkBuilder builder();
+
+    LinkBuilder builder(Rel rel);
+
+    LinkBuilder memberBuilder(Rel rel, MemberType memberType, ObjectMember objectMember, String... parts);
+
+    LinkBuilder memberBuilder(Rel rel, MemberType memberType, ObjectMember objectMember, RepresentationType representationType, String... parts);
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/bb79d33e/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndAction.java
----------------------------------------------------------------------
diff --git a/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndAction.java b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndAction.java
new file mode 100644
index 0000000..dc7c546
--- /dev/null
+++ b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndAction.java
@@ -0,0 +1,30 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+
+public class ObjectAndAction extends ObjectAndMember<ObjectAction> {
+
+    public ObjectAndAction(final ObjectAdapter objectAdapter, final ObjectAction action) {
+        super(objectAdapter, action);
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/bb79d33e/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndActionInvocation.java
----------------------------------------------------------------------
diff --git a/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndActionInvocation.java b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndActionInvocation.java
new file mode 100644
index 0000000..10795a2
--- /dev/null
+++ b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndActionInvocation.java
@@ -0,0 +1,55 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
+
+public class ObjectAndActionInvocation {
+
+    private final ObjectAdapter objectAdapter;
+    private final ObjectAction action;
+    private final JsonRepresentation arguments;
+    private final ObjectAdapter returnedAdapter;
+
+    public ObjectAndActionInvocation(final ObjectAdapter objectAdapter, final ObjectAction action, final JsonRepresentation arguments, final ObjectAdapter returnedAdapter) {
+        this.objectAdapter = objectAdapter;
+        this.action = action;
+        this.arguments = arguments;
+        this.returnedAdapter = returnedAdapter;
+    }
+
+    public ObjectAdapter getObjectAdapter() {
+        return objectAdapter;
+    }
+
+    public ObjectAction getAction() {
+        return action;
+    }
+
+    public JsonRepresentation getArguments() {
+        return arguments;
+    }
+
+    public ObjectAdapter getReturnedAdapter() {
+        return returnedAdapter;
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/bb79d33e/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndCollection.java
----------------------------------------------------------------------
diff --git a/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndCollection.java b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndCollection.java
new file mode 100644
index 0000000..3bb3189
--- /dev/null
+++ b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndCollection.java
@@ -0,0 +1,30 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+
+public class ObjectAndCollection extends ObjectAndMember<OneToManyAssociation> {
+
+    public ObjectAndCollection(final ObjectAdapter objectAdapter, final OneToManyAssociation collection) {
+        super(objectAdapter, collection);
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/bb79d33e/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndMember.java
----------------------------------------------------------------------
diff --git a/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndMember.java b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndMember.java
new file mode 100644
index 0000000..675f884
--- /dev/null
+++ b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndMember.java
@@ -0,0 +1,41 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
+
+public abstract class ObjectAndMember<T extends ObjectMember> {
+
+    private final ObjectAdapter objectAdapter;
+    private final T objectMember;
+
+    public ObjectAndMember(final ObjectAdapter objectAdapter, final T objectMember) {
+        this.objectAdapter = objectAdapter;
+        this.objectMember = objectMember;
+    }
+
+    public ObjectAdapter getObjectAdapter() {
+        return objectAdapter;
+    }
+
+    public T getMember() {
+        return objectMember;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/bb79d33e/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndProperty.java
----------------------------------------------------------------------
diff --git a/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndProperty.java b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndProperty.java
new file mode 100644
index 0000000..0b9a236
--- /dev/null
+++ b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndProperty.java
@@ -0,0 +1,30 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
+
+public class ObjectAndProperty extends ObjectAndMember<OneToOneAssociation> {
+
+    public ObjectAndProperty(final ObjectAdapter objectAdapter, final OneToOneAssociation property) {
+        super(objectAdapter, property);
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/bb79d33e/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectCollectionReprRenderer.java
----------------------------------------------------------------------
diff --git a/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectCollectionReprRenderer.java b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectCollectionReprRenderer.java
new file mode 100644
index 0000000..8dd36c9
--- /dev/null
+++ b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectCollectionReprRenderer.java
@@ -0,0 +1,157 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacetUtils;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
+import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
+import org.apache.isis.viewer.restfulobjects.applib.links.Rel;
+import org.apache.isis.viewer.restfulobjects.rendering.LinkBuilder;
+import org.apache.isis.viewer.restfulobjects.rendering.LinkFollower;
+import org.apache.isis.viewer.restfulobjects.rendering.RendererContext;
+import org.apache.isis.viewer.restfulobjects.rendering.RendererFactory;
+import org.apache.isis.viewer.restfulobjects.rendering.RendererFactoryRegistry;
+import org.apache.isis.viewer.restfulobjects.rendering.ReprRenderer;
+import org.apache.isis.viewer.restfulobjects.rendering.ReprRendererFactoryAbstract;
+import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.CollectionDescriptionReprRenderer;
+
+import com.google.common.collect.Lists;
+
+public class ObjectCollectionReprRenderer extends AbstractObjectMemberReprRenderer<ObjectCollectionReprRenderer, OneToManyAssociation> {
+
+    public static class Factory extends ReprRendererFactoryAbstract {
+
+        public Factory() {
+            super(RepresentationType.OBJECT_COLLECTION);
+        }
+
+        @Override
+        public ReprRenderer<?, ?> newRenderer(final RendererContext resourceContext, final LinkFollower linkFollower, final JsonRepresentation representation) {
+            return new ObjectCollectionReprRenderer(resourceContext, linkFollower, getRepresentationType(), representation);
+        }
+    }
+
+    private ObjectCollectionReprRenderer(final RendererContext resourceContext, final LinkFollower linkFollower, final RepresentationType representationType, final JsonRepresentation representation) {
+        super(resourceContext, linkFollower, representationType, representation, Where.PARENTED_TABLES);
+    }
+
+    @Override
+    public JsonRepresentation render() {
+        // id and memberType are rendered eagerly
+
+        renderMemberContent();
+        if (mode.isStandalone() || mode.isMutated() || !objectAdapter.representsPersistent()) {
+            addValue();
+        }
+        putDisabledReasonIfDisabled();
+
+        if (mode.isStandalone() || mode.isMutated()) {
+            addExtensionsIsisProprietaryChangedObjects();
+        }
+
+        return representation;
+    }
+
+    // ///////////////////////////////////////////////////
+    // value
+    // ///////////////////////////////////////////////////
+
+    private void addValue() {
+        final ObjectAdapter valueAdapter = objectMember.get(objectAdapter);
+        if (valueAdapter == null) {
+            return;
+        }
+
+        final CollectionFacet facet = CollectionFacetUtils.getCollectionFacetFromSpec(valueAdapter);
+        final List<JsonRepresentation> list = Lists.newArrayList();
+        for (final ObjectAdapter elementAdapter : facet.iterable(valueAdapter)) {
+
+            final LinkBuilder newBuilder = DomainObjectReprRenderer.newLinkToBuilder(resourceContext, Rel.OBJECT, elementAdapter);
+
+            list.add(newBuilder.build());
+        }
+
+        representation.mapPut("value", list);
+    }
+
+    // ///////////////////////////////////////////////////
+    // details link
+    // ///////////////////////////////////////////////////
+
+    /**
+     * Mandatory hook method to support x-ro-follow-links
+     */
+    @Override
+    protected void followDetailsLink(final JsonRepresentation detailsLink) {
+        final RendererFactory factory = RendererFactoryRegistry.instance.find(RepresentationType.OBJECT_COLLECTION);
+        final ObjectCollectionReprRenderer renderer = (ObjectCollectionReprRenderer) factory.newRenderer(getResourceContext(), getLinkFollower(), JsonRepresentation.newMap());
+        renderer.with(new ObjectAndCollection(objectAdapter, objectMember)).asFollowed();
+        detailsLink.mapPut("value", renderer.render());
+    }
+
+    // ///////////////////////////////////////////////////
+    // mutators
+    // ///////////////////////////////////////////////////
+
+    @Override
+    protected void addMutatorsIfEnabled() {
+        if (usability().isVetoed()) {
+            return;
+        }
+
+        final CollectionSemantics semantics = CollectionSemantics.determine(objectMember);
+        addMutatorLink(semantics.getAddToKey());
+        addMutatorLink(semantics.getRemoveFromKey());
+
+        return;
+    }
+
+    private void addMutatorLink(final String key) {
+        final Map<String, MutatorSpec> mutators = memberType.getMutators();
+        final MutatorSpec mutatorSpec = mutators.get(key);
+        addLinkFor(mutatorSpec);
+    }
+
+    // ///////////////////////////////////////////////////
+    // extensions and links
+    // ///////////////////////////////////////////////////
+
+    @Override
+    protected void addLinksToFormalDomainModel() {
+        final LinkBuilder linkBuilder = CollectionDescriptionReprRenderer.newLinkToBuilder(resourceContext, Rel.DESCRIBEDBY, objectAdapter.getSpecification(), objectMember);
+        getLinks().arrayAdd(linkBuilder.build());
+    }
+
+    @Override
+    protected void addLinksIsisProprietary() {
+        // none
+    }
+
+    @Override
+    protected void putExtensionsIsisProprietary() {
+        final CollectionSemantics semantics = CollectionSemantics.determine(objectMember);
+        getExtensions().mapPut("collectionSemantics", semantics.name().toLowerCase());
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/bb79d33e/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectPropertyReprRenderer.java
----------------------------------------------------------------------
diff --git a/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectPropertyReprRenderer.java b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectPropertyReprRenderer.java
new file mode 100644
index 0000000..2c1c6c1
--- /dev/null
+++ b/component/viewer/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectPropertyReprRenderer.java
@@ -0,0 +1,167 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
+import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
+import org.apache.isis.viewer.restfulobjects.applib.links.Rel;
+import org.apache.isis.viewer.restfulobjects.rendering.LinkFollower;
+import org.apache.isis.viewer.restfulobjects.rendering.RendererContext;
+import org.apache.isis.viewer.restfulobjects.rendering.RendererFactory;
+import org.apache.isis.viewer.restfulobjects.rendering.RendererFactoryRegistry;
+import org.apache.isis.viewer.restfulobjects.rendering.ReprRenderer;
+import org.apache.isis.viewer.restfulobjects.rendering.ReprRendererFactoryAbstract;
+import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.PropertyDescriptionReprRenderer;
+import org.codehaus.jackson.node.NullNode;
+
+import com.google.common.collect.Lists;
+
+public class ObjectPropertyReprRenderer extends AbstractObjectMemberReprRenderer<ObjectPropertyReprRenderer, OneToOneAssociation> {
+
+    public static class Factory extends ReprRendererFactoryAbstract {
+
+        public Factory() {
+            super(RepresentationType.OBJECT_PROPERTY);
+        }
+
+        @Override
+        public ReprRenderer<?, ?> newRenderer(final RendererContext resourceContext, final LinkFollower linkFollower, final JsonRepresentation representation) {
+            return new ObjectPropertyReprRenderer(resourceContext, linkFollower, getRepresentationType(), representation);
+        }
+    }
+
+    private ObjectPropertyReprRenderer(final RendererContext resourceContext, final LinkFollower linkFollower, final RepresentationType representationType, final JsonRepresentation representation) {
+        super(resourceContext, linkFollower, representationType, representation, Where.OBJECT_FORMS);
+    }
+
+    @Override
+    public JsonRepresentation render() {
+        // id and memberType are rendered eagerly
+
+        renderMemberContent();
+        addValue();
+
+        putDisabledReasonIfDisabled();
+
+        if (mode.isStandalone() || mode.isMutated()) {
+            addChoices();
+            addExtensionsIsisProprietaryChangedObjects();
+        }
+
+        return representation;
+    }
+
+    // ///////////////////////////////////////////////////
+    // value
+    // ///////////////////////////////////////////////////
+
+    private void addValue() {
+        representation.mapPut("value", valueRep());
+    }
+
+    private Object valueRep() {
+        final ObjectAdapter valueAdapter = objectMember.get(objectAdapter);
+        if (valueAdapter == null) {
+            return NullNode.getInstance();
+        }
+        return DomainObjectReprRenderer.valueOrRef(resourceContext, valueAdapter, objectMember.getSpecification());
+    }
+
+    // ///////////////////////////////////////////////////
+    // details link
+    // ///////////////////////////////////////////////////
+
+    /**
+     * Mandatory hook method to support x-ro-follow-links
+     */
+    @Override
+    protected void followDetailsLink(final JsonRepresentation detailsLink) {
+        final RendererFactory factory = RendererFactoryRegistry.instance.find(RepresentationType.OBJECT_PROPERTY);
+        final ObjectPropertyReprRenderer renderer = (ObjectPropertyReprRenderer) factory.newRenderer(getResourceContext(), getLinkFollower(), JsonRepresentation.newMap());
+        renderer.with(new ObjectAndProperty(objectAdapter, objectMember)).asFollowed();
+        detailsLink.mapPut("value", renderer.render());
+    }
+
+    // ///////////////////////////////////////////////////
+    // mutators
+    // ///////////////////////////////////////////////////
+
+    @Override
+    protected void addMutatorsIfEnabled() {
+        if (usability().isVetoed()) {
+            return;
+        }
+        final Map<String, MutatorSpec> mutators = memberType.getMutators();
+        for (final String mutator : mutators.keySet()) {
+            final MutatorSpec mutatorSpec = mutators.get(mutator);
+            addLinkFor(mutatorSpec);
+        }
+        return;
+    }
+
+    // ///////////////////////////////////////////////////
+    // choices
+    // ///////////////////////////////////////////////////
+
+    private ObjectPropertyReprRenderer addChoices() {
+        final Object propertyChoices = propertyChoices();
+        if (propertyChoices != null) {
+            representation.mapPut("choices", propertyChoices);
+        }
+        return this;
+    }
+
+    private Object propertyChoices() {
+        final ObjectAdapter[] choiceAdapters = objectMember.getChoices(objectAdapter);
+        if (choiceAdapters == null || choiceAdapters.length == 0) {
+            return null;
+        }
+        final List<Object> list = Lists.newArrayList();
+        for (final ObjectAdapter choiceAdapter : choiceAdapters) {
+            final ObjectSpecification objectSpec = objectMember.getSpecification();
+            list.add(DomainObjectReprRenderer.valueOrRef(resourceContext, choiceAdapter, objectSpec));
+        }
+        return list;
+    }
+
+    // ///////////////////////////////////////////////////
+    // extensions and links
+    // ///////////////////////////////////////////////////
+
+    @Override
+    protected void addLinksToFormalDomainModel() {
+        getLinks().arrayAdd(PropertyDescriptionReprRenderer.newLinkToBuilder(getResourceContext(), Rel.DESCRIBEDBY, objectAdapter.getSpecification(), objectMember).build());
+    }
+
+    @Override
+    protected void addLinksIsisProprietary() {
+        // none
+    }
+
+    @Override
+    protected void putExtensionsIsisProprietary() {
+        // none
+    }
+
+}
\ No newline at end of file