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