/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.ignite.internal.processors.rest.handlers.cache; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; import javax.cache.expiry.Duration; import javax.cache.expiry.ModifiedExpiryPolicy; import javax.cache.processor.EntryProcessor; import javax.cache.processor.EntryProcessorException; import javax.cache.processor.EntryProcessorResult; import javax.cache.processor.MutableEntry; import org.apache.ignite.Ignite; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; import org.apache.ignite.cache.CacheMetrics; import org.apache.ignite.cache.CacheMode; import org.apache.ignite.cache.CachePeekMode; import org.apache.ignite.cluster.ClusterGroup; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.compute.ComputeJob; import org.apache.ignite.compute.ComputeJobAdapter; import org.apache.ignite.compute.ComputeJobResult; import org.apache.ignite.compute.ComputeTaskAdapter; import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.IgniteInternalFuture; import org.apache.ignite.internal.IgniteKernal; import org.apache.ignite.internal.managers.discovery.GridDiscoveryManager; import org.apache.ignite.internal.processors.cache.GridCacheAdapter; import org.apache.ignite.internal.processors.cache.IgniteCacheProxy; import org.apache.ignite.internal.processors.cache.IgniteInternalCache; import org.apache.ignite.internal.processors.cache.query.GridCacheSqlMetadata; import org.apache.ignite.internal.processors.rest.GridRestCommand; import org.apache.ignite.internal.processors.rest.GridRestResponse; import org.apache.ignite.internal.processors.rest.handlers.GridRestCommandHandlerAdapter; import org.apache.ignite.internal.processors.rest.request.GridRestCacheRequest; import org.apache.ignite.internal.processors.rest.request.GridRestRequest; import org.apache.ignite.internal.processors.task.GridInternal; import org.apache.ignite.internal.util.future.GridFinishedFuture; import org.apache.ignite.internal.util.lang.IgniteClosure2X; import org.apache.ignite.internal.util.typedef.CX1; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.X; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteClosure; import org.apache.ignite.resources.IgniteInstanceResource; import org.jetbrains.annotations.Nullable; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.apache.ignite.internal.GridClosureCallMode.BALANCE; import static org.apache.ignite.internal.processors.rest.GridRestCommand.ATOMIC_DECREMENT; import static org.apache.ignite.internal.processors.rest.GridRestCommand.ATOMIC_INCREMENT; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_ADD; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_APPEND; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_CAS; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_CONTAINS_KEY; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_CONTAINS_KEYS; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_GET; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_GET_ALL; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_GET_AND_PUT; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_GET_AND_PUT_IF_ABSENT; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_GET_AND_REMOVE; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_GET_AND_REPLACE; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_METADATA; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_METRICS; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_PREPEND; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_PUT; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_PUT_ALL; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_PUT_IF_ABSENT; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_REMOVE; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_REMOVE_ALL; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_REMOVE_VALUE; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_REPLACE; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_REPLACE_VALUE; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_SIZE; import static org.apache.ignite.internal.processors.rest.GridRestCommand.DESTROY_CACHE; import static org.apache.ignite.internal.processors.rest.GridRestCommand.GET_OR_CREATE_CACHE; import static org.apache.ignite.internal.processors.task.GridTaskThreadContextKey.TC_NO_FAILOVER; /** * Command handler for API requests. */ public class GridCacheCommandHandler extends GridRestCommandHandlerAdapter { /** Supported commands. */ private static final Collection<GridRestCommand> SUPPORTED_COMMANDS = U.sealList( DESTROY_CACHE, GET_OR_CREATE_CACHE, CACHE_CONTAINS_KEYS, CACHE_CONTAINS_KEY, CACHE_GET, CACHE_GET_AND_PUT, CACHE_GET_AND_REPLACE, CACHE_GET_AND_PUT_IF_ABSENT, CACHE_PUT_IF_ABSENT, CACHE_GET_ALL, CACHE_PUT, CACHE_ADD, CACHE_PUT_ALL, CACHE_REMOVE, CACHE_REMOVE_VALUE, CACHE_REPLACE_VALUE, CACHE_GET_AND_REMOVE, CACHE_REMOVE_ALL, CACHE_REPLACE, CACHE_CAS, CACHE_APPEND, CACHE_PREPEND, CACHE_METRICS, CACHE_SIZE, CACHE_METADATA ); /** Requests with required parameter {@code key}. */ private static final EnumSet<GridRestCommand> KEY_REQUIRED_REQUESTS = EnumSet.of( CACHE_CONTAINS_KEY, CACHE_GET, CACHE_GET_AND_PUT, CACHE_GET_AND_REPLACE, CACHE_GET_AND_PUT_IF_ABSENT, CACHE_PUT_IF_ABSENT, CACHE_PUT, CACHE_ADD, CACHE_REMOVE, CACHE_REMOVE_VALUE, CACHE_REPLACE_VALUE, CACHE_GET_AND_REMOVE, CACHE_REPLACE, ATOMIC_INCREMENT, ATOMIC_DECREMENT, CACHE_CAS, CACHE_APPEND, CACHE_PREPEND ); /** * @param ctx Context. */ public GridCacheCommandHandler(GridKernalContext ctx) { super(ctx); } /** * Retrieves cache flags from corresponding bits. * * @param cacheFlagsBits Integer representation of cache flags bit set. * @return Skip store flag. */ public static boolean parseCacheFlags(int cacheFlagsBits) { if (cacheFlagsBits == 0) return false; if ((cacheFlagsBits & 1) != 0) return true; return false; } /** * Handles append and prepend commands. * * @param ctx Kernal context. * @param cache Cache. * @param key Key. * @param req Request. * @param prepend Whether to prepend. * @return Future of operation result. * @throws IgniteCheckedException In case of any exception. */ private static IgniteInternalFuture<?> appendOrPrepend( final GridKernalContext ctx, final IgniteInternalCache<Object, Object> cache, final Object key, GridRestCacheRequest req, final boolean prepend) throws IgniteCheckedException { assert cache != null; assert key != null; assert req != null; final Object val = req.value(); if (val == null) throw new IgniteCheckedException(GridRestCommandHandlerAdapter.missingParameter("val")); return ctx.closure().callLocalSafe(new Callable<Object>() { @Override public Object call() throws Exception { EntryProcessorResult<Boolean> res = cache.invoke(key, new EntryProcessor<Object, Object, Boolean>() { @Override public Boolean process(MutableEntry<Object, Object> entry, Object... objects) throws EntryProcessorException { try { Object curVal = entry.getValue(); if (curVal == null) return false; // Modify current value with appendix one. Object newVal = appendOrPrepend(curVal, val, !prepend); // Put new value asynchronously. entry.setValue(newVal); return true; } catch (IgniteCheckedException e) { throw new EntryProcessorException(e); } } }); try { return res.get(); } catch (EntryProcessorException e) { throw new IgniteCheckedException(e.getCause()); } } }, false); } /** * Append or prepend new value to the current one. * * @param origVal Original value. * @param appendVal Appendix value to add to the original one. * @param appendPlc Append or prepend policy flag. * @return Resulting value. * @throws IgniteCheckedException In case of grid exceptions. */ private static Object appendOrPrepend(Object origVal, Object appendVal, boolean appendPlc) throws IgniteCheckedException { // Strings. if (appendVal instanceof String && origVal instanceof String) return appendPlc ? origVal + (String)appendVal : (String)appendVal + origVal; // Maps. if (appendVal instanceof Map && origVal instanceof Map) { Map<Object, Object> origMap = (Map<Object, Object>)origVal; Map<Object, Object> appendMap = (Map<Object, Object>)appendVal; Map<Object, Object> map = X.cloneObject(origMap, false, true); if (appendPlc) map.putAll(appendMap); // Append. else { map.clear(); map.putAll(appendMap); // Prepend. map.putAll(origMap); } for (Map.Entry<Object, Object> e : appendMap.entrySet()) // Remove zero-valued entries. if (e.getValue() == null && map.get(e.getKey()) == null) map.remove(e.getKey()); return map; } // Generic collection. if (appendVal instanceof Collection<?> && origVal instanceof Collection<?>) { Collection<Object> origCol = (Collection<Object>)origVal; Collection<Object> appendCol = (Collection<Object>)appendVal; Collection<Object> col = X.cloneObject(origCol, false, true); if (appendPlc) col.addAll(appendCol); // Append. else { col.clear(); col.addAll(appendCol); // Prepend. col.addAll(origCol); } return col; } throw new IgniteCheckedException("Incompatible types [appendVal=" + appendVal + ",type=" + (appendVal != null ? appendVal.getClass().getSimpleName() : "NULL") + ", old=" + origVal + ",type= " + (origVal != null ? origVal.getClass().getSimpleName() : "NULL") + ']'); } /** * Creates a transformation function from {@link CacheCommand}'s results into {@link GridRestResponse}. * * @param c Cache instance to obtain affinity data. * @param key Affinity key for previous operation. * @return Rest response. */ private static IgniteClosure<IgniteInternalFuture<?>, GridRestResponse> resultWrapper( final IgniteInternalCache<Object, Object> c, @Nullable final Object key) { return new CX1<IgniteInternalFuture<?>, GridRestResponse>() { @Override public GridRestResponse applyx(IgniteInternalFuture<?> f) throws IgniteCheckedException { GridCacheRestResponse resp = new GridCacheRestResponse(); resp.setResponse(f.get()); if (key != null) resp.setAffinityNodeId(c.cache().affinity().mapKeyToNode(key).id().toString()); return resp; } }; } /** * @param ignite Grid instance. * @param cacheName Name of the cache. * @return Instance on the named cache. * @throws IgniteCheckedException If cache not found. */ private static IgniteInternalCache<Object, Object> cache(Ignite ignite, String cacheName) throws IgniteCheckedException { IgniteInternalCache<Object, Object> cache = ((IgniteKernal)ignite).getCache(cacheName); if (cache == null) throw new IgniteCheckedException( "Failed to find cache for given cache name (null for default cache): " + cacheName); return cache; } /** {@inheritDoc} */ @Override public Collection<GridRestCommand> supportedCommands() { return SUPPORTED_COMMANDS; } /** {@inheritDoc} */ @Override public IgniteInternalFuture<GridRestResponse> handleAsync(final GridRestRequest req) { assert req instanceof GridRestCacheRequest : "Invalid command for topology handler: " + req; assert SUPPORTED_COMMANDS.contains(req.command()); if (log.isDebugEnabled()) log.debug("Handling cache REST request: " + req); GridRestCacheRequest req0 = (GridRestCacheRequest)req; final String cacheName = req0.cacheName(); final Object key = req0.key(); final boolean skipStore = parseCacheFlags(req0.cacheFlags()); try { GridRestCommand cmd = req0.command(); if (key == null && KEY_REQUIRED_REQUESTS.contains(cmd)) throw new IgniteCheckedException(GridRestCommandHandlerAdapter.missingParameter("key")); final Long ttl = req0.ttl(); IgniteInternalFuture<GridRestResponse> fut; switch (cmd) { case DESTROY_CACHE: { // Do not check thread tx here since there can be active system cache txs. fut = ((IgniteKernal)ctx.grid()).destroyCacheAsync(cacheName, false).chain( new CX1<IgniteInternalFuture<?>, GridRestResponse>() { @Override public GridRestResponse applyx(IgniteInternalFuture<?> f) throws IgniteCheckedException { return new GridRestResponse(f.get()); } }); break; } case GET_OR_CREATE_CACHE: { // Do not check thread tx here since there can be active system cache txs. fut = ((IgniteKernal)ctx.grid()).getOrCreateCacheAsync(cacheName, false).chain( new CX1<IgniteInternalFuture<?>, GridRestResponse>() { @Override public GridRestResponse applyx(IgniteInternalFuture<?> f) throws IgniteCheckedException { return new GridRestResponse(f.get()); } }); break; } case CACHE_METADATA: { fut = ctx.task().execute(MetadataTask.class, null); break; } case CACHE_CONTAINS_KEYS: { fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new ContainsKeysCommand(getKeys(req0))); break; } case CACHE_CONTAINS_KEY: { fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new ContainsKeyCommand(key)); break; } case CACHE_GET: { fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new GetCommand(key)); break; } case CACHE_GET_AND_PUT: { fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new GetAndPutCommand(key, getValue(req0))); break; } case CACHE_GET_AND_REPLACE: { fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new GetAndReplaceCommand(key, getValue(req0))); break; } case CACHE_GET_AND_PUT_IF_ABSENT: { fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new GetAndPutIfAbsentCommand(key, getValue(req0))); break; } case CACHE_PUT_IF_ABSENT: { fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new PutIfAbsentCommand(key, ttl, getValue(req0))); break; } case CACHE_GET_ALL: { fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new GetAllCommand(getKeys(req0))); break; } case CACHE_PUT: { fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new PutCommand(key, ttl, getValue(req0))); break; } case CACHE_ADD: { fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new AddCommand(key, ttl, getValue(req0))); break; } case CACHE_PUT_ALL: { Map<Object, Object> map = req0.values(); if (F.isEmpty(map)) throw new IgniteCheckedException(GridRestCommandHandlerAdapter.missingParameter("values")); for (Map.Entry<Object, Object> e : map.entrySet()) { if (e.getKey() == null) throw new IgniteCheckedException("Failing putAll operation (null keys are not allowed)."); if (e.getValue() == null) throw new IgniteCheckedException("Failing putAll operation (null values are not allowed)."); } // HashMap wrapping for correct serialization map = new HashMap<>(map); fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new PutAllCommand(map)); break; } case CACHE_REMOVE: { fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new RemoveCommand(key)); break; } case CACHE_REMOVE_VALUE: { fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new RemoveValueCommand(key, getValue(req0))); break; } case CACHE_REPLACE_VALUE: { fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new ReplaceValueCommand(key, getValue(req0), req0.value2())); break; } case CACHE_GET_AND_REMOVE: { fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new GetAndRemoveCommand(key)); break; } case CACHE_REMOVE_ALL: { Map<Object, Object> map = req0.values(); // HashSet wrapping for correct serialization Set<Object> keys = map == null ? null : new HashSet<>(map.keySet()); fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new RemoveAllCommand(keys)); break; } case CACHE_REPLACE: { final Object val = req0.value(); if (val == null) throw new IgniteCheckedException(GridRestCommandHandlerAdapter.missingParameter("val")); fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new ReplaceCommand(key, ttl, val)); break; } case CACHE_CAS: { final Object val1 = req0.value(); final Object val2 = req0.value2(); fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new CasCommand(val2, val1, key)); break; } case CACHE_APPEND: { fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new AppendCommand(key, req0)); break; } case CACHE_PREPEND: { fut = executeCommand(req.destinationId(), req.clientId(), cacheName, skipStore, key, new PrependCommand(key, req0)); break; } case CACHE_METRICS: { fut = executeCommand(req.destinationId(), req.clientId(), cacheName, key, new MetricsCommand()); break; } case CACHE_SIZE: { fut = executeCommand(req.destinationId(), req.clientId(), cacheName, key, new SizeCommand()); break; } default: throw new IllegalArgumentException("Invalid command for cache handler: " + req); } return fut; } catch (IgniteException e) { U.error(log, "Failed to execute cache command: " + req, e); return new GridFinishedFuture<>(e); } catch (IgniteCheckedException e) { U.error(log, "Failed to execute cache command: " + req, e); return new GridFinishedFuture<>(e); } finally { if (log.isDebugEnabled()) log.debug("Handled cache REST request: " + req); } } /** * @param req Request. * @return Request keys. * @throws IgniteCheckedException If incorrect keys are presented. */ private Set<Object> getKeys(GridRestCacheRequest req) throws IgniteCheckedException { Set<Object> keys = req.values().keySet(); if (F.isEmpty(keys)) throw new IgniteCheckedException(GridRestCommandHandlerAdapter.missingParameter("keys")); // HashSet wrapping for correct serialization HashSet<Object> keys0 = new HashSet<>(); for (Object getKey : keys) { if (getKey == null) throw new IgniteCheckedException("Failing operation (null keys are not allowed)."); keys0.add(getKey); } return keys0; } /** * @param req Request. * @return Request value. * @throws IgniteCheckedException If incorrect keys are presented. */ private Object getValue(GridRestCacheRequest req) throws IgniteCheckedException { final Object val = req.value(); if (val == null) throw new IgniteCheckedException(GridRestCommandHandlerAdapter.missingParameter("val")); return val; } /** * Executes command on flagged cache projection. Checks {@code destId} to find if command could be performed locally * or routed to a remote node. * * @param destId Target node Id for the operation. If {@code null} - operation could be executed anywhere. * @param clientId Client ID. * @param cacheName Cache name. * @param skipStore Skip store. * @param key Key to set affinity mapping in the response. * @param op Operation to perform. * @return Operation result in future. * @throws IgniteCheckedException If failed */ private IgniteInternalFuture<GridRestResponse> executeCommand( @Nullable UUID destId, UUID clientId, final String cacheName, final boolean skipStore, final Object key, final CacheProjectionCommand op) throws IgniteCheckedException { final boolean locExec = destId == null || destId.equals(ctx.localNodeId()) || replicatedCacheAvailable(cacheName); if (locExec) { IgniteInternalCache<?, ?> prj = localCache(cacheName).forSubjectId(clientId).setSkipStore(skipStore); return op.apply((IgniteInternalCache<Object, Object>)prj, ctx). chain(resultWrapper((IgniteInternalCache<Object, Object>)prj, key)); } else { ClusterGroup prj = ctx.grid().cluster().forPredicate(F.nodeForNodeId(destId)); ctx.task().setThreadContext(TC_NO_FAILOVER, true); return ctx.closure().callAsync(BALANCE, new FlaggedCacheOperationCallable(clientId, cacheName, skipStore, op, key), prj.nodes()); } } /** * Executes command on cache. Checks {@code destId} to find if command could be performed locally or routed to a * remote node. * * @param destId Target node Id for the operation. If {@code null} - operation could be executed anywhere. * @param clientId Client ID. * @param cacheName Cache name. * @param key Key to set affinity mapping in the response. * @param op Operation to perform. * @return Operation result in future. * @throws IgniteCheckedException If failed */ private IgniteInternalFuture<GridRestResponse> executeCommand( @Nullable UUID destId, UUID clientId, final String cacheName, final Object key, final CacheCommand op) throws IgniteCheckedException { final boolean locExec = destId == null || destId.equals(ctx.localNodeId()) || ctx.cache().cache(cacheName) != null; if (locExec) { final IgniteInternalCache<Object, Object> cache = localCache(cacheName).forSubjectId(clientId); return op.apply(cache, ctx).chain(resultWrapper(cache, key)); } else { ClusterGroup prj = ctx.grid().cluster().forPredicate(F.nodeForNodeId(destId)); ctx.task().setThreadContext(TC_NO_FAILOVER, true); return ctx.closure().callAsync(BALANCE, new CacheOperationCallable(clientId, cacheName, op, key), prj.nodes()); } } /** {@inheritDoc} */ @Override public String toString() { return S.toString(GridCacheCommandHandler.class, this); } /** * @param cacheName Cache name. * @return If replicated cache with given name is locally available. */ private boolean replicatedCacheAvailable(String cacheName) { GridCacheAdapter<Object, Object> cache = ctx.cache().internalCache(cacheName); return cache != null && cache.configuration().getCacheMode() == CacheMode.REPLICATED; } /** * @param cacheName Name of the cache. * @return Instance on the named cache. * @throws IgniteCheckedException If cache not found. */ protected IgniteInternalCache<Object, Object> localCache(String cacheName) throws IgniteCheckedException { IgniteInternalCache<Object, Object> cache = ctx.cache().cache(cacheName); if (cache == null) throw new IgniteCheckedException( "Failed to find cache for given cache name (null for default cache): " + cacheName); return cache; } /** * Fixed result closure. */ private static final class FixedResult extends CX1<IgniteInternalFuture<?>, Object> { /** */ private static final long serialVersionUID = 0L; /** Closure result. */ private final Object res; /** * @param res Closure result. */ private FixedResult(Object res) { this.res = res; } /** {@inheritDoc} */ @Override public Object applyx(IgniteInternalFuture<?> f) throws IgniteCheckedException { f.get(); return res; } } /** * Type alias. */ private abstract static class CacheCommand extends IgniteClosure2X<IgniteInternalCache<Object, Object>, GridKernalContext, IgniteInternalFuture<?>> { /** */ private static final long serialVersionUID = 0L; // No-op. } /** * Type alias. */ private abstract static class CacheProjectionCommand extends IgniteClosure2X<IgniteInternalCache<Object, Object>, GridKernalContext, IgniteInternalFuture<?>> { /** */ private static final long serialVersionUID = 0L; // No-op. } /** * Class for flagged cache operations. */ @GridInternal private static class FlaggedCacheOperationCallable implements Callable<GridRestResponse>, Serializable { /** */ private static final long serialVersionUID = 0L; /** */ private final String cacheName; /** */ private final boolean skipStore; /** */ private final CacheProjectionCommand op; /** */ private final Object key; /** Client ID. */ private UUID clientId; /** */ @IgniteInstanceResource private Ignite g; /** * @param clientId Client ID. * @param cacheName Cache name. * @param skipStore Skip store. * @param op Operation. * @param key Key. */ private FlaggedCacheOperationCallable(UUID clientId, String cacheName, boolean skipStore, CacheProjectionCommand op, Object key) { this.clientId = clientId; this.cacheName = cacheName; this.skipStore = skipStore; this.op = op; this.key = key; } /** {@inheritDoc} */ @Override public GridRestResponse call() throws Exception { IgniteInternalCache<?, ?> prj = cache(g, cacheName).forSubjectId(clientId).setSkipStore(skipStore); // Need to apply both operation and response transformation remotely // as cache could be inaccessible on local node and // exception processing should be consistent with local execution. return op.apply((IgniteInternalCache<Object, Object>)prj, ((IgniteKernal)g).context()). chain(resultWrapper((IgniteInternalCache<Object, Object>)prj, key)).get(); } } /** * Class for cache operations. */ @GridInternal private static class CacheOperationCallable implements Callable<GridRestResponse>, Serializable { /** */ private static final long serialVersionUID = 0L; /** */ private final String cacheName; /** */ private final CacheCommand op; /** */ private final Object key; /** Client ID. */ private UUID clientId; /** */ @IgniteInstanceResource private Ignite g; /** * @param clientId Client ID. * @param cacheName Cache name. * @param op Operation. * @param key Key. */ private CacheOperationCallable(UUID clientId, String cacheName, CacheCommand op, Object key) { this.clientId = clientId; this.cacheName = cacheName; this.op = op; this.key = key; } /** {@inheritDoc} */ @Override public GridRestResponse call() throws Exception { final IgniteInternalCache<Object, Object> cache = cache(g, cacheName).forSubjectId(clientId); // Need to apply both operation and response transformation remotely // as cache could be inaccessible on local node and // exception processing should be consistent with local execution. return op.apply(cache, ((IgniteKernal)g).context()).chain(resultWrapper(cache, key)).get(); } } /** */ private static class ContainsKeyCommand extends CacheProjectionCommand { /** */ private static final long serialVersionUID = 0L; /** */ private final Object key; /** * @param key Key. */ ContainsKeyCommand(Object key) { this.key = key; } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { return c.containsKeyAsync(key); } } /** */ @GridInternal private static class MetadataTask extends ComputeTaskAdapter<Void, GridRestResponse> { /** */ private static final long serialVersionUID = 0L; /** */ @IgniteInstanceResource private IgniteEx ignite; /** {@inheritDoc} */ @Nullable @Override public Map<? extends ComputeJob, ClusterNode> map(List<ClusterNode> subgrid, @Nullable Void arg) throws IgniteException { GridDiscoveryManager discovery = ignite.context().discovery(); boolean sameCaches = true; int hash = discovery.nodeCaches(F.first(subgrid)).hashCode(); for (int i = 1; i < subgrid.size(); i++) { if (hash != discovery.nodeCaches(subgrid.get(i)).hashCode()) { sameCaches = false; break; } } Map<ComputeJob, ClusterNode> map = U.newHashMap(sameCaches ? 1 : subgrid.size()); if (sameCaches) map.put(new MetadataJob(), ignite.localNode()); else { for (ClusterNode node : subgrid) map.put(new MetadataJob(), node); } return map; } /** {@inheritDoc} */ @SuppressWarnings("ThrowableResultOfMethodCallIgnored") @Nullable @Override public GridRestResponse reduce(List<ComputeJobResult> results) throws IgniteException { Map<String, GridCacheSqlMetadata> map = new HashMap<>(); for (ComputeJobResult r : results) { if (!r.isCancelled() && r.getException() == null) { for (GridCacheSqlMetadata m : r.<Collection<GridCacheSqlMetadata>>getData()) { if (!map.containsKey(m.cacheName())) map.put(m.cacheName(), m); } } } Collection<GridCacheSqlMetadata> metas = new ArrayList<>(map.size()); metas.addAll(map.values()); return new GridRestResponse(metas); } /** {@inheritDoc} */ @Override public String toString() { return S.toString(MetadataTask.class, this); } } /** */ private static class MetadataJob extends ComputeJobAdapter { /** */ private static final long serialVersionUID = 0L; /** Auto-injected grid instance. */ @IgniteInstanceResource private transient IgniteEx ignite; /** {@inheritDoc} */ @Override public Collection<GridCacheSqlMetadata> execute() { IgniteCacheProxy<?, ?> cache = F.first(ignite.context().cache().publicCaches()); if (cache == null) return Collections.emptyList(); try { return cache.context().queries().sqlMetadata(); } catch (IgniteCheckedException e) { throw U.convertException(e); } } /** {@inheritDoc} */ @Override public String toString() { return S.toString(MetadataJob.class, this); } } /** */ private static class ContainsKeysCommand extends CacheProjectionCommand { /** */ private static final long serialVersionUID = 0L; /** */ private final Collection<Object> keys; /** * @param keys Keys. */ ContainsKeysCommand(Collection<Object> keys) { this.keys = keys; } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { return c.containsKeysAsync(keys); } } /** */ private static class GetCommand extends CacheProjectionCommand { /** */ private static final long serialVersionUID = 0L; /** */ private final Object key; /** * @param key Key. */ GetCommand(Object key) { this.key = key; } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { return c.getAsync(key); } } /** */ private static class GetAndPutCommand extends CacheProjectionCommand { /** */ private static final long serialVersionUID = 0L; /** Key. */ protected final Object key; /** Value. */ protected final Object val; /** * @param key Key. * @param val Value. */ GetAndPutCommand(Object key, Object val) { this.key = key; this.val = val; } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { return c.getAndPutAsync(key, val); } } /** */ private static class GetAndReplaceCommand extends GetAndPutCommand { /** */ private static final long serialVersionUID = 0L; /** * @param key Key. * @param val Value. */ GetAndReplaceCommand(Object key, Object val) { super(key, val); } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { return c.getAndReplaceAsync(key, val); } } /** */ private static class ReplaceValueCommand extends GetAndReplaceCommand { /** */ private static final long serialVersionUID = 0L; /** */ private final Object oldVal; /** * @param key Key. * @param val Value. * @param oldVal Old value. */ ReplaceValueCommand(Object key, Object val, Object oldVal) { super(key, val); this.oldVal = oldVal; } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { return c.replaceAsync(key, oldVal, val); } } /** */ private static class GetAndPutIfAbsentCommand extends GetAndPutCommand { /** */ private static final long serialVersionUID = 0L; /** * @param key Key. * @param val Value. */ GetAndPutIfAbsentCommand(Object key, Object val) { super(key, val); } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { return c.getAndPutIfAbsentAsync(key, val); } } /** */ private static class PutIfAbsentCommand extends CacheProjectionCommand { /** */ private static final long serialVersionUID = 0L; /** */ private final Object key; /** */ private final Long ttl; /** */ private final Object val; /** * @param key Key. * @param ttl TTL. * @param val Value. */ PutIfAbsentCommand(Object key, Long ttl, Object val) { this.val = val; this.ttl = ttl; this.key = key; } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { if (ttl != null && ttl > 0) { Duration duration = new Duration(MILLISECONDS, ttl); c = c.withExpiryPolicy(new ModifiedExpiryPolicy(duration)); } return c.putIfAbsentAsync(key, val); } } /** */ private static class GetAllCommand extends CacheProjectionCommand { /** */ private static final long serialVersionUID = 0L; /** */ private final Collection<Object> keys; /** * @param keys Keys. */ GetAllCommand(Collection<Object> keys) { this.keys = keys; } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { return c.getAllAsync(keys); } } /** */ private static class PutAllCommand extends CacheProjectionCommand { /** */ private static final long serialVersionUID = 0L; /** */ private final Map<Object, Object> map; /** * @param map Objects to put. */ PutAllCommand(Map<Object, Object> map) { this.map = map; } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { return c.putAllAsync(map).chain(new FixedResult(true)); } } /** */ private static class RemoveCommand extends CacheProjectionCommand { /** */ private static final long serialVersionUID = 0L; /** */ protected final Object key; /** * @param key Key. */ RemoveCommand(Object key) { this.key = key; } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { return c.removeAsync(key); } } /** */ private static class RemoveValueCommand extends GetAndPutCommand { /** */ private static final long serialVersionUID = 0L; /** * @param key Key. * @param val Value. */ RemoveValueCommand(Object key, Object val) { super(key, val); } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { return c.removeAsync(key, val); } } /** */ private static class GetAndRemoveCommand extends RemoveCommand { /** */ private static final long serialVersionUID = 0L; /** * @param key Key. */ GetAndRemoveCommand(Object key) { super(key); } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { return c.getAndRemoveAsync(key); } } /** */ private static class RemoveAllCommand extends CacheProjectionCommand { /** */ private static final long serialVersionUID = 0L; /** */ private final Collection<Object> keys; /** * @param keys Keys to remove. */ RemoveAllCommand(Collection<Object> keys) { this.keys = keys; } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { return (F.isEmpty(keys) ? c.removeAllAsync() : c.removeAllAsync(keys)) .chain(new FixedResult(true)); } } /** */ private static class CasCommand extends CacheProjectionCommand { /** */ private static final long serialVersionUID = 0L; /** */ private final Object exp; /** */ private final Object val; /** */ private final Object key; /** * @param exp Expected previous value. * @param val New value. * @param key Key. */ CasCommand(Object exp, Object val, Object key) { this.val = val; this.exp = exp; this.key = key; } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { return exp == null && val == null ? c.removeAsync(key) : exp == null ? c.putIfAbsentAsync(key, val) : val == null ? c.removeAsync(key, exp) : c.replaceAsync(key, exp, val); } } /** */ private static class PutCommand extends CacheProjectionCommand { /** */ private static final long serialVersionUID = 0L; /** */ private final Object key; /** */ private final Long ttl; /** */ private final Object val; /** * @param key Key. * @param ttl TTL. * @param val Value. */ PutCommand(Object key, Long ttl, Object val) { this.key = key; this.ttl = ttl; this.val = val; } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { if (ttl != null && ttl > 0) { Duration duration = new Duration(MILLISECONDS, ttl); c = c.withExpiryPolicy(new ModifiedExpiryPolicy(duration)); } return c.putAsync(key, val); } } /** */ private static class AddCommand extends CacheProjectionCommand { /** */ private static final long serialVersionUID = 0L; /** */ private final Object key; /** */ private final Long ttl; /** */ private final Object val; /** * @param key Key. * @param ttl TTL. * @param val Value. */ AddCommand(Object key, Long ttl, Object val) { this.key = key; this.ttl = ttl; this.val = val; } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { if (ttl != null && ttl > 0) { Duration duration = new Duration(MILLISECONDS, ttl); c = c.withExpiryPolicy(new ModifiedExpiryPolicy(duration)); } return c.putIfAbsentAsync(key, val); } } /** */ private static class ReplaceCommand extends CacheProjectionCommand { /** */ private static final long serialVersionUID = 0L; /** */ private final Object key; /** */ private final Long ttl; /** */ private final Object val; /** * @param key Key. * @param ttl TTL. * @param val Value. */ ReplaceCommand(Object key, Long ttl, Object val) { this.key = key; this.ttl = ttl; this.val = val; } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { if (ttl != null && ttl > 0) { Duration duration = new Duration(MILLISECONDS, ttl); c = c.withExpiryPolicy(new ModifiedExpiryPolicy(duration)); } return c.replaceAsync(key, val); } } /** */ private static class AppendCommand extends CacheProjectionCommand { /** */ private static final long serialVersionUID = 0L; /** */ private final Object key; /** */ private final GridRestCacheRequest req; /** * @param key Key. * @param req Operation request. */ AppendCommand(Object key, GridRestCacheRequest req) { this.key = key; this.req = req; } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) throws IgniteCheckedException { return appendOrPrepend(ctx, c, key, req, false); } } /** */ private static class PrependCommand extends CacheProjectionCommand { /** */ private static final long serialVersionUID = 0L; /** */ private final Object key; /** */ private final GridRestCacheRequest req; /** * @param key Key. * @param req Operation request. */ PrependCommand(Object key, GridRestCacheRequest req) { this.key = key; this.req = req; } /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) throws IgniteCheckedException { return appendOrPrepend(ctx, c, key, req, true); } } /** */ private static class MetricsCommand extends CacheCommand { /** */ private static final long serialVersionUID = 0L; /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { CacheMetrics metrics = c.cache().localMetrics(); assert metrics != null; return new GridFinishedFuture<Object>(new GridCacheRestMetrics( (int)metrics.getCacheGets(), (int)(metrics.getCacheRemovals() + metrics.getCachePuts()), (int)metrics.getCacheHits(), (int)metrics.getCacheMisses()) ); } } /** */ private static class SizeCommand extends CacheCommand { /** */ private static final long serialVersionUID = 0L; /** {@inheritDoc} */ @Override public IgniteInternalFuture<?> applyx(IgniteInternalCache<Object, Object> c, GridKernalContext ctx) { return c.sizeAsync(new CachePeekMode[] {CachePeekMode.PRIMARY}); } } }