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

com.aerospike.client.command.MultiCommand Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2012-2023 Aerospike, Inc.
 *
 * Portions may be licensed to Aerospike, Inc. under one or more contributor
 * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
 *
 * 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.
 */
package com.aerospike.client.command;

import java.io.IOException;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;

import com.aerospike.client.AerospikeException;
import com.aerospike.client.Key;
import com.aerospike.client.Record;
import com.aerospike.client.cluster.Cluster;
import com.aerospike.client.cluster.Connection;
import com.aerospike.client.cluster.Connection.ReadTimeout;
import com.aerospike.client.cluster.Node;
import com.aerospike.client.policy.Policy;
import com.aerospike.client.query.QueryValidate;

public abstract class MultiCommand extends SyncCommand {
	private static final int MAX_BUFFER_SIZE = 1024 * 1024 * 128;  // 128 MB

	private final Node node;
	protected final String namespace;
	private final long clusterKey;
	protected int info3;
	protected int resultCode;
	protected int generation;
	protected int expiration;
	protected int batchIndex;
	protected int fieldCount;
	protected int opCount;
	protected final boolean isOperation;
	private final boolean first;
	protected volatile boolean valid = true;

	/**
	 * Batch and server execute constructor.
	 */
	protected MultiCommand(Cluster cluster, Policy policy, Node node, boolean isOperation) {
		super(cluster, policy);
		this.node = node;
		this.isOperation = isOperation;
		this.namespace = null;
		this.clusterKey = 0;
		this.first = false;
	}

	/**
	 * Partition scan/query constructor.
	 */
	protected MultiCommand(Cluster cluster, Policy policy, Node node, String namespace, int socketTimeout, int totalTimeout) {
		super(cluster, policy, socketTimeout, totalTimeout);
		this.node = node;
		this.isOperation = false;
		this.namespace = namespace;
		this.clusterKey = 0;
		this.first = false;
	}

	/**
	 * Legacy scan/query constructor.
	 */
	protected MultiCommand(Cluster cluster, Policy policy, Node node, String namespace, long clusterKey, boolean first) {
		super(cluster, policy, policy.socketTimeout, policy.totalTimeout);
		this.node = node;
		this.isOperation = false;
		this.namespace = namespace;
		this.clusterKey = clusterKey;
		this.first = first;
	}

	public void executeAndValidate(int infoTimeout) {
		if (clusterKey != 0) {
			if (! first) {
				QueryValidate.validate(node, namespace, clusterKey, infoTimeout);
			}
			super.execute();
			QueryValidate.validate(node, namespace, clusterKey, infoTimeout);
		}
		else {
			super.execute();
		}
	}

	@Override
	protected boolean isSingle() {
		return false;
	}

	@Override
	protected Node getNode() {
		return node;
	}

	@Override
	protected boolean prepareRetry(boolean timeout) {
		return true;
	}

