2015-10-29 41 views
14

Mam HATEOAS (HAL) usługi REST i udało nam się porozmawiać z nim o poniższy kod (używając jak silnik konwersji), ale gdy próbuję merge the converters (stallone i stallone2), aplikacja będzie zawsze podnieść pierwszy konwerter, zamiast tego, który jest odpowiedni dla typu odpowiedzi, co oczywiście prowadzi do błędu.Wiele konwerterów z modernizacją 2

Jak mogę uniknąć duplikowania modyfikacji, które różnią się tylko szczegółem małego typu?

public interface Stallone { 
    @GET("/discovery") 
    Call<DiscoveryResponse> discover(); 
    @POST() 
    Call<LoginResponse> login(@Url String url, @Body LoginRequest secret); 
} 
public static void main(String... args) throws IOException { 
     // Initialize a converter for each supported (return) type 
     final Stallone stallone = new Retrofit.Builder() 
     .baseUrl(BASE) 
     .addConverterFactory(HALConverterFactory.create(DiscoveryResponse.class)) 
     .build().create(Stallone.class); 
     final Stallone stallone2 = new Retrofit.Builder() 
     .baseUrl(BASE) 
     .addConverterFactory(HALConverterFactory.create(LoginResponse.class)) 
     .build().create(Stallone.class); 

     // Follow the HAL links 
     Response<DiscoveryResponse> response = stallone.discover().execute(); 
     System.out.println(response.code() + " " + response.message()); 
     Assert.assertNotNull(response.body()); 
     String loginPath = response.body().getLogin(); 
     Assert.assertEquals(loginPath, "/login"); 

     // Follow another link 
     if (loginPath.startsWith("/")) 
     loginPath = loginPath.substring(1); 
     Response<LoginResponse> response2 = 
     stallone2.login(loginPath, 
         new LoginRequest(AUTH0TOKEN, null)).execute(); 
     System.out.println(response2.code() + " " + response2.message()); 
     Assert.assertNotNull(response2.body()); 

     String setupPath = response2.body().getSetup(); 
     Assert.assertEquals(setupPath, "/setup"); 

     System.out.println("All OK!"); 
    } 
public final class HALConverterFactory extends Converter.Factory { 

    private final Gson gson; 

    public static HALConverterFactory create(Class<?> type) { 
     return new HALConverterFactory(type); 
    } 

    private HALConverterFactory(Class<?> type) { 
     if (!HalResource.class.isAssignableFrom(type)) 
     throw new NullPointerException("Type should be a subclass of HalResource"); 
     GsonBuilder builder = new GsonBuilder(); 
     builder.registerTypeAdapter(HalResource.class, new HalSerializer()); 
     builder.registerTypeAdapter(HalResource.class, new HalDeserializer(type)); 
     builder.setExclusionStrategies(new HalExclusionStrategy()); 
     this.gson = builder.create(); 
    } 

    @Override 
    public Converter<ResponseBody, ?> fromResponseBody(Type type, Annotation[] annotations) { 
     return new HALResponseBodyConverter<>(gson); 
    } 

    @Override public Converter<?, RequestBody> toRequestBody(Type type, Annotation[] annotations) { 
     return new GsonRequestBodyConverter<>(gson, type); 
    } 
} 
final class HALResponseBodyConverter<T extends HalResource> 
    implements Converter<ResponseBody, T> { 
    private final Gson gson; 

    HALResponseBodyConverter(Gson gson) { 
     this.gson = gson; 
    } 

    @Override public T convert(ResponseBody value) throws IOException { 
     BufferedSource source = value.source(); 
     try { 
     String s = source.readString(Charset.forName("UTF-8")); 
     return (T) gson.fromJson(s, HalResource.class); 
     } catch (Exception e) { 
     throw new RuntimeException(e); 
     } finally { 
     closeQuietly(source); 
     } 
    } 

    private static void closeQuietly(Closeable closeable) { 
     if (closeable == null) return; 
     try { 
     closeable.close(); 
     } catch (IOException ignored) { 
     } 
    } 
} 

Ponownie, problem jest, że podczas próby skrócić powyższy tak:

final Stallone stallone = new Retrofit.Builder() 
    .baseUrl(BASE) 
.addConverterFactory(HALConverterFactory.create(DiscoveryResponse.class)) 
    .addConverterFactory(HALConverterFactory.create(LoginResponse.class)) 
    .build().create(Stallone.class); 

dostaniesz wyjątek u Linia Response<LoginResponse> response2 = ...:

Wyjątek w wątku "main" java.lang.ClassCastException: com.example.retrofit.DiscoveryResponse nie mogą być oddane do com.example.retrofit.LoginResponse

+1

Co to jest 'GsonRequestBodyConverter'? – naXa

Odpowiedz

18

Trzeba powrócić null od Converter.Factory, jeśli typ nie jest zgodny. Zachowaj Class<?> w polu, aby porównać je z.

@Override 
public Converter<ResponseBody, ?> fromResponseBody(Type type, Annotation[] annotations) { 
    if (!this.type.equals(type)) { 
    return null; 
    } 
    return new HALResponseBodyConverter<>(gson); 
} 

Umożliwi to użycie wielu instancji, ponieważ każda z nich dotyczy tylko jej własnego typu.

Niemniej jednak, prawdopodobnie można uciec tylko za pomocą jednego konwertera i ciągnąc klasę z Type, który jest przekazywany w.

@Override 
public Converter<ResponseBody, ?> fromResponseBody(Type type, Annotation[] annotations) { 
    if (!HALResponse.class.isAssignableFrom(type)) { 
    return null; 
    } 
    // TODO create converter with `type` now that you know what it is... 
} 

Możesz zajrzeć na konwerterze drutu w repo, które robi to dla pełnego przykładu.

0
package ch.halarious.core; 

import com.google.gson.JsonArray; 
import com.google.gson.JsonDeserializationContext; 
import com.google.gson.JsonElement; 
import com.google.gson.JsonObject; 
import com.google.gson.JsonParseException; 
import java.lang.reflect.Type; 
import java.util.ArrayList; 
import java.util.Iterator; 
import java.util.Map; 
import java.util.Set; 

/** 
* Custom Hal Deserializer 
* 
* @author jaren 
*/ 
public class CustomHalDeserializer extends HalDeserializer { 

    /** 
    * Intialisiert ein HalDeserializer-Objekt 
    * 
    * @param targetType Typ, den wir eigentlich deserialisieren sollten 
    */ 
    public CustomHalDeserializer(Class<?> targetType) { 
     super(targetType); 
    } 

    class CustomArrayList extends ArrayList implements HalResource{} 

    public HalResource deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context, Class<?> targetType) throws JsonParseException { 
     // Es handelt sich um ein JSON-Objekt. 
     JsonObject jsonObject = json.getAsJsonObject(); 
     JsonObject embeddedRoot = jsonObject.getAsJsonObject(HalConstants.EMBEDDED_ROOT); 

     if(embeddedRoot != null){ 
      Set<Map.Entry<String, JsonElement>> set = embeddedRoot.entrySet(); 
      if(set.toArray().length == 1){ 
       JsonArray ja = embeddedRoot.getAsJsonArray(set.iterator().next().getKey()); 
       if(ja.isJsonArray()) { 
        CustomArrayList arrayResult = new CustomArrayList(); 
        Iterator<JsonElement> i = ja.iterator(); 
        while(i.hasNext()){ 
         JsonElement je = i.next(); 
         arrayResult.add(super.deserialize(je, typeOfT, context, targetType)); 
        } 
        return arrayResult; 
       } 
      } 
     } 

     return super.deserialize(json, typeOfT, context, targetType); 
    } 
} 
0

zrobiłem prawie taka sama jak @ jake-Wharton powiedział w https://stackoverflow.com/a/33459073/2055854 ale dodano kilka zmian:

public class GenericConverterFactory<T> extends Converter.Factory { 

    private final Class<T> clazz; 

    public static GenericConverterFactory create(Class<T> clazz) { 
     return new GenericConverterFactory(clazz); 
    } 

    private GenericConverterFactory(Class<T> clazz) { 
     this.clazz = clazz; 
    } 

    @Override 
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { 
     if (!isNeededType(type)) { 
      return null; 
     } 

     // some converter that knows how to return your specific type T 
     return new GenericConverter(clazz); 
    } 

    private boolean isNeededType(Type type) { 
     if(type instanceof GenericArrayType) { 
      // if type is array we should check if it has the same component as our factory clazz 
      // if our factory clazz is not array getComponentType will return null 
      return ((GenericArrayType) type).getGenericComponentType().equals(clazz.getComponentType()); 
     } else if(clazz.getComponentType() == null) { 
      // if factory clazz is not array and type is not array too 
      // type is just a Class<?> and we should check if they are equal 
      return clazz.equals(type); 
     } else { 
      // otherwise our clazz is array and type is not 
      return false; 
     } 
    } 
} 

Rodzaj pochodzi z interfejsem modernizacji na przykład, jeśli masz:

public interface SomeApi{ 
    @GET("customelement") 
    CustomElement[] getCustomElements(); 
    @GET("customelement/{id}") 
    CustomElement getCustomElement(@Path("id") int id); 
} 

Dla metody getCustomElements() typ będzie GenericArrayType z GenericComponentType jako CustomElement.class, a dla drugiego typu metoda będzie po prostu CustomElement.class

Nie jestem pewien, czy to najlepsze rozwiązanie, ale dla mnie działa. Mam nadzieję, że to pomoże.

0

W moim przypadku musiałem serializować i deserializować tylko jedną klasę do XML. Do wszystkiego potrzebowałem Jsona.Więc zarejestrował moje adaptery tak:

retrofit = new Retrofit.Builder() 
       .baseUrl(BuildConfig.BASE_URL) 
       .addConverterFactory(EditUserXmlConverterFactory.create()) 
       .addConverterFactory(GsonConverterFactory.create(createGson())) 
       .client(httpClient.build()) 
       .build(); 

ponieważ nie mogłem przedłużyć SimpleXmlConverterFactory (niestety) musiałem użyć moje własne klasy i zmień następujący wiersz:

if (!(type instanceof Class)) return null; 

do

if (type != NeedToBeXML.class) return null; 

W ten sposób tylko odpowiedzi i żądania typu NeedToBeXML są konwertowane na XML - i wszystko inne JSON.