/** @throws Exception If failed. */
  public void testDisabledRest() throws Exception {
    restEnabled = false;

    final Grid g = startGrid("disabled-rest");

    try {
      Thread.sleep(2 * TOP_REFRESH_FREQ);

      // As long as we have round robin load balancer this will cause every node to be queried.
      for (int i = 0; i < NODES_CNT + 1; i++)
        assertEquals(NODES_CNT + 1, client.compute().refreshTopology(false, false).size());

      final GridClientData data = client.data(PARTITIONED_CACHE_NAME);

      // Check rest-disabled node is unavailable.
      try {
        String affKey;

        do {
          affKey = UUID.randomUUID().toString();
        } while (!data.affinity(affKey).equals(g.localNode().id()));

        data.put(affKey, "asdf");

        assertEquals("asdf", cache(0, PARTITIONED_CACHE_NAME).get(affKey));
      } catch (GridServerUnreachableException e) {
        // Thrown for direct client-node connections.
        assertTrue(
            "Unexpected exception message: " + e.getMessage(),
            e.getMessage()
                .startsWith("No available endpoints to connect (is rest enabled for this node?)"));
      } catch (GridClientException e) {
        // Thrown for routed client-router-node connections.
        String msg = e.getMessage();

        assertTrue(
            "Unexpected exception message: " + msg,
            protocol() == GridClientProtocol.TCP
                ? msg.contains("No available endpoints to connect (is rest enabled for this node?)")
                : // TCP router.
                msg.startsWith(
                    "No available nodes on the router for destination node ID")); // HTTP router.
      }

      // Check rest-enabled nodes are available.
      String affKey;

      do {
        affKey = UUID.randomUUID().toString();
      } while (data.affinity(affKey).equals(g.localNode().id()));

      data.put(affKey, "fdsa");

      assertEquals("fdsa", cache(0, PARTITIONED_CACHE_NAME).get(affKey));
    } finally {
      restEnabled = true;

      G.stop(g.name(), true);
    }
  }
  /** @throws Exception If failed. */
  public void testCreateFileColocated() throws Exception {
    GridGgfsPath path = new GridGgfsPath("/colocated");

    UUID uuid = UUID.randomUUID();

    GridUuid affKey;

    long idx = 0;

    while (true) {
      affKey = new GridUuid(uuid, idx);

      if (grid(0).mapKeyToNode(DATA_CACHE_NAME, affKey).id().equals(grid(0).localNode().id()))
        break;

      idx++;
    }

    try (GridGgfsOutputStream out = fs.create(path, 1024, true, affKey, 0, 1024, null)) {
      // Write 5M, should be enough to test distribution.
      for (int i = 0; i < 15; i++) out.write(new byte[1024 * 1024]);
    }

    GridGgfsFile info = fs.info(path);

    Collection<GridGgfsBlockLocation> affNodes = fs.affinity(path, 0, info.length());

    assertEquals(1, affNodes.size());
    Collection<UUID> nodeIds = F.first(affNodes).nodeIds();

    assertEquals(1, nodeIds.size());
    assertEquals(grid(0).localNode().id(), F.first(nodeIds));
  }
  /** @throws Exception If failed. */
  public void testClientAffinity() throws Exception {
    GridClientData partitioned = client.data(PARTITIONED_CACHE_NAME);

    Collection<Object> keys = new ArrayList<>();

    keys.addAll(Arrays.asList(Boolean.TRUE, Boolean.FALSE, 1, Integer.MAX_VALUE));

    Random rnd = new Random();
    StringBuilder sb = new StringBuilder();

    // Generate some random strings.
    for (int i = 0; i < 100; i++) {
      sb.setLength(0);

      for (int j = 0; j < 255; j++)
        // Only printable ASCII symbols for test.
        sb.append((char) (rnd.nextInt(0x7f - 0x20) + 0x20));

      keys.add(sb.toString());
    }

    // Generate some more keys to achieve better coverage.
    for (int i = 0; i < 100; i++) keys.add(UUID.randomUUID());

    for (Object key : keys) {
      UUID nodeId = grid(0).mapKeyToNode(PARTITIONED_CACHE_NAME, key).id();

      UUID clientNodeId = partitioned.affinity(key);

      assertEquals(
          "Invalid affinity mapping for REST response for key: " + key, nodeId, clientNodeId);
    }
  }
  /**
   * Converts message to bytes to send over network.
   *
   * @return Bytes representing this packet.
   */
  public byte[] toBytes() {
    byte[] buf = new byte[PACKET_SIZE];

    int off = 0;

    off = U.longToBytes(origNodeId.getLeastSignificantBits(), buf, off);
    off = U.longToBytes(origNodeId.getMostSignificantBits(), buf, off);

    off = U.longToBytes(targetNodeId.getLeastSignificantBits(), buf, off);
    off = U.longToBytes(targetNodeId.getMostSignificantBits(), buf, off);

    off = U.longToBytes(origTs, buf, off);

    off = U.longToBytes(replyTs, buf, off);

    assert off == PACKET_SIZE;

    return buf;
  }
  /**
   * Retrieves UUID value from parameters map.
   *
   * @param key Key.
   * @param params Parameters map.
   * @return UUID value from parameters map or {@code null} if null or not exists.
   * @throws GridException If parsing failed.
   */
  @Nullable
  private static UUID uuidValue(String key, Map<String, Object> params) throws GridException {
    assert key != null;

    String val = (String) params.get(key);

    try {
      return val == null ? null : UUID.fromString(val);
    } catch (NumberFormatException ignore) {
      throw new GridException("Failed to parse parameter of UUID type [" + key + "=" + val + "]");
    }
  }
  /** @throws Exception If failed. */
  public void testAffinityPut() throws Exception {
    Thread.sleep(2 * TOP_REFRESH_FREQ);

    assertEquals(NODES_CNT, client.compute().refreshTopology(false, false).size());

    Map<UUID, Grid> gridsByLocNode = new HashMap<>(NODES_CNT);

    GridClientData partitioned = client.data(PARTITIONED_CACHE_NAME);

    GridClientCompute compute = client.compute();

    for (int i = 0; i < NODES_CNT; i++) gridsByLocNode.put(grid(i).localNode().id(), grid(i));

    for (int i = 0; i < 100; i++) {
      String key = "key" + i;

      UUID primaryNodeId = grid(0).mapKeyToNode(PARTITIONED_CACHE_NAME, key).id();

      assertEquals("Affinity mismatch for key: " + key, primaryNodeId, partitioned.affinity(key));

      assertEquals(primaryNodeId, partitioned.affinity(key));

      // Must go to primary node only. Since backup count is 0, value must present on
      // primary node only.
      partitioned.put(key, "val" + key);

      for (Map.Entry<UUID, Grid> entry : gridsByLocNode.entrySet()) {
        Object val = entry.getValue().cache(PARTITIONED_CACHE_NAME).peek(key);

        if (primaryNodeId.equals(entry.getKey())) assertEquals("val" + key, val);
        else assertNull(val);
      }
    }

    // Now check that we will see value in near cache in pinned mode.
    for (int i = 100; i < 200; i++) {
      String pinnedKey = "key" + i;

      UUID primaryNodeId = grid(0).mapKeyToNode(PARTITIONED_CACHE_NAME, pinnedKey).id();

      UUID pinnedNodeId = F.first(F.view(gridsByLocNode.keySet(), F.notEqualTo(primaryNodeId)));

      GridClientNode node = compute.node(pinnedNodeId);

      partitioned.pinNodes(node).put(pinnedKey, "val" + pinnedKey);

      for (Map.Entry<UUID, Grid> entry : gridsByLocNode.entrySet()) {
        Object val = entry.getValue().cache(PARTITIONED_CACHE_NAME).peek(pinnedKey);

        if (primaryNodeId.equals(entry.getKey()) || pinnedNodeId.equals(entry.getKey()))
          assertEquals("val" + pinnedKey, val);
        else assertNull(val);
      }
    }
  }
  /** {@inheritDoc} */
  @Override
  public Map<? extends GridComputeJob, GridNode> map(List<GridNode> subgrid, Integer arg)
      throws GridException {
    assert taskSes != null;
    assert arg != null;
    assert arg > 0;

    Map<GridSessionLoadTestJob, GridNode> map = new HashMap<>(subgrid.size());

    Iterator<GridNode> iter = subgrid.iterator();

    Random rnd = new Random();

    params = new HashMap<>(arg);

    Collection<UUID> assigned = new ArrayList<>(subgrid.size());

    for (int i = 0; i < arg; i++) {
      // Recycle iterator.
      if (!iter.hasNext()) iter = subgrid.iterator();

      String paramName = UUID.randomUUID().toString();

      int paramVal = rnd.nextInt();

      taskSes.setAttribute(paramName, paramVal);

      GridNode node = iter.next();

      assigned.add(node.id());

      map.put(new GridSessionLoadTestJob(paramName), node);

      params.put(paramName, paramVal);

      if (log.isDebugEnabled())
        log.debug("Set session attribute [name=" + paramName + ", value=" + paramVal + ']');
    }

    taskSes.setAttribute("nodes", assigned);

    return map;
  }
  /**
   * Creates REST request.
   *
   * @param cmd Command.
   * @param params Parameters.
   * @return REST request.
   * @throws GridException If creation failed.
   */
  @Nullable
  private GridRestRequest createRequest(
      GridRestCommand cmd, Map<String, Object> params, ServletRequest req) throws GridException {
    GridRestRequest restReq;

    switch (cmd) {
      case CACHE_GET:
      case CACHE_GET_ALL:
      case CACHE_PUT:
      case CACHE_PUT_ALL:
      case CACHE_REMOVE:
      case CACHE_REMOVE_ALL:
      case CACHE_ADD:
      case CACHE_CAS:
      case CACHE_METRICS:
      case CACHE_REPLACE:
      case CACHE_DECREMENT:
      case CACHE_INCREMENT:
      case CACHE_APPEND:
      case CACHE_PREPEND:
        {
          GridRestCacheRequest restReq0 = new GridRestCacheRequest();

          restReq0.cacheName((String) params.get("cacheName"));
          restReq0.key(params.get("key"));
          restReq0.value(params.get("val"));
          restReq0.value2(params.get("val2"));

          Object val1 = params.get("val1");

          if (val1 != null) restReq0.value(val1);

          restReq0.cacheFlags(intValue("cacheFlags", params, 0));
          restReq0.ttl(longValue("exp", params, null));
          restReq0.initial(longValue("init", params, null));
          restReq0.delta(longValue("delta", params, null));

          if (cmd == CACHE_GET_ALL || cmd == CACHE_PUT_ALL || cmd == CACHE_REMOVE_ALL) {
            List<Object> keys = values("k", params);
            List<Object> vals = values("v", params);

            if (keys.size() < vals.size())
              throw new GridException(
                  "Number of keys must be greater or equals to number of values.");

            Map<Object, Object> map = U.newHashMap(keys.size());

            Iterator<Object> keyIt = keys.iterator();
            Iterator<Object> valIt = vals.iterator();

            while (keyIt.hasNext()) map.put(keyIt.next(), valIt.hasNext() ? valIt.next() : null);

            restReq0.values(map);
          }

          restReq = restReq0;

          break;
        }

      case TOPOLOGY:
      case NODE:
        {
          GridRestTopologyRequest restReq0 = new GridRestTopologyRequest();

          restReq0.includeMetrics(Boolean.parseBoolean((String) params.get("mtr")));
          restReq0.includeAttributes(Boolean.parseBoolean((String) params.get("attr")));

          restReq0.nodeIp((String) params.get("ip"));

          restReq0.nodeId(uuidValue("id", params));

          restReq = restReq0;

          break;
        }

      case EXE:
      case RESULT:
      case NOOP:
        {
          GridRestTaskRequest restReq0 = new GridRestTaskRequest();

          restReq0.taskId((String) params.get("id"));
          restReq0.taskName((String) params.get("name"));

          restReq0.params(values("p", params));

          restReq0.async(Boolean.parseBoolean((String) params.get("async")));

          restReq0.timeout(longValue("timeout", params, 0L));

          restReq = restReq0;

          break;
        }

      case LOG:
        {
          GridRestLogRequest restReq0 = new GridRestLogRequest();

          restReq0.path((String) params.get("path"));

          restReq0.from(intValue("from", params, -1));
          restReq0.to(intValue("to", params, -1));

          restReq = restReq0;

          break;
        }

      case VERSION:
        {
          restReq = new GridRestRequest();

          break;
        }

      default:
        throw new GridException("Invalid command: " + cmd);
    }

    restReq.address(new InetSocketAddress(req.getRemoteAddr(), req.getRemotePort()));

    restReq.command(cmd);

    if (params.containsKey("gridgain.login") || params.containsKey("gridgain.password")) {
      GridSecurityCredentials cred =
          new GridSecurityCredentials(
              (String) params.get("gridgain.login"), (String) params.get("gridgain.password"));

      restReq.credentials(cred);
    }

    String clientId = (String) params.get("clientId");

    try {
      if (clientId != null) restReq.clientId(UUID.fromString(clientId));
    } catch (Exception ignored) {
      // Ignore invalid client id. Rest handler will process this logic.
    }

    String destId = (String) params.get("destId");

    try {
      if (destId != null) restReq.destinationId(UUID.fromString(destId));
    } catch (IllegalArgumentException ignored) {
      // Don't fail - try to execute locally.
    }

    String sesTokStr = (String) params.get("sessionToken");

    try {
      if (sesTokStr != null) restReq.sessionToken(U.hexString2ByteArray(sesTokStr));
    } catch (IllegalArgumentException ignored) {
      // Ignore invalid session token.
    }

    return restReq;
  }