	@Override
	protected final void parseResult(Connection conn) throws IOException {
		// Read blocks of records.  Do not use thread local receive buffer because each
		// block will likely be too big for a cache.  Also, scan callbacks can nest
		// further database commands which would contend with the thread local receive buffer.
		// Instead, use separate heap allocated buffers.
		byte[] buf = null;
		byte[] ubuf = null;
		int receiveSize;

		while (true) {
			// Read header
			byte[] protoBuf = new byte[8];
			conn.readFully(protoBuf, 8, Command.STATE_READ_HEADER);

			long proto = Buffer.bytesToLong(protoBuf, 0);
			int size = (int)(proto & 0xFFFFFFFFFFFFL);

			if (size <= 0) {
				continue;
			}

			// Prepare buffer
			if (buf == null || size > buf.length) {
				// Corrupted data streams can result in a huge length.
				// Do a sanity check here.
				if (size > MAX_BUFFER_SIZE) {
					throw new AerospikeException("Invalid proto size: " + size);
				}

				int capacity = (size + 16383) & ~16383; // Round up in 16KB increments.
				buf = new byte[capacity];
			}

			// Read remaining message bytes in group.
			try {
				conn.readFully(buf, size, Command.STATE_READ_DETAIL);
				conn.updateLastUsed();
			}
			catch (ReadTimeout rt) {
				if (rt.offset >= 4) {
					throw rt;
				}

				// First 4 bytes of detail contains whether this is the last
				// group to be sent.  Consider this as part of header.
				// Copy proto back into buffer to complete header.
				byte[] b = new byte[12];
				int count = 0;

				for (int i = 0; i < 8; i++) {
					b[count++] = protoBuf[i];
				}

				for (int i = 0; i < rt.offset; i++) {
					b[count++] = buf[i];
				}

				throw new ReadTimeout(b, rt.offset + 8, count, Command.STATE_READ_HEADER);
			}

			long type = (proto >> 48) & 0xff;

			if (type == Command.AS_MSG_TYPE) {
				dataBuffer = buf;
				dataOffset = 0;
				receiveSize = size;
			}
			else if (type == Command.MSG_TYPE_COMPRESSED) {
				int usize = (int)Buffer.bytesToLong(buf, 0);

				if (ubuf == null || usize > ubuf.length) {
					if (usize > MAX_BUFFER_SIZE) {
						throw new AerospikeException("Invalid proto size: " + usize);
					}

					int capacity = (usize + 16383) & ~16383; // Round up in 16KB increments.
					ubuf = new byte[capacity];
				}

				Inflater inf = new Inflater();
				try {
					inf.setInput(buf, 8, size - 8);
					int rsize;

					try {
						rsize = inf.inflate(ubuf);
					}
					catch (DataFormatException dfe) {
						throw new AerospikeException.Serialize(dfe);
					}

					if (rsize != usize) {
						throw new AerospikeException("Decompressed size " + rsize + " is not expected " + usize);
					}
					dataBuffer = ubuf;
					dataOffset = 8;
					receiveSize = usize - 8;
				} finally {
					inf.end();
				}
			}
			else {
				throw new AerospikeException("Invalid proto type: " + type + " Expected: " + Command.AS_MSG_TYPE);
			}

			if (! parseGroup(receiveSize)) {
				break;
			}
		}
	}

	private boolean parseGroup(int receiveSize) {
		while (dataOffset < receiveSize) {
			dataOffset += 3;
			info3 = dataBuffer[dataOffset] & 0xFF;
			dataOffset += 2;
			resultCode = dataBuffer[dataOffset] & 0xFF;

			// If this is the end marker of the response, do not proceed further.
			if ((info3 & Command.INFO3_LAST) != 0) {
				if (resultCode != 0) {
					// The server returned a fatal error.
					throw new AerospikeException(resultCode);
				}
				return false;
			}

			dataOffset++;
			generation = Buffer.bytesToInt(dataBuffer, dataOffset);
			dataOffset += 4;
			expiration = Buffer.bytesToInt(dataBuffer, dataOffset);
			dataOffset += 4;
			batchIndex = Buffer.bytesToInt(dataBuffer, dataOffset);
			dataOffset += 4;
			fieldCount = Buffer.bytesToShort(dataBuffer, dataOffset);
			dataOffset += 2;
			opCount = Buffer.bytesToShort(dataBuffer, dataOffset);
			dataOffset += 2;

			// Note: parseRow() also handles sync error responses.
			if (! parseRow()) {
				return false;
			}
		}
		return true;
	}

	protected abstract boolean parseRow();

	protected final Record parseRecord() {
		if (opCount <= 0) {
			return new Record(null, generation, expiration);
		}

		return parseRecord(opCount, generation, expiration, isOperation);
	}

	protected final Record parseRecord(Key key) {
		if (opCount <= 0) {
			return new Record(null, generation, expiration);
		}

		return parseRecord(key, opCount, generation, expiration, isOperation);
	}

	public void stop() {
		valid = false;
	}

	public boolean isValid() {
		return valid;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy