001package ball.http;
002/*-
003 * ##########################################################################
004 * Web API Client (HTTP) Utilities
005 * $Id: ProtocolResponseHandler.java 6118 2020-06-04 19:31:45Z ball $
006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/ball-http/trunk/src/main/java/ball/http/ProtocolResponseHandler.java $
007 * %%
008 * Copyright (C) 2016 - 2020 Allen D. Ball
009 * %%
010 * Licensed under the Apache License, Version 2.0 (the "License");
011 * you may not use this file except in compliance with the License.
012 * You may obtain a copy of the License at
013 *
014 *      http://www.apache.org/licenses/LICENSE-2.0
015 *
016 * Unless required by applicable law or agreed to in writing, software
017 * distributed under the License is distributed on an "AS IS" BASIS,
018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
019 * See the License for the specific language governing permissions and
020 * limitations under the License.
021 * ##########################################################################
022 */
023import com.fasterxml.jackson.databind.JavaType;
024import com.fasterxml.jackson.databind.ObjectMapper;
025import com.fasterxml.jackson.databind.type.TypeFactory;
026import java.io.IOException;
027import java.io.InputStream;
028import java.lang.reflect.Array;
029import java.lang.reflect.GenericArrayType;
030import java.lang.reflect.Method;
031import java.lang.reflect.ParameterizedType;
032import java.lang.reflect.Type;
033/* import java.lang.reflect.TypeVariable; */
034/* import java.lang.reflect.WildcardType; */
035import java.util.Collection;
036import java.util.Map;
037import javax.xml.bind.JAXBException;
038import lombok.ToString;
039import org.apache.http.HttpEntity;
040import org.apache.http.client.ClientProtocolException;
041import org.apache.http.entity.ContentType;
042import org.apache.http.impl.client.AbstractResponseHandler;
043import org.apache.http.util.EntityUtils;
044
045import static java.util.Objects.requireNonNull;
046
047/**
048 * {@link ProtocolClient} {@link org.apache.http.client.ResponseHandler}
049 * implementation.  Makes use of {@link ProtocolClient#getUnmarshaller()}
050 * and {@link ProtocolClient#getObjectMapper()} for de-serialization.
051 *
052 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
053 * @version $Revision: 6118 $
054 */
055@ToString
056public class ProtocolResponseHandler extends AbstractResponseHandler<Object> {
057    private final ProtocolClient<?> client;
058    private final Method method;
059
060    /**
061     * Sole constructor.
062     *
063     * @param   client          The {@link ProtocolClient}.
064     * @param   method          The protocol {@link Method}.
065     */
066    protected ProtocolResponseHandler(ProtocolClient<?> client,
067                                      Method method) {
068        super();
069
070        this.client = requireNonNull(client, "client");
071        this.method = requireNonNull(method, "method");
072    }
073
074    @Override
075    public Object handleEntity(HttpEntity entity) throws ClientProtocolException,
076                                                         IOException {
077        Object object = null;
078
079        try {
080            String type =
081                ContentType.getLenientOrDefault(entity).getMimeType()
082                .replaceAll("[^\\p{Alnum}]", "_").toUpperCase();
083
084            object =
085                getClass()
086                .getDeclaredMethod(type, HttpEntity.class)
087                .invoke(this, entity);
088        } catch (NoSuchMethodException exception) {
089            object = EntityUtils.toString(entity);
090        } catch (Exception exception) {
091            if (exception instanceof ClientProtocolException) {
092                throw (ClientProtocolException) exception;
093            } else if (exception instanceof IOException) {
094                throw (IOException) exception;
095            } else {
096                throw new ClientProtocolException(exception);
097            }
098        }
099
100        return method.getReturnType().cast(object);
101    }
102
103    protected Object APPLICATION_JSON(HttpEntity entity) throws ClientProtocolException,
104                                                                IOException {
105        Object object = null;
106        ObjectMapper om = client.getObjectMapper();
107        TypeFactory factory = om.getTypeFactory();
108        JavaType type =
109            getJavaTypeFrom(factory, method.getGenericReturnType());
110
111        try (InputStream in = entity.getContent()) {
112            object = om.readValue(in, type);
113        }
114
115        return object;
116    }
117
118    private JavaType getJavaTypeFrom(TypeFactory factory, Type type) {
119        JavaType java = null;
120
121        if (type instanceof GenericArrayType) {
122            java = getJavaTypeFrom(factory, (GenericArrayType) type);
123        } else if (type instanceof ParameterizedType) {
124            java = getJavaTypeFrom(factory, (ParameterizedType) type);
125/*
126        } else if (type instanceof TypeVariable) {
127        } else if (type instanceof WildcardType) {
128*/
129        }
130
131        return (java != null) ? java : factory.constructType(type);
132    }
133
134    private JavaType getJavaTypeFrom(TypeFactory factory,
135                                     GenericArrayType type) {
136        Type element = type.getGenericComponentType();
137
138        return factory.constructArrayType(getJavaTypeFrom(factory, element));
139    }
140
141    private JavaType getJavaTypeFrom(TypeFactory factory,
142                                     ParameterizedType type) {
143        JavaType java = null;
144        Class<?> raw = (Class<?>) type.getRawType();
145        Type[] arguments = type.getActualTypeArguments();
146
147        if (Collection.class.isAssignableFrom(raw)) {
148            java =
149                factory
150                .constructCollectionType(raw.asSubclass(Collection.class),
151                                         getJavaTypeFrom(factory, arguments[0]));
152        } else if (Map.class.isAssignableFrom(raw)) {
153            java =
154                factory
155                .constructMapType(raw.asSubclass(Map.class),
156                                  getJavaTypeFrom(factory, arguments[0]),
157                                  getJavaTypeFrom(factory, arguments[1]));
158        }
159
160        return (java != null) ? java : factory.constructType(type);
161    }
162
163    protected Object APPLICATION_XML(HttpEntity entity) throws ClientProtocolException,
164                                                               IOException {
165        Object object = null;
166
167        try (InputStream in = entity.getContent()) {
168            object = client.getUnmarshaller().unmarshal(in);
169        } catch (JAXBException exception) {
170            throw new IOException(exception);
171        }
172
173        return object;
174    }
175}