/**
 * Eviction policy based on {@code First In First Out (FIFO)} algorithm. This implementation is very
 * efficient since it does not create any additional table-like data structures. The {@code FIFO}
 * ordering information is maintained by attaching ordering metadata to cache entries.
 */
public class GridCacheFifoEvictionPolicy<K, V>
    implements GridCacheEvictionPolicy<K, V>, GridCacheFifoEvictionPolicyMBean {
  /** Tag. */
  private final String meta = UUID.randomUUID().toString();

  /** Maximum size. */
  private volatile int max = GridCacheConfiguration.DFLT_CACHE_SIZE;

  /** FIFO queue. */
  private final ConcurrentLinkedDeque8<GridCacheEntry<K, V>> queue = new ConcurrentLinkedDeque8<>();

  /** Constructs FIFO eviction policy with all defaults. */
  public GridCacheFifoEvictionPolicy() {
    // No-op.
  }

  /**
   * Constructs FIFO eviction policy with maximum size. Empty entries are allowed.
   *
   * @param max Maximum allowed size of cache before entry will start getting evicted.
   */
  public GridCacheFifoEvictionPolicy(int max) {
    A.ensure(max > 0, "max > 0");

    this.max = max;
  }

  /**
   * Gets maximum allowed size of cache before entry will start getting evicted.
   *
   * @return Maximum allowed size of cache before entry will start getting evicted.
   */
  @Override
  public int getMaxSize() {
    return max;
  }

  /**
   * Sets maximum allowed size of cache before entry will start getting evicted.
   *
   * @param max Maximum allowed size of cache before entry will start getting evicted.
   */
  @Override
  public void setMaxSize(int max) {
    A.ensure(max > 0, "max > 0");

    this.max = max;
  }

  /** {@inheritDoc} */
  @Override
  public int getCurrentSize() {
    return queue.size();
  }

  /** {@inheritDoc} */
  @Override
  public String getMetaAttributeName() {
    return meta;
  }

  /**
   * Gets read-only view on internal {@code FIFO} queue in proper order.
   *
   * @return Read-only view ono internal {@code 'FIFO'} queue.
   */
  public Collection<GridCacheEntry<K, V>> queue() {
    return Collections.unmodifiableCollection(queue);
  }

  /** {@inheritDoc} */
  @Override
  public void onEntryAccessed(boolean rmv, GridCacheEntry<K, V> entry) {
    if (!rmv) {
      if (!entry.isCached()) return;

      // Shrink only if queue was changed.
      if (touch(entry)) shrink();
    } else {
      Node<GridCacheEntry<K, V>> node = entry.removeMeta(meta);

      if (node != null) queue.unlinkx(node);
    }
  }

  /**
   * @param entry Entry to touch.
   * @return {@code True} if queue has been changed by this call.
   */
  private boolean touch(GridCacheEntry<K, V> entry) {
    Node<GridCacheEntry<K, V>> node = entry.meta(meta);

    // Entry has not been enqueued yet.
    if (node == null) {
      while (true) {
        node = queue.offerLastx(entry);

        if (entry.putMetaIfAbsent(meta, node) != null) {
          // Was concurrently added, need to clear it from queue.
          queue.unlinkx(node);

          // Queue has not been changed.
          return false;
        } else if (node.item() != null) {
          if (!entry.isCached()) {
            // Was concurrently evicted, need to clear it from queue.
            queue.unlinkx(node);

            return false;
          }

          return true;
        }
        // If node was unlinked by concurrent shrink() call, we must repeat the whole cycle.
        else if (!entry.removeMeta(meta, node)) return false;
      }
    }

    // Entry is already in queue.
    return false;
  }

  /** Shrinks FIFO queue to maximum allowed size. */
  private void shrink() {
    int max = this.max;

    int startSize = queue.sizex();

    for (int i = 0; i < startSize && queue.sizex() > max; i++) {
      GridCacheEntry<K, V> entry = queue.poll();

      if (entry == null) break;

      if (!entry.evict()) {
        entry.removeMeta(meta);

        touch(entry);
      }
    }
  }

  /**
   * Checks entry for empty value.
   *
   * @param entry Entry to check.
   * @return {@code True} if entry is empty.
   */
  private boolean empty(GridCacheEntry<K, V> entry) {
    try {
      return entry.peek(F.asList(GLOBAL)) == null;
    } catch (GridException e) {
      U.error(null, e.getMessage(), e);

      assert false : "Should never happen: " + e;

      return false;
    }
  }

  /** {@inheritDoc} */
  @Override
  public String toString() {
    return S.toString(GridCacheFifoEvictionPolicy.class, this);
  }
}
  /**
   * Processes lock request.
   *
   * @param nodeId Sender node ID.
   * @param msg Lock request.
   */
  @SuppressWarnings({"unchecked", "ThrowableInstanceNeverThrown"})
  private void processLockRequest(UUID nodeId, GridDistributedLockRequest<K, V> msg) {
    assert !nodeId.equals(locNodeId);

    List<byte[]> keys = msg.keyBytes();

    int cnt = keys.size();

    GridReplicatedTxRemote<K, V> tx = null;

    GridDistributedLockResponse res;

    ClassLoader ldr = null;

    try {
      ldr = ctx.deploy().globalLoader();

      if (ldr != null) {
        res = new GridDistributedLockResponse(msg.version(), msg.futureId(), cnt);

        for (int i = 0; i < keys.size(); i++) {
          byte[] bytes = keys.get(i);
          K key = msg.keys().get(i);

          Collection<GridCacheMvccCandidate<K>> cands = msg.candidatesByIndex(i);

          if (bytes == null) continue;

          if (log.isDebugEnabled()) log.debug("Unmarshalled key: " + key);

          GridDistributedCacheEntry<K, V> entry = null;

          while (true) {
            try {
              entry = entryexx(key);

              // Handle implicit locks for pessimistic transactions.
              if (msg.inTx()) {
                tx = ctx.tm().tx(msg.version());

                if (tx != null) {
                  if (msg.txRead()) tx.addRead(key, bytes);
                  else tx.addWrite(key, bytes);
                } else {
                  tx =
                      new GridReplicatedTxRemote<K, V>(
                          nodeId,
                          msg.threadId(),
                          msg.version(),
                          null,
                          PESSIMISTIC,
                          msg.isolation(),
                          msg.isInvalidate(),
                          msg.timeout(),
                          key,
                          bytes,
                          msg.txRead(),
                          ctx);

                  tx = ctx.tm().onCreated(tx);

                  if (tx == null || !ctx.tm().onStarted(tx))
                    throw new GridCacheTxRollbackException(
                        "Failed to acquire lock "
                            + "(transaction has been completed): "
                            + msg.version());
                }
              }

              // Add remote candidate before reordering.
              entry.addRemote(
                  msg.nodeId(),
                  null,
                  msg.threadId(),
                  msg.version(),
                  msg.timeout(),
                  tx != null && tx.ec(),
                  tx != null,
                  tx != null && tx.implicitSingle());

              // Remote candidates for ordered lock queuing.
              entry.addRemoteCandidates(
                  cands, msg.version(), msg.committedVersions(), msg.rolledbackVersions());

              // Double-check in case if sender node left the grid.
              if (ctx.discovery().node(msg.nodeId()) == null) {
                if (log.isDebugEnabled())
                  log.debug(
                      "Node requesting lock left grid (lock request will be ignored): " + msg);

                if (tx != null) tx.rollback();

                return;
              }

              res.setCandidates(
                  i,
                  entry.localCandidates(),
                  ctx.tm().committedVersions(msg.version()),
                  ctx.tm().rolledbackVersions(msg.version()));

              res.addValueBytes(
                  entry.rawGet(), msg.returnValue(i) ? entry.valueBytes(null) : null, ctx);

              // Entry is legit.
              break;
            } catch (GridCacheEntryRemovedException ignored) {
              assert entry.obsoleteVersion() != null
                  : "Obsolete flag not set on removed entry: " + entry;

              if (log.isDebugEnabled())
                log.debug(
                    "Received entry removed exception (will retry on renewed entry): " + entry);

              if (tx != null) {
                tx.clearEntry(entry.key());

                if (log.isDebugEnabled())
                  log.debug(
                      "Cleared removed entry from remote transaction (will retry) [entry="
                          + entry
                          + ", tx="
                          + tx
                          + ']');
              }
            }
          }
        }
      } else {
        String err = "Failed to acquire deployment class for message: " + msg;

        U.warn(log, err);

        res =
            new GridDistributedLockResponse(msg.version(), msg.futureId(), new GridException(err));
      }
    } catch (GridCacheTxRollbackException e) {
      if (log.isDebugEnabled())
        log.debug("Received lock request for completed transaction (will ignore): " + e);

      res = new GridDistributedLockResponse(msg.version(), msg.futureId(), e);
    } catch (GridException e) {
      String err = "Failed to unmarshal at least one of the keys for lock request message: " + msg;

      log.error(err, e);

      res =
          new GridDistributedLockResponse(msg.version(), msg.futureId(), new GridException(err, e));

      if (tx != null) tx.rollback();
    } catch (GridDistributedLockCancelledException ignored) {
      // Received lock request for cancelled lock.
      if (log.isDebugEnabled())
        log.debug("Received lock request for canceled lock (will ignore): " + msg);

      if (tx != null) tx.rollback();

      // Don't send response back.
      return;
    }

    GridNode node = ctx.discovery().node(msg.nodeId());

    boolean releaseAll = false;

    if (node != null) {
      try {
        // Reply back to sender.
        ctx.io().send(node, res);
      } catch (GridException e) {
        U.error(log, "Failed to send message to node (did the node leave grid?): " + node.id(), e);

        releaseAll = ldr != null;
      }
    }
    // If sender left grid, release all locks acquired so far.
    else releaseAll = ldr != null;

    // Release all locks because sender node left grid.
    if (releaseAll) {
      for (K key : msg.keys()) {
        while (true) {
          GridDistributedCacheEntry<K, V> entry = peekexx(key);

          try {
            if (entry != null) entry.removeExplicitNodeLocks(msg.nodeId());

            break;
          } catch (GridCacheEntryRemovedException ignore) {
            if (log.isDebugEnabled())
              log.debug(
                  "Attempted to remove lock on removed entity during failure "
                      + "of replicated lock request handling (will retry): "
                      + entry);
          }
        }
      }

      U.warn(
          log, "Sender node left grid in the midst of lock acquisition (locks will be released).");
    }
  }
