package org.jboss.resteasy.client.jaxrs.internal; import org.jboss.resteasy.client.jaxrs.i18n.Messages; import org.jboss.resteasy.core.Headers; import org.jboss.resteasy.core.ProvidersContextRetainer; import org.jboss.resteasy.core.interception.jaxrs.ClientReaderInterceptorContext; import org.jboss.resteasy.specimpl.BuiltResponse; import org.jboss.resteasy.spi.HeaderValueProcessor; import org.jboss.resteasy.spi.MarshalledEntity; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.jboss.resteasy.util.HttpHeaderNames; import org.jboss.resteasy.util.HttpResponseCodes; import org.jboss.resteasy.util.InputStreamToByteArray; import org.jboss.resteasy.util.ReadFromStream; import org.jboss.resteasy.util.Types; import javax.ws.rs.ProcessingException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.Providers; import javax.ws.rs.ext.ReaderInterceptor; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.lang.annotation.Annotation; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Map; /** * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $ */ public abstract class ClientResponse extends BuiltResponse { // One thing to note, I don't cache header objects because I was too lazy to proxy the headers multivalued map protected Map<String, Object> properties; protected ClientConfiguration configuration; protected byte[] bufferedEntity; protected ClientResponse(ClientConfiguration configuration) { setClientConfiguration(configuration); } @SuppressWarnings({ "rawtypes", "unchecked" }) public void setHeaders(MultivaluedMap<String, String> headers) { this.metadata = new Headers<Object>(); this.metadata.putAll((Map)headers); } public void setProperties(Map<String, Object> properties) { this.properties = properties; } public Map<String, Object> getProperties() { return properties; } public void setClientConfiguration(ClientConfiguration configuration) { this.configuration = configuration; this.processor = configuration; } @Override public Object getEntity() { abortIfClosed(); return super.getEntity(); } @Override public boolean hasEntity() { abortIfClosed(); return getInputStream() != null && (entity != null || getMediaType() != null); } /** * In case of an InputStream or Reader and a invocation that returns no Response object, we need to make * sure the GC does not close the returned InputStream or Reader */ public void noReleaseConnection() { isClosed = true; } @Override public void close() { if (isClosed()) return; try { isClosed = true; releaseConnection(); } catch (Exception e) { throw new ProcessingException(e); } } @Override // This method is synchronized to protect against premature calling of finalize by the GC protected synchronized void finalize() throws Throwable { if (isClosed()) return; try { close(); } catch (Exception ignored) { } } @Override protected HeaderValueProcessor getHeaderValueProcessor() { return configuration; } protected abstract InputStream getInputStream(); protected InputStream getEntityStream() { if (bufferedEntity != null) return new ByteArrayInputStream(bufferedEntity); if (isClosed()) throw new ProcessingException(Messages.MESSAGES.streamIsClosed()); return getInputStream(); } protected abstract void setInputStream(InputStream is); /** * release underlying connection but do not close * * @throws IOException */ public abstract void releaseConnection() throws IOException; // this is synchronized in conjunction with finalize to protect against premature finalize called by the GC @SuppressWarnings("unchecked") public synchronized <T> T readEntity(Class<T> type, Type genericType, Annotation[] anns) { abortIfClosed(); if (entity != null) { if (type.isInstance((this.entity))) { return (T)entity; } else if (entity instanceof InputStream) { setInputStream((InputStream)entity); entity = null; } else if (bufferedEntity == null) { throw new RuntimeException(Messages.MESSAGES.entityAlreadyRead(entity.getClass())); } else { entity = null; } } if (entity == null) { if (status == HttpResponseCodes.SC_NO_CONTENT) return null; try { entity = readFrom(type, genericType, getMediaType(), anns); if (entity == null || (entity != null && !InputStream.class.isInstance(entity) && !Reader.class.isInstance(entity) && bufferedEntity == null)) { try { close(); } catch (Exception ignored) { } } } catch (RuntimeException e) { //logger.error("failed", e); try { close(); } catch (Exception ignored) { } throw e; } } return (T) entity; } // this is synchronized in conjunction with finalize to protect against premature finalize called by the GC protected synchronized <T> Object readFrom(Class<T> type, Type genericType, MediaType media, Annotation[] annotations) { Type useGeneric = genericType == null ? type : genericType; Class<?> useType = type; media = media == null ? MediaType.WILDCARD_TYPE : media; annotations = annotations == null ? this.annotations : annotations; boolean isMarshalledEntity = false; if (type.equals(MarshalledEntity.class)) { isMarshalledEntity = true; ParameterizedType param = (ParameterizedType) useGeneric; useGeneric = param.getActualTypeArguments()[0]; useType = Types.getRawType(useGeneric); } Providers current = ResteasyProviderFactory.getContextData(Providers.class); ResteasyProviderFactory.pushContext(Providers.class, configuration); Object obj = null; try { InputStream is = getEntityStream(); if (is == null) { throw new IllegalStateException(Messages.MESSAGES.inputStreamWasEmpty()); } if (isMarshalledEntity) { is = new InputStreamToByteArray(is); } ReaderInterceptor[] readerInterceptors = configuration.getReaderInterceptors(null, null); final Object finalObj = new ClientReaderInterceptorContext(readerInterceptors, configuration.getProviderFactory(), useType, useGeneric, annotations, media, getStringHeaders(), is, properties) .proceed(); obj = finalObj; if (isMarshalledEntity) { InputStreamToByteArray isba = (InputStreamToByteArray) is; final byte[] bytes = isba.toByteArray(); return new MarshalledEntity<Object>() { @Override public byte[] getMarshalledBytes() { return bytes; } @Override public Object getEntity() { return finalObj; } }; } else { return finalObj; } } catch (ProcessingException pe) { throw pe; } catch (Exception ex) { throw new ProcessingException(ex); } finally { ResteasyProviderFactory.popContextData(Providers.class); if (current != null) ResteasyProviderFactory.pushContext(Providers.class, current); if (obj instanceof ProvidersContextRetainer) { ((ProvidersContextRetainer) obj).setProviders(configuration); } } } @Override public boolean bufferEntity() { abortIfClosed(); if (bufferedEntity != null) return true; if (entity != null) return false; if (metadata.getFirst(HttpHeaderNames.CONTENT_TYPE) == null) return false; try { bufferedEntity = ReadFromStream.readFromStream(1024, getInputStream()); } catch (IOException e) { throw new ProcessingException(e); } finally { try { releaseConnection(); } catch (IOException e) { throw new ProcessingException(e); } } return true; } @Override public void abortIfClosed() { if (bufferedEntity == null) super.abortIfClosed(); } }