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.Date;
023    import java.util.Map;
024    
025    import javax.ws.rs.core.CacheControl;
026    import javax.ws.rs.core.MediaType;
027    import javax.ws.rs.core.MultivaluedMap;
028    import javax.ws.rs.core.Response;
029    import javax.ws.rs.core.Response.Status;
030    import javax.ws.rs.core.Response.Status.Family;
031    import javax.ws.rs.core.Response.StatusType;
032    
033    import com.google.common.collect.Maps;
034    
035    import org.codehaus.jackson.JsonParseException;
036    import org.codehaus.jackson.map.JsonMappingException;
037    
038    import org.apache.isis.viewer.restfulobjects.applib.util.JsonMapper;
039    import org.apache.isis.viewer.restfulobjects.applib.util.Parser;
040    
041    public class RestfulResponse<T> {
042    
043        public final static class HttpStatusCode {
044    
045            private final static Map<Status, HttpStatusCode> statii = Maps.newHashMap();
046            private final static Map<Integer, HttpStatusCode> statusCodes = Maps.newHashMap();
047    
048            private static class StatusTypeImpl implements StatusType {
049    
050                private final int statusCode;
051                private final Family family;
052                private final String reasonPhrase;
053    
054                private StatusTypeImpl(final int statusCode, final Family family, final String reasonPhrase) {
055                    this.statusCode = statusCode;
056                    this.family = family;
057                    this.reasonPhrase = reasonPhrase;
058                }
059    
060                @Override
061                public int getStatusCode() {
062                    return statusCode;
063                }
064    
065                @Override
066                public Family getFamily() {
067                    return family;
068                }
069    
070                @Override
071                public String getReasonPhrase() {
072                    return reasonPhrase;
073                }
074            }
075    
076            public static HttpStatusCode lookup(final int status) {
077                return statusCodes.get(status);
078            }
079    
080            public static Family lookupFamily(final int statusCode) {
081                switch (statusCode / 100) {
082                case 1:
083                    return Family.INFORMATIONAL;
084                case 2:
085                    return Family.SUCCESSFUL;
086                case 3:
087                    return Family.REDIRECTION;
088                case 4:
089                    return Family.CLIENT_ERROR;
090                case 5:
091                    return Family.SERVER_ERROR;
092                default:
093                    return Family.OTHER;
094                }
095            }
096    
097            // public static final int SC_CONTINUE = 100;
098            // public static final int SC_SWITCHING_PROTOCOLS = 101;
099            // public static final int SC_PROCESSING = 102;
100    
101            public final static HttpStatusCode OK = new HttpStatusCode(200, Status.OK);
102            public final static HttpStatusCode CREATED = new HttpStatusCode(201, Status.CREATED);
103    
104            // public static final int SC_ACCEPTED = 202;
105            // public static final int SC_NON_AUTHORITATIVE_INFORMATION = 203;
106    
107            public static final HttpStatusCode NO_CONTENT = new HttpStatusCode(204, Status.NO_CONTENT);
108    
109            // public static final int SC_RESET_CONTENT = 205;
110            // public static final int SC_PARTIAL_CONTENT = 206;
111            // public static final int SC_MULTI_STATUS = 207;
112            // public static final int SC_MULTIPLE_CHOICES = 300;
113            // public static final int SC_MOVED_PERMANENTLY = 301;
114            // public static final int SC_MOVED_TEMPORARILY = 302;
115            // public static final int SC_SEE_OTHER = 303;
116            public final static HttpStatusCode NOT_MODIFIED = new HttpStatusCode(304, Status.BAD_REQUEST);
117    
118            // public static final int SC_NOT_MODIFIED = 304;
119            // public static final int SC_USE_PROXY = 305;
120            // public static final int SC_TEMPORARY_REDIRECT = 307;
121    
122            public final static HttpStatusCode BAD_REQUEST = new HttpStatusCode(400, Status.BAD_REQUEST);
123            public final static HttpStatusCode UNAUTHORIZED = new HttpStatusCode(401, Status.UNAUTHORIZED);
124    
125            // public static final int SC_PAYMENT_REQUIRED = 402;
126            // public static final int SC_FORBIDDEN = 403;
127    
128            public final static HttpStatusCode NOT_FOUND = new HttpStatusCode(404, Status.NOT_FOUND);
129            public final static HttpStatusCode METHOD_NOT_ALLOWED = new HttpStatusCode(405, new StatusTypeImpl(405, Family.CLIENT_ERROR, "Method not allowed"));
130            public final static HttpStatusCode NOT_ACCEPTABLE = new HttpStatusCode(406, Status.NOT_ACCEPTABLE);
131    
132            // public static final int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
133            // public static final int SC_REQUEST_TIMEOUT = 408;
134    
135            public final static HttpStatusCode CONFLICT = new HttpStatusCode(409, Status.CONFLICT);
136    
137            // public static final int SC_GONE = 410;
138            // public static final int SC_LENGTH_REQUIRED = 411;
139            // public static final int SC_PRECONDITION_FAILED = 412;
140            // public static final int SC_REQUEST_TOO_LONG = 413;
141            // public static final int SC_REQUEST_URI_TOO_LONG = 414;
142            // public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415;
143    
144            public final static HttpStatusCode UNSUPPORTED_MEDIA_TYPE = new HttpStatusCode(415, Status.UNSUPPORTED_MEDIA_TYPE);
145    
146            // public static final int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
147            // public static final int SC_EXPECTATION_FAILED = 417;
148            // public static final int SC_INSUFFICIENT_SPACE_ON_RESOURCE = 419;
149    
150            public final static HttpStatusCode METHOD_FAILURE = new HttpStatusCode(420, new StatusTypeImpl(420, Family.CLIENT_ERROR, "Method failure"));
151    
152            // public static final int SC_UNPROCESSABLE_ENTITY = 422;
153            // public static final int SC_LOCKED = 423;
154            // public static final int SC_FAILED_DEPENDENCY = 424;
155    
156            public final static HttpStatusCode INTERNAL_SERVER_ERROR = new HttpStatusCode(500, Status.INTERNAL_SERVER_ERROR);
157            public final static HttpStatusCode NOT_IMPLEMENTED = new HttpStatusCode(501, new StatusTypeImpl(501, Family.SERVER_ERROR, "Not implemented"));
158    
159            // public static final int SC_BAD_GATEWAY = 502;
160            // public static final int SC_SERVICE_UNAVAILABLE = 503;
161            // public static final int SC_GATEWAY_TIMEOUT = 504;
162            // public static final int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
163            // public static final int SC_INSUFFICIENT_STORAGE = 507;
164    
165            public final static HttpStatusCode statusFor(final int statusCode) {
166                final HttpStatusCode httpStatusCode = statusCodes.get(statusCode);
167                if (httpStatusCode != null) {
168                    return httpStatusCode;
169                }
170                return syncStatusFor(statusCode);
171            }
172    
173            public final static HttpStatusCode statusFor(final Status status) {
174                return statii.get(status);
175            }
176    
177            private final static synchronized HttpStatusCode syncStatusFor(final int statusCode) {
178                HttpStatusCode httpStatusCode = statusCodes.get(statusCode);
179                if (httpStatusCode == null) {
180                    httpStatusCode = new HttpStatusCode(statusCode, null);
181                    statusCodes.put(statusCode, httpStatusCode);
182                }
183                return httpStatusCode;
184            }
185    
186            private final int statusCode;
187            private final Family family;
188            private final StatusType jaxrsStatusType;
189    
190            private HttpStatusCode(final int statusCode, final StatusType status) {
191                this.statusCode = statusCode;
192                this.jaxrsStatusType = status;
193                family = lookupFamily(statusCode);
194                statusCodes.put(statusCode, this);
195            }
196    
197            public int getStatusCode() {
198                return statusCode;
199            }
200    
201            public StatusType getJaxrsStatusType() {
202                return jaxrsStatusType;
203            }
204    
205            public Family getFamily() {
206                return family;
207            }
208    
209            @Override
210            public int hashCode() {
211                return statusCode;
212            }
213    
214            @Override
215            public boolean equals(final Object obj) {
216                if (this == obj) {
217                    return true;
218                }
219                if (obj == null) {
220                    return false;
221                }
222                if (getClass() != obj.getClass()) {
223                    return false;
224                }
225                final HttpStatusCode other = (HttpStatusCode) obj;
226                if (statusCode != other.statusCode) {
227                    return false;
228                }
229                return true;
230            }
231    
232            @Override
233            public String toString() {
234                return "HttpStatusCode " + statusCode + ", " + family;
235            }
236    
237        }
238    
239        public static class Header<X> {
240    
241            public final static Header<String> WARNING = new Header<String>("Warning", Parser.forString());
242            public final static Header<Date> LAST_MODIFIED = new Header<Date>("Last-Modified", Parser.forDate());
243            public final static Header<CacheControl> CACHE_CONTROL = new Header<CacheControl>("Cache-Control", Parser.forCacheControl());
244            public final static Header<MediaType> CONTENT_TYPE = new Header<MediaType>("Content-Type", Parser.forMediaType());
245    
246            private final String name;
247            private final Parser<X> parser;
248    
249            private Header(final String name, final Parser<X> parser) {
250                this.name = name;
251                this.parser = parser;
252            }
253    
254            public String getName() {
255                return name;
256            }
257    
258            public X parse(final String value) {
259                return parser.valueOf(value);
260            }
261    
262        }
263    
264        private final Response response;
265        private final HttpStatusCode httpStatusCode;
266        private final Class<T> returnType;
267    
268        @SuppressWarnings({ "rawtypes", "unchecked" })
269        public static RestfulResponse<JsonRepresentation> of(final Response response) {
270            final MediaType mediaType = getHeader(response, Header.CONTENT_TYPE);
271            final RepresentationType representationType = RepresentationType.lookup(mediaType);
272            final Class<? extends JsonRepresentation> returnType = representationType.getRepresentationClass();
273            return new RestfulResponse(response, returnType);
274        }
275    
276        @SuppressWarnings("unchecked")
277        public static <T extends JsonRepresentation> RestfulResponse<T> ofT(final Response response) {
278            return (RestfulResponse<T>) of(response);
279        }
280    
281        private RestfulResponse(final Response response, final Class<T> returnType) {
282            this.response = response;
283            this.httpStatusCode = HttpStatusCode.statusFor(response.getStatus());
284            this.returnType = returnType;
285        }
286    
287        public HttpStatusCode getStatus() {
288            return httpStatusCode;
289        }
290    
291        public T getEntity() throws JsonParseException, JsonMappingException, IOException {
292            return JsonMapper.instance().read(response, returnType);
293        }
294    
295        public <V> V getHeader(final Header<V> header) {
296            return getHeader(response, header);
297        }
298    
299        private static <V> V getHeader(final Response response, final Header<V> header) {
300            final MultivaluedMap<String, Object> metadata = response.getMetadata();
301            // always returns a String
302            final String value = (String) metadata.getFirst(header.getName());
303            return header.parse(value);
304        }
305    
306    }