package com.facebook.stetho.inspector.network; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import com.facebook.stetho.common.ExceptionUtil; import com.facebook.stetho.common.Util; import javax.annotation.Nullable; /** * Abstract class for pretty printer factory that asynchronously downloads schema needed for * pretty printing the payload */ public abstract class DownloadingAsyncPrettyPrinterFactory implements AsyncPrettyPrinterFactory { @Override public AsyncPrettyPrinter getInstance(final String headerName, final String headerValue) { final MatchResult result = matchAndParseHeader(headerName, headerValue); if (result == null) { return null; } String uri = result.getSchemaUri(); URL schemaURL = parseURL(uri); if (schemaURL == null) { return getErrorAsyncPrettyPrinter(headerName, headerValue); } else { ExecutorService executorService = AsyncPrettyPrinterExecutorHolder.getExecutorService(); if (executorService == null) { //last peer is unregistered... return null; } final Future<String> response = executorService.submit(new Request(schemaURL)); return new AsyncPrettyPrinter() { public void printTo(PrintWriter output, InputStream payload) throws IOException { try { String schema; try { schema = response.get(); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (IOException.class.isInstance(cause)) { doErrorPrint( output, payload, "Cannot successfully download schema: " + e.getMessage()); return; } else { throw e; } } doPrint(output, payload, schema); } catch (InterruptedException e) { doErrorPrint( output, payload, "Encountered spurious interrupt while downloading schema for pretty printing: " + e.getMessage()); } catch (ExecutionException e) { Throwable cause = e.getCause(); throw ExceptionUtil.propagate(cause); } } public PrettyPrinterDisplayType getPrettifiedType() { return result.getDisplayType(); } }; } } /** * Match the correct header that contains information about the schema uri * @param headerName header name of a response that needs to be pretty printed * @param headerValue header value which contains the URI for * the schema data needed to pretty print the response body * @return MatchResult that has the schema uri and the type of prettified result. Null * if there is no correct header match. */ @Nullable protected abstract MatchResult matchAndParseHeader(String headerName, String headerValue); /** * Note that the IOException thrown by this method will be propagated all the way up and * yield an error to the chrome devtools */ protected abstract void doPrint(PrintWriter output, InputStream payload, String schema) throws IOException; @Nullable private static URL parseURL(String uri) { try { return new URL(uri); } catch (MalformedURLException e) { return null; } } private static void doErrorPrint(PrintWriter output, InputStream payload, String errorMessage) throws IOException { output.print(errorMessage + "\n" + Util.readAsUTF8(payload)); } private static AsyncPrettyPrinter getErrorAsyncPrettyPrinter( final String headerName, final String headerValue) { return new AsyncPrettyPrinter() { @Override public void printTo(PrintWriter output, InputStream payload) throws IOException { String errorMessage = "[Failed to parse header: " + headerName + " : " + headerValue + " ]"; doErrorPrint(output, payload, errorMessage); } @Override public PrettyPrinterDisplayType getPrettifiedType() { return PrettyPrinterDisplayType.TEXT; } }; } protected class MatchResult { private final String mSchemaUri; private final PrettyPrinterDisplayType mDisplayType; public MatchResult(String schemaUri, PrettyPrinterDisplayType displayType) { mSchemaUri = schemaUri; mDisplayType = displayType; } public String getSchemaUri() { return mSchemaUri; } public PrettyPrinterDisplayType getDisplayType() { return mDisplayType; } } private static class Request implements Callable<String> { private URL url; public Request(URL url) { this.url = url; } @Override public String call() throws IOException { HttpURLConnection connection = (HttpURLConnection)url.openConnection(); int statusCode = connection.getResponseCode(); if (statusCode != 200) { throw new IOException("Got status code: " + statusCode + " while downloading " + "schema with url: " + url.toString()); } InputStream urlStream = connection.getInputStream(); try { return Util.readAsUTF8(urlStream); } finally { urlStream.close(); } } } }