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 includeUp() { 083 return isRegular(); 084 } 085 086 public boolean checkVisibility() { 087 return isRegular() || isUpdatePropertiesLinkArgs(); 088 } 089 090 public boolean isArgs() { 091 return isPersistLinkArgs() || isUpdatePropertiesLinkArgs(); 092 } 093 } 094 095 private ObjectAdapterLinkTo linkToBuilder; 096 private ObjectAdapter objectAdapter; 097 private Mode mode = Mode.REGULAR; 098 099 public DomainObjectReprRenderer(final RendererContext resourceContext, final LinkFollowSpecs linkFollower, final JsonRepresentation representation) { 100 super(resourceContext, linkFollower, RepresentationType.DOMAIN_OBJECT, representation); 101 usingLinkToBuilder(new DomainObjectLinkTo()); 102 } 103 104 /** 105 * Override the default {@link ObjectAdapterLinkTo} (that is used for 106 * generating links in {@link #linkTo(ObjectAdapter)}). 107 */ 108 public DomainObjectReprRenderer usingLinkToBuilder(final ObjectAdapterLinkTo objectAdapterLinkToBuilder) { 109 this.linkToBuilder = objectAdapterLinkToBuilder.usingUrlBase(rendererContext); 110 return this; 111 } 112 113 @Override 114 public DomainObjectReprRenderer with(final ObjectAdapter objectAdapter) { 115 this.objectAdapter = objectAdapter; 116 String domainTypeHref = DomainTypeReprRenderer.newLinkToBuilder(getRendererContext(), Rel.DOMAIN_TYPE, objectAdapter.getSpecification()).build().getString("href"); 117 addMediaTypeParams(X_RO_DOMAIN_TYPE, domainTypeHref); 118 return this; 119 } 120 121 @Override 122 public JsonRepresentation render() { 123 124 final boolean isService = objectAdapter.getSpecification().isService(); 125 126 if (!(mode.isArgs())) { 127 128 // self, extensions.oid 129 if (objectAdapter.representsPersistent()) { 130 if (includesSelf) { 131 addLinkToSelf(); 132 } 133 getExtensions().mapPut("oid", getOidStr()); 134 } 135 136 // title 137 final String title = objectAdapter.titleString(null); 138 representation.mapPut("title", title); 139 140 // serviceId or instance Id 141 if (isService) { 142 representation.mapPut("serviceId", ServiceUtil.id(objectAdapter.getObject())); 143 } else { 144 final String domainType = getDomainType(); 145 final String instanceId = getInstanceId(); 146 if (domainType != null) { 147 representation.mapPut("domainType", domainType); 148 representation.mapPut("instanceId", instanceId); 149 150 } 151 } 152 } 153 154 // members 155 withMembers(objectAdapter); 156 157 // described by 158 if (mode.includeDescribedBy()) { 159 addLinkToDescribedBy(); 160 } 161 if(isService && mode.includeUp()) { 162 addLinkToUp(); 163 } 164 165 if (!mode.isArgs()) { 166 // update/persist 167 addPersistLinkIfTransientAndPersistable(); 168 addUpdatePropertiesLinkIfRequired(); 169 170 // extensions 171 getExtensions().mapPut("isService", isService); 172 getExtensions().mapPut("isPersistent", objectAdapter.representsPersistent()); 173 } 174 175 return representation; 176 } 177 178 private void addLinkToSelf() { 179 final JsonRepresentation link = linkToBuilder.with(objectAdapter).builder(Rel.SELF).build(); 180 181 final LinkFollowSpecs linkFollower = getLinkFollowSpecs().follow("links"); 182 if (linkFollower.matches(link)) { 183 final DomainObjectReprRenderer renderer = new DomainObjectReprRenderer(getRendererContext(), linkFollower, JsonRepresentation.newMap()); 184 renderer.with(objectAdapter); 185 link.mapPut("value", renderer.render()); 186 } 187 188 getLinks().arrayAdd(link); 189 } 190 191 private void addLinkToDescribedBy() { 192 final JsonRepresentation link = DomainTypeReprRenderer.newLinkToBuilder(getRendererContext(), Rel.DESCRIBEDBY, objectAdapter.getSpecification()).build(); 193 194 final LinkFollowSpecs linkFollower = getLinkFollowSpecs().follow("links"); 195 if (linkFollower.matches(link)) { 196 final DomainTypeReprRenderer renderer = new DomainTypeReprRenderer(getRendererContext(), linkFollower, JsonRepresentation.newMap()); 197 renderer.with(objectAdapter.getSpecification()); 198 link.mapPut("value", renderer.render()); 199 } 200 getLinks().arrayAdd(link); 201 } 202 203 private void addLinkToUp() { 204 final JsonRepresentation link = LinkBuilder.newBuilder(rendererContext, Rel.UP.getName(), RepresentationType.LIST, "services").build(); 205 getLinks().arrayAdd(link); 206 } 207 208 private String getDomainType() { 209 return org.apache.isis.viewer.restfulobjects.rendering.util.OidUtils.getDomainType(objectAdapter); 210 } 211 212 private String getInstanceId() { 213 return org.apache.isis.viewer.restfulobjects.rendering.util.OidUtils.getInstanceId(rendererContext, objectAdapter); 214 } 215 216 private String getOidStr() { 217 return org.apache.isis.viewer.restfulobjects.rendering.util.OidUtils.getOidStr(rendererContext, objectAdapter); 218 } 219 220 private DomainObjectReprRenderer withMembers(final ObjectAdapter objectAdapter) { 221 final JsonRepresentation appendTo = 222 mode.isUpdatePropertiesLinkArgs() ? representation : JsonRepresentation.newMap(); 223 final List<ObjectAssociation> associations = objectAdapter.getSpecification().getAssociations(Contributed.EXCLUDED); 224 addAssociations(objectAdapter, appendTo, associations); 225 226 if (mode.isRegular()) { 227 final List<ObjectAction> actions = objectAdapter.getSpecification().getObjectActions(Contributed.INCLUDED); 228 addActions(objectAdapter, actions, appendTo); 229 } 230 if(!mode.isUpdatePropertiesLinkArgs()) { 231 representation.mapPut("members", appendTo); 232 } 233 return this; 234 } 235 236 private void addAssociations(final ObjectAdapter objectAdapter, final JsonRepresentation members, final List<ObjectAssociation> associations) { 237 final LinkFollowSpecs linkFollower = getLinkFollowSpecs().follow("members"); 238 for (final ObjectAssociation assoc : associations) { 239 240 if (mode.checkVisibility()) { 241 final Consent visibility = assoc.isVisible(getRendererContext().getAuthenticationSession(), objectAdapter, rendererContext.getWhere()); 242 if (!visibility.isAllowed()) { 243 continue; 244 } 245 } 246 if (assoc instanceof OneToOneAssociation) { 247 final OneToOneAssociation property = (OneToOneAssociation) assoc; 248 249 final ObjectPropertyReprRenderer renderer = new ObjectPropertyReprRenderer(getRendererContext(), linkFollower, property.getId(), JsonRepresentation.newMap()); 250 251 renderer.with(new ObjectAndProperty(objectAdapter, property)).usingLinkTo(linkToBuilder); 252 253 if (mode.isArgs()) { 254 renderer.asArguments(); 255 } 256 if(mode.isEventSerialization()) { 257 renderer.asEventSerialization(); 258 } 259 260 members.mapPut(assoc.getId(), renderer.render()); 261 } 262 263 if (mode.isArgs()) { 264 continue; 265 } 266 if (assoc instanceof OneToManyAssociation) { 267 final OneToManyAssociation collection = (OneToManyAssociation) assoc; 268 269 final ObjectCollectionReprRenderer renderer = new ObjectCollectionReprRenderer(getRendererContext(), linkFollower, collection.getId(), JsonRepresentation.newMap()); 270 271 renderer.with(new ObjectAndCollection(objectAdapter, collection)).usingLinkTo(linkToBuilder); 272 if(mode.isEventSerialization()) { 273 renderer.asEventSerialization(); 274 } 275 276 members.mapPut(assoc.getId(), renderer.render()); 277 } 278 } 279 } 280 281 private void addActions(final ObjectAdapter objectAdapter, final List<ObjectAction> actions, final JsonRepresentation members) { 282 for (final ObjectAction action : actions) { 283 final Consent visibility = action.isVisible(getRendererContext().getAuthenticationSession(), objectAdapter, rendererContext.getWhere()); 284 if (!visibility.isAllowed()) { 285 continue; 286 } 287 final LinkFollowSpecs linkFollowSpecs = getLinkFollowSpecs().follow("members["+action.getId()+"]"); 288 289 final ObjectActionReprRenderer renderer = new ObjectActionReprRenderer(getRendererContext(), linkFollowSpecs, action.getId(), JsonRepresentation.newMap()); 290 291 renderer.with(new ObjectAndAction(objectAdapter, action)).usingLinkTo(linkToBuilder); 292 293 members.mapPut(action.getId(), renderer.render()); 294 } 295 } 296 297 private void addPersistLinkIfTransientAndPersistable() { 298 if (objectAdapter.representsPersistent()) { 299 return; 300 } 301 if(objectAdapter.getSpecification().containsDoOpFacet(NotPersistableFacet.class)) { 302 return; 303 } 304 final DomainObjectReprRenderer renderer = new DomainObjectReprRenderer(getRendererContext(), null, JsonRepresentation.newMap()); 305 final JsonRepresentation domainObjectRepr = renderer.with(objectAdapter).asPersistLinkArguments().render(); 306 307 final String domainType = objectAdapter.getSpecification().getSpecId().asString(); 308 final LinkBuilder persistLinkBuilder = LinkBuilder.newBuilder(getRendererContext(), Rel.PERSIST.getName(), RepresentationType.DOMAIN_OBJECT, "objects/%s", domainType).withHttpMethod(RestfulHttpMethod.POST).withArguments(domainObjectRepr); 309 getLinks().arrayAdd(persistLinkBuilder.build()); 310 } 311 312 private DomainObjectReprRenderer asPersistLinkArguments() { 313 this.mode = Mode.PERSIST_LINK_ARGUMENTS; 314 return this; 315 } 316 317 private DomainObjectReprRenderer asUpdatePropertiesLinkArguments() { 318 this.mode = Mode.UPDATE_PROPERTIES_LINK_ARGUMENTS; 319 return this; 320 } 321 322 // not part of the spec 323 public DomainObjectReprRenderer asEventSerialization() { 324 this.mode = Mode.EVENT_SERIALIZATION; 325 return this; 326 } 327 328 329 private void addUpdatePropertiesLinkIfRequired() { 330 if(mode.isEventSerialization()) { 331 return; 332 } 333 if (!objectAdapter.representsPersistent()) { 334 return; 335 } 336 final boolean isService = objectAdapter.getSpecification().isService(); 337 if(isService) { 338 return; 339 } 340 341 final DomainObjectReprRenderer renderer = new DomainObjectReprRenderer(getRendererContext(), null, JsonRepresentation.newMap()); 342 final JsonRepresentation domainObjectRepr = renderer.with(objectAdapter).asUpdatePropertiesLinkArguments().render(); 343 344 final LinkBuilder updateLinkBuilder = LinkBuilder.newBuilder(getRendererContext(), Rel.UPDATE.getName(), RepresentationType.DOMAIN_OBJECT, "objects/%s/%s", getDomainType(), getInstanceId()).withHttpMethod(RestfulHttpMethod.PUT).withArguments(domainObjectRepr); 345 getLinks().arrayAdd(updateLinkBuilder.build()); 346 } 347 348 // /////////////////////////////////////////////////////////////////// 349 // 350 // /////////////////////////////////////////////////////////////////// 351 352 public static Object valueOrRef(final RendererContext resourceContext, final ObjectAdapter objectAdapter, final ObjectSpecification objectSpec) { 353 if(objectAdapter.isValue()) { 354 String format = null; // TODO 355 return JsonValueEncoder.asObject(objectAdapter, format); 356 } 357 final TitleFacet titleFacet = objectSpec.getFacet(TitleFacet.class); 358 final String title = titleFacet.title(objectAdapter, resourceContext.getLocalization()); 359 return DomainObjectReprRenderer.newLinkToBuilder(resourceContext, Rel.VALUE, objectAdapter).withTitle(title).build(); 360 } 361 362 363 364 // /////////////////////////////////////////////////////////////////// 365 // dependencies (from context) 366 // /////////////////////////////////////////////////////////////////// 367 368 protected static OidMarshaller getOidMarshaller() { 369 return IsisContext.getOidMarshaller(); 370 } 371 372}