Example #11
0
/**
 * Parser for extended memcache protocol. Handles parsing and encoding activity.
 *
 * @author 2012 Copyright (C) GridGain Systems
 * @version 4.0.2c.12042012
 */
public class GridTcpRestParser implements GridNioParser<GridClientMessage> {
  /** Ping message. */
  public static final GridClientMessage PING_MESSAGE = new GridClientPingPacket();

  /** Ping packet. */
  private static final byte[] PING_PACKET = new byte[] {(byte) 0x90, 0x00, 0x00, 0x00, 0x00};

  /** Meta name for parser state. */
  private static final String PARSER_STATE_META_NAME = UUID.randomUUID().toString();

  /** JDK marshaller. */
  private final GridMarshaller jdkMarshaller = new GridJdkMarshaller();

  /** Hessian marshaller. */
  @GridToStringExclude
  private final GridClientMarshaller marshaller = new GridClientProtobufMarshaller();

  /** {@inheritDoc} */
  @Nullable
  @Override
  public GridClientMessage decode(GridNioSession ses, ByteBuffer buf)
      throws IOException, GridException {
    ParserState state = ses.removeMeta(PARSER_STATE_META_NAME);

    if (state == null) state = new ParserState();

    PacketType type = state.packetType();

    if (type == null) {
      byte hdr = buf.get(buf.position());

      switch (hdr) {
        case MEMCACHE_REQ_FLAG:
          state.packet(new GridTcpRestPacket());
          state.packetType(PacketType.MEMCACHE);

          break;
        case GRIDGAIN_REQ_FLAG:
          // Skip header.
          buf.get();

          state.packetType(PacketType.GRIDGAIN);

          break;

        default:
          throw new IOException(
              "Failed to parse incoming packet (invalid packet start) [ses="
                  + ses
                  + ", b="
                  + Integer.toHexString(hdr & 0xFF)
                  + ']');
      }
    }

    GridClientMessage result = null;

    switch (state.packetType()) {
      case MEMCACHE:
        result = parseMemcachePacket(ses, buf, state);

        break;
      case GRIDGAIN:
        result = parseCustomPacket(ses, buf, state);

        break;
    }

    if (result == null)
      // Packet was not fully parsed yet.
      ses.addMeta(PARSER_STATE_META_NAME, state);

    return result;
  }

