/* * Copyright 2017 the original author or authors. * * 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 org.springframework.data.redis.connection.lettuce; import io.lettuce.core.cluster.api.async.RedisClusterAsyncCommands; import io.lettuce.core.cluster.api.sync.RedisClusterCommands; import java.util.Arrays; import java.util.List; import org.springframework.core.convert.converter.Converter; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.RedisScriptingCommands; import org.springframework.data.redis.connection.ReturnType; import org.springframework.data.redis.connection.lettuce.LettuceConnection.LettuceResult; import org.springframework.data.redis.connection.lettuce.LettuceConnection.LettuceTxResult; /** * @author Mark Paluch * @since 2.0 */ class LettuceScriptingCommands implements RedisScriptingCommands { private final LettuceConnection connection; public LettuceScriptingCommands(LettuceConnection connection) { this.connection = connection; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisScriptingCommands#scriptFlush() */ @Override public void scriptFlush() { try { if (isPipelined()) { pipeline(connection.newLettuceStatusResult(getAsyncConnection().scriptFlush())); return; } if (isQueueing()) { transaction(connection.newLettuceTxStatusResult(getConnection().scriptFlush())); return; } getConnection().scriptFlush(); } catch (Exception ex) { throw convertLettuceAccessException(ex); } } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisScriptingCommands#scriptKill() */ @Override public void scriptKill() { if (isQueueing()) { throw new UnsupportedOperationException("Script kill not permitted in a transaction"); } try { if (isPipelined()) { pipeline(connection.newLettuceStatusResult(getAsyncConnection().scriptKill())); return; } if (isQueueing()) { transaction(connection.newLettuceTxStatusResult(getConnection().scriptKill())); return; } getConnection().scriptKill(); } catch (Exception ex) { throw convertLettuceAccessException(ex); } } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisScriptingCommands#scriptLoad(byte[]) */ @Override public String scriptLoad(byte[] script) { try { if (isPipelined()) { pipeline(connection.newLettuceResult(getAsyncConnection().scriptLoad(script))); return null; } if (isQueueing()) { transaction(connection.newLettuceTxResult(getConnection().scriptLoad(script))); return null; } return getConnection().scriptLoad(script); } catch (Exception ex) { throw convertLettuceAccessException(ex); } } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisScriptingCommands#scriptExists(java.lang.String[]) */ @Override public List<Boolean> scriptExists(String... scriptSha1) { try { if (isPipelined()) { pipeline(connection.newLettuceResult(getAsyncConnection().scriptExists(scriptSha1))); return null; } if (isQueueing()) { transaction(connection.newLettuceTxResult(getConnection().scriptExists(scriptSha1))); return null; } return getConnection().scriptExists(scriptSha1); } catch (Exception ex) { throw convertLettuceAccessException(ex); } } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisScriptingCommands#eval(byte[], org.springframework.data.redis.connection.ReturnType, int, byte[][]) */ @Override public <T> T eval(byte[] script, ReturnType returnType, int numKeys, byte[]... keysAndArgs) { try { byte[][] keys = extractScriptKeys(numKeys, keysAndArgs); byte[][] args = extractScriptArgs(numKeys, keysAndArgs); String convertedScript = LettuceConverters.toString(script); if (isPipelined()) { pipeline(connection.newLettuceResult( getAsyncConnection().eval(convertedScript, LettuceConverters.toScriptOutputType(returnType), keys, args), new LettuceEvalResultsConverter<T>(returnType))); return null; } if (isQueueing()) { transaction(connection.newLettuceTxResult( getConnection().eval(convertedScript, LettuceConverters.toScriptOutputType(returnType), keys, args), new LettuceEvalResultsConverter<T>(returnType))); return null; } return new LettuceEvalResultsConverter<T>(returnType) .convert(getConnection().eval(convertedScript, LettuceConverters.toScriptOutputType(returnType), keys, args)); } catch (Exception ex) { throw convertLettuceAccessException(ex); } } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisScriptingCommands#evalSha(java.lang.String, org.springframework.data.redis.connection.ReturnType, int, byte[][]) */ @Override public <T> T evalSha(String scriptSha1, ReturnType returnType, int numKeys, byte[]... keysAndArgs) { try { byte[][] keys = extractScriptKeys(numKeys, keysAndArgs); byte[][] args = extractScriptArgs(numKeys, keysAndArgs); if (isPipelined()) { pipeline(connection.newLettuceResult( getAsyncConnection().evalsha(scriptSha1, LettuceConverters.toScriptOutputType(returnType), keys, args), new LettuceEvalResultsConverter<T>(returnType))); return null; } if (isQueueing()) { transaction(connection.newLettuceTxResult( getConnection().evalsha(scriptSha1, LettuceConverters.toScriptOutputType(returnType), keys, args), new LettuceEvalResultsConverter<T>(returnType))); return null; } return new LettuceEvalResultsConverter<T>(returnType) .convert(getConnection().evalsha(scriptSha1, LettuceConverters.toScriptOutputType(returnType), keys, args)); } catch (Exception ex) { throw convertLettuceAccessException(ex); } } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisScriptingCommands#evalSha(byte[], org.springframework.data.redis.connection.ReturnType, int, byte[][]) */ @Override public <T> T evalSha(byte[] scriptSha1, ReturnType returnType, int numKeys, byte[]... keysAndArgs) { return evalSha(LettuceConverters.toString(scriptSha1), returnType, numKeys, keysAndArgs); } private boolean isPipelined() { return connection.isPipelined(); } private boolean isQueueing() { return connection.isQueueing(); } private void pipeline(LettuceResult result) { connection.pipeline(result); } private void transaction(LettuceTxResult result) { connection.transaction(result); } private RedisClusterAsyncCommands<byte[], byte[]> getAsyncConnection() { return connection.getAsyncConnection(); } public RedisClusterCommands<byte[], byte[]> getConnection() { return connection.getConnection(); } private DataAccessException convertLettuceAccessException(Exception ex) { return connection.convertLettuceAccessException(ex); } private static byte[][] extractScriptKeys(int numKeys, byte[]... keysAndArgs) { if (numKeys > 0) { return Arrays.copyOfRange(keysAndArgs, 0, numKeys); } return new byte[0][0]; } private static byte[][] extractScriptArgs(int numKeys, byte[]... keysAndArgs) { if (keysAndArgs.length > numKeys) { return Arrays.copyOfRange(keysAndArgs, numKeys, keysAndArgs.length); } return new byte[0][0]; } private class LettuceEvalResultsConverter<T> implements Converter<Object, T> { private ReturnType returnType; public LettuceEvalResultsConverter(ReturnType returnType) { this.returnType = returnType; } @SuppressWarnings({ "rawtypes", "unchecked" }) public T convert(Object source) { if (returnType == ReturnType.MULTI) { List resultList = (List) source; for (Object obj : resultList) { if (obj instanceof Exception) { throw convertLettuceAccessException((Exception) obj); } } } return (T) source; } } }