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     */
019    package org.apache.isis.viewer.restfulobjects.applib;
020    
021    import java.io.IOException;
022    import java.util.List;
023    
024    import javax.ws.rs.core.Response;
025    import javax.ws.rs.core.Response.Status.Family;
026    
027    import com.google.common.collect.Lists;
028    
029    import org.codehaus.jackson.JsonParseException;
030    import org.codehaus.jackson.map.JsonMappingException;
031    
032    import org.apache.isis.viewer.restfulobjects.applib.links.LinkRepresentation;
033    
034    public 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    }