  /** {@inheritDoc} */
  @Override
  public ByteBuffer encode(GridNioSession ses, GridClientMessage msg)
      throws IOException, GridException {
    assert msg != null;

    if (msg instanceof GridTcpRestPacket) return encodeMemcache((GridTcpRestPacket) msg);
    else if (msg == PING_MESSAGE) return ByteBuffer.wrap(PING_PACKET);
    else {
      byte[] data = marshaller.marshal(msg);

      assert data.length > 0;

      ByteBuffer res = ByteBuffer.allocate(data.length + 5);

      res.put(GRIDGAIN_REQ_FLAG);
      res.put(U.intToBytes(data.length));
      res.put(data);

      res.flip();

      return res;
    }
  }

  /**
   * Parses memcache protocol message.
   *
   * @param ses Session.
   * @param buf Buffer containing not parsed bytes.
   * @param state Current parser state.
   * @return Parsed packet.s
   * @throws IOException If packet cannot be parsed.
   * @throws GridException If deserialization error occurred.
   */
  @Nullable
  private GridClientMessage parseMemcachePacket(
      GridNioSession ses, ByteBuffer buf, ParserState state) throws IOException, GridException {
    assert state.packetType() == PacketType.MEMCACHE;
    assert state.packet() != null;
    assert state.packet() instanceof GridTcpRestPacket;

    GridTcpRestPacket req = (GridTcpRestPacket) state.packet();
    ByteArrayOutputStream tmp = state.buffer();
    int i = state.index();

    while (buf.remaining() > 0) {
      byte b = buf.get();

      if (i == 0) req.requestFlag(b);
      else if (i == 1) req.operationCode(b);
      else if (i == 2 || i == 3) {
        tmp.write(b);

        if (i == 3) {
          req.keyLength(U.bytesToShort(tmp.toByteArray(), 0));

          tmp.reset();
        }
      } else if (i == 4) req.extrasLength(b);
      else if (i >= 8 && i <= 11) {
        tmp.write(b);

        if (i == 11) {
          req.totalLength(U.bytesToInt(tmp.toByteArray(), 0));

          tmp.reset();
        }
      } else if (i >= 12 && i <= 15) {
        tmp.write(b);

        if (i == 15) {
          req.opaque(tmp.toByteArray());

          tmp.reset();
        }
      } else if (i >= HDR_LEN && i < HDR_LEN + req.extrasLength()) {
        tmp.write(b);

        if (i == HDR_LEN + req.extrasLength() - 1) {
          req.extras(tmp.toByteArray());

          tmp.reset();
        }
      } else if (i >= HDR_LEN + req.extrasLength()
          && i < HDR_LEN + req.extrasLength() + req.keyLength()) {
        tmp.write(b);

        if (i == HDR_LEN + req.extrasLength() + req.keyLength() - 1) {
          req.key(tmp.toByteArray());

          tmp.reset();
        }
      } else if (i >= HDR_LEN + req.extrasLength() + req.keyLength()
          && i < HDR_LEN + req.totalLength()) {
        tmp.write(b);

        if (i == HDR_LEN + req.totalLength() - 1) {
          req.value(tmp.toByteArray());

          tmp.reset();
        }
      }

      if (i == HDR_LEN + req.totalLength() - 1)
        // Assembled the packet.
        return assemble(ses, req);

      i++;
    }

    state.index(i);

    return null;
  }

