users@jersey.java.net

Generic and extensible response

From: Paul Sandoz <Paul.Sandoz_at_Sun.COM>
Date: Fri, 02 Apr 2010 23:51:22 +0200

Hi,

See below for a prototype generic and extensible response class.

Once integrated in fully into Jersey it should allow us to do:

   public JResponse<Foo> get() {
     JResponse.ok("STUFF").header("X-FOO").build();
   }

so that the GenericEntty class does not need to be utilized to
preserve the type of entity if say using List<T>.

It should also support Atmosphere's requirements by being able to
extend JResponse and JResponse.JResponseBuilder whilst still retaining
all the existing methods on the JResponseBuilder builder. If this is
considered too much noise one can create separate builder class to
return JResponse or extensions of.

Paul.

/*
  *
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
  * Copyright 1997-2010 Sun Microsystems, Inc. All rights reserved.
  *
  * The contents of this file are subject to the terms of either the GNU
  * General Public License Version 2 only ("GPL") or the Common
Development
  * and Distribution License("CDDL") (collectively, the "License"). You
  * may not use this file except in compliance with the License. You
can obtain
  * a copy of the License at https://jersey.dev.java.net/CDDL+GPL.html
  * or jersey/legal/LICENSE.txt. See the License for the specific
  * language governing permissions and limitations under the License.
  *
  * When distributing the software, include this License Header Notice
in each
  * file and include the License file at jersey/legal/LICENSE.txt.
  * Sun designates this particular file as subject to the "Classpath"
exception
  * as provided by Sun in the GPL Version 2 section of the License
file that
  * accompanied this code. If applicable, add the following below the
License
  * Header, with the fields enclosed by brackets [] replaced by your own
  * identifying information: "Portions Copyrighted [year]
  * [name of copyright owner]"
  *
  * Contributor(s):
  *
  * If you wish your version of this file to be governed by only the
CDDL or
  * only the GPL Version 2, indicate your decision by adding
"[Contributor]
  * elects to include this software in this distribution under the
[CDDL or GPL
  * Version 2] license." If you don't indicate a single choice of
license, a
  * recipient has the option to distribute your version of this file
under
  * either the CDDL, the GPL Version 2 or to extend the choice of
license to
  * its licensees as provided above. However, if you add GPL Version
2 code
  * and therefore, elected the GPL Version 2 license, then the option
applies
  * only if the new code is made subject to such option by the copyright
  * holder.
  */
package com.sun.jersey.api;

import com.sun.jersey.core.header.OutBoundHeaders;
import java.net.URI;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.Response.StatusType;
import javax.ws.rs.core.Variant;

/**
  *
  * @author Paul.Sandoz_at_Sun.Com
  */
public class JResponse<E> {
     private final int status;

     private final E entity;

     private final OutBoundHeaders headers;

     public JResponse(int status, E entity, OutBoundHeaders headers) {
         this.status = status;
         this.entity = entity;
         this.headers = headers;
     }

     public JResponse(JResponse<E> that) {
         this(that.status, that.entity,
                 that.headers != null ? new
OutBoundHeaders(that.headers) : null);
     }

     protected JResponse(AJResponseBuilder<E, ?> b) {
         this(b.status, b.entity, b.headers);
     }

     public int getStatus() {
         return status;
     }

     public MultivaluedMap<String, Object> getHeaders() {
         return headers;
     }

     public E getEntity() {
         return entity;
     }


     public static <E> JResponseBuilder<E> fromResponse(Response
response) {
         JResponseBuilder b = status(response.getStatus());
         b.entity(response.getEntity());
         for (String headerName: response.getMetadata().keySet()) {
             List<Object> headerValues =
response.getMetadata().get(headerName);
             for (Object headerValue: headerValues) {
                 b.header(headerName, headerValue);
             }
         }
         return b;
     }

     public static <E> JResponseBuilder<E> fromResponse(JResponse<E>
response) {
         JResponseBuilder<E> b = status(response.getStatus());
         b.entity(response.getEntity());
         for (String headerName: response.getHeaders().keySet()) {
             List<Object> headerValues =
response.getHeaders().get(headerName);
             for (Object headerValue: headerValues) {
                 b.header(headerName, headerValue);
             }
         }
         return b;
     }
     public static <E> JResponseBuilder<E> status(StatusType status) {
         JResponseBuilder<E> b = new JResponseBuilder<E>();
         b.status(status);
         return b;
     }

