001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 */
019package org.apache.isis.viewer.restfulobjects.rendering;
020
021import java.util.List;
022import java.util.Map;
023
024import javax.ws.rs.core.MediaType;
025
026import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
027import org.apache.isis.core.metamodel.spec.ObjectSpecification;
028import org.apache.isis.core.runtime.system.context.IsisContext;
029import org.apache.isis.core.runtime.system.transaction.UpdateNotifier;
030import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
031import org.apache.isis.viewer.restfulobjects.applib.Rel;
032import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
033import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.DomainObjectReprRenderer;
034import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.DomainTypeReprRenderer;
035
036import com.google.common.collect.Maps;
037
038public abstract class ReprRendererAbstract<R extends ReprRendererAbstract<R, T>, T> implements ReprRenderer<R, T> {
039
040    protected final RendererContext rendererContext;
041    private final LinkFollowSpecs linkFollower;
042    private final RepresentationType representationType;
043    protected final JsonRepresentation representation;
044    private final Map<String,String> mediaTypeParams = Maps.newLinkedHashMap();
045
046    protected boolean includesSelf;
047
048    public ReprRendererAbstract(final RendererContext rendererContext, final LinkFollowSpecs linkFollower, final RepresentationType representationType, final JsonRepresentation representation) {
049        this.rendererContext = rendererContext;
050        this.linkFollower = asProvidedElseCreate(linkFollower);
051        this.representationType = representationType;
052        this.representation = representation;
053    }
054
055    public RendererContext getRendererContext() {
056        return rendererContext;
057    }
058
059    public LinkFollowSpecs getLinkFollowSpecs() {
060        return linkFollower;
061    }
062
063    private LinkFollowSpecs asProvidedElseCreate(final LinkFollowSpecs linkFollower) {
064        if (linkFollower != null) {
065            return linkFollower;
066        }
067        return LinkFollowSpecs.create(rendererContext.getFollowLinks());
068    }
069
070    @Override
071    public MediaType getMediaType() {
072        return representationType.getMediaType(mediaTypeParams);
073    }
074
075    protected void addMediaTypeParams(String param, String paramValue) {
076        mediaTypeParams.put(param, paramValue);
077    }
078
079    @SuppressWarnings("unchecked")
080    public R includesSelf() {
081        this.includesSelf = true;
082        return (R) this;
083    }
084
085    public R withLink(final Rel rel, final String href) {
086        if (href != null) {
087            getLinks().arrayAdd(LinkBuilder.newBuilder(rendererContext, rel.getName(), representationType, href).build());
088        }
089        return cast(this);
090    }
091
092    public R withLink(final Rel rel, final JsonRepresentation link) {
093        final String relStr = link.getString("rel");
094        if (relStr == null || !relStr.equals(rel.getName())) {
095            throw new IllegalArgumentException("Provided link does not have a 'rel' of '" + rel.getName() + "'; was: " + link);
096        }
097        if (link != null) {
098            getLinks().arrayAdd(link);
099        }
100        return cast(this);
101    }
102
103
104    /**
105     * Will lazily create links array as required
106     */
107    protected JsonRepresentation getLinks() {
108        JsonRepresentation links = representation.getArray("links");
109        if (links == null) {
110            links = JsonRepresentation.newArray();
111            representation.mapPut("links", links);
112        }
113        return links;
114    }
115
116    protected void addLink(final Rel rel, final ObjectSpecification objectSpec) {
117        if (objectSpec == null) {
118            return;
119        }
120        final LinkBuilder linkBuilder = DomainTypeReprRenderer.newLinkToBuilder(getRendererContext(), rel, objectSpec);
121        JsonRepresentation link = linkBuilder.build();
122        getLinks().arrayAdd(link);
123        
124        final LinkFollowSpecs linkFollower = getLinkFollowSpecs().follow("links");
125        if (linkFollower.matches(link)) {
126            final DomainTypeReprRenderer renderer = new DomainTypeReprRenderer(getRendererContext(), linkFollower, JsonRepresentation.newMap())
127                .with(objectSpec);
128            link.mapPut("value", renderer.render());
129        }
130
131    }
132
133    /**
134     * Will lazily create extensions map as required
135     */
136    protected JsonRepresentation getExtensions() {
137        JsonRepresentation extensions = representation.getMap("extensions");
138        if (extensions == null) {
139            extensions = JsonRepresentation.newMap();
140            representation.mapPut("extensions", extensions);
141        }
142        return extensions;
143    }
144
145    public R withExtensions(final JsonRepresentation extensions) {
146        if (!extensions.isMap()) {
147            throw new IllegalArgumentException("extensions must be a map");
148        }
149        representation.mapPut("extensions", extensions);
150        return cast(this);
151    }
152
153    @SuppressWarnings("unchecked")
154    protected static <R extends ReprRendererAbstract<R, T>, T> R cast(final ReprRendererAbstract<R, T> builder) {
155        return (R) builder;
156    }
157
158    @Override
159    public abstract JsonRepresentation render();
160
161    /**
162     * Convenience for representations that are returned from objects that
163     * mutate state.
164     */
165    protected final void addExtensionsIsisProprietaryChangedObjects() {
166        final UpdateNotifier updateNotifier = getUpdateNotifier();
167
168        addToExtensions("changed", updateNotifier.getChangedObjects());
169        addToExtensions("disposed", updateNotifier.getDisposedObjects());
170    }
171
172    private void addToExtensions(final String key, final List<ObjectAdapter> adapters) {
173        final JsonRepresentation adapterList = JsonRepresentation.newArray();
174        getExtensions().mapPut(key, adapterList);
175        for (final ObjectAdapter adapter : adapters) {
176            adapterList.arrayAdd(DomainObjectReprRenderer.newLinkToBuilder(getRendererContext(), Rel.VALUE, adapter).build());
177        }
178    }
179
180    protected List<ObjectAdapter> getServiceAdapters() {
181        return IsisContext.getPersistenceSession().getServices();
182    }
183
184    protected UpdateNotifier getUpdateNotifier() {
185        return IsisContext.getCurrentTransaction().getUpdateNotifier();
186    }
187
188}