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.codehaus.jackson.node.NullNode; 022 023import org.apache.isis.applib.annotation.Render.Type; 024import org.apache.isis.core.metamodel.adapter.ObjectAdapter; 025import org.apache.isis.core.metamodel.adapter.oid.Oid; 026import org.apache.isis.core.metamodel.adapter.oid.OidMarshaller; 027import org.apache.isis.core.metamodel.adapter.oid.TypedOid; 028import org.apache.isis.core.metamodel.consent.Consent; 029import org.apache.isis.core.metamodel.facets.members.resolve.RenderFacet; 030import org.apache.isis.core.metamodel.facets.object.notpersistable.NotPersistableFacet; 031import org.apache.isis.core.metamodel.facets.object.title.TitleFacet; 032import org.apache.isis.core.metamodel.facets.object.value.ValueFacet; 033import org.apache.isis.core.metamodel.services.ServiceUtil; 034import org.apache.isis.core.metamodel.spec.ObjectActionSet; 035import org.apache.isis.core.metamodel.spec.ObjectSpecification; 036import org.apache.isis.core.metamodel.spec.feature.ObjectAction; 037import org.apache.isis.core.metamodel.spec.feature.ObjectActionContainer.Contributed; 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.core.runtime.system.context.IsisContext; 042import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation; 043import org.apache.isis.viewer.restfulobjects.applib.Rel; 044import org.apache.isis.viewer.restfulobjects.applib.RepresentationType; 045import org.apache.isis.viewer.restfulobjects.applib.RestfulHttpMethod; 046import org.apache.isis.viewer.restfulobjects.rendering.LinkBuilder; 047import org.apache.isis.viewer.restfulobjects.rendering.LinkFollowSpecs; 048import org.apache.isis.viewer.restfulobjects.rendering.RendererContext; 049import org.apache.isis.viewer.restfulobjects.rendering.ReprRendererAbstract; 050import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.DomainTypeReprRenderer; 051import org.apache.isis.viewer.restfulobjects.rendering.util.OidUtils; 052 053public class DomainObjectReprRenderer extends ReprRendererAbstract<DomainObjectReprRenderer, ObjectAdapter> { 054 055 private static final String X_RO_DOMAIN_TYPE = "x-ro-domain-type"; 056 057 public static LinkBuilder newLinkToBuilder(final RendererContext rendererContext, final Rel rel, final ObjectAdapter objectAdapter) { 058 String domainType = OidUtils.getDomainType(objectAdapter); 059 String instanceId = OidUtils.getInstanceId(rendererContext, objectAdapter); 060 final String url = "objects/" + domainType + "/" + instanceId; 061 final LinkBuilder builder = LinkBuilder.newBuilder(rendererContext, rel.getName(), RepresentationType.DOMAIN_OBJECT, url).withTitle(objectAdapter.titleString()); 062 return builder; 063 } 064 065 private static enum Mode { 066 REGULAR(false, true, true), 067 PERSIST_LINK_ARGUMENTS(true, true, false), 068 MODIFY_PROPERTIES_LINK_ARGUMENTS(true, false, false), 069 EVENT_SERIALIZATION(false, false, true); 070 071 private final boolean representsArguments; 072 private final boolean describedBy; 073 private boolean checkVisibility; 074 075 private Mode(final boolean representsArguments, final boolean describedBy, final boolean visibilityCheck) { 076 this.representsArguments = representsArguments; 077 this.describedBy = describedBy; 078 this.checkVisibility = visibilityCheck; 079 } 080 081 public boolean representsArguments() { 082 return representsArguments; 083 } 084 085 public boolean includesDescribedBy() { 086 return describedBy; 087 } 088 089 public boolean checkVisibility() { 090 return checkVisibility; 091 } 092 } 093 094 private ObjectAdapterLinkTo linkToBuilder; 095 private ObjectAdapter objectAdapter; 096 private Mode mode = Mode.REGULAR; 097 098 public DomainObjectReprRenderer(final RendererContext resourceContext, final LinkFollowSpecs linkFollower, final JsonRepresentation representation) { 099 super(resourceContext, linkFollower, RepresentationType.DOMAIN_OBJECT, representation); 100 usingLinkToBuilder(new DomainObjectLinkTo()); 101 } 102 103 /** 104 * Override the default {@link ObjectAdapterLinkTo} (that is used for 105 * generating links in {@link #linkTo(ObjectAdapter)}). 106 */ 107 public DomainObjectReprRenderer usingLinkToBuilder(final ObjectAdapterLinkTo objectAdapterLinkToBuilder) { 108 this.linkToBuilder = objectAdapterLinkToBuilder.usingUrlBase(rendererContext); 109 return this; 110 } 111 112 @Override 113 public DomainObjectReprRenderer with(final ObjectAdapter objectAdapter) { 114 this.objectAdapter = objectAdapter; 115 String domainTypeHref = DomainTypeReprRenderer.newLinkToBuilder(getRendererContext(), Rel.DOMAIN_TYPE, objectAdapter.getSpecification()).build().getString("href"); 116 addMediaTypeParams(X_RO_DOMAIN_TYPE, domainTypeHref); 117 return this; 118 } 119 120 @Override 121 public JsonRepresentation render() { 122 123 // self, extensions.oid 124 if (!mode.representsArguments()) { 125 if (objectAdapter.representsPersistent()) { 126 if (includesSelf) { 127 addLinkToSelf(); 128 } 129 getExtensions().mapPut("oid", getOidStr()); 130 } 131 } 132 133 // title 134 if (!mode.representsArguments()) { 135 final String title = objectAdapter.titleString(null); 136 representation.mapPut("title", title); 137 138 } 139 140 // serviceId or instance Id 141 if (!mode.representsArguments()) { 142 final boolean isService = objectAdapter.getSpecification().isService(); 143 if (isService) { 144 representation.mapPut("serviceId", ServiceUtil.id(objectAdapter.getObject())); 145 } else { 146 final String domainType = getDomainType(); 147 final String instanceId = getInstanceId(); 148 if(domainType != null) { 149 representation.mapPut("domainType", domainType); 150 representation.mapPut("instanceId", instanceId); 151 152 } 153 } 154 } 155 156 // members 157 withMembers(objectAdapter); 158 159 // described by 160 if (mode.includesDescribedBy()) { 161 addLinkToDescribedBy(); 162 } 163 164 if (!mode.representsArguments()) { 165 // update/persist 166 addPersistLinkIfTransientAndPersistable(); 167 addUpdatePropertiesLinkIfPersistentAndNotService(); 168 169 // extensions 170 final boolean isService = objectAdapter.getSpecification().isService(); 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 String getDomainType() { 204 return org.apache.isis.viewer.restfulobjects.rendering.util.OidUtils.getDomainType(objectAdapter); 205 } 206 207 private String getInstanceId() { 208 return org.apache.isis.viewer.restfulobjects.rendering.util.OidUtils.getInstanceId(rendererContext, objectAdapter); 209 } 210 211 private String getOidStr() { 212 return org.apache.isis.viewer.restfulobjects.rendering.util.OidUtils.getOidStr(rendererContext, objectAdapter); 213 } 214 215 private DomainObjectReprRenderer withMembers(final ObjectAdapter objectAdapter) { 216 final JsonRepresentation members = JsonRepresentation.newMap(); 217 final List<ObjectAssociation> associations = objectAdapter.getSpecification().getAssociations(); 218 addAssociations(objectAdapter, members, associations); 219 220 if (!mode.representsArguments()) { 221 final List<ObjectAction> actions = objectAdapter.getSpecification().getObjectActions(Contributed.INCLUDED); 222 addActions(objectAdapter, actions, members); 223 } 224 representation.mapPut("members", members); 225 return this; 226 } 227 228 private void addAssociations(final ObjectAdapter objectAdapter, final JsonRepresentation members, final List<ObjectAssociation> associations) { 229 final LinkFollowSpecs linkFollower = getLinkFollowSpecs().follow("members"); 230 for (final ObjectAssociation assoc : associations) { 231 232 if (mode.checkVisibility()) { 233 final Consent visibility = assoc.isVisible(getRendererContext().getAuthenticationSession(), objectAdapter, rendererContext.getWhere()); 234 if (!visibility.isAllowed()) { 235 continue; 236 } 237 } 238 if (assoc instanceof OneToOneAssociation) { 239 final OneToOneAssociation property = (OneToOneAssociation) assoc; 240 241 final ObjectPropertyReprRenderer renderer = new ObjectPropertyReprRenderer(getRendererContext(), linkFollower, property.getId(), JsonRepresentation.newMap()); 242 243 renderer.with(new ObjectAndProperty(objectAdapter, property)).usingLinkTo(linkToBuilder); 244 245 if (mode.representsArguments()) { 246 renderer.asArguments(); 247 } 248 249 members.mapPut(assoc.getId(), renderer.render()); 250 } 251 252 if (mode.representsArguments()) { 253 // don't include collections 254 continue; 255 } 256 if (assoc instanceof OneToManyAssociation) { 257 final OneToManyAssociation collection = (OneToManyAssociation) assoc; 258 259 final ObjectCollectionReprRenderer renderer = new ObjectCollectionReprRenderer(getRendererContext(), linkFollower, collection.getId(), JsonRepresentation.newMap()); 260 261 renderer.with(new ObjectAndCollection(objectAdapter, collection)).usingLinkTo(linkToBuilder); 262 263 members.mapPut(assoc.getId(), renderer.render()); 264 } 265 } 266 } 267 268 private void addActions(final ObjectAdapter objectAdapter, final List<ObjectAction> actions, final JsonRepresentation members) { 269 for (final ObjectAction action : actions) { 270 final Consent visibility = action.isVisible(getRendererContext().getAuthenticationSession(), objectAdapter, rendererContext.getWhere()); 271 if (!visibility.isAllowed()) { 272 continue; 273 } 274 if (action.getType().isSet()) { 275 final ObjectActionSet objectActionSet = (ObjectActionSet) action; 276 final List<ObjectAction> subactions = objectActionSet.getActions(); 277 addActions(objectAdapter, subactions, members); 278 279 } else { 280 final LinkFollowSpecs linkFollowSpecs = getLinkFollowSpecs().follow("members["+action.getId()+"]"); 281 282 final ObjectActionReprRenderer renderer = new ObjectActionReprRenderer(getRendererContext(), linkFollowSpecs, action.getId(), JsonRepresentation.newMap()); 283 284 renderer.with(new ObjectAndAction(objectAdapter, action)).usingLinkTo(linkToBuilder); 285 286 members.mapPut(action.getId(), renderer.render()); 287 } 288 } 289 } 290 291 private void addPersistLinkIfTransientAndPersistable() { 292 if (objectAdapter.representsPersistent()) { 293 return; 294 } 295 if(objectAdapter.getSpecification().containsDoOpFacet(NotPersistableFacet.class)) { 296 return; 297 } 298 final DomainObjectReprRenderer renderer = new DomainObjectReprRenderer(getRendererContext(), null, JsonRepresentation.newMap()); 299 final JsonRepresentation domainObjectRepr = renderer.with(objectAdapter).asPersistLinkArguments().render(); 300 301 final String domainType = objectAdapter.getSpecification().getSpecId().asString(); 302 final LinkBuilder persistLinkBuilder = LinkBuilder.newBuilder(getRendererContext(), Rel.PERSIST.getName(), RepresentationType.DOMAIN_OBJECT, "objects/%s", domainType).withHttpMethod(RestfulHttpMethod.POST).withArguments(domainObjectRepr); 303 getLinks().arrayAdd(persistLinkBuilder.build()); 304 } 305 306 private DomainObjectReprRenderer asPersistLinkArguments() { 307 this.mode = Mode.PERSIST_LINK_ARGUMENTS; 308 return this; 309 } 310 311 private DomainObjectReprRenderer asModifyPropertiesLinkArguments() { 312 this.mode = Mode.MODIFY_PROPERTIES_LINK_ARGUMENTS; 313 return this; 314 } 315 316 // not part of the spec 317 public DomainObjectReprRenderer asEventSerialization() { 318 this.mode = Mode.EVENT_SERIALIZATION; 319 return this; 320 } 321 322 323 private void addUpdatePropertiesLinkIfPersistentAndNotService() { 324 if (!objectAdapter.representsPersistent()) { 325 return; 326 } 327 final boolean isService = objectAdapter.getSpecification().isService(); 328 if(isService) { 329 return; 330 } 331 332 final DomainObjectReprRenderer renderer = new DomainObjectReprRenderer(getRendererContext(), null, JsonRepresentation.newMap()); 333 final JsonRepresentation domainObjectRepr = renderer.with(objectAdapter).asModifyPropertiesLinkArguments().render(); 334 335 final LinkBuilder persistLinkBuilder = LinkBuilder.newBuilder(getRendererContext(), Rel.MODIFY.getName(), RepresentationType.DOMAIN_OBJECT, "objects/%s/%s", getDomainType(), getInstanceId()).withHttpMethod(RestfulHttpMethod.PUT).withArguments(domainObjectRepr); 336 getLinks().arrayAdd(persistLinkBuilder.build()); 337 } 338 339 // /////////////////////////////////////////////////////////////////// 340 // 341 // /////////////////////////////////////////////////////////////////// 342 343 public static void appendValueAndFormatOrRef(final RendererContext resourceContext, final ObjectAdapter objectAdapter, final ObjectSpecification objectSpec, JsonRepresentation repr) { 344 345 final ValueFacet valueFacet = objectSpec.getFacet(ValueFacet.class); 346 if (valueFacet != null) { 347 JsonValueEncoder.appendValueAndFormat(objectSpec, objectAdapter, repr); 348 return; 349 } 350 351 if(objectAdapter == null) { 352 repr.mapPut("value", NullNode.getInstance()); 353 } else { 354 final TitleFacet titleFacet = objectSpec.getFacet(TitleFacet.class); 355 final String title = titleFacet.title(objectAdapter, resourceContext.getLocalization()); 356 JsonRepresentation ref = DomainObjectReprRenderer.newLinkToBuilder(resourceContext, Rel.VALUE, objectAdapter).withTitle(title).build(); 357 358 repr.mapPut("value", ref); 359 } 360 361 } 362 363 public static Object valueOrRef(final RendererContext resourceContext, final ObjectAdapter objectAdapter, final ObjectSpecification objectSpec) { 364 final ValueFacet valueFacet = objectSpec.getFacet(ValueFacet.class); 365 if (valueFacet != null) { 366 return JsonValueEncoder.asObject(objectAdapter); 367 } 368 final TitleFacet titleFacet = objectSpec.getFacet(TitleFacet.class); 369 final String title = titleFacet.title(objectAdapter, resourceContext.getLocalization()); 370 return DomainObjectReprRenderer.newLinkToBuilder(resourceContext, Rel.VALUE, objectAdapter).withTitle(title).build(); 371 } 372 373 374 375 // /////////////////////////////////////////////////////////////////// 376 // dependencies (from context) 377 // /////////////////////////////////////////////////////////////////// 378 379 protected static OidMarshaller getOidMarshaller() { 380 return IsisContext.getOidMarshaller(); 381 } 382 383}