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.applib.client;
020
021import java.io.IOException;
022import java.util.List;
023
024import javax.ws.rs.core.Response;
025import javax.ws.rs.core.Response.Status.Family;
026
027import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
028import org.apache.isis.viewer.restfulobjects.applib.LinkRepresentation;
029import org.codehaus.jackson.JsonParseException;
030import org.codehaus.jackson.map.JsonMappingException;
031
032import com.google.common.collect.Lists;
033
034public class RepresentationWalker {
035
036    static class Step {
037        private final String key;
038        private final LinkRepresentation link;
039        private final JsonRepresentation body;
040        private final RestfulResponse<? extends JsonRepresentation> response;
041        private String error;
042        private final Exception exception;
043
044        public Step(final String key, final LinkRepresentation link, final JsonRepresentation body, final RestfulResponse<? extends JsonRepresentation> response, final String error, final Exception exception) {
045            this.key = key;
046            this.link = link;
047            this.body = body;
048            this.response = response;
049            this.error = error;
050            this.exception = exception;
051        }
052
053        @Override
054        public String toString() {
055            return "Step [key=" + key + ", link=" + (link != null ? link.getHref() : "(null)") + ", error=" + error + "]";
056        }
057
058    }
059
060    private final RestfulClient restfulClient;
061    private final List<Step> steps = Lists.newLinkedList();
062
063    public RepresentationWalker(final RestfulClient restfulClient, final Response response) {
064        this.restfulClient = restfulClient;
065        final RestfulResponse<JsonRepresentation> jsonResp = RestfulResponse.of(response);
066
067        addStep(null, null, null, jsonResp, null, null);
068    }
069
070    private Step addStep(final String key, final LinkRepresentation link, final JsonRepresentation body, final RestfulResponse<JsonRepresentation> jsonResp, final String error, final Exception ex) {
071        final Step step = new Step(key, link, body, jsonResp, error, ex);
072        steps.add(0, step);
073        if (error != null) {
074            if (jsonResp.getStatus().getFamily() != Family.SUCCESSFUL) {
075                step.error = "response status code: " + jsonResp.getStatus();
076            }
077        }
078        return step;
079    }
080
081    public void walk(final String path) {
082        walk(path, null);
083    }
084
085    public void walk(final String path, final JsonRepresentation invokeBody) {
086        final Step previousStep = currentStep();
087        if (previousStep.error != null) {
088            return;
089        }
090
091        final RestfulResponse<? extends JsonRepresentation> jsonResponse = previousStep.response;
092        JsonRepresentation entity;
093        try {
094            entity = jsonResponse.getEntity();
095        } catch (final Exception e) {
096            addStep(path, null, null, null, "exception: " + e.getMessage(), e);
097            return;
098        }
099
100        LinkRepresentation link;
101        try {
102            link = entity.getLink(path);
103        } catch (final Exception e) {
104            addStep(path, null, null, null, "exception: " + e.getMessage(), e);
105            return;
106        }
107        if (link == null) {
108            addStep(path, null, null, null, "no such link '" + path + "'", null);
109            return;
110        }
111
112        final RestfulResponse<JsonRepresentation> response;
113        try {
114            if (invokeBody != null) {
115                response = restfulClient.follow(link, invokeBody);
116            } else {
117                response = restfulClient.follow(link);
118            }
119        } catch (final Exception e) {
120            addStep(path, link, null, null, "failed to follow link: " + e.getMessage(), e);
121            return;
122        }
123
124        addStep(path, link, null, response, null, null);
125    }
126
127    /**
128     * The entity returned from the previous walk.
129     * 
130     * <p>
131     * Will return null if the previous walk returned an error.
132     */
133    public JsonRepresentation getEntity() throws JsonParseException, JsonMappingException, IOException {
134        final Step currentStep = currentStep();
135        if (currentStep.response == null || currentStep.error != null) {
136            return null;
137        }
138        return currentStep.response.getEntity();
139    }
140
141    /**
142     * The response returned from the previous walk.
143     * 
144     * <p>
145     * Once a walk/performed has been attempted, is guaranteed to return a
146     * non-null value. (Conversely, will return <tt>null</tt> immediately after
147     * instantiation and prior to a walk being attempted/performed).
148     */
149    public RestfulResponse<?> getResponse() {
150        final Step currentStep = currentStep();
151        return currentStep != null ? currentStep.response : null;
152    }
153
154    /**
155     * The error (if any) that occurred from the previous walk.
156     */
157    public String getError() {
158        final Step currentStep = currentStep();
159        return currentStep != null ? currentStep.error : null;
160    }
161
162    /**
163     * The exception (if any) that occurred from the previous walk.
164     * 
165     * <p>
166     * Will only ever be populated if {@link #getError()} is non-null.
167     */
168    public Exception getException() {
169        final Step currentStep = currentStep();
170        return currentStep != null ? currentStep.exception : null;
171    }
172
173    /**
174     * The step that has just been walked.
175     */
176    private Step currentStep() {
177        return steps.get(0);
178    }
179
180}