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

com.gemstone.gemfire.cache.client.internal.AbstractOp Maven / Gradle / Ivy

There is a newer version: 2.0-BETA
Show newest version
/*
 * Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License. You
 * may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License. See accompanying
 * LICENSE file.
 */
package com.gemstone.gemfire.cache.client.internal;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.net.SocketTimeoutException;

import com.gemstone.gemfire.InternalGemFireError;
import com.gemstone.gemfire.internal.cache.PutAllPartialResultException;
import com.gemstone.gemfire.internal.cache.TXId;
import com.gemstone.gemfire.internal.cache.TXManagerImpl;
import com.gemstone.gemfire.cache.client.ServerConnectivityException;
import com.gemstone.gemfire.cache.client.ServerOperationException;
import com.gemstone.gemfire.distributed.internal.DistributionManager;
import com.gemstone.gemfire.i18n.LogWriterI18n;
import com.gemstone.gemfire.internal.HeapDataOutputStream;
import com.gemstone.gemfire.internal.cache.BridgeServerImpl;
import com.gemstone.gemfire.internal.cache.tier.MessageType;
import com.gemstone.gemfire.internal.cache.tier.sockets.ChunkedMessage;
import com.gemstone.gemfire.internal.cache.tier.sockets.Message;
import com.gemstone.gemfire.internal.cache.tier.sockets.Part;
import com.gemstone.gemfire.internal.cache.tier.sockets.ServerConnection;
import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
import com.gemstone.gemfire.internal.shared.Version;

/**
 * Represents an operation that can be performed in a client by sending
 * a message to a server.
 * @since 5.7
 */
public abstract class AbstractOp implements Op {
  private final Message msg;

  protected AbstractOp(LogWriterI18n lw, int msgType, int msgParts) {
    this.msg = new Message(msgParts, Version.CURRENT_GFE);
    getMessage().setLogger(lw);
    getMessage().setMessageType(msgType);
  }

  /**
   * Returns the message that this op will send to the server
   */
  protected Message getMessage() {
    return this.msg;
  }
  protected void initMessagePart() {
    
  }
  protected LogWriterI18n getLogger() {
    return getMessage().getLogger();
  }

  /**
   * Sets the transaction id on the message
   */
  private void setMsgTransactionId() {
    if (participateInTransaction()
        && getMessage().getTransactionId() == TXManagerImpl.NO_TX) {
      final TXId txId = TXManagerImpl.getCurrentTXId();
      if (txId != null) {
        // TODO: merge: client TX should be updated to deal with full TXIds
        getMessage().setTransactionId(txId.getUniqId());
      }
    }
  }

  /**
   * Attempts to send this operation's message out on the
   * given connection
   * @param cnx the connection to use when sending
   * @throws Exception if the send fails
   */
  protected void attemptSend(Connection cnx) throws Exception {
    setMsgTransactionId();
    if (DistributionManager.VERBOSE || BridgeServerImpl.VERBOSE) {
      LogWriterI18n log = getLogger();
      log.info(LocalizedStrings.DEBUG, "Sending op=" + getShortClassName() 
          + /*" message="+ getMessage() +*/ " using " + cnx.getEndpoint().getMemberId());
    }
    getMessage().setComms(cnx.getSocket(), cnx.getInputStream(),
        cnx.getOutputStream(), cnx.getCommBuffer(), cnx.getStats());
    try {
      sendMessage(cnx);
    } finally {
      getMessage().unsetComms();
    }
  }

  /** returns the class name w/o package information.  useful in logging */
  public String getShortClassName() {
    String cname = getClass().getName();
    return cname.substring(getClass().getPackage().getName().length()+1);
  }

  /**
   * New implementations of AbstractOp should override this method if the
   * implementation should be excluded from client authentication. e.g.
   * PingOp#sendMessage(Connection cnx)
   * 
   * @see AbstractOp#needsUserId()
   * @see AbstractOp#processSecureBytes(Connection, Message)
   * @see ServerConnection#updateAndGetSecurityPart()
   */
  protected void sendMessage(Connection cnx) throws Exception {
    if (cnx.getServer().getRequiresCredentials()) {
      // Security is enabled on client as well as on server
      getMessage().setEarlyAck(Message.MESSAGE_HAS_SECURE_PART);
      HeapDataOutputStream hdos = new HeapDataOutputStream(Version.CURRENT_GFE);
      long userId = -1;

      if (UserAttributes.userAttributes.get() == null) { // single user mode
        userId = cnx.getServer().getUserId();
      } else { // multi user mode
        Object id = UserAttributes.userAttributes.get().getServerToId().get(
            cnx.getServer());
        if (id == null) {
          // This will ensure that this op is retried on another server, unless
          // the retryCount is exhausted. Fix for Bug 41501
          throw new ServerConnectivityException(
              "Connection error while authenticating user");
        }
        userId = (Long)id;
      }
      try {
        hdos.writeLong(cnx.getConnectionID());
        hdos.writeLong(userId);
        getMessage().setSecurePart(
            ((ConnectionImpl)cnx).getHandShake().encryptBytes(
                hdos.toByteArray()));
      } finally {
        hdos.close();
      }
    }
    getMessage().send(false);
  }

