Package org.xlightweb

Source Code of org.xlightweb.NonBlockingBodyDataSource

/*
*  Copyright (c) xlightweb.org, 2008 - 2009. All rights reserved.
*
*  This library is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public
*  License as published by the Free Software Foundation; either
*  version 2.1 of the License, or (at your option) any later version.
*
*  This library is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*  Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public
*  License along with this library; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
* Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
* The latest copy of this software may be found on http://www.xlightweb.org/
*/
package org.xlightweb;


import java.io.Closeable;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.TimerTask;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.xlightweb.AbstractHttpConnection.IMultimodeExecutor;
import org.xsocket.DataConverter;
import org.xsocket.IDataSource;
import org.xsocket.MaxReadSizeExceededException;
import org.xsocket.connection.AbstractNonBlockingStream;
import org.xsocket.connection.IConnection;
import org.xsocket.connection.IWriteCompletionHandler;
import org.xsocket.connection.IConnection.FlushMode;



/**
*
* I/O resource capable of providing body data in a non blocking way.
* Read operations returns immediately 
*
* @author grro@xlightweb.org
*/
public final class NonBlockingBodyDataSource implements IDataSource, ReadableByteChannel, Closeable {
 
 
  private static final Logger LOG = Logger.getLogger(NonBlockingBodyDataSource.class.getName());

  // max write buffer size
  private static int maxWriteBufferSize = AbstractHttpConnection.getMaxWriteBufferSize();
 
 
  // body type
  private final BodyType bodyType;
 


  // delegee classes
  private final NonBlockingStream nonBlockingStream = new NonBlockingStream();
  private final HandlerCaller handlerCaller = new HandlerCaller();

 
  // close flag
  private final AtomicBoolean isOpen = new AtomicBoolean(true);
  private final AtomicBoolean isDestroyed = new AtomicBoolean(false);
  private final AtomicBoolean isUnderlyingConnectionOpen = new AtomicBoolean(true);

 
  // life cycle management
  private static final long MIN_WATCHDOG_PERIOD_MILLIS = 10 * 1000;
  public static final long DEFAULT_RECEIVE_TIMEOUT_MILLIS = Long.MAX_VALUE;

  private long bodyDataReceiveTimeoutMillis = DEFAULT_RECEIVE_TIMEOUT_MILLIS;
  private long creationTimeMillis = 0;
  private long lastTimeDataReceivedMillis = System.currentTimeMillis();
  private TimeoutWatchDogTask watchDogTask;
   
  private boolean isDestroyConnectionAfterReceived = false;
  private boolean isCloseConnectionAfterReceived = false;
  private AtomicBoolean isOnDisconnectCalled = new AtomicBoolean(false);
 

  // the underlying connection
  private final AbstractHttpConnection httpConnection;

  // listener management
  private final ArrayList<IBodyCloseListener> closeListeners = new ArrayList<IBodyCloseListener>();
  private final ArrayList<IBodyCompleteListener> completeListeners = new ArrayList<IBodyCompleteListener>()
  private final AtomicBoolean isComplete = new AtomicBoolean(false);

 
  // handler management
  private final AtomicReference<IBodyDataSourceDisconnectHandler> disconnectHandler = new AtomicReference<IBodyDataSourceDisconnectHandler>(null);
  private final AtomicReference<IBodyDataHandler> handler = new AtomicReference<IBodyDataHandler>(null);
  private final AtomicBoolean isMultithreaded = new AtomicBoolean(true);
  private boolean isSystem = false;
 
 
  // suspend support
  private final AtomicBoolean isSuspended = new AtomicBoolean(false);
 
  // exception support
  private final AtomicReference<IExceptionHandler> exceptionHandler = new AtomicReference<IExceptionHandler>(null);
  private final AtomicReference<IOException> exceptionHolder = new AtomicReference<IOException>();
 
 
  // callback execution support
  private final IMultimodeExecutor executor;

 
  // write completion support
  private final AtomicBoolean isCompletionSupportActivated = new AtomicBoolean(false);
  private final WriteCompletionManager writeCompletionManager = new WriteCompletionManager(getId());

 
 
  /**
   * constructor
   *
   * @param bodyType the body type
   * @param encoding the encoding
   */
  NonBlockingBodyDataSource(BodyType bodyType, String encoding) {
    this.bodyType = bodyType;
      httpConnection = null;
    executor = new DefaultMultimodeExecutor();
    nonBlockingStream.setEncoding(encoding);
  }
 
 

 
  /**
   * constructor
   *
   * @param bodyType        the body type
   * @param encoding        the encoding
   * @param httpConnection  the underlying httpConnection
   * @param executor        the executor
   */
  NonBlockingBodyDataSource(BodyType bodyType, String encoding, AbstractHttpConnection httpConnection, IMultimodeExecutor executor) {
    this.bodyType = bodyType;
    this.executor = executor;
    nonBlockingStream.setEncoding(encoding);
    this.httpConnection = httpConnection;
  }
 

  /**
   * constructor
   *
   * @param bodyType the body type
   * @param body     the body
   * @param encoding the encoding
   */
  NonBlockingBodyDataSource(BodyType bodyType, String body, String encoding) {
    this(bodyType, new ByteBuffer[] { DataConverter.toByteBuffer(body, encoding) }, encoding);
  }

  /**
   * constructor
   *
     * @param bodyType the body type
   * @param body     the body
   * @param encoding the encoding
   */
  NonBlockingBodyDataSource(BodyType bodyType, byte[] body, String encoding) {
    this(bodyType, new ByteBuffer[] { ByteBuffer.wrap(body) }, encoding);

  }

 
  /**
   * constructor
   *
     * @param bodyType the body type
   * @param body     the body
   * @param encoding the encoding
   */
  NonBlockingBodyDataSource(BodyType bodyType, ByteBuffer[] body, String encoding) {
    this.bodyType = bodyType;
      httpConnection = null;
    executor = new DefaultMultimodeExecutor();
   
    if (encoding != null) {
      nonBlockingStream.setEncoding(encoding);
    }
    nonBlockingStream.append(body, null);
    isComplete.set(true);
  }

 
  /**
   * constructor
   *
     * @param bodyType       the body type
   * @param bodyDatasource the body data source
   * @param encoding       the encoding
   */
  NonBlockingBodyDataSource(BodyType bodyType, ReadableByteChannel bodyDatasource, String encoding) throws IOException {
    this(bodyType, bodyDatasource, 8192, encoding);
  }
 
 
 
  /**
   * constructor
   *
   * @param bodyDatasource the body data source
   * @param encoding the encoding
   */
  NonBlockingBodyDataSource(FileChannel bodyDatasource, String encoding) throws IOException {
    this(BodyType.IN_MEMORY, bodyDatasource, (int) bodyDatasource.size(), encoding);
  }
 

  /**
   * constructor
   *
   * @param bodyDatasource the body data source
   * @param encoding the encoding
   */
  private NonBlockingBodyDataSource(BodyType bodyType, ReadableByteChannel bodyDatasource, int chunkSize, String encoding) throws IOException {
    this.bodyType = bodyType;
      httpConnection = null;
    executor = new DefaultMultimodeExecutor();
    setEncoding(encoding);
 
   
    List<ByteBuffer> buffers = new ArrayList<ByteBuffer>();
   
    int read = 0;
    do {
      ByteBuffer transferBuffer = ByteBuffer.allocate(chunkSize);
      read = bodyDatasource.read(transferBuffer);

      if (read > 0) {
        if (transferBuffer.remaining() == 0) {
          transferBuffer.flip();
          buffers.add(transferBuffer);

        } else {
          transferBuffer.flip();
          buffers.add(transferBuffer.slice());
        }
      }
    } while (read > 0);
   
    nonBlockingStream.append(buffers.toArray(new ByteBuffer[buffers.size()]), null);
    isComplete.set(true);
  }
 
 
  void setEncoding(String encoding) {
    nonBlockingStream.setEncoding(encoding);
  }
 

 
  /**
   * returns the id
   *
   * @return the id
   */
  String getId() {
    if (httpConnection != null) {
      return httpConnection.getId();
    } else {
      return Integer.toString(hashCode());
    }
  }

 
  /**
   * returns the assigned http connection
   *
   * @return the assigned http connection
   */
  AbstractHttpConnection getConnection() {
    return httpConnection;
  }
 
 
  /**
   * copies the body content
   * 
   * @return the copy
   */
  ByteBuffer[] copyContent() {
    return nonBlockingStream.copyContent();
  }
 

 
  /**
   * destroys the data source
   */
  void destroy() {
    isDestroyed.set(true);
    if (httpConnection != null) {
      httpConnection.destroy();
    }
  }
   
 
  /**
   * initiates that the assigned connection will be destroyed, if the body is received completely
   * 
   * @param isDestroyConnectionAfterReceived true, if the connection should be destroyed
   */
  void setDestroyAfterReceived(boolean isDestroyConnectionAfterReceived) {
    this.isDestroyConnectionAfterReceived = isDestroyConnectionAfterReceived;
    if (isComplete.get()) {
      handleReceivingFinished();
    }
  }

  /**
   * initiates that the assigned connection will be destroyed, if the body is received completely
   *
   * @param isCloseConnectionAfterReceived true, if the connection should be closed
   */
  void setCloseAfterReceived(boolean isCloseConnectionAfterReceived) {
    this.isCloseConnectionAfterReceived = isCloseConnectionAfterReceived;
    if (isComplete.get()) {
      handleReceivingFinished();
    }
  }


