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 }