  /**
   * Parses custom packet serialized by hessian marshaller.
   *
   * @param ses Session.
   * @param buf Buffer containing not parsed bytes.
   * @param state Parser state.
   * @return Parsed message.
   * @throws IOException If packet parsing or deserialization failed.
   */
  @Nullable
  private GridClientMessage parseCustomPacket(GridNioSession ses, ByteBuffer buf, ParserState state)
      throws IOException {
    assert state.packetType() == PacketType.GRIDGAIN;
    assert state.packet() == null;

    ByteArrayOutputStream tmp = state.buffer();

    int len = state.index();

    while (buf.remaining() > 0) {
      byte b = buf.get();

      if (len == 0) {
        tmp.write(b);

        if (tmp.size() == 4) {
          len = U.bytesToInt(tmp.toByteArray(), 0);

          tmp.reset();

          if (len == 0) return PING_MESSAGE;
          else if (len < 0)
            throw new IOException(
                "Failed to parse incoming packet (invalid packet length) [ses="
                    + ses
                    + ", len="
                    + len
                    + ']');

          state.index(len);
        }
      } else {
        tmp.write(b);

        if (tmp.size() == len) return marshaller.unmarshal(tmp.toByteArray());
      }
    }

    return null;
  }

  /**
   * Encodes memcache message to a raw byte array.
   *
   * @param msg Message being serialized.
   * @return Serialized message.
   * @throws GridException If serialization failed.
   */
  private ByteBuffer encodeMemcache(GridTcpRestPacket msg) throws GridException {
    GridByteArrayList res = new GridByteArrayList(HDR_LEN);

    int keyLength = 0;

    int keyFlags = 0;

    if (msg.key() != null) {
      ByteArrayOutputStream rawKey = new ByteArrayOutputStream();

      keyFlags = encodeObj(msg.key(), rawKey);

      msg.key(rawKey.toByteArray());

      keyLength = rawKey.size();
    }

    int dataLength = 0;

    int valFlags = 0;

    if (msg.value() != null) {
      ByteArrayOutputStream rawVal = new ByteArrayOutputStream();

      valFlags = encodeObj(msg.value(), rawVal);

      msg.value(rawVal.toByteArray());

      dataLength = rawVal.size();
    }

    int flagsLength = 0;

    if (msg.addFlags()) // || keyFlags > 0 || valFlags > 0)
    flagsLength = FLAGS_LENGTH;

    res.add(MEMCACHE_RES_FLAG);

    res.add(msg.operationCode());

    // Cast is required due to packet layout.
    res.add((short) keyLength);

    // Cast is required due to packet layout.
    res.add((byte) flagsLength);

    // Data type is always 0x00.
    res.add((byte) 0x00);

    res.add((short) msg.status());

    res.add(keyLength + flagsLength + dataLength);

    res.add(msg.opaque(), 0, msg.opaque().length);

    // CAS, unused.
    res.add(0L);

    assert res.size() == HDR_LEN;

    if (flagsLength > 0) {
      res.add((short) keyFlags);
      res.add((short) valFlags);
    }

    assert msg.key() == null || msg.key() instanceof byte[];
    assert msg.value() == null || msg.value() instanceof byte[];

    if (keyLength > 0) res.add((byte[]) msg.key(), 0, ((byte[]) msg.key()).length);

    if (dataLength > 0) res.add((byte[]) msg.value(), 0, ((byte[]) msg.value()).length);

    return ByteBuffer.wrap(res.entireArray());
  }

