Open Source Repository

Home /mail/mail-1.4.1 | Repository Home



com/sun/mail/iap/Protocol.java
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

/*
 * @(#)Protocol.java  1.39 07/06/01
 */

package com.sun.mail.iap;

import java.util.Vector;
import java.util.Properties;
import java.io.*;
import java.net.*;
import com.sun.mail.util.*;

/**
 * General protocol handling code for IMAP-like protocols. <p>
 *
 * The Protocol object is multithread safe.
 *
 @version 1.39, 07/06/01
 @author  John Mani
 @author  Max Spivak
 @author  Bill Shannon
 */

public class Protocol {
    protected String host;
    private Socket socket;
    // in case we turn on TLS, we'll need these later
    protected boolean debug;
    protected boolean quote;
    protected PrintStream out;
    protected Properties props;
    protected String prefix;

    private boolean connected = false;    // did constructor succeed?
    private TraceInputStream traceInput;  // the Tracer
    private volatile ResponseInputStream input;

    private TraceOutputStream traceOutput;  // the Tracer
    private volatile DataOutputStream output;

    private int tagCounter = 0;

    private volatile Vector handlers = null// response handlers

    private volatile long timestamp;

    private static final byte[] CRLF = { (byte)'\r'(byte)'\n'};
 
    /**
     * Constructor. <p>
     
     * Opens a connection to the given host at given port.
     *
     @param host  host to connect to
     @param port  portnumber to connect to
     @param debug     debug mode
     @param out  debug output stream
     @param props     Properties object used by this protocol
     @param prefix   Prefix to prepend to property keys
     */
    public Protocol(String host, int port, boolean debug,
        PrintStream out, Properties props, String prefix,
        boolean isSSLthrows IOException, ProtocolException {
  try {
      this.host = host;
      this.debug = debug;
      this.out = out;
      this.props = props;
      this.prefix = prefix;

      socket = SocketFetcher.getSocket(host, port, props, prefix, isSSL);
      String s = props.getProperty("mail.debug.quote");
      quote = s != null && s.equalsIgnoreCase("true");

      initStreams(out);

      // Read server greeting
      processGreeting(readResponse());

      timestamp = System.currentTimeMillis();
 
      connected = true;  // must be last statement in constructor
  finally {
      /*
       * If we get here because an exception was thrown, we need
       * to disconnect to avoid leaving a connected socket that
       * no one will be able to use because this object was never
       * completely constructed.
       */
      if (!connected)
    disconnect();
  }
    }

    private void initStreams(PrintStream outthrows IOException {
  traceInput = new TraceInputStream(socket.getInputStream(), out);
  traceInput.setTrace(debug);
  traceInput.setQuote(quote);
  input = new ResponseInputStream(traceInput);

  traceOutput = new TraceOutputStream(socket.getOutputStream(), out);
  traceOutput.setTrace(debug);
  traceOutput.setQuote(quote);
  output = new DataOutputStream(new BufferedOutputStream(traceOutput));
    }

    /**
     * Constructor for debugging.
     */
    public Protocol(InputStream in, OutputStream out, boolean debug)
        throws IOException {
  this.host = "localhost";
  this.debug = debug;
  this.quote = false;
  this.out = System.out;

  // XXX - inlined initStreams, won't allow later startTLS
  traceInput = new TraceInputStream(in, System.out);
  traceInput.setTrace(debug);
  traceInput.setQuote(quote);
  input = new ResponseInputStream(traceInput);

  traceOutput = new TraceOutputStream(out, System.out);
  traceOutput.setTrace(debug);
  traceOutput.setQuote(quote);
  output = new DataOutputStream(new BufferedOutputStream(traceOutput));

        timestamp = System.currentTimeMillis();
    }

    /**
     * Returns the timestamp.
     */
 
    public long getTimestamp() {
        return timestamp;
    }
 
    /**
     * Adds a response handler.
     */
    public synchronized void addResponseHandler(ResponseHandler h) {
  if (handlers == null)
      handlers = new Vector();
  handlers.addElement(h);
    }

    /**
     * Removed the specified response handler.
     */
    public synchronized void removeResponseHandler(ResponseHandler h) {
  if (handlers != null)
      handlers.removeElement(h);
    }

    /**
     * Notify response handlers
     */
    public void notifyResponseHandlers(Response[] responses) {
  if (handlers == null)
      return;
  
  for (int i = 0; i < responses.length; i++) { // go thru responses
      Response r = responses[i];

      // skip responses that have already been handled
      if (r == null)
    continue;

      int size = handlers.size();
      if (size == 0)
    return;
      // Need to copy handlers list because handlers can be removed
      // when handling a response.
      Object[] h = new Object[size];
      handlers.copyInto(h);

      // dispatch 'em
      for (int j = 0; j < size; j++)
    ((ResponseHandler)h[j]).handleResponse(r);
  }
    }

    protected void processGreeting(Response rthrows ProtocolException {
  if (r.isBYE())
      throw new ConnectionException(this, r);
    }

    /**
     * Return the Protocol's InputStream.
     */
    protected ResponseInputStream getInputStream() {
  return input;
    }

    /**
     * Return the Protocol's OutputStream
     */
    protected OutputStream getOutputStream() {
  return output;
    }

    /**
     * Returns whether this Protocol supports non-synchronizing literals
     * Default is false. Subclasses should override this if required
     */
    protected synchronized boolean supportsNonSyncLiterals() {
  return false;
    }

    public Response readResponse() 
    throws IOException, ProtocolException {
  return new Response(this);
    }

    /**
     * Return a buffer to be used to read a response.
     * The default implementation returns null, which causes
     * a new buffer to be allocated for every response.
     *
     @since  JavaMail 1.4.1
     */
    protected ByteArray getResponseBuffer() {
  return null;
    }

    public String writeCommand(String command, Argument args
    throws IOException, ProtocolException {
  // assert Thread.holdsLock(this);
  // can't assert because it's called from constructor
  String tag = "A" + Integer.toString(tagCounter++, 10)// unique tag

  output.writeBytes(tag + " " + command);
    
  if (args != null) {
      output.write(' ');
      args.write(this);
  }

  output.write(CRLF);
  output.flush();
  return tag;
    }

    /**
     * Send a command to the server. Collect all responses until either
     * the corresponding command completion response or a BYE response 
     * (indicating server failure).  Return all the collected responses.
     *
     @param  command  the command
     @param  args  the arguments
     @return    array of Response objects returned by the server
     */
    public synchronized Response[] command(String command, Argument args) {
  Vector v = new Vector();
  boolean done = false;
  String tag = null;
  Response r = null;

  // write the command
  try {
      tag = writeCommand(command, args);
  catch (LiteralException lex) {
      v.addElement(lex.getResponse());
      done = true;
  catch (Exception ex) {
      // Convert this into a BYE response
      v.addElement(Response.byeResponse(ex));
      done = true;
  }

  while (!done) {
      try {
    r = readResponse();
      catch (IOException ioex) {
    // convert this into a BYE response
    r = Response.byeResponse(ioex);
      catch (ProtocolException pex) {
    continue// skip this response
      }
    
      v.addElement(r);

      if (r.isBYE()) // shouldn't wait for command completion response
    done = true;

      // If this is a matching command completion response, we are done
      if (r.isTagged() && r.getTag().equals(tag))
    done = true;
  }

  Response[] responses = new Response[v.size()];
  v.copyInto(responses);
        timestamp = System.currentTimeMillis();
  return responses;
    }

    /**
     * Convenience routine to handle OK, NO, BAD and BYE responses.
     */
    public void handleResult(Response responsethrows ProtocolException {
  if (response.isOK())
      return;
  else if (response.isNO())
      throw new CommandFailedException(response);
  else if (response.isBAD())
      throw new BadCommandException(response);
  else if (response.isBYE()) {
      disconnect();
      throw new ConnectionException(this, response);
  }
    }

    /**
     * Convenience routine to handle simple IAP commands
     * that do not have responses specific to that command.
     */
    public void simpleCommand(String cmd, Argument args)
      throws ProtocolException {
  // Issue command
  Response[] r = command(cmd, args);

  // dispatch untagged responses
  notifyResponseHandlers(r);

  // Handle result of this command
  handleResult(r[r.length-1]);
    }

    /**
     * Start TLS on the current connection.
     <code>cmd</code> is the command to issue to start TLS negotiation.
     * If the command succeeds, we begin TLS negotiation.
     */
    public synchronized void startTLS(String cmd)
        throws IOException, ProtocolException {
  simpleCommand(cmd, null);
  socket = SocketFetcher.startTLS(socket, props, prefix);
  initStreams(out);
    }

    /**
     * Disconnect.
     */
    protected synchronized void disconnect() {
  if (socket != null) {
      try {
    socket.close();
      catch (IOException e) {
    // ignore it
      }
      socket = null;
  }
    }

    /**
     * Finalizer.
     */
    protected void finalize() throws Throwable {
  super.finalize();
  disconnect();
    }
}