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