  /**
   * Validates incoming packet and deserializes all fields that need to be deserialized.
   *
   * @param ses Session on which packet is being parsed.
   * @param req Raw packet.
   * @return Same packet with fields deserialized.
   * @throws IOException If parsing failed.
   * @throws GridException If deserialization failed.
   */
  private GridClientMessage assemble(GridNioSession ses, GridTcpRestPacket req)
      throws IOException, GridException {
    byte[] extras = req.extras();

    // First, decode key and value, if any
    if (req.key() != null || req.value() != null) {
      short keyFlags = 0;
      short valFlags = 0;

      if (req.hasFlags()) {
        if (extras == null || extras.length < FLAGS_LENGTH)
          throw new IOException(
              "Failed to parse incoming packet (flags required for command) [ses="
                  + ses
                  + ", opCode="
                  + Integer.toHexString(req.operationCode() & 0xFF)
                  + ']');

        keyFlags = U.bytesToShort(extras, 0);
        valFlags = U.bytesToShort(extras, 2);
      }

      if (req.key() != null) {
        assert req.key() instanceof byte[];

        byte[] rawKey = (byte[]) req.key();

        // Only values can be hessian-encoded.
        req.key(decodeObj(keyFlags, rawKey));
      }

      if (req.value() != null) {
        assert req.value() instanceof byte[];

        byte[] rawVal = (byte[]) req.value();

        req.value(decodeObj(valFlags, rawVal));
      }
    }

    if (req.hasExpiration()) {
      if (extras == null || extras.length < 8)
        throw new IOException(
            "Failed to parse incoming packet (expiration value required for command) [ses="
                + ses
                + ", opCode="
                + Integer.toHexString(req.operationCode() & 0xFF)
                + ']');

      req.expiration(U.bytesToInt(extras, 4) & 0xFFFFFFFFL);
    }

    if (req.hasInitial()) {
      if (extras == null || extras.length < 16)
        throw new IOException(
            "Failed to parse incoming packet (initial value required for command) [ses="
                + ses
                + ", opCode="
                + Integer.toHexString(req.operationCode() & 0xFF)
                + ']');

      req.initial(U.bytesToLong(extras, 8));
    }

    if (req.hasDelta()) {
      if (extras == null || extras.length < 8)
        throw new IOException(
            "Failed to parse incoming packet (delta value required for command) [ses="
                + ses
                + ", opCode="
                + Integer.toHexString(req.operationCode() & 0xFF)
                + ']');

      req.delta(U.bytesToLong(extras, 0));
    }

    if (extras != null) {
      // Clients that include cache name must always include flags.
      int length = 4;

      if (req.hasExpiration()) length += 4;

      if (req.hasDelta()) length += 8;

      if (req.hasInitial()) length += 8;

      if (extras.length - length > 0) {
        byte[] cacheName = new byte[extras.length - length];

        System.arraycopy(extras, length, cacheName, 0, extras.length - length);

        req.cacheName(new String(cacheName));
      }
    }

    return req;
  }