     public static <E> JResponseBuilder<E> status(Response.Status
status) {
         return status((StatusType)status);
     }

     public static <E> JResponseBuilder<E> status(int status) {
         JResponseBuilder<E> b = new JResponseBuilder<E>();
         b.status(status);
         return b;
     }

     public static <E> JResponseBuilder<E> ok() {
         JResponseBuilder b = status(Status.OK);
         return b;
     }

     public static <E> JResponseBuilder<E> ok(E entity) {
         JResponseBuilder<E> b = ok();
         b.entity(entity);
         return b;
     }

     public static <E> JResponseBuilder<E> ok(E entity, MediaType
type) {
         JResponseBuilder<E> b = ok();
         b.entity(entity);
         b.type(type);
         return b;
     }

     public static <E> JResponseBuilder<E> ok(E entity, String type) {
         JResponseBuilder<E> b = ok();
         b.entity(entity);
         b.type(type);
         return b;
     }

     public static <E> JResponseBuilder<E> ok(E entity, Variant
variant) {
         JResponseBuilder<E> b = ok();
         b.entity(entity);
         b.variant(variant);
         return b;
     }

     public static <E> JResponseBuilder<E> serverError() {
         JResponseBuilder<E> b = status(Status.INTERNAL_SERVER_ERROR);
         return b;
     }

     public static <E> JResponseBuilder<E> created(URI location) {
         JResponseBuilder<E> b =
JResponse.<E>status(Status.CREATED).location(location);
         return b;
     }

     public static <E> JResponseBuilder<E> noContent() {
         JResponseBuilder<E> b = status(Status.NO_CONTENT);
         return b;
     }

     public static <E> JResponseBuilder<E> notModified() {
         JResponseBuilder<E> b = status(Status.NOT_MODIFIED);
         return b;
     }

     public static <E> JResponseBuilder<E> notModified(EntityTag tag) {
         JResponseBuilder<E> b = notModified();
         b.tag(tag);
         return b;
     }

     public static <E> JResponseBuilder<E> notModified(String tag) {
         JResponseBuilder b = notModified();
         b.tag(tag);
         return b;
     }

     public static <E> JResponseBuilder<E> seeOther(URI location) {
         JResponseBuilder<E> b =
JResponse.<E>status(Status.SEE_OTHER).location(location);
         return b;
     }

     public static <E> JResponseBuilder<E> temporaryRedirect(URI
location) {
         JResponseBuilder<E> b =
JResponse.<E>status(Status.TEMPORARY_REDIRECT).location(location);
         return b;
     }

     public static <E> JResponseBuilder<E> notAcceptable(List<Variant>
variants) {
         JResponseBuilder<E> b =
JResponse.<E>status(Status.NOT_ACCEPTABLE).variants(variants);
         return b;
     }

     public static final class JResponseBuilder<E> extends
AJResponseBuilder<E, JResponseBuilder<E>> {
         protected JResponseBuilder() {}

         protected JResponseBuilder(JResponseBuilder<E> that) {
             super(that);
         }

         @Override
         public JResponseBuilder<E> clone() {
             return new JResponseBuilder<E>(this);
         }

         public JResponse<E> build() {
             JResponse<E> r = new JResponse<E>(this);
             reset();
             return r;
         }
     }