  /**
   * suspend the (underlying connection of the) body data source
   * 
   * @throws IOException if an error occurs
   */
  private void suspend() throws IOException {
   
    if ((httpConnection != null) && !httpConnection.isReceivingSuspended()) {
     
      boolean suspended = isSuspended.getAndSet(true);

      // is not already suspended
      if (!suspended) {
        if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("suspend receiving data");
        }
       
        isSuspended.set(true);
        Runnable suspendTask = new Runnable() {
         
          public void run() {
            try {
              httpConnection.suspendReceiving();
            } catch (IOException ioe) {
              if (LOG.isLoggable(Level.FINE)) {
                LOG.fine("error occured by resuming " + NonBlockingBodyDataSource.this + " " + ioe.toString());
              }
            }
          }
        };
       
        executor.processNonthreaded(suspendTask);
      }
    }
  }
 

  /**
   * resume the (underlying connection of the) body data source
   *
   * @throws IOException if an error occurs
   */
  private void resume() throws IOException {
   
    if (httpConnection != null) {
     
      boolean suspended = isSuspended.getAndSet(false);
     
      // is suspended?
      if (suspended) {
        Runnable resumeTask = new Runnable() {
         
          public void run() {
            try {
              if (httpConnection.isReceivingSuspended()) {
                if (LOG.isLoggable(Level.FINE)) {
                  LOG.fine("resume receiving data");
                }
                httpConnection.resumeReceiving();
                callBodyHandler(true, true);
              }
            } catch (IOException ioe) {
              if (LOG.isLoggable(Level.FINE)) {
                LOG.fine("error occured by resuming " + NonBlockingBodyDataSource.this + " " + ioe.toString());
              }
            }
          }       
        };
        executor.processNonthreaded(resumeTask);
      }
    }
  }
 
 
  /**
   * on disconnect call back
   */
  void onDisconnect() {
   
    // is disconnect already called?
    if (isOnDisconnectCalled.getAndSet(true)) {
      return;
    }
   
 
    if (LOG.isLoggable(Level.FINE)) {
      if (!isComplete.get()) {
        LOG.fine("protocol error occured (connection is closed, but more data is expected). May be connection has been closed by peer?");
      }
   
    }
 
    isUnderlyingConnectionOpen.set(false);
    callBodyHandler(true, false);
   
    IBodyDataSourceDisconnectHandler dh = disconnectHandler.get();
    if (dh != null) {
      Runnable task = new OnDisconnectCaller(dh);
      processNonthreaded(task);
    }
  }

 
  private static final class OnDisconnectCaller implements Runnable {
   
    private IBodyDataSourceDisconnectHandler dh = null;
   
    public OnDisconnectCaller(IBodyDataSourceDisconnectHandler dh) {
      this.dh = dh;
    }
   
    public void run() {
      dh.onDisconnect();
    }
  };
 
 
  /**
   * returns the body type
   *
   * @return the body type
   */
  BodyType getBodyType() {
    return bodyType;
  }
 
 
 
  /**
   * returns body data receive timeout
   *
   * @return the body data receive timeout or <code>null</code>
   */
  public long getBodyDataReceiveTimeoutMillis() {
    return bodyDataReceiveTimeoutMillis;
  }
 

  /**
   * set the body data receive timeout
   *
   * @param bodyDataReceiveTimeoutMillis the timeout
   */
  public void setBodyDataReceiveTimeoutMillis(long bodyDataReceiveTimeoutMillis) {
   
    if (bodyDataReceiveTimeoutMillis <= 0) {
      if (!isComplete.get()) {
        setIOException(new ReceiveTimeoutException(bodyDataReceiveTimeoutMillis));
      }
      return;
    }
   
    creationTimeMillis = System.currentTimeMillis();
   
    if (this.bodyDataReceiveTimeoutMillis != bodyDataReceiveTimeoutMillis) {
      this.bodyDataReceiveTimeoutMillis = bodyDataReceiveTimeoutMillis;
   
      if (bodyDataReceiveTimeoutMillis == Long.MAX_VALUE) {
        terminateWatchDog();

      } else{
       
        long watchdogPeriod = 100;
        if (bodyDataReceiveTimeoutMillis > 1000) {
          watchdogPeriod = bodyDataReceiveTimeoutMillis / 10;
        }
       
        if (watchdogPeriod > MIN_WATCHDOG_PERIOD_MILLIS) {
          watchdogPeriod = MIN_WATCHDOG_PERIOD_MILLIS;
        }
       
        updateWatchDog(watchdogPeriod);
      }
    }
  }
 
 
  private synchronized void updateWatchDog(long watchDogPeriod) {
    terminateWatchDog();

        watchDogTask = new TimeoutWatchDogTask(this);
        AbstractHttpConnection.schedule(watchDogTask, watchDogPeriod, watchDogPeriod);
  }

 
  private synchronized void terminateWatchDog() {
    if (watchDogTask != null) {
            watchDogTask.cancel();
            watchDogTask = null;
        }
  }
 
 
  private void checkTimeouts() {
   
    if (isComplete.get()) {
      terminateWatchDog();
      return;
    }
   
    long currentTimeMillis = System.currentTimeMillis();
     
    if (currentTimeMillis > (lastTimeDataReceivedMillis + bodyDataReceiveTimeoutMillis) &&
      currentTimeMillis > (creationTimeMillis + bodyDataReceiveTimeoutMillis)) {
     
      if (LOG.isLoggable(Level.FINE)) {
        LOG.fine("receive timeout reached. set exception");
      }

      if (!isComplete.get()) {
        setIOException(new ReceiveTimeoutException());
      }
      destroy();
    }
  }
 

  /**
   * sets the exception handler<
   *
   * @param eh the exception handler<
   */
  void setExceptionHandler(IExceptionHandler eh) {
    exceptionHandler.set(eh);
  }
 
 
  /**
   * sets a exception
   *
   * @param ioe the exception
   */
  void setIOException(IOException ioe) {
   
    if (LOG.isLoggable(Level.FINE)) {
      LOG.fine("error occured " + ioe.toString());
    }
   
    if (exceptionHolder.get() == null) {
     
      IExceptionHandler eh = exceptionHandler.get();
      if (eh != null) {
        eh.onException(ioe);
      }
     
      exceptionHolder.set(ioe);
    }
   
    closeUnclean();
  }
 
  private void throwExceptionIfExist() throws IOException {
    if (exceptionHolder.get() != null) {
      IOException ex = exceptionHolder.get();
      exceptionHolder.set(null);
      throw ex;
    }
  }
 
 
 
 
  /**
   * returns the body encoding
   *
   * @return the body encoding
   */
  String getEncoding() {
    return nonBlockingStream.getEncoding();
  }

   

  /**
   * returns true, if the body data source is open
   *
   *  @return  true, if the body data source is open
   */
  public boolean isOpen() {

    return isOpen.get();
  }
 
 
  /**
   * closes the body data source
   *
   *  @throws IOException if an exception occurs
   */
  public void close() throws IOException {
    close(true);
  }
 
 
  private void close(boolean isSetComplete) throws IOException {
   
      if (!isComplete.get() && (httpConnection != null)) {
          httpConnection.setPersistent(false);
      }
    
      if (isSetComplete) {
        isComplete.set(true);
      }
     
    terminateWatchDog();
    nonBlockingStream.close();
   
    callBodyHandler(true, false);
    handleReceivingFinished();
   
    callCloseListener();
  }
 
 
 

  @SuppressWarnings("unchecked")
  private void callCloseListener() {
    ArrayList<IBodyCloseListener> closeListenersCopy = null;
    synchronized (closeListeners) {
      closeListenersCopy = (ArrayList<IBodyCloseListener>) closeListeners.clone();
    }
   
   
    for (IBodyCloseListener bodyCloseListener : closeListenersCopy) {
      removeCloseListener(bodyCloseListener);
      callCloseListener(bodyCloseListener);
    }
  }
 
 
 
  private void callCloseListener(IBodyCloseListener listener) {
   
    Runnable task = new BodyCloselistenerCaller(listener);
   
    if (HttpUtils.isBodyCloseListenerMutlithreaded(listener)) {
      executor.processMultithreaded(task);
    } else {
      executor.processNonthreaded(task);
    }
  }
 
 
  private static final class BodyCloselistenerCaller implements Runnable {
 
    private IBodyCloseListener listener = null;
   
    public BodyCloselistenerCaller(IBodyCloseListener listener) {
      this.listener = listener;
    }
   
    public void run() {
      try {
        listener.onClose();
      } catch (IOException ioe) {
        if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("Error occured by calling close listener " + listener + " " + ioe.toString());
        }
      }
    }
  }
 
  
  private int getSize() throws IOException {
    return nonBlockingStream.available();
  }
 

  /**
   * returns the available bytes
   *
   * @return the number of available bytes, possibly zero, or -1 if the channel has reached end-of-stream
   *
   * @throws ProtocolException if a protocol error occurs
   * @throws IOException if some other exception occurs
   */
  public int available() throws ProtocolException, IOException  {
   
    // if an exception is pending -> throwing it
    if (exceptionHolder.get() != null) {
      IOException ex = exceptionHolder.get();
      exceptionHolder.set(null);
     
      // ClosedChannelException should not occur here. Anyway, handle it because available() should never throw a ClosedChannelException
      if (!(ex instanceof ClosedChannelException)) {
        throw ex;
      }
    }
   
    // retrieve the available data size
    int available = nonBlockingStream.getSize();
   
    // non data available?
    if ((available == 0)) {
     
      // if body is complete return -1 to signal end-of-stream
      if (isComplete.get()) {
        return -1;
       
      } else {
       
        // if underlying connection is open return 0 because more data can be received
        if (isUnderlyingConnectionOpen.get()) {
          return 0;
         
        // if underlying connection is closed throwing a ClosedChannelException, because
        // the body is not complete
        } else {
          isOpen.set(false);
          throw new ClosedChannelException();
        }
      }
     
    } else {
      return available;
    }
  }
 
 
  /**
   * returns the current content size
   *
   * @return the current content size
   * @throws IOException if an exception occurs
   */
  int size() throws IOException  {
    int available = nonBlockingStream.getSize();
    if ((available <= 0) && isComplete.get()) {
      return -1;
    } else {
      return available;
    }
  }
 
  private int getVersion() throws IOException {
    return nonBlockingStream.getReadBufferVersion();
  }
 
 
  /**
   * get the version of read buffer. The version number increases, if
   * the read buffer queue has been modified
   *
   * @return the read buffer version
   * @throws IOException if an exception occurs
   */
  public int getReadBufferVersion() throws IOException {
    throwExceptionIfExist();
    return nonBlockingStream.getReadBufferVersion();
  }
 

  /**
   * Marks the read position in the connection. Subsequent calls to resetToReadMark() will attempt
   * to reposition the connection to this point.
   *
   */
  public void markReadPosition() {
    nonBlockingStream.markReadPosition();
  }


  /**
   * Resets to the marked read position. If the connection has been marked,
   * then attempt to reposition it at the mark.
   *
   * @return true, if reset was successful
   */
  public boolean resetToReadMark() {
    return nonBlockingStream.resetToReadMark();
  }


 
  /**
   * remove the read mark
   */
  public void removeReadMark() {
    nonBlockingStream.removeReadMark();
  }

 
  /**
   * Returns the index of the first occurrence of the given string.
   *
   * @param str any string
   * @return if the string argument occurs as a substring within this object, then
   *         the index of the first character of the first such substring is returned;
   *         if it does not occur as a substring, -1 is returned.
    * @throws IOException If some other I/O error occurs
   */
  public int indexOf(String str) throws IOException {
    throwExceptionIfExist();
    return nonBlockingStream.indexOf(str);
  }
 


  /**
   * Returns the index  of the first occurrence of the given string.
   *
   * @param str          any string
   * @param encoding     the encoding to use
   * @return if the string argument occurs as a substring within this object, then
   *         the index of the first character of the first such substring is returned;
   *         if it does not occur as a substring, -1 is returned.
    * @throws IOException If some other I/O error occurs
   */
  public int indexOf(String str, String encoding) throws IOException, MaxReadSizeExceededException {
    throwExceptionIfExist();
    return nonBlockingStream.indexOf(str, encoding);
  }

 
 

 

  /**
   * read a byte
   *
   * @return the byte value
   * @throws IOException If some other I/O error occurs
    * @throws BufferUnderflowException if not enough data is available
   */
  public byte readByte() throws ProtocolException, IOException, BufferUnderflowException {
    throwExceptionIfExist();
    return nonBlockingStream.readByte();
  }

 
  /**
   * read a short value
   *
   * @return the short value
   * @throws IOException If some other I/O error occurs
    * @throws BufferUnderflowException if not enough data is available
   */
  public short readShort() throws ProtocolException, IOException, BufferUnderflowException {
    throwExceptionIfExist();
    return nonBlockingStream.readShort();
  }
 

  /**
   * read an int
   *
   * @return the int value
   * @throws IOException If some other I/O error occurs
    * @throws BufferUnderflowException if not enough data is available
   */
  public int readInt() throws ProtocolException, IOException, BufferUnderflowException {
    throwExceptionIfExist();
    return nonBlockingStream.readInt();
  }
 


  /**
   * read a long
   *
   * @return the long value
   * @throws IOException If some other I/O error occurs
    * @throws BufferUnderflowException if not enough data is available
   */
  public long readLong() throws IOException, BufferUnderflowException {
    throwExceptionIfExist();
    return nonBlockingStream.readLong();
  }
 

  /**
   * read a double
   *
   * @return the double value
   * @throws IOException If some other I/O error occurs
    * @throws BufferUnderflowException if not enough data is available
   */
  public double readDouble() throws ProtocolException, IOException, BufferUnderflowException {
    throwExceptionIfExist();
    return nonBlockingStream.readDouble();
  }
 

  /**
   * see {@link ReadableByteChannel#read(ByteBuffer)}
   */
  public int read(ByteBuffer buffer) throws ProtocolException, IOException {
    throwExceptionIfExist();
   
    int size = buffer.remaining();
    int available = available();
   
    if (available == -1) {
      close();
      return -1;
    }
   
    if (available == 0) {
      return 0;
    }
   
    if (available > 0) {
      if (available < size) {
        size = available;
      }

      if (size > 0) {
        ByteBuffer[] bufs = readByteBufferByLength(size);
        copyBuffers(bufs, buffer);
      }     
    } 
   
    return size;
  }

 
  private void copyBuffers(ByteBuffer[] source, ByteBuffer target) {
    for (ByteBuffer buf : source) {
      if (buf.hasRemaining()) {
        target.put(buf);
      }
    }
  }

 
 
 
 
  /**
   * read a ByteBuffer by using a delimiter. The default encoding will be used to decode the delimiter
   * To avoid memory leaks the {@link IReadWriteableConnection#readByteBufferByDelimiter(String, int)} method is generally preferable 
   * <br>
     * For performance reasons, the ByteBuffer readByteBuffer method is
     * generally preferable to get bytes
   *
   * @param delimiter   the delimiter
   * @return the ByteBuffer
   * @throws IOException If some other I/O error occurs
    * @throws BufferUnderflowException if not enough data is available
   */
  public ByteBuffer[] readByteBufferByDelimiter(String delimiter) throws ProtocolException, IOException, BufferUnderflowException {
    return readByteBufferByDelimiter(delimiter, Integer.MAX_VALUE);
  }
 
 
  /**
   * read a ByteBuffer by using a delimiter
   *
     * For performance reasons, the ByteBuffer readByteBuffer method is
     * generally preferable to get bytes
   *
   * @param delimiter   the delimiter
   * @param maxLength   the max length of bytes that should be read. If the limit is exceeded a MaxReadSizeExceededException will been thrown  
   * @return the ByteBuffer
   * @throws MaxReadSizeExceededException If the max read length has been exceeded and the delimiter hasn�t been found    
   * @throws IOException If some other I/O error occurs
    * @throws BufferUnderflowException if not enough data is available  
   */
  public ByteBuffer[] readByteBufferByDelimiter(String delimiter, int maxLength) throws ProtocolException, IOException, BufferUnderflowException, MaxReadSizeExceededException {
    throwExceptionIfExist();
    return nonBlockingStream.readByteBufferByDelimiter(delimiter, maxLength);
  }
 
 
  /**
   * read a ByteBuffer 
   *
   * @param length   the length could be negative, in this case a empty array will be returned
   * @return the ByteBuffer
   * @throws IOException If some other I/O error occurs
    * @throws BufferUnderflowException if not enough data is available
   */
  public ByteBuffer[] readByteBufferByLength(int length) throws IOException, BufferUnderflowException {
    throwExceptionIfExist();
    ByteBuffer[] bufs =  nonBlockingStream.readByteBufferByLength(length);
   
    if (LOG.isLoggable(Level.FINE)) {
      int size = 0;
      for (ByteBuffer buffer : bufs) {
        size += buffer.remaining();
      }
      LOG.fine(size + " data read (remaining " + nonBlockingStream.getSize() + ")");
    }
   
    return bufs;
  }
 

  /**
   * read a byte array by using a delimiter
   *
     * For performance reasons, the ByteBuffer readByteBuffer method is
     * generally preferable to get bytes
   *
   * @param delimiter   the delimiter 
   * @return the read bytes
   * @throws IOException If some other I/O error occurs
    * @throws BufferUnderflowException if not enough data is available
   */   
  public byte[] readBytesByDelimiter(String delimiter) throws ProtocolException, IOException, BufferUnderflowException {
    return DataConverter.toBytes(readByteBufferByDelimiter(delimiter));
  }
 

  /**
   * read a byte array by using a delimiter
   *
     * For performance reasons, the ByteBuffer readByteBuffer method is
     * generally preferable to get bytes
   *
   * @param delimiter   the delimiter
   * @param maxLength   the max length of bytes that should be read. If the limit is exceeded a MaxReadSizeExceededException will been thrown
   * @return the read bytes
   * @throws MaxReadSizeExceededException If the max read length has been exceeded and the delimiter hasn�t been found   
   * @throws IOException If some other I/O error occurs
    * @throws BufferUnderflowException if not enough data is available
   */
  public byte[] readBytesByDelimiter(String delimiter, int maxLength) throws ProtocolException, IOException, BufferUnderflowException, MaxReadSizeExceededException {
    return DataConverter.toBytes(readByteBufferByDelimiter(delimiter, maxLength));
  }
 
 
  /**
   * read bytes by using a length definition
   * 
   * @param length the amount of bytes to read 
   * @return the read bytes
   * @throws IOException If some other I/O error occurs
     * @throws IllegalArgumentException, if the length parameter is negative
    * @throws BufferUnderflowException if not enough data is available
   */ 
  public byte[] readBytesByLength(int length) throws ProtocolException, IOException, BufferUnderflowException {
    return DataConverter.toBytes(readByteBufferByLength(length));
  }
 


  /**
   * read a string by using a delimiter
   *
   * @param delimiter   the delimiter
   * @return the string
   * @throws IOException If some other I/O error occurs
    * @throws UnsupportedEncodingException if the default encoding is not supported
    * @throws BufferUnderflowException if not enough data is available
    */
  public String readStringByDelimiter(String delimiter) throws ProtocolException, IOException, BufferUnderflowException, UnsupportedEncodingException {
    return DataConverter.toString(readByteBufferByDelimiter(delimiter), getEncoding());
  }
 

 

  /**
   * read a string by using a delimiter
   *
   * @param delimiter   the delimiter
   * @param maxLength   the max length of bytes that should be read. If the limit is exceeded a MaxReadSizeExceededException will been thrown
   * @return the string
   * @throws MaxReadSizeExceededException If the max read length has been exceeded and the delimiter hasn�t been found   
   * @throws IOException If some other I/O error occurs
    * @throws UnsupportedEncodingException If the given encoding is not supported
    * @throws BufferUnderflowException if not enough data is available
   */
  public String readStringByDelimiter(String delimiter, int maxLength) throws ProtocolException, IOException, BufferUnderflowException, UnsupportedEncodingException, MaxReadSizeExceededException {
    return DataConverter.toString(readByteBufferByDelimiter(delimiter, maxLength), getEncoding());
  }
 
 
 
  /**
   * read a string by using a length definition
   *
   * @param length the amount of bytes to read 
   * @return the string
   * @throws IOException If some other I/O error occurs
    * @throws UnsupportedEncodingException if the given encoding is not supported
     * @throws IllegalArgumentException, if the length parameter is negative
    * @throws BufferUnderflowException if not enough data is available
   */
  public String readStringByLength(int length) throws ProtocolException, IOException, BufferUnderflowException, UnsupportedEncodingException {
    return DataConverter.toString(readByteBufferByLength(length), getEncoding());
  }
 
 

  /**
   * transfer the data of the this source channel to the given data sink
   *
   * @param dataSink   the data sink
   * @param length     the size to transfer
   *
   * @return the number of transfered bytes
   * @throws ClosedChannelException If either this channel or the target channel is closed
   * @throws IOException If some other I/O error occurs
   */
  public long transferTo(WritableByteChannel dataSink, int length) throws ProtocolException, IOException, ClosedChannelException {
    throwExceptionIfExist();
   
    if (length > 0) {
      long written = 0;

      ByteBuffer[] buffers = readByteBufferByLength(length);
      for (ByteBuffer buffer : buffers) {
        while(buffer.hasRemaining()) {
          written += dataSink.write(buffer);
        }
      }

      return written;

    } else {
      return 0;
    }
  }
 
 

  /**
   * transfer the available data of the this source channel to the given data sink
   *
   * @param dataSink   the data sink
   *
   * @return the number of transfered bytes
   * @throws ClosedChannelException If either this channel or the target channel is closed
   * @throws IOException If some other I/O error occurs
   */
  public long transferTo(BodyDataSink dataSink) throws ProtocolException, IOException, ClosedChannelException {
    return transferTo(dataSink, available());
  }
   
   
 

  /**
   * transfer the data of the this source channel to the given data sink
   *
   * @param dataSink            the data sink
   * @param length              the size to transfer
   *
   * @return the number of transfered bytes
   * @throws ClosedChannelException If either this channel or the target channel is closed
   * @throws IOException If some other I/O error occurs
   */ 
  public long transferTo(final BodyDataSink dataSink, final int length) throws ProtocolException, IOException, ClosedChannelException {   
    throwExceptionIfExist();
   
    long written = 0;
   
    if (length > 0) {

      boolean savedAutoflush = dataSink.isAutoflush();
      dataSink.setAutoflush(true);
 
     
      // completion handler adapter is only required in async mode
      if (dataSink.getFlushmode() == FlushMode.ASYNC) {
        final ByteBuffer[] bufs = nonBlockingStream.readByteBufferByLength(length);
       
        IWriteCompletionHandler adapter = new IWriteCompletionHandler() {
           
          public void onWritten(int length) throws IOException {
            flowControlOnWritten(dataSink);
            writeCompletionManager.onWritten(bufs, true);
          }
           
          public void onException(IOException ioe) {
            if (LOG.isLoggable(Level.FINE)) {
              LOG.fine("error occured by writing " + ioe.toString());
            }
            flowControlOnWritten(dataSink);
            writeCompletionManager.onWriteException(ioe, bufs);
            destroy();
          }
        };
                 
        flowControlOnWrite(dataSink, adapter);
        dataSink.write(bufs, adapter);
        written = length;
 
       
      } else {
        written = transferTo((WritableByteChannel) dataSink, length);
      }

      dataSink.setAutoflush(savedAutoflush);
    }
   
    return written;
  }
 
 
  private void flowControlOnWrite(BodyDataSink dataSink, IWriteCompletionHandler adapter) throws IOException {
   
    // suspend only if data sink is network endpoint
    if (dataSink.isNetworkEndpoint()) {
      if ((maxWriteBufferSize != Integer.MAX_VALUE) && (dataSink.getPendingWriteDataSize() > maxWriteBufferSize)) {
        if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("pending write buffer " +  dataSink.getPendingWriteDataSize() + " is larger than max size " + maxWriteBufferSize + ". suspend receving");
        }
        suspend();
      }
    }
  }
 

  private void flowControlOnWritten(BodyDataSink dataSink) {
   
    if ((maxWriteBufferSize != Integer.MAX_VALUE) && (dataSink.getPendingWriteDataSize() > maxWriteBufferSize)) {
     
      return;
     
    } else {
      try {
        resume();
      } catch (IOException e) {
        if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("error occured by resume receving " + this + " " + e.toString());
        }
      }
    }
  }

 
  /**
   * adds a complete listener
   *
   * @param listener  the complete listener
   */
  public void addCompleteListener(IBodyCompleteListener listener) {
 
    synchronized (completeListeners) {
      completeListeners.add(listener);
    }
   
    if (isComplete.get()) {
      callCompleteListener(listener);
    }
  }
 
 
 
  private boolean removeCompleteListener(IBodyCompleteListener listener) {
    synchronized (completeListeners) {
      return completeListeners.remove(listener);
    }
  }
 


  /**
   * adds a close listener
   *
   * @param closeListener  the close listener
   */
  void addCloseListener(IBodyCloseListener closeListener) {
    synchronized (closeListeners) {
      closeListeners.add(closeListener);
    }
  }

 
  /**
   * removes a close listener
   *
   * @param closeListener the close listener
   * @return true, if the listener is removed
   */
  boolean removeCloseListener(IBodyCloseListener closeListener) {
    synchronized (closeListeners) {
      return closeListeners.remove(closeListener);
    }
  }
 
 

  /**
   * replaces the complete listener
   *
   * @param listener  the complete listener
   * @return the old listeners
   */
  @SuppressWarnings("unchecked")
  List<IBodyCompleteListener> replaceCompleteListener(IBodyCompleteListener listener) {
   
    List<IBodyCompleteListener> oldCompleteListeners = null;
    synchronized (completeListeners) {
      oldCompleteListeners = (List<IBodyCompleteListener>) completeListeners.clone();
      completeListeners.clear();
      completeListeners.add(listener);
    }
   
    return oldCompleteListeners;
  }
 
   
 
 
  /**
   * set complete flag
   *
   * @param isComplete  the complete flag
   */
  void setComplete(boolean isComplete) {
   
    if (LOG.isLoggable(Level.FINE)) {
      LOG.fine("[" + getId() + "] complete message received");
    }

    if (isComplete) {
      // terminate watch dog
      terminateWatchDog();
 
 
      // set complete flag
      this.isComplete.set(isComplete);
     
 
      // call body handler
      callBodyHandler(true, false);
 
     
      // notify listeners
      callCompleteListeners();
      handleReceivingFinished();
    }
  }
 
 
  private void handleReceivingFinished() {
   
    if ((isDestroyConnectionAfterReceived) && (httpConnection != null)) {
      if (LOG.isLoggable(Level.FINE)) {
        LOG.fine("[" + getId() + "] auto destroying connection");
      }
      httpConnection.destroy();
    }
   
    if ((isCloseConnectionAfterReceived) && (httpConnection != null)) {
      if (LOG.isLoggable(Level.FINE)) {
        LOG.fine("[" + getId() + "] auto closing connection");
      }
      httpConnection.closeSilence();
    }
  }
 
 
 
 
 
  @SuppressWarnings("unchecked")
  private void callCompleteListeners() {
    List<IBodyCompleteListener> completeListenersCopy = null;
    synchronized (completeListeners) {
      if (!completeListeners.isEmpty()) {
        completeListenersCopy = (List<IBodyCompleteListener>) completeListeners.clone();
      }
    }
   
    if (completeListenersCopy != null) {
      for (IBodyCompleteListener listener : completeListenersCopy) {
        removeCompleteListener(listener);
        callCompleteListener(listener);
      }
    }     
  }
 
  private void callCompleteListener(IBodyCompleteListener listener) {
   
    if (LOG.isLoggable(Level.FINER)) {
      LOG.finer("call complete listener " + listener);
    }
   
    Runnable task = new CompleteListenerCaller(listener);
     
    if (HttpUtils.isBodyCompleteListenerMutlithreaded(listener)) {
      executor.processMultithreaded(task);
    } else {
      executor.processNonthreaded(task);
    }
  }
 
 
  private static final class CompleteListenerCaller implements Runnable {
   
    private IBodyCompleteListener listener = null;
   
    public CompleteListenerCaller(IBodyCompleteListener listener) {
      this.listener = listener;
    }
   
    public void run() {
      try {
        listener.onComplete();
      } catch (IOException ioe) {
        if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("Error occured by calling complete listener " + listener + " " + ioe.toString());
        }
      }
    }
  }
 
 
  /**
   * processes the task multi threaded
   *
   * @param task  the task
   */
  void processMultithreaded(Runnable task) {
    executor.processMultithreaded(task);
  }
 
 
  /**
   * processes the task non threaded
   *
   * @param task the task
   */
  void processNonthreaded(Runnable task) {
    executor.processNonthreaded(task);
  }
 

  /**
   * return true, if all body data has been received
   * 
   * @return true, if all body data has been received
   * @throws IOException if an exception occurs
   */
  boolean isComplete() throws IOException {
    throwExceptionIfExist();
    return isComplete.get();
  }


 
  /**
   * set a disconect handler
   *
   * @param dh the disconnect handler
   */
  void setDisconnectHandler(IBodyDataSourceDisconnectHandler dh) {
    disconnectHandler.set(dh);
  }
 
 
  /**
   * sets a system data handler
   *
   * @param bodyHandler   the handler
   * @throws IOException if an exception occurs
   */
  void setSystemDataHandler(IBodyDataHandler bodyHandler) throws IOException {
    isSystem = true;
    setDataHandler(bodyHandler);
  }
 
 
 
  /**
   * returns the body data handler or <code>null</code> if no data handler is assigned
   *
   * @return the body data handler or <code>null</code> if no data handler is assigned
   */
  public IBodyDataHandler getDataHandler() {
    return handler.get();
  }


  /**
   * set the body handler
   *
   * @param bodyHandler  the body handler
   */
  public void setDataHandler(IBodyDataHandler bodyHandler) {
    if (bodyHandler == null) {
      handler.set(null);
     
    } else {
      isMultithreaded.set(HttpUtils.isMutlithreaded(bodyHandler));
      handler.set(bodyHandler);
     
      callBodyHandler(false, false);
    }
  }
 
 
  /**
   * replace the body handler
   *
   * @param bodyHandler  the body handler
   * @return the old body date handler
   */
  IBodyDataHandler replaceDataHandler(IBodyDataHandler bodyHandler) {
    if (bodyHandler == null) {
      return handler.getAndSet(null);
     
    } else {
      isMultithreaded.set(HttpUtils.isMutlithreaded(bodyHandler));
      IBodyDataHandler bdh = handler.getAndSet(bodyHandler);
     
      callBodyHandler(false, false);
      return bdh;
    }
  }
 
 
 
 
  /**
   * appends data
   *
   * @param isContentImmutable if the buffer is immutable
   * @param data               the data to append
   */
  void append(boolean isContentImmutable, ByteBuffer data) throws IOException {
    preAppend();
   
    if (!isContentImmutable) {
      if (LOG.isLoggable(Level.FINE)) {
        LOG.fine("buffer to append is mutable. coping it");
      }
      data = HttpUtils.copy(data);
    }
   
    if (data != null) {
      nonBlockingStream.append(data);
    }
   
    callBodyHandler(false, false);
  }

 
  /**
   * appends data
   *
   * @param isContentImmutable if the buffer is immutable
   * @param data               the data to append
   * @param completionHandler  the completion handler
   */
  void append(boolean isContentImmutable, ByteBuffer[] data, IWriteCompletionHandler completionHandler) throws IOException {
    preAppend();
   
    if (!isContentImmutable) {
      if (LOG.isLoggable(Level.FINE)) {
        LOG.fine("buffer to append is mutable. coping it");
      }
      data = HttpUtils.copy(data);
    }
   
    if (data != null) {
      nonBlockingStream.append(data, completionHandler);
    }
   
    callBodyHandler(false, false);
  }

 
  private void preAppend() throws IOException {
    lastTimeDataReceivedMillis = System.currentTimeMillis();
   
    if (!isOpen.get() || isDestroyed.get()) {
      throw new ClosedChannelException();
    }
  }
 
 

 
 
  private void callBodyHandler(boolean forceCall, boolean forceMultithreaded) {
   
   
    if (handler.get() != null) {
      handlerCaller.setForceCall(forceCall);

      if ((httpConnection == null) || isSystem) {
        performCallLoop(forceCall);

      } else {
        if (forceMultithreaded || isMultithreaded.get()) {
          executor.processMultithreaded(handlerCaller);
         
        } else {
          executor.processNonthreaded(handlerCaller);
        }
      }
    }
  }
 
 
  private void performCallLoop(boolean forceCall) {
    try {
     
      while ((getSize() != 0) || forceCall) {
        int version = getVersion();
       
        boolean success = call();
        if (!success) {
          return;
        }
         
        if (version == getVersion()) {
          return;
        }
      }
     
    } catch (BufferUnderflowException bue) {
      // swallow it
     
    } catch (IOException ioe) {
      if (LOG.isLoggable(Level.FINE)) {
        LOG.fine("error occured by performing handler call " + ioe.toString());
      }
    }   
  }
 
 
  private boolean call() {
    try {
      IBodyDataHandler bdh = handler.get();
      if (bdh != null) {
        return bdh.onData(NonBlockingBodyDataSource.this);
      } else {
        return false;
      }
     
    } catch (BufferUnderflowException ignore) {
     
    } catch (RuntimeException re) {
      if (LOG.isLoggable(Level.FINE)) {
        LOG.fine("closing data source because an error has been occured by handling data by bodyHandler. " + handler + " Reason: " + re.toString());
      }
      closeUnclean();
     
      throw re;
    }
   
    return false;
  }
 
 
  /**
   * returns the writer transfer chunk size
   *
   * @return the writer transfer chunk size
   */
  int getWriteTransferChunkeSize() {
    return nonBlockingStream.getWriteTransferChunkeSize();
  }
 
 
  private void closeUnclean() {
    try {
      close(false);
    } catch (IOException ioe) {
      if (LOG.isLoggable(Level.FINE)) {
        LOG.fine("Error occured by closing data source " + this + " " + ioe.toString());
      }
    }
  }
 
 
  boolean isMoreInputDataExpected() {
    if (isComplete.get()) {
      return false;
    }
   
    if (!isUnderlyingConnectionOpen.get()) {
      return false;
    }
   
    return true;
  }
 
 
  /**
   * {@inheritDoc}
   */
  @Override
  public String toString() {
      try {
          return nonBlockingStream.toString();
      } catch (Exception e) {
          return "error occured by performing toString: " + DataConverter.toString(e);
      }
  }
 
 
  /**
   * the handler caller
   *
   */
  final class HandlerCaller implements Runnable {
 
   
    private boolean forceCall = false;
   
   
    private void setForceCall(boolean forceCall) {
      this.forceCall = forceCall;
    }
   
   
    /**
     * {@inheritDoc}
     */
    public void run() {   
      performCallLoop(forceCall);
    }
  }
 
 
  private final class NonBlockingStream extends AbstractNonBlockingStream {

    private void append(ByteBuffer buffer) {
      appendDataToReadBuffer(new ByteBuffer[] { buffer }, buffer.remaining());
    }
   
   
    private void append(ByteBuffer[] buffers, IWriteCompletionHandler completionHandler) {
      if (completionHandler != null) {
        isCompletionSupportActivated.set(true);
        writeCompletionManager.registerCompletionHandler(completionHandler, executor, buffers);
      }
     
     
      int size = 0;
      for (ByteBuffer byteBuffer : buffers) {
        size += byteBuffer.remaining();
      }   
      
      appendDataToReadBuffer(buffers, size);
    }
   
   
    int getSize() {
      return getReadQueueSize();
    }
   
    
    ByteBuffer[] copyContent() {
      return super.copyReadQueue();
    }
   
   
    @Override
    protected ByteBuffer[] onRead(ByteBuffer[] readBufs) throws IOException {
      if ((NonBlockingBodyDataSource.this.getSize() == 0) && (NonBlockingBodyDataSource.this.isComplete.get())) {
        try {
          NonBlockingBodyDataSource.this.close();
        } catch (IOException ioe) {
          if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("error occured by closing body data source " + ioe.toString());
          }
        }
      }

      return readBufs;
    }

   
    @Override
    public void close() throws IOException {
      super.close();
     
      if (isOpen.get()) {
        terminateWatchDog();
      }
     
      isOpen.set(false)
    }
   

    @Override
    protected boolean isDataWriteable() {
      return isOpen.get();
    }
   
    @Override
    protected boolean isMoreInputDataExpected() {
      return NonBlockingBodyDataSource.this.isMoreInputDataExpected();
    }

    public boolean isOpen() {
      return NonBlockingBodyDataSource.this.isOpen();
    }
   
   
    @Override
    protected int getWriteTransferChunkeSize() {
      if (httpConnection != null) {
        try {
          return (Integer) httpConnection.getOption(IConnection.SO_SNDBUF);
        } catch (IOException ignore) { }
      }
     
      return super.getWriteTransferChunkeSize();
    }
   

    @Override
    public String toString() {
      return printReadBuffer(NonBlockingBodyDataSource.this.getEncoding());
    }
  }
 
 
  private static final class TimeoutWatchDogTask extends TimerTask {
   
    private WeakReference<NonBlockingBodyDataSource>  dataSourceRef = null;
   
    public TimeoutWatchDogTask(NonBlockingBodyDataSource dataSource) {
      dataSourceRef = new WeakReference<NonBlockingBodyDataSource>(dataSource);
    }
 
   
    @Override
    public void run() {
      try {
        NonBlockingBodyDataSource dataSource = dataSourceRef.get();
       
        if (dataSource == null)  {
          this.cancel();
         
        } else {
          dataSource.checkTimeouts();
        }
      } catch (Exception e) {
          // eat and log exception
          if (LOG.isLoggable(Level.FINE)) {
              LOG.fine("error occured by checking timeouts " + e.toString());
          }
      }
    }   
  }
 
  private static class DefaultMultimodeExecutor implements IMultimodeExecutor {
   
    private static final Executor defaultExecutor = Executors.newCachedThreadPool()
   
    public void processMultithreaded(Runnable task) {
      defaultExecutor.execute(task);
    }
   
    public void processNonthreaded(Runnable task) {
      task.run();
    }
  }
 
 
  /**
   * exception handler
   * @author grro
   *
   */
  static interface IExceptionHandler {
   
    /**
     * call back
     *
     * @param ioe  the exception
     */
    void onException(IOException ioe);
  }
 
 
  /**
   * disconnect handler
   *
   * @author grro
   */
  static interface IBodyDataSourceDisconnectHandler {

    /**
     * call back
     */
    void onDisconnect();
  }
}
TOP

Related Classes of org.xlightweb.NonBlockingBodyDataSource

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.