  /**
   * Decodes value from a given byte array to the object according to the flags given.
   *
   * @param flags Flags.
   * @param bytes Byte array to decode.
   * @return Decoded value.
   * @throws GridException If deserialization failed.
   */
  private Object decodeObj(short flags, byte[] bytes) throws GridException {
    assert bytes != null;

    if ((flags & SERIALIZED_FLAG) != 0)
      return jdkMarshaller.unmarshal(new ByteArrayInputStream(bytes), null);

    int masked = flags & 0xff00;

    switch (masked) {
      case BOOLEAN_FLAG:
        return bytes[0] == '1';
      case INT_FLAG:
        return U.bytesToInt(bytes, 0);
      case LONG_FLAG:
        return U.bytesToLong(bytes, 0);
      case DATE_FLAG:
        return new Date(U.bytesToLong(bytes, 0));
      case BYTE_FLAG:
        return bytes[0];
      case FLOAT_FLAG:
        return Float.intBitsToFloat(U.bytesToInt(bytes, 0));
      case DOUBLE_FLAG:
        return Double.longBitsToDouble(U.bytesToLong(bytes, 0));
      case BYTE_ARR_FLAG:
        return bytes;
      default:
        return new String(bytes);
    }
  }

  /**
   * Encodes given object to a byte array and returns flags that describe the type of serialized
   * object.
   *
   * @param obj Object to serialize.
   * @param out Output stream to which object should be written.
   * @return Serialization flags.
   * @throws GridException If JDK serialization failed.
   */
  private int encodeObj(Object obj, ByteArrayOutputStream out) throws GridException {
    int flags = 0;

    byte[] data = null;

    if (obj instanceof String) {
      data = ((String) obj).getBytes();
    } else if (obj instanceof Boolean) {
      data = new byte[] {(byte) ((Boolean) obj ? '1' : '0')};

      flags |= BOOLEAN_FLAG;
    } else if (obj instanceof Integer) {
      data = U.intToBytes((Integer) obj);

      flags |= INT_FLAG;
    } else if (obj instanceof Long) {
      data = U.longToBytes((Long) obj);

      flags |= LONG_FLAG;
    } else if (obj instanceof Date) {
      data = U.longToBytes(((Date) obj).getTime());

      flags |= DATE_FLAG;
    } else if (obj instanceof Byte) {
      data = new byte[] {(Byte) obj};

      flags |= BYTE_FLAG;
    } else if (obj instanceof Float) {
      data = U.intToBytes(Float.floatToIntBits((Float) obj));

      flags |= FLOAT_FLAG;
    } else if (obj instanceof Double) {
      data = U.longToBytes(Double.doubleToLongBits((Double) obj));

      flags |= DOUBLE_FLAG;
    } else if (obj instanceof byte[]) {
      data = (byte[]) obj;

      flags |= BYTE_ARR_FLAG;
    } else {
      jdkMarshaller.marshal(obj, out);

      flags |= SERIALIZED_FLAG;
    }

    if (data != null) out.write(data, 0, data.length);

    return flags;
  }