  /**
   * Attempts to read a response to this operation by reading it from the
   * given connection, and returning it.
   * @param cnx the connection to read the response from
   * @return the result of the operation
   *         or null if the operation has no result.
   * @throws Exception if the execute failed
   */
  protected Object attemptReadResponse(Connection cnx) throws Exception {
    Message msg = createResponseMessage();
    if (msg != null) {
      msg.setComms(cnx.getSocket(), cnx.getInputStream(),
          cnx.getOutputStream(), cnx.getCommBuffer(), cnx.getStats());
      msg.setLogger(getMessage().getLogger());
      if (msg instanceof ChunkedMessage) {
        try {
          return processResponse(msg, cnx);
        } finally {
          msg.unsetComms();
          // TODO (ashetkar) Handle the case when we fail to read the connection id.
          processSecureBytes(cnx, msg);
        }
      } else {
        try {
          msg.recv();
        } finally {
          msg.unsetComms();
          processSecureBytes(cnx, msg);
        }
        return processResponse(msg, cnx);
      }
    } else {
      return null;
    }
  }

  /**
   * New implementations of AbstractOp should override this method if the
   * implementation should be excluded from client authentication. e.g.
   * PingOp#processSecureBytes(Connection cnx, Message message)
   * 
   * @see AbstractOp#sendMessage(Connection)
   * @see AbstractOp#needsUserId()
   * @see ServerConnection#updateAndGetSecurityPart()
   */
  protected void processSecureBytes(Connection cnx, Message message)
      throws Exception {
    if (cnx.getServer().getRequiresCredentials()) {
      if (!message.isSecureMode()) {
        // This can be seen during shutdown
        if (getLogger().fineEnabled()) {
          getLogger().fine(
              "Response message from " + cnx + " for " + this
                  + " has no secure part.");
        }
        return;
      }
      byte[] partBytes = message.getSecureBytes();
      if (partBytes == null) {
        if (getLogger().fineEnabled()) {
          getLogger().fine(
              "Response message for " + this + " has no bytes in secure part.");
        }
        return;
      }
      byte[] bytes = ((ConnectionImpl)cnx).getHandShake().decryptBytes(
          partBytes);
      DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));
      cnx.setConnectionID(dis.readLong());
    }
  }

  /**
   * By default just create a normal one part msg.
   * Subclasses can override this.
   */
  protected Message createResponseMessage() {
    return new Message(1, Version.CURRENT_GFE);
  }
  
  protected Object processResponse(Message m, Connection con) throws Exception {
    return processResponse(m);
  }
  
  /**
   * Processes the given response message returning the result, if any,
   * of the processing.
   * @return the result of processing the response; null if no result
   * @throws Exception if response could not be processed or
   * we received a response with a server exception.
   */
  protected abstract Object processResponse(Message msg) throws Exception;

  /**
   * Return true of msgType indicates the operation
   * had an error on the server.
   */
  protected abstract boolean isErrorResponse(int msgType);
  /**
   * Process a response that contains an ack.
   * @param msg the message containing the response
   * @param opName text describing this op
   * @throws Exception if response could not be processed or
   * we received a response with a server exception.
   */
  protected void processAck(Message msg, String opName)
    throws Exception
  {
    final int msgType = msg.getMessageType();
    if (msgType == MessageType.REPLY) {
      return;
    } else {
      Part part = msg.getPart(0);
      if (msgType == MessageType.EXCEPTION) {
        String s = ": While performing a remote " + opName;
        Throwable t = (Throwable) part.getObject();
        if (t instanceof PutAllPartialResultException) {
          throw (PutAllPartialResultException)t;
        } else {
          throw new ServerOperationException(s, t);
        }
        // Get the exception toString part.
        // This was added for c++ thin client and not used in java
        // Part exceptionToStringPart = msg.getPart(1);
      } else if (isErrorResponse(msgType)) {
        throw new ServerOperationException(part.getString());
      } else {
        throw new InternalGemFireError("Unexpected message type "
                                       + MessageType.getString(msgType));
      }
    }
  }
  /**
   * Process a response that contains a single Object result.
   * @param msg the message containing the response
   * @param opName text describing this op
   * @return the result of the response
   * @throws Exception if response could not be processed or
   * we received a response with a server exception.
   */
  protected final Object processObjResponse(Message msg, String opName)
    throws Exception
  {
    Part part = msg.getPart(0);
    final int msgType = msg.getMessageType();
    if (msgType == MessageType.RESPONSE) {
      return part.getObject();
    } else {
      if (msgType == MessageType.EXCEPTION) {
        String s = "While performing a remote " + opName;
        throw new ServerOperationException(s, (Throwable) part.getObject());
        // Get the exception toString part.
        // This was added for c++ thin client and not used in java
        // Part exceptionToStringPart = msg.getPart(1);
      } else if (isErrorResponse(msgType)) {
        throw new ServerOperationException(part.getString());
      } else {
        throw new InternalGemFireError("Unexpected message type "
                                       + MessageType.getString(msgType));
      }
    }
  }
  /**
   * Used by subclasses who get chunked responses.
   */
  public interface ChunkHandler {
    /**
     * This method will be called once for every incoming chunk
     * @param msg the current chunk to handle
     */
    public void handle(ChunkedMessage msg) throws Exception;
  }
  /**
   * Process a chunked response that contains a single Object result.
   * @param msg the message containing the response
   * @param opName text describing this op
   * @param callback used to handle each chunks data
   * @throws Exception if response could not be processed or
   * we received a response with a server exception.
   */
  protected final void processChunkedResponse(ChunkedMessage msg, String opName, ChunkHandler callback)
    throws Exception
  {
    msg.readHeader();
    final int msgType = msg.getMessageType();
    if (msgType == MessageType.RESPONSE) {
      do {
        msg.receiveChunk();
        callback.handle(msg);
      } while (!msg.isLastChunk());
    } else {
      if (msgType == MessageType.EXCEPTION) {
        msg.receiveChunk();
        Part part = msg.getPart(0);
        String s = "While performing a remote " + opName;
        throw new ServerOperationException(s, (Throwable) part.getObject());
        // Get the exception toString part.
        // This was added for c++ thin client and not used in java
        // Part exceptionToStringPart = msg.getPart(1);
      } else if (isErrorResponse(msgType)) {
        msg.receiveChunk();
        Part part = msg.getPart(0);
        throw new ServerOperationException(part.getString());
      } else {
        throw new InternalGemFireError("Unexpected message type "
                                       + MessageType.getString(msgType));
      }
    }
  }

  /**
   * Set to true if this attempt failed
   */
  protected boolean failed;
  /**
   * Set to true if this attempt timed out
   */
  protected boolean timedOut;

  /* (non-Javadoc)
   * @see com.gemstone.gemfire.cache.client.internal.Op#attempt(com.gemstone.gemfire.cache.client.internal.Connection)
   */
  public Object attempt(Connection cnx) throws Exception {
    this.failed = true;
    this.timedOut = false;
    long start = startAttempt(cnx.getStats());
    try {
      try {
        attemptSend(cnx);
        this.failed = false;
      } finally {
        endSendAttempt(cnx.getStats(), start);
      }
      this.failed = true;
      try {
        Object result = attemptReadResponse(cnx);
        this.failed = false;
        return result;
      } catch (SocketTimeoutException ste) {
        this.failed = false;
        this.timedOut = true;
        throw ste;
      } catch(Exception e) {
        throw e;
      }
    } finally {
      endAttempt(cnx.getStats(), start);
    }
  }
  protected final boolean hasFailed() {
    return this.failed;
  }
  protected final boolean hasTimedOut() {
    return this.timedOut;
  }
  protected abstract long startAttempt(ConnectionStats stats);
  protected abstract void endSendAttempt(ConnectionStats stats, long start);
  protected abstract void endAttempt(ConnectionStats stats, long start);

  /**
   * New implementations of AbstractOp should override this method to return
   * false if the implementation should be excluded from client authentication.
   * e.g. PingOp#needsUserId()
   * 

* Also, such an operation's MessageType must be added in the * 'if' condition in {@link ServerConnection#updateAndGetSecurityPart()} * * @return boolean * @see AbstractOp#sendMessage(Connection) * @see AbstractOp#processSecureBytes(Connection, Message) * @see ServerConnection#updateAndGetSecurityPart() */ protected boolean needsUserId() { return true; } /** * Subclasses for AbstractOp should override this method to return * false in this message should not participate in any existing transaction * @return true if the message should participate in transaction */ protected boolean participateInTransaction() { return true; } @Override public boolean useThreadLocalConnection() { return true; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy