001/**
002 *  Licensed to the Apache Software Foundation (ASF) under one or more
003 *  contributor license agreements.  See the NOTICE file distributed with
004 *  this work for additional information regarding copyright ownership.
005 *  The ASF licenses this file to You under the Apache License, Version 2.0
006 *  (the "License"); you may not use this file except in compliance with
007 *  the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS,
013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 *  See the License for the specific language governing permissions and
015 *  limitations under the License.
016 */
017package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
018
019import java.util.List;
020
021import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
022import org.apache.isis.core.metamodel.adapter.oid.OidMarshaller;
023import org.apache.isis.core.metamodel.consent.Consent;
024import org.apache.isis.core.metamodel.facets.object.notpersistable.NotPersistableFacet;
025import org.apache.isis.core.metamodel.facets.object.title.TitleFacet;
026import org.apache.isis.core.metamodel.services.ServiceUtil;
027import org.apache.isis.core.metamodel.spec.ObjectSpecification;
028import org.apache.isis.core.metamodel.spec.feature.Contributed;
029import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
030import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
031import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
032import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
033import org.apache.isis.core.runtime.system.context.IsisContext;
034import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
035import org.apache.isis.viewer.restfulobjects.applib.Rel;
036import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
037import org.apache.isis.viewer.restfulobjects.applib.RestfulHttpMethod;
038import org.apache.isis.viewer.restfulobjects.rendering.LinkBuilder;
039import org.apache.isis.viewer.restfulobjects.rendering.LinkFollowSpecs;
040import org.apache.isis.viewer.restfulobjects.rendering.RendererContext;
041import org.apache.isis.viewer.restfulobjects.rendering.ReprRendererAbstract;
042import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.DomainTypeReprRenderer;
043import org.apache.isis.viewer.restfulobjects.rendering.util.OidUtils;
044
045public class DomainObjectReprRenderer extends ReprRendererAbstract<DomainObjectReprRenderer, ObjectAdapter> {
046
047    private static final String X_RO_DOMAIN_TYPE = "x-ro-domain-type";
048
049    public static LinkBuilder newLinkToBuilder(final RendererContext rendererContext, final Rel rel, final ObjectAdapter objectAdapter) {
050        String domainType = OidUtils.getDomainType(objectAdapter);
051        String instanceId = OidUtils.getInstanceId(rendererContext, objectAdapter);
052        final String url = "objects/" + domainType + "/" + instanceId;
053        return LinkBuilder.newBuilder(rendererContext, rel.getName(), RepresentationType.DOMAIN_OBJECT, url).withTitle(objectAdapter.titleString(null));
054    }
055
056    private static enum Mode {
057        REGULAR, 
058        PERSIST_LINK_ARGUMENTS, 
059        UPDATE_PROPERTIES_LINK_ARGUMENTS,
060        EVENT_SERIALIZATION;
061
062        public boolean isRegular() {
063            return this == REGULAR;
064        }
065
066        public boolean isPersistLinkArgs() {
067            return this == PERSIST_LINK_ARGUMENTS;
068        }
069        
070        public boolean isUpdatePropertiesLinkArgs() {
071            return this == UPDATE_PROPERTIES_LINK_ARGUMENTS;
072        }
073
074        public boolean isEventSerialization() {
075            return this == EVENT_SERIALIZATION;
076        }
077
078        public boolean includeDescribedBy() {
079            return isRegular() || isPersistLinkArgs();
080        }
081        
082        public boolean checkVisibility() {
083            return isRegular() || isUpdatePropertiesLinkArgs();
084        }
085
086        public boolean isArgs() {
087            return isPersistLinkArgs() || isUpdatePropertiesLinkArgs();
088        }
089    }
090
091    private ObjectAdapterLinkTo linkToBuilder;
092    private ObjectAdapter objectAdapter;
093    private Mode mode = Mode.REGULAR;
094
095    public DomainObjectReprRenderer(final RendererContext resourceContext, final LinkFollowSpecs linkFollower, final JsonRepresentation representation) {
096        super(resourceContext, linkFollower, RepresentationType.DOMAIN_OBJECT, representation);
097        usingLinkToBuilder(new DomainObjectLinkTo());
098    }
099
100    /**
101     * Override the default {@link ObjectAdapterLinkTo} (that is used for
102     * generating links in {@link #linkTo(ObjectAdapter)}).
103     */
104    public DomainObjectReprRenderer usingLinkToBuilder(final ObjectAdapterLinkTo objectAdapterLinkToBuilder) {
105        this.linkToBuilder = objectAdapterLinkToBuilder.usingUrlBase(rendererContext);
106        return this;
107    }
108
109    @Override
110    public DomainObjectReprRenderer with(final ObjectAdapter objectAdapter) {
111        this.objectAdapter = objectAdapter;
112        String domainTypeHref = DomainTypeReprRenderer.newLinkToBuilder(getRendererContext(), Rel.DOMAIN_TYPE, objectAdapter.getSpecification()).build().getString("href");
113        addMediaTypeParams(X_RO_DOMAIN_TYPE, domainTypeHref);
114        return this;
115    }
116    
117    @Override
118    public JsonRepresentation render() {
119
120        if (!(mode.isArgs())) {
121            
122            // self, extensions.oid
123            if (objectAdapter.representsPersistent()) {
124                if (includesSelf) {
125                    addLinkToSelf();
126                }
127                getExtensions().mapPut("oid", getOidStr());
128            }
129            
130            // title
131            final String title = objectAdapter.titleString(null);
132            representation.mapPut("title", title);
133            
134            // serviceId or instance Id
135            final boolean isService = objectAdapter.getSpecification().isService();
136            if (isService) {
137                representation.mapPut("serviceId", ServiceUtil.id(objectAdapter.getObject()));
138            } else {
139                final String domainType = getDomainType();
140                final String instanceId = getInstanceId();
141                if (domainType != null) {
142                    representation.mapPut("domainType", domainType);
143                    representation.mapPut("instanceId", instanceId);
144                    
145                }
146            }
147        }
148
149        // members
150        withMembers(objectAdapter);
151
152        // described by
153        if (mode.includeDescribedBy()) {
154            addLinkToDescribedBy();
155        }
156
157        if (!mode.isArgs()) {
158            // update/persist
159            addPersistLinkIfTransientAndPersistable();
160            addUpdatePropertiesLinkIfRequired();
161
162            // extensions
163            final boolean isService = objectAdapter.getSpecification().isService();
164            getExtensions().mapPut("isService", isService);
165            getExtensions().mapPut("isPersistent", objectAdapter.representsPersistent());
166        }
167
168        return representation;
169    }
170
171    private void addLinkToSelf() {
172        final JsonRepresentation link = linkToBuilder.with(objectAdapter).builder(Rel.SELF).build();
173
174        final LinkFollowSpecs linkFollower = getLinkFollowSpecs().follow("links");
175        if (linkFollower.matches(link)) {
176            final DomainObjectReprRenderer renderer = new DomainObjectReprRenderer(getRendererContext(), linkFollower, JsonRepresentation.newMap());
177            renderer.with(objectAdapter);
178            link.mapPut("value", renderer.render());
179        }
180
181        getLinks().arrayAdd(link);
182    }
183
184    private void addLinkToDescribedBy() {
185        final JsonRepresentation link = DomainTypeReprRenderer.newLinkToBuilder(getRendererContext(), Rel.DESCRIBEDBY, objectAdapter.getSpecification()).build();
186
187        final LinkFollowSpecs linkFollower = getLinkFollowSpecs().follow("links");
188        if (linkFollower.matches(link)) {
189            final DomainTypeReprRenderer renderer = new DomainTypeReprRenderer(getRendererContext(), linkFollower, JsonRepresentation.newMap());
190            renderer.with(objectAdapter.getSpecification());
191            link.mapPut("value", renderer.render());
192        }
193        getLinks().arrayAdd(link);
194    }
195
196    private String getDomainType() {
197        return org.apache.isis.viewer.restfulobjects.rendering.util.OidUtils.getDomainType(objectAdapter);
198    }
199
200    private String getInstanceId() {
201        return org.apache.isis.viewer.restfulobjects.rendering.util.OidUtils.getInstanceId(rendererContext, objectAdapter);
202    }
203
204    private String getOidStr() {
205        return org.apache.isis.viewer.restfulobjects.rendering.util.OidUtils.getOidStr(rendererContext, objectAdapter);
206    }
207
208    private DomainObjectReprRenderer withMembers(final ObjectAdapter objectAdapter) {
209        final JsonRepresentation appendTo = 
210                mode.isUpdatePropertiesLinkArgs() ? representation : JsonRepresentation.newMap();
211        final List<ObjectAssociation> associations = objectAdapter.getSpecification().getAssociations(Contributed.EXCLUDED);
212        addAssociations(objectAdapter, appendTo, associations);
213
214        if (mode.isRegular()) {
215            final List<ObjectAction> actions = objectAdapter.getSpecification().getObjectActions(Contributed.INCLUDED);
216            addActions(objectAdapter, actions, appendTo);
217        }
218        if(!mode.isUpdatePropertiesLinkArgs()) {
219            representation.mapPut("members", appendTo);
220        }
221        return this;
222    }
223
224    private void addAssociations(final ObjectAdapter objectAdapter, final JsonRepresentation members, final List<ObjectAssociation> associations) {
225        final LinkFollowSpecs linkFollower = getLinkFollowSpecs().follow("members");
226        for (final ObjectAssociation assoc : associations) {
227
228            if (mode.checkVisibility()) {
229                final Consent visibility = assoc.isVisible(getRendererContext().getAuthenticationSession(), objectAdapter, rendererContext.getWhere());
230                if (!visibility.isAllowed()) {
231                    continue;
232                }
233            }
234            if (assoc instanceof OneToOneAssociation) {
235                final OneToOneAssociation property = (OneToOneAssociation) assoc;
236
237                final ObjectPropertyReprRenderer renderer = new ObjectPropertyReprRenderer(getRendererContext(), linkFollower, property.getId(), JsonRepresentation.newMap());
238
239                renderer.with(new ObjectAndProperty(objectAdapter, property)).usingLinkTo(linkToBuilder);
240
241                if (mode.isArgs()) {
242                    renderer.asArguments();
243                }
244                if(mode.isEventSerialization()) {
245                    renderer.asEventSerialization();
246                }
247
248                members.mapPut(assoc.getId(), renderer.render());
249            }
250
251            if (mode.isArgs()) {
252                continue;
253            }
254            if (assoc instanceof OneToManyAssociation) {
255                final OneToManyAssociation collection = (OneToManyAssociation) assoc;
256
257                final ObjectCollectionReprRenderer renderer = new ObjectCollectionReprRenderer(getRendererContext(), linkFollower, collection.getId(), JsonRepresentation.newMap());
258
259                renderer.with(new ObjectAndCollection(objectAdapter, collection)).usingLinkTo(linkToBuilder);
260                if(mode.isEventSerialization()) {
261                    renderer.asEventSerialization();
262                }
263
264                members.mapPut(assoc.getId(), renderer.render());
265            }
266        }
267    }
268
269    private void addActions(final ObjectAdapter objectAdapter, final List<ObjectAction> actions, final JsonRepresentation members) {
270        for (final ObjectAction action : actions) {
271            final Consent visibility = action.isVisible(getRendererContext().getAuthenticationSession(), objectAdapter, rendererContext.getWhere());
272            if (!visibility.isAllowed()) {
273                continue;
274            }
275            final LinkFollowSpecs linkFollowSpecs = getLinkFollowSpecs().follow("members["+action.getId()+"]");
276            
277            final ObjectActionReprRenderer renderer = new ObjectActionReprRenderer(getRendererContext(), linkFollowSpecs, action.getId(), JsonRepresentation.newMap());
278
279            renderer.with(new ObjectAndAction(objectAdapter, action)).usingLinkTo(linkToBuilder);
280
281            members.mapPut(action.getId(), renderer.render());
282        }
283    }
284
285    private void addPersistLinkIfTransientAndPersistable() {
286        if (objectAdapter.representsPersistent()) {
287            return;
288        }
289        if(objectAdapter.getSpecification().containsDoOpFacet(NotPersistableFacet.class)) {
290            return;
291        }
292        final DomainObjectReprRenderer renderer = new DomainObjectReprRenderer(getRendererContext(), null, JsonRepresentation.newMap());
293        final JsonRepresentation domainObjectRepr = renderer.with(objectAdapter).asPersistLinkArguments().render();
294
295        final String domainType = objectAdapter.getSpecification().getSpecId().asString();
296        final LinkBuilder persistLinkBuilder = LinkBuilder.newBuilder(getRendererContext(), Rel.PERSIST.getName(), RepresentationType.DOMAIN_OBJECT, "objects/%s", domainType).withHttpMethod(RestfulHttpMethod.POST).withArguments(domainObjectRepr);
297        getLinks().arrayAdd(persistLinkBuilder.build());
298    }
299
300    private DomainObjectReprRenderer asPersistLinkArguments() {
301        this.mode = Mode.PERSIST_LINK_ARGUMENTS;
302        return this;
303    }
304
305    private DomainObjectReprRenderer asUpdatePropertiesLinkArguments() {
306        this.mode = Mode.UPDATE_PROPERTIES_LINK_ARGUMENTS;
307        return this;
308    }
309
310    // not part of the spec
311    public DomainObjectReprRenderer asEventSerialization() {
312        this.mode = Mode.EVENT_SERIALIZATION;
313        return this;
314    }
315
316
317    private void addUpdatePropertiesLinkIfRequired() {
318        if(mode.isEventSerialization()) {
319            return;
320        }
321        if (!objectAdapter.representsPersistent()) {
322            return;
323        }
324        final boolean isService = objectAdapter.getSpecification().isService();
325        if(isService) {
326            return;
327        }
328
329        final DomainObjectReprRenderer renderer = new DomainObjectReprRenderer(getRendererContext(), null, JsonRepresentation.newMap());
330        final JsonRepresentation domainObjectRepr = renderer.with(objectAdapter).asUpdatePropertiesLinkArguments().render();
331
332        final LinkBuilder updateLinkBuilder = LinkBuilder.newBuilder(getRendererContext(), Rel.UPDATE.getName(), RepresentationType.DOMAIN_OBJECT, "objects/%s/%s", getDomainType(), getInstanceId()).withHttpMethod(RestfulHttpMethod.PUT).withArguments(domainObjectRepr);
333        getLinks().arrayAdd(updateLinkBuilder.build());
334    }
335
336    // ///////////////////////////////////////////////////////////////////
337    //
338    // ///////////////////////////////////////////////////////////////////
339
340    public static Object valueOrRef(final RendererContext resourceContext, final ObjectAdapter objectAdapter, final ObjectSpecification objectSpec) {
341        if(objectAdapter.isValue()) {
342            return JsonValueEncoder.asObject(objectAdapter);
343        }
344        final TitleFacet titleFacet = objectSpec.getFacet(TitleFacet.class);
345        final String title = titleFacet.title(objectAdapter, resourceContext.getLocalization());
346        return DomainObjectReprRenderer.newLinkToBuilder(resourceContext, Rel.VALUE, objectAdapter).withTitle(title).build();
347    }
348
349 
350    
351    // ///////////////////////////////////////////////////////////////////
352    // dependencies (from context)
353    // ///////////////////////////////////////////////////////////////////
354
355    protected static OidMarshaller getOidMarshaller() {
356                return IsisContext.getOidMarshaller();
357        }
358
359}