  /** {@inheritDoc} */
  public String toString() {
    return S.toString(
        GridTcpRestParser.class, this, "clientMarshaller", marshaller.getClass().getSimpleName());
  }

  /** Type of message being parsed. */
  private enum PacketType {
    /** Memcache protocol message. */
    MEMCACHE,

    /** Custom hessian-serialized message. */
    GRIDGAIN
  }

  /** Holder for parser state and temporary buffer. */
  private static class ParserState {
    /** Parser index. */
    private int idx;

    /** Temporary data buffer. */
    private ByteArrayOutputStream buf = new ByteArrayOutputStream();

    /** Packet being assembled. */
    private GridClientMessage packet;

    /** Packet type. */
    private PacketType packetType;

    /** @return Stored parser index. */
    private int index() {
      return idx;
    }

    /** @param idx Index to store. */
    private void index(int idx) {
      this.idx = idx;
    }

    /** @return Temporary data buffer. */
    private ByteArrayOutputStream buffer() {
      return buf;
    }

    /** @return Pending packet. */
    private GridClientMessage packet() {
      return packet;
    }

    /** @param packet Pending packet. */
    private void packet(GridClientMessage packet) {
      assert this.packet == null;

      this.packet = packet;
    }

    /** @return Pending packet type. */
    private PacketType packetType() {
      return packetType;
    }

    /** @param packetType Pending packet type. */
    private void packetType(PacketType packetType) {
      this.packetType = packetType;
    }

    /** {@inheritDoc} */
    @Override
    public String toString() {
      return S.toString(ParserState.class, this);
    }
  }
}
 /**
  * Checks if this candidate matches version or thread-nodeId combination.
  *
  * @param nodeId Node ID to check.
  * @param ver Version to check.
  * @param threadId Thread ID to check.
  * @return {@code True} if matched.
  */
 public boolean matches(GridCacheVersion ver, UUID nodeId, long threadId) {
   return ver.equals(this.ver) || (nodeId.equals(this.nodeId) && threadId == this.threadId);
 }
Example #13
0
 /** {@inheritDoc} */
 @Override
 public boolean isTaskNode() {
   return taskNodeId.equals(ctx.discovery().localNode().id());
 }