All Downloads are FREE. Search and download functionalities are using the official Maven repository.

xapi.jre.io.IOServiceDefault Maven / Gradle / Ivy

Go to download

Everything needed to run a comprehensive dev environment. Just type X_ and pick a service from autocomplete; new dev modules will be added as they are built. The only dev service not included in the uber jar is xapi-dev-maven, as it includes all runtime dependencies of maven, adding ~4 seconds to build time, and 6 megabytes to the final output jar size (without xapi-dev-maven, it's ~1MB).

The newest version!
/**
 *
 */
package xapi.jre.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.SocketException;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map.Entry;

import javax.inject.Provider;

import xapi.annotation.inject.InstanceDefault;
import xapi.annotation.inject.SingletonDefault;
import xapi.collect.X_Collect;
import xapi.collect.api.StringDictionary;
import xapi.collect.api.StringTo;
import xapi.collect.api.StringTo.Many;
import xapi.inject.impl.SingletonProvider;
import xapi.io.IOConstants;
import xapi.io.X_IO;
import xapi.io.api.CancelledException;
import xapi.io.api.IOCallback;
import xapi.io.api.IOMessage;
import xapi.io.api.IORequest;
import xapi.io.api.LineReader;
import xapi.io.api.StringReader;
import xapi.io.impl.AbstractIOService;
import xapi.io.service.IOService;
import xapi.log.X_Log;
import xapi.log.api.LogLevel;
import xapi.time.X_Time;
import xapi.time.api.Moment;
import xapi.util.X_Runtime;
import xapi.util.X_Util;
import xapi.util.api.ReceivesValue;
import xapi.util.impl.RunUnsafe;

/**
 * @author James X. Nelson ([email protected], @james)
 *
 */
@InstanceDefault(implFor=IOService.class)
@SingletonDefault(implFor=IOService.class)
public class IOServiceDefault extends AbstractIOService  {

  /**
   * @author James X. Nelson ([email protected], @james)
   *
   */
  public class IORequestDefault extends AbstractIORequest {

    private Thread connectionThread;

    @Override
    public void cancel() {
      super.cancel();
      if (connectionThread != null) {
        connectionThread.interrupt();
        connectionThread = null;
        synchronized (this) {
          notifyAll();
        }
      }
    }

    @Override
    public String response() {
      if (!super.isStarted()) {
        synchronized (this) {
          try {
            wait();
          } catch (final InterruptedException e) {
            cancel();
            Thread.currentThread().interrupt();
          }
        }
      }
      if (connectionThread != null) {
        synchronized (connectionThread) {
          try {
            if (connectionThread != null) {
              connectionThread.join();
            }
          } catch (final InterruptedException ignored) {
            cancel();
            Thread.currentThread().interrupt();
          }
          connectionThread = null;
        }
      }
      return getValue();
    }

    public void setConnectionThread(final Thread thread) {
      this.connectionThread = thread;
    }

  }

  @Override
  public IORequest get(final String uri, final StringDictionary headers, final IOCallback> callback) {
    final String url = normalize(uri);
    if (callback.isCancelled()) {
      return cancelled;
    }
    final Moment startUp = X_Time.now();
    try {
      final URL asUrl = new URL(url);
      final URLConnection connect = asUrl.openConnection();
      connect.setDoInput(true);
      connect.setDoOutput(false);
      if (headers != null) {
        headers.forKeys(new ReceivesValue() {
          @Override
          public void set(final String key) {
            final String value = headers.getValue(key);
            connect.setRequestProperty(key, value);
          }
        });
      }
      applySettings(connect, IOConstants.METHOD_GET);
      final LogLevel logLevel = logLevel();
      if (X_Log.loggable(logLevel)) {
        X_Log.log(getClass(), logLevel, "Startup time for ",url,"took",X_Time.difference(startUp));
      }
      final IORequestDefault request = createRequest();
      sendRequest(connect, request, callback, url, headers, null);
      return request;
    } catch (final Throwable e) {
      callback.onError(e);
      if (X_Runtime.isDebug()) {
        X_Log.warn("IO Error", e);
      }
      return cancelled;
    }
  }

  /**
   * @see xapi.io.service.IOService#post(java.lang.String, java.lang.String, xapi.collect.api.StringDictionary, xapi.io.api.IOCallback)
   */
  @Override
  public IORequest post(final String uri, final String body, final StringDictionary headers,
      final IOCallback> callback) {
    final String url = normalize(uri);
    if (callback.isCancelled()) {
      return cancelled;
    }
    final Moment startUp = X_Time.now();
    try {
      final URL asUrl = new URL(url);
      final URLConnection connect = asUrl.openConnection();
      connect.setDoInput(true);
      connect.setDoOutput(true);
      if (headers != null) {
        headers.forKeys(new ReceivesValue() {
          @Override
          public void set(final String key) {
            final String value = headers.getValue(key);
            connect.setRequestProperty(key, value);
          }
        });
      }
      applySettings(connect, IOConstants.METHOD_POST);
      final LogLevel logLevel = logLevel();
      if (X_Log.loggable(logLevel)) {
        X_Log.log(getClass(), logLevel, "Startup time for ",url,"took",X_Time.difference(startUp));
      }
      final IORequestDefault request = createRequest();
      sendRequest(connect, request, callback, url, headers, body);
      return request;
    } catch (final Throwable e) {
      callback.onError(e);
      if (X_Runtime.isDebug()) {
        X_Log.warn("IO Error", e);
      }
      return cancelled;
    }
  }


  protected void sendRequest(final URLConnection connect, final IORequestDefault request, final IOCallback> callback, final String url, final StringDictionary headers, final String body) {
    final LogLevel logLevel = logLevel();
    final Moment before = X_Time.now();
    X_Time.runUnsafe(new RunUnsafe() {
      @Override
      protected void doRun() throws Throwable {
        if (X_Log.loggable(logLevel)) {
          X_Log.log(getClass(), logLevel, "Starting IO for ",url,"took",X_Time.difference(before));
        }
        if (request.isCancelled()) {
          callback.onError(new CancelledException(request));
          return;
        }
        request.setConnectionThread(Thread.currentThread());
        synchronized (request) {
          request.start();
          request.notifyAll();
        }
        InputStream in;
        String res;
        try {
          if (body != null) {
            // We need to send data on the output stream first
            final Moment start = X_Time.now();
            try(
              final OutputStream out = connect.getOutputStream();
            ) {
              in = toStream(body, headers);
              X_IO.drain(out, in);
              if (X_Log.loggable(logLevel)) {
                X_Log.log(getClass(), logLevel, "Sending data for ",url,"took",X_Time.difference(start));
              }
            }
          }
          final Moment start = X_Time.now();
          try {
            in = connect.getInputStream();
            try {
              res = drainInput(in, callback);
            } finally {
              in.close();
            }
          } catch (final SocketException e) {
            in = connect.getInputStream();
            if (request.isCancelled()) {
              callback.onError(new CancelledException(request));
              return;
            }
            try {
              res = drainInput(in, callback);
            } finally {
              in.close();
            }
          }
          if (X_Log.loggable(logLevel)) {
            X_Log.log(getClass(), logLevel, "Receiving data for ",url,"took",X_Time.difference(start));
          }
          if (request.isCancelled()) {
            callback.onError(new CancelledException(request));
            return;
          }

          final Provider> resultHeaders = new SingletonProvider>() {

            @Override
            protected Many initialValue() {
              final Many headers = X_Collect.newStringMultiMap(String.class);
              for (final Entry> entry : connect.getHeaderFields().entrySet()) {
                for (final String value : entry.getValue()) {
                  headers.add(entry.getKey(), value);
                }
              }
              return headers;
            }
          };
          request.setValue(res);
          request.setResultHeaders(resultHeaders);
          if (connect instanceof HttpURLConnection) {
            final int status = ((HttpURLConnection)connect).getResponseCode();
            final String message = ((HttpURLConnection)connect).getResponseMessage();
            request.setStatus(status, message);
          } else {
            request.setStatus(IORequest.STATUS_NOT_HTTP, "Request not using http: "+connect.getClass());
          }

          final Moment callbackTime = X_Time.now();
          try {
            callback.onSuccess(new IOMessage() {
              @Override
              public String body() {
                return request.getValue();
              }

              @Override
              public int modifier() {
                return IOConstants.METHOD_GET;
              }

              @Override
              public String url() {
                return url;
              }

              @Override
              public StringTo.Many headers() {
                return resultHeaders.get();
              }

              @Override
              public int statusCode() {
                return request.getStatusCode();
              }

              @Override
              public String statusMessage() {
                return request.getStatusText();
              }
            });
          } catch (final Throwable t) {
            X_Log.error("Error invoking IO callback on",callback,"for request",url, t);
            callback.onError(X_Util.unwrap(t));
          }
          if (X_Log.loggable(logLevel)) {
            X_Log.log(getClass(), logLevel, "Callback time for ",url,"took",X_Time.difference(callbackTime));
          }
        } catch (final Throwable t) {
          request.cancel();
          callback.onError(X_Util.unwrap(t));
        } finally {
          request.connectionThread = null;
        }

      }
    });

  }

  private InputStream toStream(final String body, final StringDictionary headers) {
    return X_IO.toStreamUtf8(body);
  }

  protected IORequestDefault createRequest() {
    return new IORequestDefault();
  }

  protected String drainInput
    ( final InputStream in, final IOCallback> callback)
    throws IOException {
    try {
      final String message;
      final BufferedReader read = new BufferedReader(new InputStreamReader(in));
      final StringReader messageReader = new StringReader();
      if (callback instanceof LineReader) {
        messageReader.forwardTo((LineReader)callback);
      }
      String line;
      messageReader.onStart();
      while ((line = read.readLine())!=null) {
        messageReader.onLine(line);
      }
      // grab body >before< calling onEnd, as it cleans up its memory
      message = messageReader.toString();
      messageReader.onEnd();
      // All done.
      return message;
    } finally {
      in.close();
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy