/** *Copyright [2010-2011] [dennis zhuang(killme2008@gmail.com)] *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.google.code.hs4j.impl; import java.io.IOException; import java.net.InetSocketAddress; import java.sql.ResultSet; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import com.google.code.hs4j.Command; import com.google.code.hs4j.CommandFactory; import com.google.code.hs4j.Filter; import com.google.code.hs4j.FindOperator; import com.google.code.hs4j.HSClient; import com.google.code.hs4j.HSClientStateListener; import com.google.code.hs4j.IndexSession; import com.google.code.hs4j.ModifyStatement; import com.google.code.hs4j.command.text.TextCommandFactory; import com.google.code.hs4j.exception.HandlerSocketException; import com.google.code.hs4j.network.core.Session; import com.google.code.hs4j.network.core.SocketOption; import com.google.code.hs4j.network.hs.HandlerSocketClientStateListenerAdapter; import com.google.code.hs4j.network.hs.HandlerSocketConnector; import com.google.code.hs4j.network.hs.HandlerSocketConnectorImpl; import com.google.code.hs4j.network.hs.HandlerSocketHandler; import com.google.code.hs4j.network.hs.HandlerSocketSession; import com.google.code.hs4j.network.hs.codec.HandlerSocketCodecFactory; import com.google.code.hs4j.utils.HSUtils; /** * HSClient implementation * * @author dennis * @date 2010-11-29 */ public class HSClientImpl implements HSClient { private static final String EMPTY_STR = ""; private boolean started = false; private final CommandFactory commandFactory; private HandlerSocketConnectorImpl connector; private long opTimeout = DEFAULT_OP_TIMEOUT; private HandlerSocketHandler ioHandler; private String encoding = DEFAULT_ENCODING; /** * Index id counter */ private static AtomicInteger INDEX_COUNTER = new AtomicInteger(); @SuppressWarnings("unchecked") private Map<SocketOption, Object> socketOptions = HSClientBuilderImpl .getDefaultSocketOptions(); private final ConcurrentHashMap<Integer/* index id */, IndexRecord/* * index * info */> indexMap = new ConcurrentHashMap<Integer, IndexRecord>(); private final CopyOnWriteArrayList<HSClientStateListener> hsClientStateListeners = new CopyOnWriteArrayList<HSClientStateListener>(); private final InetSocketAddress remoteAddr; public IndexSession openIndexSession(int indexId, String dbname, String tableName, String indexName, String[] columns) throws InterruptedException, TimeoutException, HandlerSocketException { return this.openIndexSession(indexId, dbname, tableName, indexName, columns, null); } public IndexSession openIndexSession(int indexId, String dbname, String tableName, String indexName, String[] columns, String[] fcolumns) throws InterruptedException, TimeoutException, HandlerSocketException { this.checkParams(dbname, tableName, indexName, columns, fcolumns); if (this.openIndex(indexId, dbname, tableName, indexName, columns, fcolumns)) { return new IndexSessionImpl(this, indexId, columns); } else { return null; } } public String getEncoding() { return encoding; } public void setEncoding(String encoding) { if (encoding == null || encoding.trim().length() == 0) throw new IllegalArgumentException("Invalid encoding:" + encoding); this.encoding = encoding; if (this.commandFactory != null) { this.commandFactory.setEncoding(encoding); } } public Map<Integer, IndexRecord> getIndexMap() { return Collections .<Integer, IndexRecord> unmodifiableMap(this.indexMap); } private void checkParams(String dbname, String tableName, String indexName, String[] columns, String[] fcolumns) { if (HSUtils.isBlank(dbname)) { throw new IllegalArgumentException("blank dbname:" + dbname); } if (HSUtils.isBlank(tableName)) { throw new IllegalArgumentException("blank tableName:" + tableName); } if (HSUtils.isBlank(indexName)) { throw new IllegalArgumentException("blank indexName:" + indexName); } if (columns == null || columns.length == 0) { throw new IllegalArgumentException("empty columns"); } for (String col : columns) { if (HSUtils.isBlank(col)) { throw new IllegalArgumentException("blank column name:" + col); } } if (fcolumns != null && fcolumns.length != 0) { for (String col : fcolumns) { if (HSUtils.isBlank(col)) { throw new IllegalArgumentException("blank fcolumn name:" + col); } } } } public IndexSession openIndexSession(String dbname, String tableName, String indexName, String[] columns) throws InterruptedException, TimeoutException, HandlerSocketException { return this.openIndexSession(INDEX_COUNTER.incrementAndGet(), dbname, tableName, indexName, columns); } public IndexSession openIndexSession(String dbname, String tableName, String indexName, String[] columns, String[] fcolumns) throws InterruptedException, TimeoutException, HandlerSocketException { return this.openIndexSession(INDEX_COUNTER.incrementAndGet(), dbname, tableName, indexName, columns, fcolumns); } public CopyOnWriteArrayList<HSClientStateListener> getHSClientStateListeners() { return this.hsClientStateListeners; } /** * New a HSFClient instance with host and port * * @param hostname * HandlerSocket hostname * @param port * HandlerSocket port * @throws IOException */ public HSClientImpl(String hostname, int port) throws IOException { this(hostname, port, 1); } /** * New a HSFClient instance with host and port * * @param hostname * HandlerSocket hostname * @param port * HandlerSocket port * @throws IOException */ public HSClientImpl(String hostname, int port, int poolSize) throws IOException { this(new InetSocketAddress(hostname, port), poolSize); } /** * New a HSFClient instance with a InetSocketAddress * * @param inetSocketAddress * HandlerSocket address * @throws IOException */ public HSClientImpl(InetSocketAddress inetSocketAddress) throws IOException { this(inetSocketAddress, DEFAULT_CONNECTION_POOL_SIZE); } /** * New a HSFClient instance with a InetSocketAddress and poolSize * * @param inetSocketAddress * HandlerSocket address * @throws IOException */ public HSClientImpl(InetSocketAddress inetSocketAddress, int poolSize) throws IOException { this(new TextCommandFactory(), inetSocketAddress, null, null, poolSize); } /** * New a HSFClient * * @param commandFactory * The protocol commands factory * @param remoteAddr * @throws IOException */ @SuppressWarnings("unchecked") public HSClientImpl(CommandFactory commandFactory, InetSocketAddress remoteAddr, List<HSClientStateListener> stateListeners, final Map<SocketOption, Object> socketOptions, int poolSize) throws IOException { super(); if (commandFactory == null) { throw new NullPointerException("null commandFactory"); } if (remoteAddr == null) { throw new NullPointerException("null remoteAddr"); } if (stateListeners != null) { this.hsClientStateListeners.addAll(stateListeners); } if (poolSize <= 0) { throw new IllegalArgumentException( "poolSize must be greater than zero"); } if (socketOptions != null) { this.socketOptions = socketOptions; } this.commandFactory = commandFactory; if (this.commandFactory != null) { this.commandFactory.setEncoding(this.encoding); } this.remoteAddr = remoteAddr; this.initConnectorAndConnect(commandFactory, remoteAddr, poolSize); } public InetSocketAddress getRemoteAddr() { return this.remoteAddr; } private void initConnectorAndConnect(CommandFactory commandFactory, InetSocketAddress remoteAddr, int poolSize) throws IOException { this.connector = new HandlerSocketConnectorImpl(HSClientBuilderImpl .getDefaultConfiguration(), commandFactory, poolSize, this); this.ioHandler = new HandlerSocketHandler(this); this.connector.setHandler(this.ioHandler); this.connector.setCodecFactory(new HandlerSocketCodecFactory()); this.connector.setSessionTimeout(-1); this.connector.setSocketOptions(this.socketOptions); for (HSClientStateListener listener : this.hsClientStateListeners) { this.connector .addStateListener(new HandlerSocketClientStateListenerAdapter( listener, this)); } this.connector.start(); for (int i = 0; i < poolSize; i++) { try { if (!this.connector.connect(remoteAddr).get( DEFAULT_CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)) { throw new IOException("Connect to " + remoteAddr + " fail"); } } catch (Exception e) { throw new IOException("Connect to " + remoteAddr + " fail,cause by:" + e.getMessage()); } } // waiting pool ready while (this.connector.getSessionList().size() < poolSize) { try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } this.started = true; } public HandlerSocketConnector getConnector() { return this.connector; } public ResultSet find(int indexId, String[] keys, FindOperator operator, int limit, int offset) throws InterruptedException, TimeoutException, HandlerSocketException { return this.find(indexId, keys, operator, limit, offset, null ); } public ResultSet find(int indexId, String[] keys, FindOperator operator, int limit, int offset, Filter[] filters ) throws InterruptedException, TimeoutException, HandlerSocketException { IndexRecord indexRecord = this.getRecord(indexId); Command cmd = this.commandFactory.createFindCommand(String .valueOf(indexId), operator, keys, limit, offset, indexRecord.fieldList, filters); this.connector.send(cmd); this.awaitResponse(cmd); return (ResultSet) cmd.getResult(); } private IndexRecord getRecord(int indexId) throws HandlerSocketException { IndexRecord indexRecord = this.indexMap.get(indexId); if (indexRecord == null) { throw new HandlerSocketException("Please open index first,indexId=" + indexId); } return indexRecord; } public ResultSet find(int indexId, String[] keys) throws InterruptedException, TimeoutException, HandlerSocketException { return this.find(indexId, keys, FindOperator.EQ, 1, 0); } public boolean insert(int indexId, String[] values) throws InterruptedException, TimeoutException, HandlerSocketException { byte[][] bytes = HSUtils.getByteArrayFromStringArray(values,this.encoding); return insert0(indexId, bytes); } public String insertIgnore(int indexId, String[] values) throws InterruptedException, TimeoutException, HandlerSocketException { byte[][] bytes = HSUtils.getByteArrayFromStringArray(values,this.encoding); Command cmd = this.commandFactory.createInsertIgnoreCommand(String .valueOf(indexId), bytes); this.connector.send(cmd); this.awaitResponse(cmd); return cmd.getResponseStatus() == 0 ? (String)cmd.getResult() : EMPTY_STR; } public ModifyStatement createStatement(int indexId) throws HandlerSocketException { IndexRecord indexRecord = getRecord(indexId); return new HandlerSocketModifyStatement(indexId, indexRecord.fieldList.length, this); } protected boolean insert0(int indexId, byte[][] bytes) throws HandlerSocketException, InterruptedException, TimeoutException { Command cmd = this.commandFactory.createInsertCommand(String .valueOf(indexId), bytes); this.connector.send(cmd); this.awaitResponse(cmd); return cmd.getResponseStatus() == 0; } public void notifyConnected(HandlerSocketSession session) { for (HSClientStateListener listener : this.hsClientStateListeners) { listener.onConnected(this, session.getRemoteSocketAddress()); } } public int delete(int indexId, String[] keys, FindOperator operator, int limit, int offset) throws InterruptedException, TimeoutException, HandlerSocketException { Command cmd = this.commandFactory.createDeleteCommand(String .valueOf(indexId), operator, keys, limit, offset); this.connector.send(cmd); this.awaitResponse(cmd); return (Integer) cmd.getResult(); } public int delete(int indexId, String[] keys, FindOperator operator) throws InterruptedException, TimeoutException, HandlerSocketException { return this.delete(indexId, keys, operator, 1, 0); } public int delete(int indexId, String[] keys) throws InterruptedException, TimeoutException, HandlerSocketException { return this.delete(indexId, keys, FindOperator.EQ); } public int update(int indexId, String[] keys, String[] values, FindOperator operator, int limit, int offset) throws InterruptedException, TimeoutException, HandlerSocketException { byte[][] bytes = HSUtils.getByteArrayFromStringArray(values,this.encoding); return update0(indexId, keys, operator, limit, offset, bytes); } protected int update0(int indexId, String[] keys, FindOperator operator, int limit, int offset, byte[][] bytes) throws HandlerSocketException, InterruptedException, TimeoutException { Command cmd = this.commandFactory.createUpdateCommand(String .valueOf(indexId), operator, keys, bytes, limit, offset); this.connector.send(cmd); this.awaitResponse(cmd); return (Integer) cmd.getResult(); } public int update(int indexId, String[] keys, String[] values, FindOperator operator) throws InterruptedException, TimeoutException, HandlerSocketException { return this.update(indexId, keys, values, operator, 1, 0); } protected int incr(int indexId, String[] keys, FindOperator operator, int limit, int offset, byte[][] bytes) throws HandlerSocketException, InterruptedException, TimeoutException { Command cmd = this.commandFactory.createIncrCommand(String .valueOf(indexId), operator, keys, bytes, limit, offset); this.connector.send(cmd); this.awaitResponse(cmd); return (Integer) cmd.getResult(); } protected int decr(int indexId, String[] keys, FindOperator operator, int limit, int offset, byte[][] bytes) throws HandlerSocketException, InterruptedException, TimeoutException { Command cmd = this.commandFactory.createDecrCommand(String .valueOf(indexId), operator, keys, bytes, limit, offset); this.connector.send(cmd); this.awaitResponse(cmd); return (Integer) cmd.getResult(); } public boolean isStarted() { return this.started; } public boolean openIndex(int indexId, String dbname, String tableName, String indexName, String[] columns, String[] fcolumns) throws InterruptedException, TimeoutException, HandlerSocketException { this.checkParams(dbname, tableName, indexName, columns, fcolumns); IndexRecord record = new IndexRecord(indexId, dbname, tableName, indexName, columns, fcolumns); this.indexMap.put(indexId, record); List<Session> sessionList = this.connector.getSessionList(); if (sessionList == null || sessionList.isEmpty()) { throw new HandlerSocketException("Empty connections"); } boolean result = true; for (Session session : sessionList) { Command cmd = this.commandFactory.createOpenIndexCommand(String .valueOf(indexId), dbname, tableName, indexName, columns, fcolumns); session.write(cmd); this.awaitResponse(cmd); result = result && cmd.getResponseStatus() == 0; } return result; } public boolean openIndex(int indexId, String dbname, String tableName, String indexName, String[] columns) throws InterruptedException, TimeoutException, HandlerSocketException { return this.openIndex(indexId, dbname, tableName, indexName, columns, null); } private void awaitResponse(Command cmd) throws InterruptedException, TimeoutException, HandlerSocketException { if (!cmd.await(this.opTimeout, TimeUnit.MILLISECONDS)) { throw new TimeoutException("Operation timeout in " + this.opTimeout + " ms."); } if (cmd.getExceptionMessage() != null) { throw new HandlerSocketException(cmd.getExceptionMessage()); } } public void setOpTimeout(long opTimeout) { if (opTimeout <= 0) { throw new IllegalArgumentException( "opTimeout must be greater than zero"); } this.opTimeout = opTimeout; } public void setHealConnectionInterval(long interval) { this.connector.setHealSessionInterval(interval); } public long getHealConnectionInterval() { if (null != this.connector) { return this.connector.getHealSessionInterval(); } return -1L; } public boolean isAllowAutoReconnect() { if (null != this.connector) { return this.connector.isAllowAutoReconnect(); } return false; } public void setAllowAutoReconnect(boolean allowAutoReconnect) { if (null != this.connector) { this.connector.setAllowAutoReconnect(allowAutoReconnect); } } /** * Set tcp socket option * * @param socketOption * @param value */ public <T> void setSocketOption(SocketOption<T> socketOption, T value) { this.socketOptions.put(socketOption, value); } public synchronized void shutdown() throws IOException { if (!this.started) { return; } this.started = false; this.connector.stop(); } }