XML : Retrofit and SimpleXML for unknown root element?

We are currently working on setting up Retrofit for an XML API - unfortunately each request can return a response with one of two different root elements.

Normally, each response looks like this (of course, the actual elements contained within the <response> tag vary with each request):

  <?xml version="1.0" encoding="UTF-8"?>  <response>      <SomeInfo>Some Info</SomeInfo>      <MoreInfo>More Info</MoreInfo>  </response>    

And each error looks like this (the structure here is the same for each response):

  <?xml version="1.0" encoding="UTF-8"?>  <error>      <code>125002</code>      <message></message>  </error>    

Now, the only way we've found so far to get this work in a somewhat generic way is the following:

  public interface Api {        @GET("/api/sessionToken")      Observable<ResponseBody> requestSessionToken();        @GET("/api/pinStatus")      Observable<ResponseBody> requestPinStatus();  }    public class RestClient {        public RestClient() {          // ...          mApiService = retrofit.create(Api.class);      }        public Observable<PinStatusResponse> requestPinStatus() {          return mApiService.requestPinStatus()                  .flatMap(foo(PinStatusResponse.class, PinStatusResponseData.class));      }        public Observable<SessionTokenResponse> requestSessionToken() {          return mApiService.requestSessionToken()                  .flatMap(foo(SessionTokenResponse.class, SessionTokenResponseData.class));      }        private final <O extends MyResponse, I> Func1<ResponseBody, Observable<T>> foo(final Class<O> outerCls, final Class<I> innerCls) {          return new Func1<ResponseBody, Observable<O>>() {              @Override              public Observable<O> call(ResponseBody responseBody) {                  try {                      final String xmlString = responseBody.string();                        final XmlPullParser parser = Xml.newPullParser();                      parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);                      parser.setInput(new ByteArrayInputStream(xmlString.getBytes(Charset.forName("UTF-8"))), null);                      parser.nextTag();                        final String rootTag = parser.getName();                        final Serializer serializer = new Persister();                        if (TextUtils.equals(rootTag, "error")) {                          final MyError myError = serializer.read(MyError.class, xmlString);                          return Observable.just((O) outerCls.getConstructor(MyError.class, innerCls).newInstance(myError, null));                      } else if (TextUtils.equals(rootTag, "response")) {                          final I data = serializer.read(innerCls, xmlString);                          return Observable.just((T) outerCls.getConstructor(MyError.class, innerCls).newInstance(null, data));                      }                  } catch (XmlPullParserException e) {                      return Observable.error(e);                  } catch (IOException e) {                      return Observable.error(e);                  } catch (Exception e) {                      return Observable.error(e);                  }                  return Observable.error(new Exception("Should not be reached..."));              }          };      }  ​}    

Where the Response classes look like this:

  public abstract class MyResponse<T> {      public final MyError error;      public final T data;        protected MyResponse(MyError error, T data) {          this.error = error;          this.data = data;      }  }    

and:

  public final class PinStatusResponse extends MyResponse<PinStatusResponseData> {      public PinStatusResponse(MyError error, PinStatusResponseData data) {          super(error, data);      }  }    

And all the *Data classes correspond directly to the (non-error) XML responses.

Now, my question is: Is there really no easier way to solve this? (And if so, is this a sign of bad API design?).

No comments:

Post a Comment