     public static abstract class AJResponseBuilder<E, B extends
AJResponseBuilder> {
         protected int status = 204;

         protected E entity;

         protected OutBoundHeaders headers;

         protected AJResponseBuilder() {}

         protected AJResponseBuilder(AJResponseBuilder<E, ?> that) {
             this.status = that.status;
             this.entity = that.entity;
             if (that.headers != null) {
                 this.headers = new OutBoundHeaders(that.headers);
             } else {
                 this.headers = null;
             }
         }

         protected void reset() {
             status = 204;
             entity = null;
             headers = null;
         }

         OutBoundHeaders getHeaders() {
             if (headers == null)
                 headers = new OutBoundHeaders();
             return headers;
         }

         public B status(int status) {
             this.status = status;
             return (B)this;
         }

         public B status(StatusType status) {
             if (status == null)
                 throw new IllegalArgumentException();
             return status(status.getStatusCode());
         };

         public B status(Status status) {
             return status((StatusType)status);
         };

         public B entity(E entity) {
             this.entity = entity;
             return (B)this;
         }

         public B type(MediaType type) {
             headerSingle(HttpHeaders.CONTENT_TYPE, type);
             return (B)this;
         }

         public B type(String type) {
             return type(type == null ? null : MediaType.valueOf(type));
         }

         public B variant(Variant variant) {
             if (variant == null) {
                 type((MediaType)null);
                 language((String)null);
                 header(HttpHeaders.CONTENT_ENCODING, null);
                 return (B)this;
             }

             type(variant.getMediaType());
             // TODO set charset
             language(variant.getLanguage());
             encoding(variant.getEncoding());

             return (B)this;
         }

         public B variants(List<Variant> variants) {
             if (variants == null) {
                 header(HttpHeaders.VARY, null);
                 return (B)this;
             }

             if (variants.isEmpty())
                 return (B)this;

             MediaType accept = variants.get(0).getMediaType();
             boolean vAccept = false;

             Locale acceptLanguage = variants.get(0).getLanguage();
             boolean vAcceptLanguage = false;

             String acceptEncoding = variants.get(0).getEncoding();
             boolean vAcceptEncoding = false;

             for (Variant v : variants) {
                 vAccept |= !vAccept && vary(v.getMediaType(), accept);
                 vAcceptLanguage |= !vAcceptLanguage &&
vary(v.getLanguage(), acceptLanguage);
                 vAcceptEncoding |= !vAcceptEncoding &&
vary(v.getEncoding(), acceptEncoding);
             }

             StringBuilder vary = new StringBuilder();
             append(vary, vAccept, HttpHeaders.ACCEPT);
             append(vary, vAcceptLanguage, HttpHeaders.ACCEPT_LANGUAGE);
             append(vary, vAcceptEncoding, HttpHeaders.ACCEPT_ENCODING);

             if (vary.length() > 0)
                 headerSingle(HttpHeaders.VARY, vary.toString());
             return (B)this;
         }

         private boolean vary(MediaType v, MediaType vary) {
             return v != null && !v.equals(vary);
         }

         private boolean vary(Locale v, Locale vary) {
             return v != null && !v.equals(vary);
         }

         private boolean vary(String v, String vary) {
             return v != null && !v.equalsIgnoreCase(vary);
         }

         private void append(StringBuilder sb, boolean v, String s) {
             if (v) {
                 if (sb.length() > 0)
                     sb.append(',');
                 sb.append(s);
             }
         }

         public B language(String language) {
             headerSingle(HttpHeaders.CONTENT_LANGUAGE, language);
             return (B)this;
         }

         public B language(Locale language) {
             headerSingle(HttpHeaders.CONTENT_LANGUAGE, language);
             return (B)this;
         }

         public B location(URI location) {
             headerSingle(HttpHeaders.LOCATION, location);
             return (B)this;
         }

         public B contentLocation(URI location) {
             headerSingle(HttpHeaders.CONTENT_LOCATION, location);
             return (B)this;
         }

         public B encoding(String encoding) {
             headerSingle(HttpHeaders.CONTENT_ENCODING, encoding);
             return (B)this;
         }

         public B tag(EntityTag tag) {
             headerSingle(HttpHeaders.ETAG, tag);
             return (B)this;
         }

         public B tag(String tag) {
             return tag(tag == null ? null : new EntityTag(tag));
         }

         public B lastModified(Date lastModified) {
             headerSingle(HttpHeaders.LAST_MODIFIED, lastModified);
             return (B)this;
         }

         public B cacheControl(CacheControl cacheControl) {
             headerSingle(HttpHeaders.CACHE_CONTROL, cacheControl);
             return (B)this;
         }

         public B expires(Date expires) {
             headerSingle(HttpHeaders.EXPIRES, expires);
             return (B)this;
         }

         public B cookie(NewCookie... cookies) {
             if (cookies != null) {
                 for (NewCookie cookie : cookies)
                     header(HttpHeaders.SET_COOKIE, cookie);
             } else {
                 header(HttpHeaders.SET_COOKIE, null);
             }
             return (B)this;
         }

         public B header(String name, Object value) {
             return header(name, value, false);
         }

         public B headerSingle(String name, Object value) {
             return header(name, value, true);
         }

         public B header(String name, Object value, boolean single) {
             if (value != null) {
                 if (single) {
                     getHeaders().add(name, value);
                 } else {
                     getHeaders().putSingle(name, value);
                 }
             } else {
                 getHeaders().remove(name);
             }
             return (B)this;
         }
     }
}